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;:stopmeasurements carryduration(native units), metadata carriesbackend,measured?,width,height, andresult(: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.
Summary
Functions
Whether a rasterizer backend is reachable (else render/3 errors out).
Render a scene to PNG bytes.
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).
The rasterizer backend that auto-detection would pick right now.
Functions
@spec available?() :: boolean()
Whether a rasterizer backend is reachable (else render/3 errors out).
@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.
@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).
@spec which_backend() :: :resvg_nif | :resvg_cli | :rsvg | :magick | :none
The rasterizer backend that auto-detection would pick right now.