# `Easel`
[🔗](https://github.com/jasonstiebs/easel/blob/v0.3.4/lib/easel.ex#L1)

Easel lets you build Canvas 2D drawing operations as data.

Create a canvas, pipe it through drawing functions to build up
a list of operations, then render to a backend (browser via LiveView,
native window via wx, or consume the ops list directly).

All Canvas 2D API functions are available directly on this module as
`snake_cased` versions of their JavaScript counterparts. Use `set/3`
and `call/3` for properties or methods not yet generated.

## Example

    canvas =
      Easel.new(300, 300)
      |> Easel.set_fill_style("blue")
      |> Easel.fill_rect(0, 0, 100, 100)
      |> Easel.set_line_width(10)
      |> Easel.stroke_rect(100, 100, 100, 100)
      |> Easel.render()

The resulting `%Easel{}` struct contains an `ops` list that maps
directly to Canvas 2D API calls:

    canvas.ops
    #=> [["set", ["fillStyle", "blue"]], ["fillRect", [0, 0, 100, 100]], ...]

## Templates and Instances

For scenes with many similar shapes, define a template once and stamp
out instances with per-instance transforms:

    canvas =
      Easel.new(800, 600)
      |> Easel.template(:particle, fn c ->
        c |> Easel.begin_path() |> Easel.arc(0, 0, 3, 0, 6.28) |> Easel.fill()
      end)
      |> Easel.instances(:particle, [
        %{x: 100, y: 200, fill: "red"},
        %{x: 300, y: 400, fill: "blue", alpha: 0.5}
      ])
      |> Easel.render()

The LiveView hook renders instances client-side (template ops cached,
only instance data sent per frame). For other backends, call `expand/1`
to flatten into plain ops.

## Backends

  * `Easel.LiveView` - Phoenix LiveView component with colocated JS hook
  * `Easel.WX` - Native desktop window via Erlang's `:wx` (wxWidgets)
  * `Easel.Terminal` - Experimental terminal renderer (wx off-screen raster + ASCII)
  * Custom - consume `canvas.ops` directly in your own renderer

# `arc`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc

# `arc`

# `arc_to`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arcTo

# `begin_path`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath

# `bezier_curve_to`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo

# `call`

Calls a Canvas 2D context method with the given arguments.

    Easel.call(canvas, "fillRect", [0, 0, 100, 100])

# `clear_rect`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect

# `clip`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip

# `clip`

# `clip`

# `close_path`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/closePath

# `create_image_data`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createImageData

# `create_image_data`

# `create_linear_gradient`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient

# `create_pattern`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern

# `create_radial_gradient`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createRadialGradient

# `draw_focus_if_needed`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawFocusIfNeeded

# `draw_image`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

# `draw_image`

# `draw_image`

# `ellipse`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/ellipse

# `ellipse`

# `expand`

Expands `__instances` ops into plain Canvas 2D ops.

The LiveView hook handles instances natively on the client (more efficient),
but other backends (wx, custom) can call this to get a flat ops list that
any Canvas 2D renderer can execute.

Automatically calls `render/1` first if needed.

## Example

    canvas
    |> Easel.template(:dot, fn c -> ... end)
    |> Easel.instances(:dot, [%{x: 10, y: 20}])
    |> Easel.expand()
    # ops now contain save/translate/rotate/.../restore sequences

# `fill`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fill

# `fill`

# `fill`

# `fill_rect`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillRect

# `fill_text`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillText

# `fill_text`

# `get_image_data`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData

# `get_line_dash`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getLineDash

# `get_transform`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getTransform

# `instances`

Draws instances of a previously defined template.

Each instance is a map that may contain:

  * `:x`, `:y` - translation (default `0`, `0`)
  * `:rotate` - rotation in radians (default `0`)
  * `:scale_x`, `:scale_y` - scale factors (default `1`, `1`)
  * `:fill` - fill style override (applied before template ops)
  * `:stroke` - stroke style override
  * `:alpha` - global alpha override

The hook renders each instance via `save → translate → rotate → scale →
[style overrides] → [template ops] → restore`.

You can optionally quantize float values before serialization by passing
`precision` globally or per field:

    Easel.instances(canvas, :boid, instances, precision: 2)
    Easel.instances(canvas, :boid, instances, x: 1, y: 1, rotate: 3)

## Example

    canvas
    |> Easel.instances(:boid, Enum.map(boids, fn b ->
      %{x: b.x, y: b.y, rotate: :math.atan2(b.vy, b.vx),
        fill: "hsl(#{hue}, 70%, 60%)"}
    end))

# `is_point_in_path`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath

# `is_point_in_path`

# `is_point_in_path`

# `is_point_in_stroke`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke

# `is_point_in_stroke`

# `line_to`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineTo

# `measure_text`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText

# `move_to`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/moveTo

# `new`

Creates a new canvas with no dimensions set.

# `new`

Creates a new canvas with the given `width` and `height`.

# `push_op`

Pushes a raw operation onto the canvas.

Operations are stored in reverse order for efficient prepend.
Call `render/1` to finalize the ops list into correct order.

# `put_image_data`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData

# `put_image_data`

# `quadratic_curve_to`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo

# `rect`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rect

# `render`

Finalizes the canvas by reversing the ops list into execution order.

Must be called before passing the canvas to a backend for rendering.
Safe to call multiple times - subsequent calls are no-ops.

# `reset_transform`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform

# `restore`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore

# `rotate`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate

# `save`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save

# `scale`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/scale

# `set`

Sets a Canvas 2D context property.

    Easel.set(canvas, "fillStyle", "blue")

# `set_fill_style`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle

# `set_filter`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter

# `set_font`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/font

# `set_global_alpha`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalAlpha

# `set_global_composite_operation`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

# `set_image_smoothing_enabled`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled

# `set_line_cap`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap

# `set_line_dash`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash

# `set_line_dash_offset`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset

# `set_line_join`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin

# `set_line_width`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineWidth

# `set_miter_limit`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/miterLimit

# `set_shadow_blur`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur

# `set_shadow_color`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor

# `set_shadow_offset_x`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX

# `set_shadow_offset_y`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY

# `set_stroke_style`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle

# `set_text_align`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign

# `set_text_baseline`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

# `set_transform`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform

# `set_transform`

# `set_transform`

# `stroke`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke

# `stroke`

# `stroke_rect`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeRect

# `stroke_text`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeText

# `stroke_text`

# `template`

Defines a reusable drawing template.

A template is a named set of ops that can be instantiated many times
with different transforms. The template function receives a fresh
`%Easel{}` and should return one with ops added (don't call `render/1`).

## Example

    canvas =
      Easel.new(800, 600)
      |> Easel.template(:boid, fn c ->
        c
        |> Easel.begin_path()
        |> Easel.move_to(12, 0)
        |> Easel.line_to(-4, -5)
        |> Easel.line_to(-4, 5)
        |> Easel.close_path()
        |> Easel.fill()
      end)

# `transform`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform

# `translate`

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate

# `with_template_opts`

Sets template-level instance options for this canvas.

Useful when templates are prepared in one canvas and instances are emitted
from another (for example, static templates cached in assigns).

---

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