# `Plushie.Canvas.Shape`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.7.2/lib/plushie/canvas/shape.ex#L1)

Pure builder functions returning typed canvas shape structs.

Every function returns a struct from `Plushie.Canvas.*` (element
modules like `Plushie.Canvas.Rect`, `Plushie.Canvas.Circle`, etc.).
Each element module has an `encode/1` function for wire-format
conversion. These are plain functions, not macros; they can be
called anywhere.

Structure macros (`group`, `layer`) live in `Plushie.UI` and are
available inside canvas do-blocks. Inside
those blocks, `text`, `image`, and `svg` calls resolve
automatically to their canvas shape variants. For helper functions
outside canvas blocks, import this module directly.

## When to import this module

Import `Plushie.Canvas.Shape` when you build shapes in standalone
helper functions (defp) outside a `Plushie.UI` canvas block:

    import Plushie.Canvas.Shape

    defp draw_indicator(x, y, color) do
      [
        circle(x, y, 6, fill: color),
        circle(x, y, 3, fill: "#fff")
      ]
    end

Inside `Plushie.UI` canvas/layer/group blocks, all shape functions
are already in scope via `import Plushie.UI`. No extra import
needed.

## Basic shapes

    rect(10, 20, 100, 50, fill: "#ff0000")
    circle(50, 50, 25, stroke: stroke("#000", 2))
    line(0, 0, 100, 100, stroke: stroke("#333", 1, cap: "round"))
    text(10, 10, "Hello", fill: "#000", size: 16)

## Paths

    path([move_to(0, 0), line_to(100, 0), line_to(50, 80), close()],
      fill: "#0088ff",
      stroke: stroke("#000", 2)
    )

## Transforms

Transform commands are interleaved with shapes in a layer's shape list:

    [
      push_transform(),
      translate(100, 100),
      rotate(45),
      rect(0, 0, 50, 50, fill: "#f00"),
      pop_transform()
    ]

## Clipping

Clip regions restrict drawing to a rectangular area:

    [
      push_clip(10, 10, 100, 80),
      rect(0, 0, 200, 200, fill: "#ff0000"),
      pop_clip()
    ]

Clip regions nest: inner clips are intersected with outer clips.

## Per-shape opacity

All shapes (except SVG) accept an `:opacity` option (0.0-1.0) that
multiplies into the fill and stroke color alpha channels:

    rect(0, 0, 100, 100, fill: "#ff0000", opacity: 0.5)
    circle(50, 50, 25, stroke: stroke("#000", 2), opacity: 0.3)

## Text alignment

The `text/4` builder accepts `:align_x` and `:align_y` options:

    text(100, 50, "Centered", fill: "#000", align_x: "center", align_y: "center")

Valid values for `:align_x`: `"left"`, `"center"`, `"right"`.
Valid values for `:align_y`: `"top"`, `"center"`, `"bottom"`.

## Gradients

Use `linear_gradient/3` as a `fill` value:

    rect(0, 0, 200, 100,
      fill: linear_gradient({0, 0}, {200, 0}, [{0.0, "#ff0000"}, {1.0, "#0000ff"}])
    )

## Interactive shapes

Make shapes interactive with the `interactive` macro (requires a
string id as the first argument):

    interactive "btn", on_click: true, cursor: "pointer" do
      rect(0, 0, 100, 40, fill: "#3498db")
    end

# `arc`

```elixir
@spec arc(
  cx :: number(),
  cy :: number(),
  r :: number(),
  start_angle :: Plushie.Canvas.Angle.t(),
  end_angle :: Plushie.Canvas.Angle.t()
) :: list()
```

Arc path command (center, radius, start and end angles in degrees).

# `arc_to`

```elixir
@spec arc_to(
  x1 :: number(),
  y1 :: number(),
  x2 :: number(),
  y2 :: number(),
  radius :: number()
) :: list()
```

Tangent arc path command.

# `bezier_to`

```elixir
@spec bezier_to(
  cp1x :: number(),
  cp1y :: number(),
  cp2x :: number(),
  cp2y :: number(),
  x :: number(),
  y :: number()
) :: list()
```

Cubic bezier curve path command.

# `circle`

```elixir
@spec circle(x :: number(), y :: number(), r :: number(), opts :: keyword()) ::
  Plushie.Canvas.Circle.t()
```

Builds a circle shape.

# `clip`

```elixir
@spec clip(x :: number(), y :: number(), w :: number(), h :: number()) ::
  Plushie.Canvas.Clip.t()
```

Create a clip rectangle for a group.

# `close`

```elixir
@spec close() :: String.t()
```

