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.
59 lines
2.5 KiB
TypeScript
59 lines
2.5 KiB
TypeScript
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
|
|
/** `@napi-rs/canvas` is an optional dependency; null when its native binary is not installed. */
|
|
export async function loadCanvas(): Promise<typeof import("@napi-rs/canvas") | null> {
|
|
try {
|
|
return await import("@napi-rs/canvas");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Number of non-white pixels in the box (x0,y0,x1,y1 in pt from the top-left) of page 1 drawn at `scale`. */
|
|
export async function inkPixels(
|
|
canvasLib: typeof import("@napi-rs/canvas"),
|
|
bytes: Uint8Array,
|
|
box: { x0: number; y0: number; x1: number; y1: number },
|
|
scale = 2,
|
|
): Promise<number> {
|
|
const task = getDocument({ data: new Uint8Array(bytes), useSystemFonts: false, disableFontFace: true });
|
|
const doc = await task.promise;
|
|
try {
|
|
const page = await doc.getPage(1);
|
|
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;
|
|
const x = Math.floor(box.x0 * scale);
|
|
const y = Math.floor(box.y0 * scale);
|
|
const w = Math.ceil((box.x1 - box.x0) * scale);
|
|
const h = Math.ceil((box.y1 - box.y0) * scale);
|
|
const data = ctx.getImageData(x, y, w, h).data;
|
|
let ink = 0;
|
|
for (let i = 0; i < data.length; i += 4) if (data[i] < 250 || data[i + 1] < 250 || data[i + 2] < 250) ink++;
|
|
return ink;
|
|
} finally {
|
|
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();
|
|
}
|
|
}
|