issueAndArchive keeps an issued invoice even when rendering or archiving fails (Issued - not archived, with a Render & archive action). The export menu offers a searchable PDF or a 300 DPI image-only PDF whose metadata is the invoice number alone; flatten failures offer Retry or searchable export and never silently lower the resolution. History re-exports the original as issued by default and warns when a re-render's layout fingerprint differs. The render client gains a FIFO assemble lane that previews cannot supersede. A Diagnostics panel runs fixtures in the real worker and compares against goldens generated from Node.
58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
import { flattenPages, type CanvasLike, type FlattenResult, type PdfDocLike } from "../pdf/flatten";
|
|
|
|
/** Lets the browser paint and handle input; falls back to a timer when rAF is paused (hidden window). */
|
|
export const yieldToBrowser = (): Promise<void> =>
|
|
new Promise<void>((resolve) => {
|
|
let done = false;
|
|
const finish = () => {
|
|
if (!done) {
|
|
done = true;
|
|
resolve();
|
|
}
|
|
};
|
|
setTimeout(finish, 50);
|
|
requestAnimationFrame(() => setTimeout(finish, 0));
|
|
});
|
|
|
|
/** JPEG bytes of a canvas via the browser's encoder. */
|
|
export function encodeCanvasJpeg(canvas: CanvasLike, quality: number): Promise<Uint8Array> {
|
|
return new Promise((resolve, reject) => {
|
|
(canvas as unknown as HTMLCanvasElement).toBlob(
|
|
(blob) => {
|
|
if (!blob) return reject(new Error("the browser could not encode the page image (out of memory?)"));
|
|
blob.arrayBuffer().then((buf) => resolve(new Uint8Array(buf)), reject);
|
|
},
|
|
"image/jpeg",
|
|
quality,
|
|
);
|
|
});
|
|
}
|
|
|
|
export interface BrowserFlattenOptions {
|
|
signal?: AbortSignal;
|
|
onProgress?: (page: number, pages: number) => void;
|
|
}
|
|
|
|
/** Flattens PDF bytes at 300 DPI with pdf.js and a DOM canvas; opens and closes the document itself. */
|
|
export async function flattenInBrowser(bytes: Uint8Array, opts: BrowserFlattenOptions = {}): Promise<FlattenResult> {
|
|
const { openPdf, closePdf } = await import("./pdfjs");
|
|
const doc = await openPdf(bytes);
|
|
try {
|
|
return await flattenPages({
|
|
pdf: doc as unknown as PdfDocLike,
|
|
createCanvas: (width, height) => {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
return canvas as unknown as CanvasLike;
|
|
},
|
|
encodeJpeg: encodeCanvasJpeg,
|
|
yieldToUi: yieldToBrowser,
|
|
signal: opts.signal,
|
|
onProgress: opts.onProgress,
|
|
});
|
|
} finally {
|
|
await closePdf(doc).catch(() => {});
|
|
}
|
|
}
|