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.
51 lines
2.1 KiB
TypeScript
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";
|