# `Curves.Transform`
[🔗](https://github.com/greetingsfellowhumans/curves/blob/main/lib/curves/transform/transform.ex#L1)

Utility functions that allow you to move, resize, stretch, flip, rotate and reverse a curve,
without changing the points you defined it with.

| To... | Use |
| --- | --- |
| move it | `set_position/2`, `inc_position/2`, `inc_x/2`, `inc_y/2` |
| resize or stretch it | `set_scale/3`, `inc_scale/3` (a number, or `{x, y}` for each direction) |
| mirror it | `flip_x/2`, `flip_y/2` |
| rotate it | `set_rotation/3`, `inc_rotation/3` (degrees, counter-clockwise) |
| travel it the other way | `reverse/1` |

The curve is first scaled and then rotated around its own `{0, 0}`, and finally moved to its
position (the `:origin` option, `set_position/2` and the like). This is the usual
scale-rotate-translate order (like SVG's `transform="translate(…) rotate(…) scale(…)"`), so
scaling or rotating a curve never moves it away from its position. To scale, flip or rotate around
some other point, such as the curve's middle, pass `around: :center` or `around: {x, y}`.

Positions are `{x, y}` tuples of numbers, and amounts are numbers.
Anything else raises an `ArgumentError`.

## Drawings

Every function here also takes a `Curves.Drawing` (from `Curves.from_svg/2`), or a group or shape
in one, and changes it as a whole, the same way it changes a curve. Everything in it moves
together, text and other kept markup too. `around: :center` is the middle of its shapes: text and
other markup can't be measured, so they don't count.
`Curves.Drawing.update/3` and `update_in/3` change one part, found by its `id`:

```elixir
drawing
|> Curves.Transform.set_scale(2)
|> Curves.Drawing.update("wheel", &Curves.Transform.inc_rotation(&1, 45, around: :center))
```

The change is kept on the drawing, group or shape (in its `:origin`, `:scale` and `:rotation`),
so it can be set again later, like a curve's. `Curves.Drawing.shapes/1` and `curves/1` give the
curves where they end up, and `Curves.Svg` draws it there. A `Curves.Drawing.Subpath` changes its
curve.

# `around`

```elixir
@type around() :: nil | :center | Curves.Types.point_tuple()
```

The point to scale, flip or rotate around: `:center` (the middle of the curve's points) or an
`{x, y}` point, in the curve's own coordinates. `nil` (the default) is the curve's own `{0, 0}`.

# `transformable`

```elixir
@type transformable() ::
  Curves.Types.curve_struct()
  | Curves.Drawing.t()
  | Curves.Drawing.Group.t()
  | Curves.Drawing.Shape.t()
  | Curves.Drawing.Subpath.t()
```

Anything these functions change: a curve, or a `Curves.Drawing`, or a group, shape or subpath
in one. A drawing, group or shape changes as a whole (everything in it, text too); a subpath
changes its curve.

# `flip_x`

```elixir
@spec flip_x(transformable(), opts :: [{:around, around()}]) :: transformable()
```

Mirror the curve left to right (its x scale turns negative, or back again).

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
    iex> Curves.solve!(Transform.flip_x(curve), 1.0)
    {-3.0, 2.0}
    iex> Curves.solve!(Transform.flip_x(curve, around: :center), 1.0)
    {1.0, 2.0}

# `flip_y`

```elixir
@spec flip_y(transformable(), opts :: [{:around, around()}]) :: transformable()
```

Mirror the curve top to bottom (its y scale turns negative, or back again).

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
    iex> Curves.solve!(Transform.flip_y(curve), 1.0)
    {3.0, -2.0}
    iex> Curves.solve!(Transform.flip_y(curve, around: :center), 1.0)
    {3.0, 0.0}

# `inc_position`

```elixir
@spec inc_position(curve :: transformable(), Curves.Types.point_tuple()) ::
  transformable()
```

Using the current position increase both the x and y coordinates.
It is also possible to decrease by passing a negative number

## Examples
    iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
    iex> {x, _y} = Curves.solve!(curve, 0.5)
    iex> x
    10.5
    iex> curve = Transform.inc_position(curve, {10, 10})
    iex> {x, _y} = Curves.solve!(curve, 0.5)
    iex> x
    20.5

# `inc_rotation`

```elixir
@spec inc_rotation(transformable(), number(), opts :: [{:around, around()}]) ::
  transformable()
```

Increase the rotation (counter-clockwise) by the given amount, in degrees.

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier(:linear)
    iex> curve = Transform.inc_rotation(curve, 45.0)
    iex> {x, y} = Curves.solve!(curve, 0.5)
    iex> {Float.round(x, 5), Float.round(y, 5)}
    {0.0, 0.70711}

# `inc_scale`

```elixir
@spec inc_scale(
  transformable(),
  amount :: number() | {number(), number()},
  opts :: [{:around, around()}]
) :: transformable()
```

Resize the curve, adding to the existing scale. With an `{x, y}` pair, each direction is added to
separately; a single number adds to both. A flipped direction has a negative scale, so adding to
it brings it towards `0`, making the curve smaller that way.

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier(:linear)
    iex> curve = Transform.inc_scale(curve, 10.0)
    iex> Curves.solve!(curve, 0.5)
    {5.5, 5.5}
    iex> curve = Transform.inc_scale(curve, {0, -10})
    iex> Curves.solve!(curve, 0.5)
    {5.5, 0.5}

# `inc_x`

```elixir
@spec inc_x(curve :: transformable(), xinc :: Curves.Types.coord()) :: transformable()
```

Using the current position increase the x coordinates.
It is also possible to decrease by passing a negative number

## Examples
    iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
    iex> {x, _y} = Curves.solve!(curve, 0.5)
    iex> x
    10.5
    iex> curve = Transform.inc_x(curve, 10)
    iex> {x, y} = Curves.solve!(curve, 0.5)
    iex> x
    20.5
    iex> y
    10.5

# `inc_y`

```elixir
@spec inc_y(curve :: transformable(), yinc :: Curves.Types.coord()) :: transformable()
```

Using the current position increase the y coordinates.
It is also possible to decrease by passing a negative number

## Examples
    iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
    iex> {x, y} = Curves.solve!(curve, 0.5)
    iex> x
    10.5
    iex> y
    10.5
    iex> curve = Transform.inc_y(curve, -10)
    iex> {x, y} = Curves.solve!(curve, 0.5)
    iex> x
    10.5
    iex> y
    0.5

# `reverse`

```elixir
@spec reverse(transformable()) :: transformable()
```

The same curve, travelled the other way: `t` (or `u`) `= 0.0` is now its old end. Its shape,
position and transforms don't change. Handy to play an easing backwards, or follow a path in reverse.

## Examples
    iex> curve = Curves.define_bezier([{0, 0}, {1, 2}])
    iex> Curves.solve!(Transform.reverse(curve), 0.0)
    {1.0, 2.0}
    iex> spline = Curves.define_catmull_rom([{0, 0}, {1, 1}, {2, 0}])
    iex> Curves.solve!(Transform.reverse(spline), 2.0)
    {0.0, 0.0}

# `set_position`

```elixir
@spec set_position(curve :: transformable(), Curves.Types.point_tuple()) ::
  transformable()
```

Ignore current position and set a new one.

## Examples
    iex> curve = Curves.define_bezier(:linear)
    iex> {x, _y} = Curves.solve!(curve, 0.5)
    iex> x
    0.5
    iex> curve = Curves.Transform.set_position(curve, {10, 10})
    iex> {x, _y} = Curves.solve!(curve, 0.5)
    iex> x
    10.5

# `set_rotation`

```elixir
@spec set_rotation(transformable(), number(), opts :: [{:around, around()}]) ::
  transformable()
```

Rotate the curve counter-clockwise, in degrees, replacing the existing rotation. It turns around its
position (the `:origin` option or `set_position/2`, `{0.0, 0.0}` by default).

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier(:linear_up_right)
    iex> curve = Transform.set_rotation(curve, 90)
    iex> {x, y} = Curves.solve!(curve, 1.0)
    iex> {Float.round(x, 5), Float.round(y, 5)}
    {-1.0, 1.0}

Turned around its middle, the line stays centred on the same spot:

    iex> curve = Curves.define_bezier([{0, 0}, {2, 0}]) |> Transform.set_rotation(90, around: :center)
    iex> for t <- [0.0, 1.0], {x, y} = Curves.solve!(curve, t), do: {Float.round(x, 5), Float.round(y, 5)}
    [{1.0, -1.0}, {1.0, 1.0}]

# `set_scale`

```elixir
@spec set_scale(
  transformable(),
  scale :: number() | {number(), number()},
  opts :: [{:around, around()}]
) :: transformable()
```

Resize the curve, replacing the existing scale. The default scale is `1.0`: `2.0` doubles the curve and
`0.5` halves it. A negative scale flips it.

Give an `{x, y}` pair to scale the two directions separately, e.g. `{2, 0.5}` makes it twice as wide
and half as tall.

The curve is scaled and rotated around its own `{0, 0}`. Pass `around:` to turn around a
different point instead, which then stays exactly where it is: either `:center` (the middle of the
curve's points) or an `{x, y}` point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.

`around:` keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to `0` (or the scale back to `1`) later, without `around:`, leaves the curve moved.

## Examples
    iex> curve = Curves.define_bezier(:linear)
    iex> Curves.solve!(curve, 0.5)
    {0.5, 0.5}
    iex> curve = Transform.set_scale(curve, 10.0)
    iex> Curves.solve!(curve, 0.5)
    {5.0, 5.0}

Separate x and y scales:

    iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale({2, 0.5})
    iex> Curves.solve!(curve, 1.0)
    {4.0, 1.0}

The curve is scaled around its own `{0, 0}`, so a moved curve stays where it was put:

    iex> curve = Curves.define_bezier([{0, 0}, {4, 0}]) |> Transform.set_position({10, 10})
    iex> curve = Transform.set_scale(curve, 2)
    iex> Curves.solve!(curve, 0.0)
    {10.0, 10.0}
    iex> Curves.solve!(curve, 1.0)
    {18.0, 10.0}

Scaled around its middle instead, it grows equally in every direction:

    iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale(2, around: :center)
    iex> {Curves.solve!(curve, 0.0), Curves.solve!(curve, 1.0)}
    {{-1.0, -1.0}, {3.0, 3.0}}

---

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