Rebuild the Classic template on the engine with A4/Letter and a template harness

Classic now renders through buildRenderModel, computeFrame, the template
and renderCore, with S.No and HSN/SAC columns, GST slots only through
checked Slot components, taxable value, reverse-charge line, cancelled tag,
continuation band and Page n of N. Letter absorbs its height deficit in
elastic gaps. A harness runs 15 fixtures on A4 and Letter plus an item
sweep and asserts zero audit errors, repeated headers, no split blocks,
rupee extraction and deterministic output. The legacy InvoiceDocument is
removed.
This commit is contained in:
2026-10-04 05:31:14 +05:30
parent 8d4f8e666b
commit a014a907e4
40 changed files with 2185 additions and 832 deletions
+7 -3
View File
@@ -28,11 +28,15 @@ export interface RenderedPdf {
export async function renderPdfText(element: ReactElement): Promise<RenderedPdf> {
// The renderToBuffer type wants a <Document> element; any element that renders one works.
const buf = await renderToBuffer(element as Parameters<typeof renderToBuffer>[0]);
const raw = buf.toString("latin1");
const bytes = new Uint8Array(buf);
return parsePdfText(new Uint8Array(buf));
}
/** The text boxes pdf.js finds on each page of already-rendered PDF bytes. */
export async function parsePdfText(bytes: Uint8Array): Promise<RenderedPdf> {
const raw = Buffer.from(bytes).toString("latin1");
const task = getDocument({
data: new Uint8Array(buf),
data: new Uint8Array(bytes),
useSystemFonts: false,
disableFontFace: true,
});
+18
View File
@@ -38,3 +38,21 @@ export async function inkPixels(
await task.destroy();
}
}
/** PNG of one page (1-based) at `scale`, white background; for visual review only. */
export async function pagePng(canvasLib: typeof import("@napi-rs/canvas"), bytes: Uint8Array, pageNo: number, scale = 1.5): Promise<Buffer> {
const task = getDocument({ data: new Uint8Array(bytes), useSystemFonts: false, disableFontFace: true });
const doc = await task.promise;
try {
const page = await doc.getPage(pageNo);
const viewport = page.getViewport({ scale });
const canvas = canvasLib.createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
await page.render({ canvasContext: ctx as never, canvas: canvas as never, viewport }).promise;
return canvas.toBuffer("image/png");
} finally {
await task.destroy();
}
}