Files
pi-imgen/tests/e2e/render.mjs
T
mozempk c74b3c8b5e fix(render): satisfy the Renderer contract the commands declare
index.ts was written against assumed renderer signatures (render(req)) while
typst.ts exposed render(resolved, jobDir, outPath). It type-checked -- only
typeof === 'function' was ever asserted -- but would have failed at runtime.

typst.ts now exports the request-shaped render()/renderAllFormats() the
commands' Renderer interface declares; the low-level entry points are
renderOne()/renderFormats(). smartCrop() likewise adopts retouch.ts's richer
contract, returning the size read back from the written file.

Verified e2e: a3-portrait -> PNG+PDF, ig-story -> PNG, Italian progress.
2026-08-27 09:55:48 +02:00

54 lines
2.6 KiB
JavaScript

import { createJiti } from "jiti";
import { mkdtempSync, copyFileSync, existsSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const jiti = createJiti(import.meta.url, { interopDefault: true });
const R = "/home/moze/Sorgenti/pi-imgen";
const typst = await jiti.import(join(R, "extensions/imgen/render/typst.ts"));
const cfgMod = await jiti.import(join(R, "extensions/imgen/config.ts"));
const job = mkdtempSync(join(tmpdir(), "imgen-e2e-"));
copyFileSync(join(R, "tests/art.png"), join(job, "art.png"));
const cfg = { ...cfgMod.DEFAULTS, outputDir: job };
const spec = {
slug: "sagra-castagna", format: "a3-portrait", template: "hero-bottom",
palette: { bg: "#f4efe6", ink: "#101014", accent: "#b03a24", scrim: "#000000" },
fonts: { display: "Archivo", body: "Archivo" },
art: { prompt: "autumn chestnuts", negative: "text, letters", seed: 7, source: "generate" },
blocks: [
{ role: "title", text: "Sagra della Castagna" },
{ role: "date", text: "Sabato 12 settembre, ore 21:00" },
{ role: "venue", text: "Chiostro di San Domenico, Città Alta" },
],
};
const resolved = await typst.resolveSpec(spec, cfg, join(job, "art.png"), { format: "a3-portrait" });
console.log("resolved.art_file =", resolved.art_file);
console.log("resolved.ink_on_art =", resolved.ink_on_art);
console.log("resolved.ink_on_bg =", resolved.ink_on_bg);
console.log("resolved.needs_scrim =", resolved.needs_scrim);
console.log("resolved.page =", JSON.stringify(resolved.page));
console.log("resolved.font_axes =", JSON.stringify(resolved.font_axes));
const TY = process.env.TYPST;
const out = join(job, "poster.pdf");
await typst.renderOne(resolved, job, out, { binary: TY });
const d = readFileSync(out);
const tb = /\/TrimBox\s*\[([^\]]*)\]/.exec(d.toString("latin1"));
if (!tb) { console.log("TrimBox: ABSENT"); process.exit(1); }
const [x0,y0,x1,y1] = tb[1].split(/\s+/).map(Number);
console.log(`TrimBox mm = ${((x1-x0)/72*25.4).toFixed(1)} x ${((y1-y0)/72*25.4).toFixed(1)}`);
console.log("bundle staged =", existsSync(join(job, ".typst/lib.typ")), existsSync(join(job, ".typst/hero-bottom.typ")));
// The request-shaped API is what the commands actually call.
const outcome = await typst.render({
spec, artPath: join(job, "art.png"), outDir: job, config: cfg,
formats: ["a3-portrait", "ig-story"], pdf: true,
onProgress: (m) => console.log(" progresso:", m),
});
console.log("files =", outcome.files.map(f => f.split("/").pop()).join(", "));
console.log("warnings =", outcome.warnings ?? "(nessuno)");
console.log("OK");