# `EmergeSkia`
[🔗](https://github.com/emerge-elixir/emerge/blob/v0.2.1/lib/emerge_skia.ex#L1)

Minimal Skia renderer for the Emerge layout engine.

This library renders retained Emerge trees through the native Rust layout,
event, and Skia pipeline.

## Example

    # Start renderer
    {:ok, renderer} =
      EmergeSkia.start(
        otp_app: :my_app,
        title: "My App",
        width: 800,
        height: 600
      )

    import Emerge.UI
    import Emerge.UI.Color
    import Emerge.UI.Size
    import Emerge.UI.Space

    tree =
      el(
        [
          width(px(220)),
          height(px(80)),
          Emerge.UI.Background.color(color(:sky, 500)),
          Emerge.UI.Border.rounded(10),
          padding(16),
          Emerge.UI.Font.color(color(:white)),
          Emerge.UI.Font.size(24)
        ],
        text("Hello!")
      )

    {_state, _assigned} = EmergeSkia.upload_tree(renderer, tree)

    # Stop when done
    EmergeSkia.stop(renderer)

## Color Format

For `Emerge.UI` styling, prefer `Emerge.UI.Color.color/1..3`,
`Emerge.UI.Color.color_rgb/3`, and `Emerge.UI.Color.color_rgba/4`.

`EmergeSkia.rgb/3` and `EmergeSkia.rgba/4` are still available when you need
packed 32-bit unsigned integers in RGBA format: `0xRRGGBBAA`

- `0xFF0000FF` = Red (fully opaque)
- `0x00FF00FF` = Green (fully opaque)
- `0x0000FFFF` = Blue (fully opaque)
- `0x00000080` = Black at 50% opacity

When you register an input target with `set_input_target/2`, Wayland close
requests are delivered separately as
`{:emerge_skia_close, :window_close_requested}`. This lifecycle message
bypasses the input mask so higher-level runtimes can shut down promptly.

# `color`

```elixir
@type color() :: non_neg_integer()
```

# `renderer`

```elixir
@type renderer() :: reference() | EmergeSkia.Macos.Renderer.t()
```

# `video_target`

```elixir
@type video_target() :: EmergeSkia.VideoTarget.t()
```

# `input_mask_all`

```elixir
@spec input_mask_all() :: non_neg_integer()
```

Returns the input mask for all events.

# `input_mask_codepoint`

```elixir
@spec input_mask_codepoint() :: non_neg_integer()
```

Returns the input mask for text input events.

# `input_mask_cursor_button`

```elixir
@spec input_mask_cursor_button() :: non_neg_integer()
```

Returns the input mask for cursor button events.

# `input_mask_cursor_enter`

```elixir
@spec input_mask_cursor_enter() :: non_neg_integer()
```

Returns the input mask for cursor enter/exit events.

# `input_mask_cursor_pos`

```elixir
@spec input_mask_cursor_pos() :: non_neg_integer()
```

Returns the input mask for cursor position events.

# `input_mask_cursor_scroll`

```elixir
@spec input_mask_cursor_scroll() :: non_neg_integer()
```

Returns the input mask for cursor scroll events.

# `input_mask_focus`

```elixir
@spec input_mask_focus() :: non_neg_integer()
```

Returns the input mask for window focus events.

# `input_mask_key`

```elixir
@spec input_mask_key() :: non_neg_integer()
```

Returns the input mask for key events.

# `input_mask_resize`

```elixir
@spec input_mask_resize() :: non_neg_integer()
```

Returns the input mask for window resize events.

# `load_font_file`

```elixir
@spec load_font_file(String.t(), non_neg_integer(), boolean(), Path.t()) ::
  :ok | {:error, term()}
```

Load a font from a file path.

The font is registered by name and can be used with `Font.family/1` in elements.
Load different variants (bold, italic) with separate calls using appropriate weight/italic params.

## Parameters
- `name` - Font family name to register (e.g., "my-font")
- `weight` - Font weight (100-900, 400=normal, 700=bold)
- `italic` - Whether this is an italic variant
- `path` - Path to the TTF font file

## Example

    # Load font variants
    :ok = EmergeSkia.load_font_file("my-font", 400, false, "priv/fonts/MyFont-Regular.ttf")
    :ok = EmergeSkia.load_font_file("my-font", 700, false, "priv/fonts/MyFont-Bold.ttf")
    :ok = EmergeSkia.load_font_file("my-font", 400, true, "priv/fonts/MyFont-Italic.ttf")

    # Use in elements
    el([Font.family("my-font"), Font.size(16)], text("Hello"))
    el([Font.family("my-font"), Font.bold()], text("Bold text"))

# `measure_text`

```elixir
@spec measure_text(String.t(), number()) :: {float(), float(), float(), float()}
```

Measure text dimensions for layout purposes.

Returns `{width, line_height, ascent, descent}` where:
- `width` - Horizontal extent of the text
- `line_height` - Total line height (ascent + descent)
- `ascent` - Distance from baseline to top (positive)
- `descent` - Distance from baseline to bottom (positive)

# `patch_tree`

```elixir
@spec patch_tree(renderer(), Emerge.Engine.diff_state(), Emerge.tree()) ::
  {Emerge.Engine.diff_state(), Emerge.tree()}
```

Apply patches for a new tree, run layout, and render.

Window dimensions come from the initial start config and are updated
automatically when the window is resized (handled on the Rust side).

# `render_to_pixels`

```elixir
@spec render_to_pixels(
  Emerge.tree(),
  keyword()
) :: binary()
```

Render a tree to an RGBA pixel buffer (synchronous, no window).

This is useful for testing, headless rendering, and image generation.
Each call creates a fresh CPU surface, runs layout, renders the tree, and
returns the pixels.

## Options

- `otp_app` - OTP application used to resolve logical assets from its `priv` dir (**required**)
- `width` - Output width in pixels (**required**)
- `height` - Output height in pixels (**required**)
- `scale` - Layout scale factor (default: `1.0`)
- `assets` - Asset runtime policy options (same shape as `start/1`)
- `asset_mode` - `:await` to block for asset resolution, or `:snapshot` to capture the current placeholder state (default: `:await`)
- `asset_timeout_ms` - Maximum wait time for `asset_mode: :await` (default: `30000`)

Returns a binary containing RGBA pixel data (4 bytes per pixel, row-major order).
The binary size is `width * height * 4` bytes.

## Example

    import Emerge.UI
    import Emerge.UI.Color
    import Emerge.UI.Size

    pixels =
      EmergeSkia.render_to_pixels(
        el(
          [width(px(100)), height(px(100)), Emerge.UI.Background.color(color(:red, 500))],
          none()
        ),
        otp_app: :my_app,
        width: 100,
        height: 100
      )

    # pixels is 100 * 100 * 4 = 40000 bytes

# `render_to_png`

```elixir
@spec render_to_png(
  Emerge.tree(),
  keyword()
) :: binary()
```

Render a tree to an encoded PNG binary (synchronous, no window).

This is useful for generating screenshots and documentation assets.
Each call creates a fresh CPU surface, runs layout, renders the tree, and
returns PNG file bytes.

## Options

- `otp_app` - OTP application used to resolve logical assets from its `priv` dir (**required**)
- `width` - Output width in pixels (**required**)
- `height` - Output height in pixels (**required**)
- `scale` - Layout scale factor (default: `1.0`)
- `assets` - Asset runtime policy options (same shape as `start/1`)
- `asset_mode` - `:await` to block for asset resolution, or `:snapshot` to capture the current placeholder state (default: `:await`)
- `asset_timeout_ms` - Maximum wait time for `asset_mode: :await` (default: `30000`)

Returns a binary containing the full encoded PNG file.

## Example

    import Emerge.UI
    import Emerge.UI.Color
    import Emerge.UI.Size

    png =
      EmergeSkia.render_to_png(
        el(
          [width(px(100)), height(px(100)), Emerge.UI.Background.color(color(:red, 500))],
          none()
        ),
        otp_app: :my_app,
        width: 100,
        height: 100
      )

    File.write!("preview.png", png)

# `rgb`

```elixir
@spec rgb(0..255, 0..255, 0..255) :: color()
```

Convert RGB values to a color integer.

## Examples

    iex> EmergeSkia.rgb(255, 0, 0)
    0xFF0000FF

    iex> EmergeSkia.rgb(0, 255, 0)
    0x00FF00FF

# `rgba`

```elixir
@spec rgba(0..255, 0..255, 0..255, 0..255) :: color()
```

Convert RGBA values to a color integer.

## Examples

    iex> EmergeSkia.rgba(255, 0, 0, 128)
    0xFF000080

    iex> EmergeSkia.rgba(0, 0, 0, 255)
    0x000000FF

# `running?`

```elixir
@spec running?(renderer()) :: boolean()
```

Check if the renderer window is still open.

# `set_input_mask`

```elixir
@spec set_input_mask(renderer(), non_neg_integer()) :: :ok
```

Set the input event mask to filter which events are sent.

Wayland close notifications are always delivered to the input target as
`{:emerge_skia_close, :window_close_requested}` and are not filtered by this
mask.

## Example

    # Only capture mouse button and key events
    import Bitwise
    mask = EmergeSkia.input_mask_cursor_button() ||| EmergeSkia.input_mask_key()
    EmergeSkia.set_input_mask(renderer, mask)

# `set_input_target`

```elixir
@spec set_input_target(renderer(), pid() | nil) :: :ok
```

Set the target process to receive renderer events.

Events are sent directly to the target process as
`{:emerge_skia_event, event}` messages.

Raw input event payloads include:

- `{:cursor_pos, {x, y}}`
- `{:cursor_button, {button, action, mods, {x, y}}}`
- `{:cursor_scroll, {{dx, dy}, {x, y}}}`
- `{:key, {key, action, mods}}`
- `{:codepoint, {char, mods}}`
- `{:text_commit, {text, mods}}`
- `{:text_preedit, {text, cursor}}`
- `:text_preedit_clear`
- `{:cursor_entered, entered}`
- `{:resized, {width, height, scale}}`
- `{:focused, focused}`

On Wayland, close notifications are sent separately as:

- `{:emerge_skia_close, :window_close_requested}`

This lifecycle message bypasses the input mask so close requests are still
delivered when other raw input categories are disabled.

On DRM, raw `{:cursor_pos, {x, y}}` delivery is latest-wins under load so
pointer motion does not stall rendering. Button, scroll, key, and text events
remain ordered.

Element event payloads include:

- `{id_bin, event_type}`
- `{id_bin, event_type, payload}`

where `id_bin` is an opaque element id and `event_type` is an atom such as
`:press`, `:click`, `:swipe_up`, `:swipe_down`, `:swipe_left`,
`:swipe_right`, `:change`, `:key_down`, `:key_up`, or `:key_press`.

Routed `:key_down`, `:key_up`, and `:key_press` payloads currently carry an
opaque binding route id used by higher-level runtimes.

Higher-level runtimes should route element events with
`Emerge.Engine.lookup_event/3` or `Emerge.Engine.dispatch_event/3`/`4`.

Where:
- `button` is an atom like `:left`, `:right`, `:middle`
- `action` is 0 for release, 1 for press
- `mods` is a list of modifier atoms like `[:shift, :ctrl]`
- `key` is a canonical atom like `:escape`, `:enter`, `:a`, `:digit_1`, `:arrow_left`, or `:plus`

Raw key events stay layout-independent. Text-producing input is delivered separately
through text commit/preedit events. For example, `Shift+=` reports raw key `:equal`
with `[:shift]` and still commits the text `"+"`.

## Example

    EmergeSkia.set_input_target(renderer, self())

    receive do
      {:emerge_skia_event, {:cursor_button, {button, 1, _mods, {x, y}}}} ->
        IO.puts("Clicked #{button} at #{x}, #{y}")

      {:emerge_skia_event, {:key, {key, 1, _mods}}} ->
        IO.puts("Key pressed: #{key}")
    end

# `set_log_target`

```elixir
@spec set_log_target(renderer(), pid() | nil) :: :ok
```

Set the target process to receive native renderer log messages.

Native logs are sent directly to the target process as
`{:emerge_skia_log, level, source, message}` messages.

# `start`

```elixir
@spec start() :: no_return()
```

# `start`

```elixir
@spec start(keyword()) :: {:ok, renderer()} | {:error, term()}
@spec start(String.t()) :: no_return()
```

Start a new renderer window.

## Options

- `otp_app` - OTP application used to resolve logical assets from its `priv` dir (**required**)
 - `backend` - Backend selection (`:wayland`, `:drm`, or `:macos`). Defaults to `:wayland` for Linux desktop builds, `:macos` on Darwin, and `:drm` for Nerves-style builds. The requested backend must also be present in `config :emerge, compiled_backends: [...]`.
 - `macos_backend` - macOS surface backend selection (`:auto`, `:metal`, or `:raster`). Defaults to `:auto` and is only supported with `backend: :macos`.
- `title` - Window title (default: "Emerge")
- `width` - Window width in pixels (default: 800)
- `height` - Window height in pixels (default: 600)
- `scroll_line_pixels` - Pixel distance used for each discrete mouse-wheel line step (default: `30.0`)
- `drm_card` - DRM device path (default: `/dev/dri/card0`)
- `hw_cursor` - Enable hardware cursor when available (default: true)
- `drm_cursor` - Optional DRM-only cursor overrides for `default`, `text`, and `pointer`
- `input_log` - Log DRM input devices on startup (default: false)
- `render_log` - Log DRM render/present diagnostics (default: false)
- `close_signal_log` - Log detailed Wayland window-close diagnostics to stderr (default: false)
- `renderer_stats_log` - Log renderer timing stats every 5 seconds, including frame rate and min/avg/max/count timing windows for layout, event resolve, and patch completion (default: false)
- `assets` - Asset runtime policy options (optional)

Native renderer logs are delivered to the process that starts the renderer as
`{:emerge_skia_log, level, source, message}` messages. Call
`set_log_target/2` to redirect them.

 `assets` options:
- `runtime_paths.enabled` (default: `false`)
- `runtime_paths.allowlist` (default: `[]`)
- `runtime_paths.follow_symlinks` (default: `false`)
- `runtime_paths.max_file_size` (default: `25_000_000`)
- `runtime_paths.extensions` (default image/SVG extension allowlist)
- `fonts` (default: `[]`)

Each `assets.fonts` entry supports:
- `family` (required)
- `source` (required, logical path under `<otp_app>/priv` or `%Emerge.Assets.Ref{}`)
- `weight` (default: `400`)
- `italic` (default: `false`)

Each `drm_cursor` entry supports:
- `source` (required, `.png` or `.svg`; logical path under `<otp_app>/priv`, `%Emerge.Assets.Ref{}`, or an absolute runtime path allowed by `assets.runtime_paths`)
- `hotspot` (required `{x, y}` tuple; integers and floats are allowed)

DRM cursor overrides are applied only on the `:drm` backend. Missing icons fall back to
the built-in `mocu-black-right` theme.

Compile-time backend selection is configured separately with
`config :emerge, compiled_backends: [...]`. If omitted, desktop builds assume
`[:wayland]` and Nerves-style builds assume `[:drm]`.

# `start`

```elixir
@spec start(String.t(), non_neg_integer()) :: no_return()
```

# `start`

```elixir
@spec start(String.t(), non_neg_integer(), non_neg_integer()) :: no_return()
```

# `stop`

```elixir
@spec stop(renderer()) :: :ok
```

Stop the renderer and close the window.

# `submit_prime`

```elixir
@spec submit_prime(video_target(), map()) :: :ok | {:error, term()}
```

Submit a DRM Prime descriptor to a video target.

# `upload_tree`

```elixir
@spec upload_tree(renderer(), Emerge.tree()) ::
  {Emerge.Engine.diff_state(), Emerge.tree()}
```

Upload a full EMRG tree, run layout, and render.

Window dimensions come from the initial start config and are updated
automatically when the window is resized (handled on the Rust side).

# `video_target`

```elixir
@spec video_target(
  renderer(),
  keyword()
) :: {:ok, video_target()} | {:error, term()}
```

Create a renderer-owned video target.

V1 supports fixed-size `:prime` targets only on Prime-capable backends
(`:wayland` and `:drm`).

---

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