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.
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import { createElement as h, type ReactElement } from "react";
|
|
import { Document, Image, Page } from "@react-pdf/renderer";
|
|
|
|
export interface AssemblePage {
|
|
/** A baseline JPEG; react-pdf embeds it as is (DCTDecode passthrough, no re-encoding). */
|
|
jpeg: Uint8Array;
|
|
widthPt: number;
|
|
heightPt: number;
|
|
}
|
|
|
|
export interface AssembleInput {
|
|
pages: AssemblePage[];
|
|
/** The only metadata written: a flattened file must not leak the client name or GSTINs. */
|
|
title: string;
|
|
}
|
|
|
|
/**
|
|
* An image-only document: one full-bleed JPEG per page. No author, subject or keywords, and the
|
|
* title is whatever the caller passes (the invoice number). The images go in as untyped Blobs because
|
|
* that is the form react-pdf's browser build resolves without sniffing the type.
|
|
*/
|
|
export function assembleImagePdf({ pages, title }: AssembleInput): ReactElement {
|
|
return h(
|
|
Document,
|
|
{ title },
|
|
...pages.map((p, i) =>
|
|
h(
|
|
Page,
|
|
{ key: i, size: [p.widthPt, p.heightPt] as [number, number], style: { padding: 0 }, wrap: false },
|
|
h(Image, {
|
|
src: new Blob([p.jpeg as BlobPart]) as never,
|
|
style: { width: p.widthPt, height: p.heightPt },
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|