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'
|
import "./vendor/pdfjs/pdf.mjs";
|
||||||
const pdfjsLib = globalThis.pdfjsLib
|
const pdfjsLib = globalThis.pdfjsLib;
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath('pdf.worker.mjs')
|
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
|
// 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
|
// 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 render = async (page, doc, zoom) => {
|
||||||
const scale = zoom * devicePixelRatio
|
const scale = zoom * devicePixelRatio;
|
||||||
doc.documentElement.style.transform = `scale(${1 / devicePixelRatio})`
|
doc.documentElement.style.transform = `scale(${1 / devicePixelRatio})`;
|
||||||
doc.documentElement.style.transformOrigin = 'top left'
|
doc.documentElement.style.transformOrigin = "top left";
|
||||||
doc.documentElement.style.setProperty('--scale-factor', scale)
|
doc.documentElement.style.setProperty("--scale-factor", scale);
|
||||||
const viewport = page.getViewport({ scale })
|
const viewport = page.getViewport({ scale });
|
||||||
|
|
||||||
// the canvas must be in the `PDFDocument`'s `ownerDocument`
|
// the canvas must be in the `PDFDocument`'s `ownerDocument`
|
||||||
// (`globalThis.document` by default); that's where the fonts are loaded
|
// (`globalThis.document` by default); that's where the fonts are loaded
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas");
|
||||||
canvas.height = viewport.height
|
canvas.height = viewport.height;
|
||||||
canvas.width = viewport.width
|
canvas.width = viewport.width;
|
||||||
const canvasContext = canvas.getContext('2d')
|
const canvasContext = canvas.getContext("2d");
|
||||||
await page.render({ canvasContext, viewport }).promise
|
await page.render({ canvasContext, viewport }).promise;
|
||||||
doc.querySelector('#canvas').replaceChildren(doc.adoptNode(canvas))
|
doc.querySelector("#canvas").replaceChildren(doc.adoptNode(canvas));
|
||||||
|
|
||||||
const container = doc.querySelector('.textLayer')
|
const container = doc.querySelector(".textLayer");
|
||||||
const textLayer = new pdfjsLib.TextLayer({
|
const textLayer = new pdfjsLib.TextLayer({
|
||||||
textContentSource: await page.streamTextContent(),
|
textContentSource: await page.streamTextContent(),
|
||||||
container, viewport,
|
container,
|
||||||
})
|
viewport,
|
||||||
await textLayer.render()
|
});
|
||||||
|
await textLayer.render();
|
||||||
|
|
||||||
// hide "offscreen" canvases appended to docuemnt when rendering text layer
|
// hide "offscreen" canvases appended to docuemnt when rendering text layer
|
||||||
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/pdf_viewer.css#L51-L58
|
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/pdf_viewer.css#L51-L58
|
||||||
for (const canvas of document.querySelectorAll('.hiddenCanvasElement'))
|
for (const canvas of document.querySelectorAll(".hiddenCanvasElement"))
|
||||||
Object.assign(canvas.style, {
|
Object.assign(canvas.style, {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: '0',
|
top: "0",
|
||||||
left: '0',
|
left: "0",
|
||||||
width: '0',
|
width: "0",
|
||||||
height: '0',
|
height: "0",
|
||||||
display: 'none',
|
display: "none",
|
||||||
})
|
});
|
||||||
|
|
||||||
// fix text selection
|
// fix text selection
|
||||||
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.js#L105-L107
|
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.js#L105-L107
|
||||||
const endOfContent = document.createElement('div')
|
const endOfContent = document.createElement("div");
|
||||||
endOfContent.className = 'endOfContent'
|
endOfContent.className = "endOfContent";
|
||||||
container.append(endOfContent)
|
container.append(endOfContent);
|
||||||
// TODO: this only works in Firefox; see https://github.com/mozilla/pdf.js/pull/17923
|
// TODO: this only works in Firefox; see https://github.com/mozilla/pdf.js/pull/17923
|
||||||
container.onpointerdown = () => container.classList.add('selecting')
|
container.onpointerdown = () => container.classList.add("selecting");
|
||||||
container.onpointerup = () => container.classList.remove('selecting')
|
container.onpointerup = () => container.classList.remove("selecting");
|
||||||
|
|
||||||
const div = doc.querySelector('.annotationLayer')
|
const div = doc.querySelector(".annotationLayer");
|
||||||
const linkService = {
|
const linkService = {
|
||||||
goToDestination: () => {},
|
goToDestination: () => {},
|
||||||
getDestinationHash: dest => JSON.stringify(dest),
|
getDestinationHash: (dest) => JSON.stringify(dest),
|
||||||
addLinkAttributes: (link, url) => link.href = url,
|
addLinkAttributes: (link, url) => (link.href = url),
|
||||||
}
|
};
|
||||||
await new pdfjsLib.AnnotationLayer({ page, viewport, div, linkService })
|
await new pdfjsLib.AnnotationLayer({
|
||||||
.render({ annotations: await page.getAnnotations() })
|
page,
|
||||||
}
|
viewport,
|
||||||
|
div,
|
||||||
|
linkService,
|
||||||
|
}).render({ annotations: await page.getAnnotations() });
|
||||||
|
};
|
||||||
|
|
||||||
const renderPage = async (page, getImageBlob) => {
|
const renderPage = async (page, getImageBlob) => {
|
||||||
const viewport = page.getViewport({ scale: 1 })
|
const viewport = page.getViewport({ scale: 1 });
|
||||||
if (getImageBlob) {
|
if (getImageBlob) {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas");
|
||||||
canvas.height = viewport.height
|
canvas.height = viewport.height;
|
||||||
canvas.width = viewport.width
|
canvas.width = viewport.width;
|
||||||
const canvasContext = canvas.getContext('2d')
|
const canvasContext = canvas.getContext("2d");
|
||||||
await page.render({ canvasContext, viewport }).promise
|
await page.render({ canvasContext, viewport }).promise;
|
||||||
return new Promise(resolve => canvas.toBlob(resolve))
|
return new Promise((resolve) => canvas.toBlob(resolve));
|
||||||
}
|
}
|
||||||
const src = URL.createObjectURL(new Blob([`
|
const src = URL.createObjectURL(
|
||||||
|
new Blob(
|
||||||
|
[
|
||||||
|
`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -101,80 +114,87 @@ const renderPage = async (page, getImageBlob) => {
|
|||||||
<div id="canvas"></div>
|
<div id="canvas"></div>
|
||||||
<div class="textLayer"></div>
|
<div class="textLayer"></div>
|
||||||
<div class="annotationLayer"></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 => ({
|
const makeTOCItem = (item) => ({
|
||||||
label: item.title,
|
label: item.title,
|
||||||
href: JSON.stringify(item.dest),
|
href: JSON.stringify(item.dest),
|
||||||
subitems: item.items.length ? item.items.map(makeTOCItem) : null,
|
subitems: item.items.length ? item.items.map(makeTOCItem) : null,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const makePDF = async file => {
|
export const makePDF = async (file) => {
|
||||||
const transport = new pdfjsLib.PDFDataRangeTransport(file.size, [])
|
const transport = new pdfjsLib.PDFDataRangeTransport(file.size, []);
|
||||||
transport.requestDataRange = (begin, end) => {
|
transport.requestDataRange = (begin, end) => {
|
||||||
file.slice(begin, end).arrayBuffer().then(chunk => {
|
file
|
||||||
transport.onDataRange(begin, chunk)
|
.slice(begin, end)
|
||||||
})
|
.arrayBuffer()
|
||||||
}
|
.then((chunk) => {
|
||||||
|
transport.onDataRange(begin, chunk);
|
||||||
|
});
|
||||||
|
};
|
||||||
const pdf = await pdfjsLib.getDocument({
|
const pdf = await pdfjsLib.getDocument({
|
||||||
range: transport,
|
range: transport,
|
||||||
cMapUrl: pdfjsPath('cmaps/'),
|
cMapUrl: pdfjsPath("cmaps/"),
|
||||||
standardFontDataUrl: pdfjsPath('standard_fonts/'),
|
standardFontDataUrl: pdfjsPath("standard_fonts/"),
|
||||||
isEvalSupported: false,
|
isEvalSupported: false,
|
||||||
}).promise
|
}).promise;
|
||||||
|
|
||||||
const book = { rendition: { layout: 'pre-paginated' } }
|
const book = { rendition: { layout: "pre-paginated" } };
|
||||||
|
|
||||||
const { metadata, info } = await pdf.getMetadata() ?? {}
|
const { metadata, info } = (await pdf.getMetadata()) ?? {};
|
||||||
// TODO: for better results, parse `metadata.getRaw()`
|
// TODO: for better results, parse `metadata.getRaw()`
|
||||||
book.metadata = {
|
book.metadata = {
|
||||||
title: metadata?.get('dc:title') ?? info?.Title,
|
title: metadata?.get("dc:title") ?? info?.Title,
|
||||||
author: metadata?.get('dc:creator') ?? info?.Author,
|
author: metadata?.get("dc:creator") ?? info?.Author,
|
||||||
contributor: metadata?.get('dc:contributor'),
|
contributor: metadata?.get("dc:contributor"),
|
||||||
description: metadata?.get('dc:description') ?? info?.Subject,
|
description: metadata?.get("dc:description") ?? info?.Subject,
|
||||||
language: metadata?.get('dc:language'),
|
language: metadata?.get("dc:language"),
|
||||||
publisher: metadata?.get('dc:publisher'),
|
publisher: metadata?.get("dc:publisher"),
|
||||||
subject: metadata?.get('dc:subject'),
|
subject: metadata?.get("dc:subject"),
|
||||||
identifier: metadata?.get('dc:identifier'),
|
identifier: metadata?.get("dc:identifier"),
|
||||||
source: metadata?.get('dc:source'),
|
source: metadata?.get("dc:source"),
|
||||||
rights: metadata?.get('dc:rights'),
|
rights: metadata?.get("dc:rights"),
|
||||||
}
|
};
|
||||||
|
|
||||||
const outline = await pdf.getOutline()
|
const outline = await pdf.getOutline();
|
||||||
book.toc = outline?.map(makeTOCItem)
|
book.toc = outline?.map(makeTOCItem);
|
||||||
|
|
||||||
const cache = new Map()
|
const cache = new Map();
|
||||||
book.sections = Array.from({ length: pdf.numPages }).map((_, i) => ({
|
book.sections = Array.from({ length: pdf.numPages }).map((_, i) => ({
|
||||||
id: i,
|
id: i,
|
||||||
load: async () => {
|
load: async () => {
|
||||||
const cached = cache.get(i)
|
const cached = cache.get(i);
|
||||||
if (cached) return cached
|
if (cached) return cached;
|
||||||
const url = await renderPage(await pdf.getPage(i + 1))
|
const url = await renderPage(await pdf.getPage(i + 1));
|
||||||
cache.set(i, url)
|
cache.set(i, url);
|
||||||
return url
|
return url;
|
||||||
},
|
},
|
||||||
size: 1000,
|
size: 1000,
|
||||||
}))
|
}));
|
||||||
book.isExternal = uri => /^\w+:/i.test(uri)
|
book.isExternal = (uri) => /^\w+:/i.test(uri);
|
||||||
book.resolveHref = async href => {
|
book.resolveHref = async (href) => {
|
||||||
const parsed = JSON.parse(href)
|
const parsed = JSON.parse(href);
|
||||||
const dest = typeof parsed === 'string'
|
const dest =
|
||||||
? await pdf.getDestination(parsed) : parsed
|
typeof parsed === "string" ? await pdf.getDestination(parsed) : parsed;
|
||||||
const index = await pdf.getPageIndex(dest[0])
|
const index = await pdf.getPageIndex(dest[0]);
|
||||||
return { index }
|
return { index };
|
||||||
}
|
};
|
||||||
book.splitTOCHref = async href => {
|
book.splitTOCHref = async (href) => {
|
||||||
const parsed = JSON.parse(href)
|
const parsed = JSON.parse(href);
|
||||||
const dest = typeof parsed === 'string'
|
const dest =
|
||||||
? await pdf.getDestination(parsed) : parsed
|
typeof parsed === "string" ? await pdf.getDestination(parsed) : parsed;
|
||||||
const index = await pdf.getPageIndex(dest[0])
|
const index = await pdf.getPageIndex(dest[0]);
|
||||||
return [index, null]
|
return [index, null];
|
||||||
}
|
};
|
||||||
book.getTOCFragment = doc => doc.documentElement
|
book.getTOCFragment = (doc) => doc.documentElement;
|
||||||
book.getCover = async () => renderPage(await pdf.getPage(1), true)
|
book.getCover = async () => renderPage(await pdf.getPage(1), true);
|
||||||
book.destroy = () => pdf.destroy()
|
book.destroy = () => pdf.destroy();
|
||||||
return book
|
return book;
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user