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

Resolves a scene's *computed* layout before it is drawn:

  * **Text wrap** — each text/stamp element's resolved content is wrapped to
    its box width, storing the lines and the rendered block height.
    Measurement-accurate when `measure: true` + resvg is available (wrap
    points match the render); a character-count estimate otherwise.
  * **Button auto-width** — a button with `auto_width: true` gets its box
    widened to the measured label plus horizontal padding.
  * **Anchor reflow** — an element anchored to another (edge + gap + align)
    is repositioned relative to the target's *rendered* box; chains resolve
    in dependency order and cycles are rejected (`validate/1`) — during
    `resolve/3` a cyclic anchor is dropped so rendering still proceeds.

Runs on every render (cheap in estimate mode; `OpenFresco.render/3` opts
into measurement). Results are stashed on transient `:_lines` /
`:_rendered_h` element keys the SVG generator reads.

## International text: the supported-scripts matrix

Line-break opportunities are honest about their limits:

  * **Space-separated LTR scripts** (Latin, Cyrillic, Greek, …) — fully
    supported in both estimate and measured modes.
  * **CJK and other UAX #14 scripts** — supported **when the optional
    `:unicode_string` dependency is installed**: break opportunities come
    from UAX #14 line-breaking segmentation instead of whitespace.
    Without it, unspaced CJK runs as one unbreakable unit (it will
    overflow rather than mis-break).
  * **Thai / Khmer / Lao / Myanmar** — NOT supported: correct breaking
    requires dictionary segmentation, which neither this library nor
    UAX #14 alone provides. Text renders, but break points inside
    unspaced runs will be wrong.
  * **RTL / bidi** (Arabic, Hebrew) — *visual ordering and shaping* are
    delegated to the rasterizer (resvg shapes with rustybuzz, including
    bidi). Set the element's `:direction` (`"rtl"`) so renderers order
    runs correctly. Wrap-point measurement treats the text as a
    sequence of space-separated words, which is usually correct for
    Arabic/Hebrew prose.
  * **Grapheme clusters** (emoji ZWJ sequences, combining marks) — never
    split: breaking only happens at whitespace or UAX #14 opportunities,
    both of which respect cluster boundaries.

# `resolve`

```elixir
@spec resolve(OpenFresco.Scene.t(), map(), keyword()) :: OpenFresco.Scene.t()
```

Resolve wrap, auto-width, and anchor positions. Returns the scene with
updated element boxes and `:_lines` / `:_rendered_h` annotations. Never
raises; a cyclic anchor is left un-applied.

# `validate`

```elixir
@spec validate(OpenFresco.Scene.t()) :: :ok | {:error, {:anchor_cycle, [String.t()]}}
```

`:ok`, or `{:error, {:anchor_cycle, ids}}` if the anchor graph has a cycle.

---

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