wave E stage 2: renderSvg goes native-first - resvg in-process

Visual-maker SVGs never execute script, so a browser added nothing but a
process. resvg rasterizes in-process now (white background and fitTo
width mirror the old Chrome-page semantics; svgViewport unchanged,
quirks included for parity). The old Chrome path stays verbatim as the
fallback for exotic-SVG parse failures - it was the only path before,
so nothing regresses.

The resvg binary (4MB per platform, one per build) rides the standalone
via the same tracing pattern. Gates 1519/1519; native path pinned
against real SVGs (intrinsic size, viewBox-only 900 default, opts
override, system-font text).
This commit is contained in:
amosblomqvist
2026-10-11 06:52:32 +03:00
parent 951c7704b9
commit e9cf841ce7
5 changed files with 322 additions and 0 deletions
+20
View File
@@ -261,6 +261,26 @@ function crc32(buf: Buffer): number {
}
async function renderSvg(svg: string, opts: RenderSvgOptions = {}): Promise<Buffer> {
// NATIVE FIRST (Wave E stage 2, owner 2026-10-10): resvg rasterizes
// static SVG in-process — visual-maker SVGs never execute script, so a
// browser adds nothing but a process. White background mirrors the old
// Chrome page (html,body{background:#fff}); fitTo width mirrors
// svgViewport semantics (attrs win, viewBox-only gets the 900 default,
// opts override). The browser path below stays as the fallback for
// exotic-SVG features resvg cannot parse — failures there used to be
// the ONLY path, so nothing regresses.
const { width } = svgViewport(svg, opts);
try {
const { Resvg } = await import("@resvg/resvg-js");
const resvg = new Resvg(svg, {
fitTo: { mode: "width", value: width },
font: { loadSystemFonts: true },
background: "#ffffff",
});
return providerSafePng(resvg.render().asPng());
} catch {
/* fall through to the browser path */
}
const dir = tempDir("lh-visual-");
const file = path.join(dir, "figure.html");
fs.writeFileSync(