Satori/resvg: SVG text labels vanish when embedded as base64 data URI, woff fonts ignored
satori + @resvg/resvg-js OG-image pipeline: SVG <text> labels silently vanish when a chart SVG is embedded into the satori template as a data:image/svg+xml;base64 <img>. Everything else renders; no warning or error. Rendering the same SVG string directly through new Resvg(svg).render() draws the labels fine on a dev machine, so the bug looks intermittent. Second trap stacked on top: resvg's fontdb only parses ttf/otf, so .woff files passed via font.fontFiles are silently ignored (satori reads woff fine), and on a fontless deploy container loadSystemFonts:true finds nothing — text renders on dev laptops and vanishes in prod only.
Two root causes: (1) resvg parses nested SVG <image> content without the caller's fontdb, so <text> inside an embedded SVG is dropped even when font.fontFiles is set; (2) resvg's fontdb reads only ttf/otf and silently skips woff.
Fix:
- Pre-rasterize the inner SVG with a direct Resvg pass and embed the resulting PNG data URL in the satori template instead of the SVG. Oversample 2x for crispness: fitTo {mode:'width', value: size*2} with the <img> displayed at size.
- Give resvg a TTF derived from the committed woff at runtime: WOFF1 is just a zlib wrapper around sfnt tables (~45 lines with node:zlib inflateSync — 44-byte header, 20-byte table dir entries, inflate each table, rebuild the sfnt directory with searchRange/entrySelector), write it to tmpdir once per process, pass via font.fontFiles. Keeps the woff as the single committed font asset (satori consumes it directly).
- Set loadSystemFonts:false for determinism and speed (system fonts load per Resvg instance).
Regression test that catches both failure modes: render the SVG with your fontFiles vs {loadSystemFonts:false} alone and assert the PNG with fonts is strictly larger — glyphs must add pixels.