// Browser-only module worker: the one place react-pdf runs in the app. Not importable from Node tests. import { installStreamAsyncIterator } from "../../lib/streamPolyfill"; import { initBrowserRendering } from "./browserInit"; import { runAssembleJob } from "./assembleJob"; import { renderInvoicePdf } from "./invoice"; import { registerJobFonts } from "./userFontJob"; import type { WorkerMessage, WorkerRequest } from "./jobs"; interface WorkerScope { onmessage: ((event: MessageEvent) => void) | null; postMessage(message: WorkerMessage, options?: { transfer: Transferable[] }): void; } const scope = self as unknown as WorkerScope; installStreamAsyncIterator(); const post = (message: WorkerMessage, transfer: Transferable[] = []) => scope.postMessage(message, { transfer }); const failure = (id: number | null, err: unknown): WorkerMessage => ({ type: "error", id, message: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined, }); // Handing a buffer over instead of copying it only works when the view covers all of it. const whole = (b: Uint8Array) => b.byteOffset === 0 && b.byteLength === b.buffer.byteLength; const started = initBrowserRendering(); scope.onmessage = async (event) => { const request = event.data; if (request?.type === "assemble-images") { const { job } = request; try { const out = await runAssembleJob(job); const bytes = whole(out.bytes) ? out.bytes : out.bytes.slice(); post({ type: "assembled", result: { ...out, bytes } }, [bytes.buffer]); } catch (err) { post(failure(job.id, err)); } return; } if (request?.type !== "render") return; const { job } = request; try { await started.catch(() => initBrowserRendering()); await registerJobFonts(job.prefs, job.userFonts); const t0 = performance.now(); const out = await renderInvoicePdf({ model: job.model, prefs: job.prefs }); const ms = Math.round(performance.now() - t0); const bytes = whole(out.bytes) ? out.bytes : out.bytes.slice(); post( { type: "result", result: { id: job.id, bytes, pages: out.pages, issues: out.issues, fingerprint: out.fingerprint, ms } }, [bytes.buffer], ); } catch (err) { post(failure(job.id, err)); } }; started.then( () => post({ type: "ready" }), (err) => post(failure(null, err)), );