# `OpenFresco.Svg`
[🔗](https://github.com/alexdont/open_fresco/blob/v0.2.0/lib/open_fresco/svg.ex#L1)

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.

# `render`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
