From c38486c296dab97db69ef54e89bd9572214d1d2c Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 19 Apr 2026 14:11:01 -0400 Subject: [PATCH] Fix PDF.js vendor path glob and reformat with consistent style - Change pdfjsPath template from 'vendor/pdfjs/' to './vendor/pdfjs/' so the relative glob resolves correctly when the module is bundled with Vite - Reformat entire file: 2-space indentation, double quotes, semicolons, trailing commas, parenthesized arrow function parameters, and aligned multi-line call arguments for consistency with the rest of the codebase --- pdf.js | 290 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 155 insertions(+), 135 deletions(-) diff --git a/pdf.js b/pdf.js index c998bf4..ee5d32c 100644 --- a/pdf.js +++ b/pdf.js @@ -1,82 +1,95 @@ -const pdfjsPath = path => new URL(`vendor/pdfjs/${path}`, import.meta.url).toString() +const pdfjsPath = (path) => + new URL(`./vendor/pdfjs/${path}`, import.meta.url).toString(); -import './vendor/pdfjs/pdf.mjs' -const pdfjsLib = globalThis.pdfjsLib -pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath('pdf.worker.mjs') +import "./vendor/pdfjs/pdf.mjs"; +const pdfjsLib = globalThis.pdfjsLib; +pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath("pdf.worker.mjs"); -const fetchText = async url => await (await fetch(url)).text() +const fetchText = async (url) => await (await fetch(url)).text(); // https://raw.githubusercontent.com/mozilla/pdf.js/refs/tags/v5.5.207/web/text_layer_builder.css -const textLayerBuilderCSS = await fetchText(pdfjsPath('text_layer_builder.css')) +const textLayerBuilderCSS = await fetchText( + pdfjsPath("text_layer_builder.css"), +); // https://raw.githubusercontent.com/mozilla/pdf.js/refs/tags/v5.5.207/web/annotation_layer_builder.css -const annotationLayerBuilderCSS = await fetchText(pdfjsPath('annotation_layer_builder.css')) +const annotationLayerBuilderCSS = await fetchText( + pdfjsPath("annotation_layer_builder.css"), +); const render = async (page, doc, zoom) => { - const scale = zoom * devicePixelRatio - doc.documentElement.style.transform = `scale(${1 / devicePixelRatio})` - doc.documentElement.style.transformOrigin = 'top left' - doc.documentElement.style.setProperty('--scale-factor', scale) - const viewport = page.getViewport({ scale }) + const scale = zoom * devicePixelRatio; + doc.documentElement.style.transform = `scale(${1 / devicePixelRatio})`; + doc.documentElement.style.transformOrigin = "top left"; + doc.documentElement.style.setProperty("--scale-factor", scale); + const viewport = page.getViewport({ scale }); - // the canvas must be in the `PDFDocument`'s `ownerDocument` - // (`globalThis.document` by default); that's where the fonts are loaded - const canvas = document.createElement('canvas') - canvas.height = viewport.height - canvas.width = viewport.width - const canvasContext = canvas.getContext('2d') - await page.render({ canvasContext, viewport }).promise - doc.querySelector('#canvas').replaceChildren(doc.adoptNode(canvas)) + // the canvas must be in the `PDFDocument`'s `ownerDocument` + // (`globalThis.document` by default); that's where the fonts are loaded + const canvas = document.createElement("canvas"); + canvas.height = viewport.height; + canvas.width = viewport.width; + const canvasContext = canvas.getContext("2d"); + await page.render({ canvasContext, viewport }).promise; + doc.querySelector("#canvas").replaceChildren(doc.adoptNode(canvas)); - const container = doc.querySelector('.textLayer') - const textLayer = new pdfjsLib.TextLayer({ - textContentSource: await page.streamTextContent(), - container, viewport, - }) - await textLayer.render() + const container = doc.querySelector(".textLayer"); + const textLayer = new pdfjsLib.TextLayer({ + textContentSource: await page.streamTextContent(), + container, + viewport, + }); + await textLayer.render(); - // hide "offscreen" canvases appended to docuemnt when rendering text layer - // https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/pdf_viewer.css#L51-L58 - for (const canvas of document.querySelectorAll('.hiddenCanvasElement')) - Object.assign(canvas.style, { - position: 'absolute', - top: '0', - left: '0', - width: '0', - height: '0', - display: 'none', - }) + // hide "offscreen" canvases appended to docuemnt when rendering text layer + // https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/pdf_viewer.css#L51-L58 + for (const canvas of document.querySelectorAll(".hiddenCanvasElement")) + Object.assign(canvas.style, { + position: "absolute", + top: "0", + left: "0", + width: "0", + height: "0", + display: "none", + }); - // fix text selection - // https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.js#L105-L107 - const endOfContent = document.createElement('div') - endOfContent.className = 'endOfContent' - container.append(endOfContent) - // TODO: this only works in Firefox; see https://github.com/mozilla/pdf.js/pull/17923 - container.onpointerdown = () => container.classList.add('selecting') - container.onpointerup = () => container.classList.remove('selecting') + // fix text selection + // https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.js#L105-L107 + const endOfContent = document.createElement("div"); + endOfContent.className = "endOfContent"; + container.append(endOfContent); + // TODO: this only works in Firefox; see https://github.com/mozilla/pdf.js/pull/17923 + container.onpointerdown = () => container.classList.add("selecting"); + container.onpointerup = () => container.classList.remove("selecting"); - const div = doc.querySelector('.annotationLayer') - const linkService = { - goToDestination: () => {}, - getDestinationHash: dest => JSON.stringify(dest), - addLinkAttributes: (link, url) => link.href = url, - } - await new pdfjsLib.AnnotationLayer({ page, viewport, div, linkService }) - .render({ annotations: await page.getAnnotations() }) -} + const div = doc.querySelector(".annotationLayer"); + const linkService = { + goToDestination: () => {}, + getDestinationHash: (dest) => JSON.stringify(dest), + addLinkAttributes: (link, url) => (link.href = url), + }; + await new pdfjsLib.AnnotationLayer({ + page, + viewport, + div, + linkService, + }).render({ annotations: await page.getAnnotations() }); +}; const renderPage = async (page, getImageBlob) => { - const viewport = page.getViewport({ scale: 1 }) - if (getImageBlob) { - const canvas = document.createElement('canvas') - canvas.height = viewport.height - canvas.width = viewport.width - const canvasContext = canvas.getContext('2d') - await page.render({ canvasContext, viewport }).promise - return new Promise(resolve => canvas.toBlob(resolve)) - } - const src = URL.createObjectURL(new Blob([` + const viewport = page.getViewport({ scale: 1 }); + if (getImageBlob) { + const canvas = document.createElement("canvas"); + canvas.height = viewport.height; + canvas.width = viewport.width; + const canvasContext = canvas.getContext("2d"); + await page.render({ canvasContext, viewport }).promise; + return new Promise((resolve) => canvas.toBlob(resolve)); + } + const src = URL.createObjectURL( + new Blob( + [ + ` @@ -101,80 +114,87 @@ const renderPage = async (page, getImageBlob) => {
- `], { type: 'text/html' })) - const onZoom = ({ doc, scale }) => render(page, doc, scale) - return { src, onZoom } -} + `, + ], + { type: "text/html" }, + ), + ); + const onZoom = ({ doc, scale }) => render(page, doc, scale); + return { src, onZoom }; +}; -const makeTOCItem = item => ({ - label: item.title, - href: JSON.stringify(item.dest), - subitems: item.items.length ? item.items.map(makeTOCItem) : null, -}) +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 transport = new pdfjsLib.PDFDataRangeTransport(file.size, []) - transport.requestDataRange = (begin, end) => { - file.slice(begin, end).arrayBuffer().then(chunk => { - transport.onDataRange(begin, chunk) - }) - } - const pdf = await pdfjsLib.getDocument({ - range: transport, - cMapUrl: pdfjsPath('cmaps/'), - standardFontDataUrl: pdfjsPath('standard_fonts/'), - isEvalSupported: false, - }).promise +export const makePDF = async (file) => { + const transport = new pdfjsLib.PDFDataRangeTransport(file.size, []); + transport.requestDataRange = (begin, end) => { + file + .slice(begin, end) + .arrayBuffer() + .then((chunk) => { + transport.onDataRange(begin, chunk); + }); + }; + const pdf = await pdfjsLib.getDocument({ + range: transport, + cMapUrl: pdfjsPath("cmaps/"), + standardFontDataUrl: pdfjsPath("standard_fonts/"), + isEvalSupported: false, + }).promise; - const book = { rendition: { layout: 'pre-paginated' } } + const book = { rendition: { layout: "pre-paginated" } }; - const { metadata, info } = await pdf.getMetadata() ?? {} - // TODO: for better results, parse `metadata.getRaw()` - book.metadata = { - title: metadata?.get('dc:title') ?? info?.Title, - author: metadata?.get('dc:creator') ?? info?.Author, - contributor: metadata?.get('dc:contributor'), - description: metadata?.get('dc:description') ?? info?.Subject, - language: metadata?.get('dc:language'), - publisher: metadata?.get('dc:publisher'), - subject: metadata?.get('dc:subject'), - identifier: metadata?.get('dc:identifier'), - source: metadata?.get('dc:source'), - rights: metadata?.get('dc:rights'), - } + const { metadata, info } = (await pdf.getMetadata()) ?? {}; + // TODO: for better results, parse `metadata.getRaw()` + book.metadata = { + title: metadata?.get("dc:title") ?? info?.Title, + author: metadata?.get("dc:creator") ?? info?.Author, + contributor: metadata?.get("dc:contributor"), + description: metadata?.get("dc:description") ?? info?.Subject, + language: metadata?.get("dc:language"), + publisher: metadata?.get("dc:publisher"), + subject: metadata?.get("dc:subject"), + identifier: metadata?.get("dc:identifier"), + source: metadata?.get("dc:source"), + rights: metadata?.get("dc:rights"), + }; - const outline = await pdf.getOutline() - book.toc = outline?.map(makeTOCItem) + 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.isExternal = uri => /^\w+:/i.test(uri) - 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) - book.destroy = () => pdf.destroy() - return book -} + 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.isExternal = (uri) => /^\w+:/i.test(uri); + 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); + book.destroy = () => pdf.destroy(); + return book; +};