Scene → SVG generator. The shared intermediate representation: the editor preview and (later) the PNG rasterizer both consume this output, so what you edit is what renders.
Pure and deterministic — render/3 is a function of (scene, values).
All user-authored strings are XML-escaped, and canvas dimensions are
clamped as an OOM guard.
Layout (wrap, anchors, auto-width, place) is resolved by
OpenFresco.Layout before emission — measurement-accurate when
measure: true and resvg are available.
Security contract
Scene JSON is potentially attacker-influenced DB content rendered into admin DOMs (via the editor) and rasterizers, so safety is a contract, not an emergent property:
- Fixed emitted tag set — the generator only ever emits:
svg defs linearGradient stop mask clipPath rect image use text tspan filter feGaussianBlur feFlood feComposite feMerge feMergeNode g. Neverscript,foreignObject,a, or event-handler attributes.OpenFresco.Conformance.check_svg/1verifies output against this allowlist (use it in host CI). - Every user-authored string is XML-escaped at the emission site
(
&,<,>,",') — text content, ids, fonts, colors, hrefs. - URL scheme policy — image hrefs are
data:(or plain relative paths) unless the host opts intoremote_images: :allow;file:and exotic schemes are always denied (seeresolve_href). - Stage-vs-PNG divergence — browsers execute things resvg ignores,
which is exactly why the scheme policy and tag allowlist are
enforced at generation time, identically for both surfaces. Hosts
embedding stages should still ship a CSP without
unsafe-inlinescript; the generated SVG needs no scripts or external fetches.
Summary
Functions
Render a scene to an SVG binary, resolving values.
Render a scene as separable parts:
%{width, height, defs, body, selection} (binaries).
Functions
@spec render(OpenFresco.Scene.t(), map(), keyword()) :: binary()
Render a scene to an SVG binary, resolving values.
Options:
:globals— a map of[[global]]values (site host, page URL, locale…) merged withvaluesbefore substitution. Slot values win on a key collision.:max_dimension— clamp canvas width/height (default10_000).
@spec render_parts(OpenFresco.Scene.t(), map(), keyword()) :: %{ width: pos_integer(), height: pos_integer(), defs: binary(), body: binary(), selection: binary() }
Render a scene as separable parts:
%{width, height, defs, body, selection} (binaries).
This exists so LiveView hosts (the editor) can emit defs, body, and
selection as separate dynamics inside one <svg> — LiveView then
diffs them independently, and a selection change or drag commit doesn't
retransmit multi-megabyte data-URL images. Combine with
split_images: true (the editor's default), which moves <image>
elements into defs and leaves lightweight <use> references in the
paint order, so body stays small even for image-heavy scenes.
render/3 is render_parts/3 composed into a single binary; both are
pure functions of (scene, values, opts).