Add the Northline template family: Purple Pop and Citrus Split
Purple Pop (cream page, purple pills, asterisk and dome decor, opt-in logo) and Citrus Split (grey split panel, halftone chevrons, icon contacts, logo in the measured slot) are rebuilt from the reference PDFs on the engine with A4/Letter support, GST slots and zero audit errors. Adds static DM Sans, Jost and Poppins 800 as look-alikes for the commercial fonts. The picker now lists ten templates.
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
// Side-by-side review of the reference-based templates: the reference original (left) against our render (right), plus
|
||||
// the multi-page, Letter, long-text and logo-variant samples. Run: npm run templates:compare [-- crimson-grid slate-band]
|
||||
// Output: /tmp/invoice-c4/ (override with --out=DIR). With --numbers it also prints text baselines (reference vs ours) for tuning.
|
||||
// the multi-page, Letter, long-text and logo-variant samples. Run: npm run templates:compare [-- purple-pop citrus-split]
|
||||
// Output: /tmp/invoice-c5/ (override with --out=DIR). With --numbers it also prints text baselines (reference vs ours) for tuning.
|
||||
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { CANVA_DIR, loadCanvas, loadRenderer, rasterize, textItems } from "./lib.mjs";
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const OUT = argv.find((a) => a.startsWith("--out="))?.slice(6) ?? "/tmp/invoice-c4";
|
||||
const OUT = argv.find((a) => a.startsWith("--out="))?.slice(6) ?? "/tmp/invoice-c5";
|
||||
const SCALE = 1.6;
|
||||
const PDF_NO = { marble: 1, highlighter: 2, monolith: 3, linea: 4, monogram: 5, serenity: 6, "crimson-grid": 7, "cobalt-stripe": 8, "slate-band": 9, "teal-swoosh": 10, "tangerine-ledger": 11, "purple-pop": 12, "citrus-split": 13 };
|
||||
const ROWS = { linea: 7, "crimson-grid": 3, "tangerine-ledger": 3, "slate-band": 5, "teal-swoosh": 5 };
|
||||
const ROWS = { linea: 7, "crimson-grid": 3, "tangerine-ledger": 3, "slate-band": 5, "teal-swoosh": 5, "purple-pop": 3, "citrus-split": 3 };
|
||||
const DARK = ["slate-band"];
|
||||
const args = process.argv.slice(2);
|
||||
const numbers = args.includes("--numbers");
|
||||
const ids = args.filter((a) => !a.startsWith("--"));
|
||||
const targets = ids.length ? ids : ["crimson-grid", "tangerine-ledger", "slate-band", "teal-swoosh"];
|
||||
const targets = ids.length ? ids : ["purple-pop", "citrus-split"];
|
||||
|
||||
const LONG = "Unit 4, Third Floor, The Grand Heritage Business Centre, Plot 1187-B, Old Airport Road Extension, Opposite the Municipal Corporation Ward Office, Domlur Second Stage, Bengaluru Urban District, Karnataka State, India - 560071; Registered office: Level 12, Tower C, Global Technology Park, Outer Ring Road, Marathahalli, Bengaluru - 560103";
|
||||
const TOKEN = "Supercalifragilisticexpialidocious-Pneumonoultramicroscopic-1234567890";
|
||||
@@ -45,10 +45,13 @@ async function main() {
|
||||
const items = (hsn) => NAMES.slice(0, rows).map((description, i) => ({ id: i + 1, description, mode: "rate", rate: 100, unit: "hour", quantity: 1, amount: 0, sortOrder: i, ...(hsn ? { hsnSac: "998397" } : {}) }));
|
||||
const sample = (over = {}) => ({ name: `s-${id}`, items: items(over.registered !== false), signature: true, invoice: { poNumber: "", notes: "Payment due within 30 days.\nLate payments attract 2% monthly interest." }, ...over });
|
||||
const render = async (spec, size = "A4") => {
|
||||
const prefs = r.prefsFor(size, { templateId: id });
|
||||
// Purple Pop has no placeholder, so its logo samples turn the opt-in on (Linea's compare stays logo-free, like format.pdf).
|
||||
const prefs = r.prefsFor(size, { templateId: id, ...(id === "purple-pop" && spec.logo && spec.logo !== "none" ? { logo: { show: true } } : {}) });
|
||||
return r.renderFixture(spec, prefs);
|
||||
};
|
||||
|
||||
// Templates with no reference placeholder show a logo only when the user turns it on.
|
||||
const OPT_IN = ["linea", "purple-pop"];
|
||||
const withLogo = await render(sample({ logo: "real" }));
|
||||
await side(reference, await rasterize(withLogo.result.bytes, 1, SCALE), `compare-${id}.png`);
|
||||
const noLogo = await render(sample({ registered: false, logo: "none" }));
|
||||
@@ -61,8 +64,8 @@ async function main() {
|
||||
for (const it of b) console.log(` ours ${String(it.baseline).padStart(6)} ${String(it.x).padStart(6)} w${String(it.w).padStart(6)} s${it.size} ${it.str.slice(0, 40)}`);
|
||||
}
|
||||
|
||||
if (id === "linea") {
|
||||
// The opt-in logo band (off by default so Linea keeps matching format.pdf).
|
||||
if (OPT_IN.includes(id)) {
|
||||
// The opt-in logo (off by default: Linea keeps matching format.pdf, Purple Pop has no placeholder).
|
||||
const shown = await r.renderFixture(sample({ logo: "real" }), r.prefsFor("A4", { templateId: id, logo: { show: true } }));
|
||||
writeFileSync(path.join(OUT, `${id}-A4-logo-optin.png`), (await rasterize(shown.result.bytes, 1, SCALE)).toBuffer("image/png"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user