ADR-002: Node + Playwright Renderer for Image Composition
- Status
-
Accepted
- Date
-
2026-06-09
- Authors
-
Netresearch DTT GmbH
Context
Two of the three artifacts are images built from branded HTML: the Schaubild diagram and the Instagram story. The reference Schaubild variant and the flat story are exact renderings of an LLM-authored, theme-wrapped HTML fragment — every label, number and term must come out pixel-correct, with web fonts and CSS layout honoured. A PHP imaging library cannot lay out and rasterise arbitrary HTML/CSS; only a real browser engine can.
The artifacts must also support an AI-background variant: a generated background image with the HTML text layer composited over it. The text layer therefore has to be renderable on a transparent canvas so the background shows through.
Decision
Render HTML to PNG with a bundled Node script that drives the system Chromium through Playwright, and composite separately with GD.
-
A single CommonJS renderer.
Resources/reads HTML from stdin and writes a PNG, takingPrivate/ Node Renderer/ render. cjs --,width --(an integer orheight auto),--,scale --andout --/transparent --. It usesopaque playwright-only — Chromium is the host's apt binary, located viacore CHROMIUM_, withPATH PLAYWRIGHT_so Playwright never downloads its own.SKIP_ BROWSER_ DOWNLOAD=1 waitandUntil: 'networkidle' document.ensure web fonts are loaded before the screenshot.fonts. ready Hardening (2026-09). The HTML contains LLM output derived from the fetched source, so it is rendered as untrusted: the browser context has JavaScript disabled and service workers blocked, and a context-level route aborts every request except
data:andblob:URLs. The route does not see a<link rel="prefetch">or the target of a redirect, so Chromium is also launched with a proxy address nothing listens on and no bypass list: any request the route misses fails at the network layer, loopback addresses included. The renderer has no network access at all.The templates still
@importtheir web fonts (Raleway, Open Sans, Inter) from Google Fonts.render.replaces each such rule, before the HTML reaches the page, withcjs @font-rules over the unmodified upstream variable fonts inface Resources/(SIL Open Font License, source commit and checksums in itsPrivate/ Fonts fonts.), inlined asjson data:URLs; the storedsource_keeps thehtml @import. Inter is pinned toopsz14, the instance Google Fonts serves. The Schaubild and Story PNGs render byte-identical to the Google-loaded renders, and the PDFs rasterise identically. - A PHP boundary that shells out safely.
Playwrightbuilds the argv, passes HTML on stdin (avoiding argv length limits and shell quoting), and runs the process through aHtml To Image Renderer Process(Symfony Process) seam that unit tests can replace.Runner Interface height=nullrenders full-page/auto-height (the diagram); a fixed height clips to the viewport (the story);transparentmaps toomitfor the overlay layers.Background - Composition stays in PHP with GD.
Gdoverlays the transparent foreground PNG onto the AI background. The foreground defines the output canvas; the background — whose aspect ratio rarely matches, sinceImage Compositor gpt-only emits 1:1 / 3:2 / 2:3 — is scaled to cover (centre-cropped, no distortion), then alpha-composited under the foreground. GD is used because Imagick is not installed in this stack.image- 1
Consequences
- HTML renderings are pixel-accurate: web fonts, gradients and layout match the branded templates, and labels stay exactly as authored.
- A transparent text layer over a cover-scaled AI background gives the
html_and KI-background variants without distorting the designed layout.bg - The runtime gains a hard dependency on a Node.js runtime plus a system
chromiumbinary on every host that generates — including the worker. This is documented as an explicit requirement (see Installation). - Each render is a process spawn; it is bounded by a process timeout and a
--Chromium launch suited to containerised execution.no- sandbox - The process boundary (stdin HTML, argv flags, a swappable process runner) keeps the renderer fully unit-testable without a browser.