Close path command.

# `ellipse`

```elixir
@spec ellipse(
  cx :: number(),
  cy :: number(),
  rx :: number(),
  ry :: number(),
  rotation :: Plushie.Canvas.Angle.t(),
  start_angle :: Plushie.Canvas.Angle.t(),
  end_angle :: Plushie.Canvas.Angle.t()
) :: list()
```

Ellipse path command.

# `image`

```elixir
@spec image(
  source :: String.t(),
  x :: number(),
  y :: number(),
  w :: number(),
  h :: number(),
  opts :: keyword()
) :: Plushie.Canvas.Image.t()
```

Draws a raster image on the canvas at the given position and size.

## Options

- `:rotation` - rotation angle in degrees (or `{value, :rad}` for radians).
- `:opacity` - opacity multiplier (0.0-1.0).

# `line`

```elixir
@spec line(
  x1 :: number(),
  y1 :: number(),
  x2 :: number(),
  y2 :: number(),
  opts :: keyword()
) :: Plushie.Canvas.Line.t()
```

Builds a line shape.

# `line_to`

```elixir
@spec line_to(x :: number(), y :: number()) :: list()
```

Line-to path command.

# `linear_gradient`

```elixir
@spec linear_gradient(
  from :: {number(), number()},
  to :: {number(), number()},
  stops :: [{number(), String.t()}]
) :: Plushie.Canvas.Gradient.t()
```

Builds a linear gradient, usable as a `fill` value.

Stops are `{offset, color}` tuples where offset is 0.0..1.0.

# `move_to`

```elixir
@spec move_to(x :: number(), y :: number()) :: list()
```

Move-to path command.

# `path`

```elixir
@spec path(commands :: [map() | list() | String.t()], opts :: keyword()) ::
  Plushie.Canvas.Path.t()
```

Builds an arbitrary path shape.

Commands are produced by `move_to/2`, `line_to/2`, `bezier_to/6`,
`quadratic_to/4`, `arc/5`, `arc_to/5`, `ellipse/7`, `rounded_rect/5`,
and `close/0`.

# `quadratic_to`

```elixir
@spec quadratic_to(cpx :: number(), cpy :: number(), x :: number(), y :: number()) ::
  list()
```

Quadratic bezier curve path command.

# `rect`

```elixir
@spec rect(
  x :: number(),
  y :: number(),
  w :: number(),
  h :: number(),
  opts :: keyword()
) ::
  Plushie.Canvas.Rect.t()
```

Builds a rectangle shape.

# `rotate`

```elixir
@spec rotate(angle :: Plushie.Canvas.Angle.t()) :: Plushie.Canvas.Transform.Rotate.t()
```

Create a rotation transform.

Accepts degrees by default, or explicit unit tuples:

    rotate(45)              # 45 degrees
    rotate({45, :deg})      # explicit degrees
    rotate({0.785, :rad})   # explicit radians

# `rounded_rect`

```elixir
@spec rounded_rect(
  x :: number(),
  y :: number(),
  w :: number(),
  h :: number(),
  radius :: number()
) :: list()
```

Rounded rectangle path command.

# `scale`

```elixir
@spec scale(factor :: number()) :: Plushie.Canvas.Transform.Scale.t()
```

Create a uniform scale transform.

# `scale`

```elixir
@spec scale(x :: number(), y :: number()) :: Plushie.Canvas.Transform.Scale.t()
```

Create a non-uniform scale transform.

# `stroke`

```elixir
@spec stroke(color :: String.t(), width :: number(), opts :: keyword()) ::
  Plushie.Canvas.Stroke.t()
```

Builds a stroke descriptor.

## Options

- `:cap` - line cap: `"butt"`, `"round"`, or `"square"`. Default: `"butt"`.
- `:join` - line join: `"miter"`, `"round"`, or `"bevel"`. Default: `"miter"`.
- `:dash` - dash pattern as `{segments, offset}` where segments is a list
  of numbers and offset is the starting offset.

# `svg`

```elixir
@spec svg(
  source :: String.t(),
  x :: number(),
  y :: number(),
  w :: number(),
  h :: number()
) :: Plushie.Canvas.Svg.t()
```

Draws an SVG on the canvas at the given position and size.

# `text`

```elixir
@spec text(x :: number(), y :: number(), content :: String.t(), opts :: keyword()) ::
  Plushie.Canvas.Text.t()
```

Builds a text shape.

# `translate`

```elixir
@spec translate(x :: number(), y :: number()) ::
  Plushie.Canvas.Transform.Translate.t()
```

Create a translation transform.

---

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