OpenFresco.Svg (open_fresco v0.2.0)

Copy Markdown View Source

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. Never script, foreignObject, a, or event-handler attributes. OpenFresco.Conformance.check_svg/1 verifies 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 into remote_images: :allow; file: and exotic schemes are always denied (see resolve_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-inline script; 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

render(scene, values \\ %{}, opts \\ [])

@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 with values before substitution. Slot values win on a key collision.
  • :max_dimension — clamp canvas width/height (default 10_000).

render_parts(scene, values \\ %{}, opts \\ [])

@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).