OpenFresco.Scene (open_fresco v0.1.0)

Copy Markdown View Source

The scene model: a fixed-size canvas plus a z-ordered list of elements.

A scene is a design with holes. Text and image values may be literals or placeholder/1 references resolved at render time against a values map (see OpenFresco.render_svg/3). The struct is versioned and serializes to/from JSON (to_json!/1 / from_json!/1) so it can be stored — e.g. inside a Fresco canvas's extensions["open_fresco"] blob — and migrated forward (migrate/1).

Elements

Elements are plain maps (mirroring Fresco.Canvas's image maps) with a :type tag and a common :box (%{x, y, w, h}), :id, and :z (z-order, ascending). Build them with the constructors:

  • text/2 — a text block (:value, :size, :weight, :font, :fill, :align, :line_height)
  • image/2 — a positioned image (:value, :fit, :radius)
  • shape/2 — a filled (rounded) rectangle (:fill, :radius)
  • button/2 — a shape + centered label composite (:label, :preset, :fill, :text_fill, :radius)

Fills

Any fillable surface takes a fill built with solid/1, gradient/2, or image_fill/2. Fills are tagged maps so a fill-type switch in an editor can round-trip the inactive variants' values.

Values

A value is either a literal binary or placeholder(key). resolve/2 turns a value into {:ok, resolved} or :unresolved against a map.

Summary

Functions

Append an element (map from a constructor) to the scene.

An anchor: position an element relative to another (by id). edge is :below / :above / :left / :right; :gap (default 16) is the space between; :align (:start / :center / :end, default :start) aligns the cross-axis. Pass as the :anchor option to an element builder. When the target's rendered height changes (e.g. a title wraps), the anchored element moves during layout. Chains resolve in order; cycles are rejected.

A button composite (rounded shape + centered label). :preset is :solid (fill), :outline (stroke only), or :soft (tinted fill). auto_width: true sizes the box to the measured label plus :padding (horizontal, default 24) during layout; otherwise the explicit :box wins.

Parse a scene from a JSON string, running migrate/1.

Build a scene from a decoded JSON map (string keys), running migrate/1.

A linear-gradient fill. angle is in degrees, measured clockwise from "to the right" (0 = left→right, 90 = top→bottom). Each stop is %{offset: 0..1, color: "#rrggbb", alpha: 0..1}.

A positioned-image element. :value resolves to an href (or draws the unresolved stand-in). Supports an :underlay for legibility, same as text.

An image fill. fit is :cover, :contain, or :stretch. Used as a canvas :background, an optional :overlay_color (:dark/:light) + :overlay_opacity tints the image (og's hero-darkening legibility trick).

Migrate an older scene to the current version. Currently the identity for "1"; the seam is here so stored scenes survive format changes.

A new empty scene. :width / :height default to the 1200×630 OG size; :background is an optional fill (default nil = transparent).

A canvas-relative placement (CSS-inset style), resolved against the canvas size at render time — so a template built with :place adapts to any canvas dimensions (landscape, square, portrait). Pass as an element's :place option; it overrides the static :box position on the axes it specifies.

A placeholder value, resolved at render time against the values map.

Set the canvas size (elements with :place re-place at render time).

Resolve a value against a values map.

A filled (rounded) rectangle element. :fill may be solid/gradient/image; :stroke (+ :stroke_width) adds a border; :underlay a legibility box.

A solid-color fill.

A preset-text element — text/2 with type: :stamp. Mirrors og's stamp (a reusable text snippet, e.g. a fixed byline); :preset is an alias for :value. Renders identically to text.

A text-block element. Text word-wraps to :box width and vertically aligns via :valign (:top / :middle / :bottom). An :underlay (opacity > 0) draws a soft glow behind the text for legibility over busy backgrounds — :underlay_color is :dark or :light.

Serialize to a JSON string.

Serialize to a JSON-ready map (string keys).

Types

box()

@type box() :: %{x: number(), y: number(), w: number(), h: number()}

element()

@type element() :: %{:id => String.t(), :type => atom(), optional(atom()) => any()}

fill()

@type fill() ::
  %{type: :solid, color: String.t()}
  | %{type: :gradient, angle: number(), stops: [gradient_stop()]}
  | %{type: :image, value: value(), fit: :cover | :contain | :stretch}

gradient_stop()

@type gradient_stop() :: %{offset: number(), color: String.t(), alpha: number()}

t()

@type t() :: %OpenFresco.Scene{
  canvas: %{width: number(), height: number(), background: fill() | nil},
  elements: [element()],
  version: String.t()
}

value()

@type value() :: String.t() | %{placeholder: String.t()}

Functions

add(scene, element)

@spec add(t(), element()) :: t()

Append an element (map from a constructor) to the scene.

anchor(to, edge, opts \\ [])

@spec anchor(String.t(), atom(), keyword()) :: map()

An anchor: position an element relative to another (by id). edge is :below / :above / :left / :right; :gap (default 16) is the space between; :align (:start / :center / :end, default :start) aligns the cross-axis. Pass as the :anchor option to an element builder. When the target's rendered height changes (e.g. a title wraps), the anchored element moves during layout. Chains resolve in order; cycles are rejected.

button(id, opts \\ [])

@spec button(
  String.t(),
  keyword()
) :: element()

A button composite (rounded shape + centered label). :preset is :solid (fill), :outline (stroke only), or :soft (tinted fill). auto_width: true sizes the box to the measured label plus :padding (horizontal, default 24) during layout; otherwise the explicit :box wins.

from_json!(json)

@spec from_json!(String.t()) :: t()

Parse a scene from a JSON string, running migrate/1.

from_map!(m)

@spec from_map!(map()) :: t()

Build a scene from a decoded JSON map (string keys), running migrate/1.

gradient(angle, stops)

@spec gradient(number(), [gradient_stop()]) :: fill()

A linear-gradient fill. angle is in degrees, measured clockwise from "to the right" (0 = left→right, 90 = top→bottom). Each stop is %{offset: 0..1, color: "#rrggbb", alpha: 0..1}.

image(id, opts \\ [])

@spec image(
  String.t(),
  keyword()
) :: element()

A positioned-image element. :value resolves to an href (or draws the unresolved stand-in). Supports an :underlay for legibility, same as text.

image_fill(value, fit \\ :cover, opts \\ [])

@spec image_fill(value(), :cover | :contain | :stretch, keyword()) :: fill()

An image fill. fit is :cover, :contain, or :stretch. Used as a canvas :background, an optional :overlay_color (:dark/:light) + :overlay_opacity tints the image (og's hero-darkening legibility trick).

migrate(scene)

@spec migrate(t()) :: t()

Migrate an older scene to the current version. Currently the identity for "1"; the seam is here so stored scenes survive format changes.

new(opts \\ [])

@spec new(keyword()) :: t()

A new empty scene. :width / :height default to the 1200×630 OG size; :background is an optional fill (default nil = transparent).

place(opts)

@spec place(keyword()) :: map()

A canvas-relative placement (CSS-inset style), resolved against the canvas size at render time — so a template built with :place adapts to any canvas dimensions (landscape, square, portrait). Pass as an element's :place option; it overrides the static :box position on the axes it specifies.

Keys (all optional, canvas px): :left / :right / :top / :bottom insets from the edges, and :w / :h explicit sizes. Per axis:

  • both insets → stretches between them (w = width - left - right)
  • one inset + size → pinned to that edge at that size
  • size only → centered on that axis
  • neither → keeps the element's :box value

Examples

# A content strip 64px in from both sides, pinned 48px above the bottom:
Scene.place(left: 64, right: 64, bottom: 48, h: 200)

# A centered square badge:
Scene.place(w: 120, h: 120, top: 40)

placeholder(key)

@spec placeholder(String.t()) :: value()

A placeholder value, resolved at render time against the values map.

put_size(scene, w, h)

@spec put_size(t(), number(), number()) :: t()

Set the canvas size (elements with :place re-place at render time).

resolve(value, values \\ %{})

@spec resolve(value(), map()) :: {:ok, any()} | :unresolved

Resolve a value against a values map.

A literal binary resolves to itself. A placeholder resolves to values[key] when present and non-nil, else :unresolved.

shape(id, opts \\ [])

@spec shape(
  String.t(),
  keyword()
) :: element()

A filled (rounded) rectangle element. :fill may be solid/gradient/image; :stroke (+ :stroke_width) adds a border; :underlay a legibility box.

solid(color)

@spec solid(String.t()) :: fill()

A solid-color fill.

stamp(id, opts \\ [])

@spec stamp(
  String.t(),
  keyword()
) :: element()

A preset-text element — text/2 with type: :stamp. Mirrors og's stamp (a reusable text snippet, e.g. a fixed byline); :preset is an alias for :value. Renders identically to text.

text(id, opts \\ [])

@spec text(
  String.t(),
  keyword()
) :: element()

A text-block element. Text word-wraps to :box width and vertically aligns via :valign (:top / :middle / :bottom). An :underlay (opacity > 0) draws a soft glow behind the text for legibility over busy backgrounds — :underlay_color is :dark or :light.

to_json!(scene)

@spec to_json!(t()) :: String.t()

Serialize to a JSON string.

to_map(scene)

@spec to_map(t()) :: map()

Serialize to a JSON-ready map (string keys).