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

The PNG facade: scene → SVG → raster → PNG bytes. Browser-free and
deterministic.

`render/3` **never raises** — every failure (a missing rasterizer, a
crafted scene, a subprocess timeout, a blown budget) is an
`{:error, term}`. It performs no network I/O; image inputs in the scene
must be `data:` URLs (or resolved to bytes by the `:resolver` — see
`OpenFresco.render_svg/3`).

## Budgets

Canvas dimensions alone understate the work a crafted scene can demand,
so total work is budgeted too (all overridable per call):

  * `:max_dimension` — canvas clamp (default 10 000 px per side)
  * `:max_elements` — element count (default 1 000)
  * `:max_image_bytes` — total *decoded* size of inlined images,
    estimated from base64 payloads (default 64 MiB)
  * `:max_svg_bytes` — the generated SVG document (default 96 MiB)

Exceeding a budget returns `{:error, {:budget, which, actual, limit}}`.

## Telemetry

  * `[:open_fresco, :render, :start | :stop | :exception]` — a span per
    PNG render; `:stop` measurements carry `duration` (native units),
    metadata carries `backend`, `measured?`, `width`, `height`, and
    `result` (`:ok` / `:error`).
  * `[:open_fresco, :element, :empty]` — emitted by the SVG generator
    whenever an element paints nothing (unknown type, unresolved or
    policy-denied image), with the element id and reason.

## Backend choice & fallback

The backend auto-detects in preference order (see
`OpenFresco.Rasterizer.which_backend/0`). Different backends paint
different pixels — for cache-honest or reproducible output pass
`backend: :resvg_nif` (etc.) explicitly: a forced backend that isn't
available **hard-errors instead of silently falling back**. The
manifest (below) records what actually ran.

## The render manifest

Success meta includes `:manifest` — the actual pixel-producing inputs,
for cache keys that are honest about determinism:

    %{
      generator: "ofsvg-1",            # SVG generator revision
      renderer: "rast-1",              # rasterization contract revision
      backend: :resvg_nif,             # what actually painted
      backend_version: "0.5.1",
      measured: true,                  # measurement-accurate wrap?
      fonts: "system" | digest,        # OpenFresco.Fonts.digest/1
      svg_sha256: "…"                  # digest of the exact SVG painted
    }

`svg_sha256` subsumes scene, values, globals, resolver output, and
layout — two renders with equal manifests produce identical pixels on
the same host.

## Concurrency & crash safety

The resvg NIF (rustler) catches Rust panics and returns errors, but a
hard native abort (allocator exhaustion on a huge decode) can still take
the BEAM down — that's the failure envelope of any in-process NIF. The
budgets above bound allocations; hosts that want full isolation on the
crawler path should force `backend: :resvg_cli` (a separate OS process,
killed on timeout) and accept the per-render subprocess cost. For
crawler-facing endpoints, render through a bounded pool:

    Task.Supervisor.async_nolink(MyApp.RenderSup, fn ->
      OpenFresco.render(scene, values)
    end)

with `max_children` sized to your CPU budget.

# `available?`

```elixir
@spec available?() :: boolean()
```

Whether a rasterizer backend is reachable (else `render/3` errors out).

# `render`

```elixir
@spec render(OpenFresco.Scene.t(), map(), keyword()) ::
  {:ok, binary(),
   %{width: pos_integer(), height: pos_integer(), manifest: map()}}
  | {:error, term()}
```

Render a scene to PNG bytes.

Returns `{:ok, png_binary, %{width: w, height: h, manifest: manifest}}`
or `{:error, term}`.

`values` / `opts` are as `OpenFresco.render_svg/3` (`:globals`,
`:max_dimension`, `:remote_images`, `:resolver`, `:font_dirs`, …), plus:
  * `:timeout` — subprocess-backend wallclock ms (default 5000).
  * `:backend` — force a rasterizer backend; missing forced backends
    error instead of falling back.
  * the budget options documented above.

# `version`

```elixir
@spec version() :: String.t()
```

A version string for cache keys — folds the SVG generator version and the
rasterization revision. For caches keyed on the full determinism surface
use the per-render `:manifest` instead (backend, fonts, and the exact
SVG all participate there).

# `which_backend`

```elixir
@spec which_backend() :: :resvg_nif | :resvg_cli | :rsvg | :magick | :none
```

The rasterizer backend that auto-detection would pick right now.

---

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