-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bcfb942
commit a63a68e
Showing
6 changed files
with
76,736 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
/* global pdfjsLib */ | ||
|
||
// https://github.com/mozilla/pdf.js/blob/f04967017f22e46d70d11468dd928b4cdc2f6ea1/web/text_layer_builder.css | ||
const textLayerBuilderCSS = ` | ||
/* Copyright 2014 Mozilla Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
:root { | ||
--highlight-bg-color: rgb(180 0 170); | ||
--highlight-selected-bg-color: rgb(0 100 0); | ||
} | ||
@media screen and (forced-colors: active) { | ||
:root { | ||
--highlight-bg-color: Highlight; | ||
--highlight-selected-bg-color: ButtonText; | ||
} | ||
} | ||
.textLayer { | ||
position: absolute; | ||
text-align: initial; | ||
inset: 0; | ||
overflow: hidden; | ||
opacity: 0.25; | ||
line-height: 1; | ||
text-size-adjust: none; | ||
forced-color-adjust: none; | ||
transform-origin: 0 0; | ||
z-index: 2; | ||
} | ||
.textLayer :is(span, br) { | ||
color: transparent; | ||
position: absolute; | ||
white-space: pre; | ||
cursor: text; | ||
transform-origin: 0% 0%; | ||
} | ||
/* Only necessary in Google Chrome, see issue 14205, and most unfortunately | ||
* the problem doesn't show up in "text" reference tests. */ | ||
/*#if !MOZCENTRAL*/ | ||
.textLayer span.markedContent { | ||
top: 0; | ||
height: 0; | ||
} | ||
/*#endif*/ | ||
.textLayer .highlight { | ||
margin: -1px; | ||
padding: 1px; | ||
background-color: var(--highlight-bg-color); | ||
border-radius: 4px; | ||
} | ||
.textLayer .highlight.appended { | ||
position: initial; | ||
} | ||
.textLayer .highlight.begin { | ||
border-radius: 4px 0 0 4px; | ||
} | ||
.textLayer .highlight.end { | ||
border-radius: 0 4px 4px 0; | ||
} | ||
.textLayer .highlight.middle { | ||
border-radius: 0; | ||
} | ||
.textLayer .highlight.selected { | ||
background-color: var(--highlight-selected-bg-color); | ||
} | ||
.textLayer ::selection { | ||
/*#if !MOZCENTRAL*/ | ||
background: blue; | ||
/*#endif*/ | ||
background: AccentColor; /* stylelint-disable-line declaration-block-no-duplicate-properties */ | ||
} | ||
/* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */ | ||
/*#if !MOZCENTRAL*/ | ||
.textLayer br::selection { | ||
background: transparent; | ||
} | ||
/*#endif*/ | ||
.textLayer .endOfContent { | ||
display: block; | ||
position: absolute; | ||
inset: 100% 0 0; | ||
z-index: -1; | ||
cursor: default; | ||
user-select: none; | ||
} | ||
.textLayer .endOfContent.active { | ||
top: 0; | ||
} | ||
` | ||
|
||
const renderPage = async (page, getImageBlob) => { | ||
const scale = devicePixelRatio | ||
const viewport = page.getViewport({ scale }) | ||
|
||
const canvas = document.createElement('canvas') | ||
canvas.height = viewport.height | ||
canvas.width = viewport.width | ||
const canvasContext = canvas.getContext('2d') | ||
await page.render({ canvasContext, viewport }).promise | ||
const blob = await new Promise(resolve => canvas.toBlob(resolve)) | ||
if (getImageBlob) return blob | ||
|
||
/* | ||
// with the SVG backend | ||
const operatorList = await page.getOperatorList() | ||
const svgGraphics = new pdfjsLib.SVGGraphics(page.commonObjs, page.objs) | ||
const svg = await svgGraphics.getSVG(operatorList, viewport) | ||
const str = new XMLSerializer().serializeToString(svg) | ||
const blob = new Blob([str], { type: 'image/svg+xml' }) | ||
*/ | ||
|
||
const container = document.createElement('div') | ||
container.classList.add('textLayer') | ||
await pdfjsLib.renderTextLayer({ | ||
textContentSource: await page.getTextContent(), | ||
container, viewport, | ||
}).promise | ||
|
||
const src = URL.createObjectURL(blob) | ||
const url = URL.createObjectURL(new Blob([` | ||
<!DOCTYPE html> | ||
<meta charset="utf-8"> | ||
<style> | ||
:root { | ||
--scale-factor: ${scale}; | ||
} | ||
html, body { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
${textLayerBuilderCSS} | ||
</style> | ||
<img src="${src}"> | ||
${container.outerHTML} | ||
`], { type: 'text/html' })) | ||
return url | ||
} | ||
|
||
const makeTOCItem = item => ({ | ||
label: item.title, | ||
href: JSON.stringify(item.dest), | ||
subitems: item.items.length ? item.items.map(makeTOCItem) : null, | ||
}) | ||
|
||
export const makePDF = async file => { | ||
const data = new Uint8Array(await file.arrayBuffer()) | ||
const pdf = await pdfjsLib.getDocument({ data }).promise | ||
|
||
const book = { rendition: { layout: 'pre-paginated' } } | ||
|
||
const info = (await pdf.getMetadata())?.info | ||
book.metadata = { | ||
title: info?.Title, | ||
author: info?.Author, | ||
} | ||
|
||
const outline = await pdf.getOutline() | ||
book.toc = outline?.map(makeTOCItem) | ||
|
||
const cache = new Map() | ||
book.sections = Array.from({ length: pdf.numPages }).map((_, i) => ({ | ||
id: i, | ||
load: async () => { | ||
const cached = cache.get(i) | ||
if (cached) return cached | ||
const url = await renderPage(await pdf.getPage(i + 1)) | ||
cache.set(i, url) | ||
return url | ||
}, | ||
size: 1000, | ||
})) | ||
book.resolveHref = async href => { | ||
const parsed = JSON.parse(href) | ||
const dest = typeof parsed === 'string' | ||
? await pdf.getDestination(parsed) : parsed | ||
const index = await pdf.getPageIndex(dest[0]) | ||
return { index } | ||
} | ||
book.splitTOCHref = async href => { | ||
const parsed = JSON.parse(href) | ||
const dest = typeof parsed === 'string' | ||
? await pdf.getDestination(parsed) : parsed | ||
const index = await pdf.getPageIndex(dest[0]) | ||
return [index, null] | ||
} | ||
book.getTOCFragment = doc => doc.documentElement | ||
book.getCover = async () => renderPage(await pdf.getPage(1), true) | ||
return book | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.