Files
Voiced/src/lib/pdfjs.ts
T
xavierk cedf4839bd Replace the HTML preview with a worker-rendered PDF preview
Rendering moves into a module Web Worker behind a latest-wins RenderClient
with a watchdog and a handshake-based main-thread fallback. The preview
shows the real PDF through pdf.js at 2x with zoom, page indicator, audit
issues popover and a focus modal. A4/Letter and margin controls are frozen
into each invoice at issue and can be saved as the default (migration M3).
react-pdf and pdf.js are lazy-loaded; the main chunk shrinks from 1.8 MB
to 0.5 MB. The HTML twin and its styles are removed.
2026-10-04 05:47:19 +05:30

51 lines
2.1 KiB
TypeScript

import { getDocument, GlobalWorkerOptions, type PDFDocumentProxy, type PDFPageProxy, type RenderTask } from "pdfjs-dist";
import { installStreamAsyncIterator } from "./streamPolyfill";
GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString();
installStreamAsyncIterator();
export type { PDFDocumentProxy, PDFPageProxy };
/**
* Opens PDF bytes. The bytes are copied because pdf.js transfers the buffer to its worker, which
* would detach the caller's array. Glyphs are drawn as paths (`disableFontFace`) so the preview looks
* the same on WebKitGTK and WebView2. (pdf.js 6 dropped `isEvalSupported`; it no longer evaluates font code.)
*/
export function openPdf(bytes: Uint8Array): Promise<PDFDocumentProxy> {
return getDocument({ data: bytes.slice(), disableFontFace: true }).promise;
}
/** Releases the document and its worker resources. */
export function closePdf(doc: PDFDocumentProxy): Promise<void> {
return doc.loadingTask.destroy();
}
const tasks = new WeakMap<object, RenderTask>();
/** Cancels whatever render is running for `slot` (a canvas, or any object the caller keys on). */
export function cancelPageRender(slot: object): void {
tasks.get(slot)?.cancel();
tasks.delete(slot);
}
/**
* Draws `page` into `canvas` at `scale` device pixels per PDF point, sizing the backing store to
* match. Starting a render for a slot cancels its previous one; a cancelled render rejects with
* pdf.js's RenderingCancelledException (see isRenderCancelled).
*/
export async function renderPageToCanvas(page: PDFPageProxy, canvas: HTMLCanvasElement, scale: number, slot: object = canvas): Promise<void> {
cancelPageRender(slot);
const viewport = page.getViewport({ scale });
canvas.width = Math.ceil(viewport.width);
canvas.height = Math.ceil(viewport.height);
const task = page.render({ canvas, viewport });
tasks.set(slot, task);
try {
await task.promise;
} finally {
if (tasks.get(slot) === task) tasks.delete(slot);
}
}
export const isRenderCancelled = (err: unknown): boolean => (err as { name?: string } | null)?.name === "RenderingCancelledException";