mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
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
This commit is contained in:
@@ -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(
|
||||
[
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="utf-8">
|
||||
@@ -101,80 +114,87 @@ const renderPage = async (page, getImageBlob) => {
|
||||
<div id="canvas"></div>
|
||||
<div class="textLayer"></div>
|
||||
<div class="annotationLayer"></div>
|
||||
`], { 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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user