/* 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([` ${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 }