Visualize.IR.Path (Visualize v0.2.25)

Copy Markdown View Source

Backend-agnostic path command representation.

Stores drawing commands as tuples that can be converted to any output format by backends (SVG, Canvas, etc.).

Command Types

  • Movement: {:M, x, y}, {:m, dx, dy} (absolute/relative move)
  • Lines: {:L, x, y}, {:l, dx, dy}, {:H, x}, {:V, y}
  • Curves: {:C, x1, y1, x2, y2, x, y} (cubic bezier)
  • Arcs: {:A, rx, ry, rotation, large_arc, sweep, x, y}
  • Close: :Z

Examples

iex> alias Visualize.IR.Path
iex> Path.new()
...> |> Path.move_to(10, 20)
...> |> Path.line_to(100, 200)
...> |> Path.close()
%Visualize.IR.Path{commands: [{:M, 10, 20}, {:L, 100, 200}, :Z]}

Summary

Functions

Rewrites every command as an absolute M, L, C, Q, A or Z.

Appends commands from another path.

Close the path.

Returns the number of commands in the path.

Combines multiple paths into one.

Cubic Bezier curve to absolute position.

Cubic Bezier curve to relative position.

Returns whether the path is empty (has no commands).

Rewrites the smooth curve commands S/s/T/t as C/c/Q/q.

The number formatter every serialiser shares (D-12, D-32): an integer verbatim; a float with at most four decimals, trailing zeros and point trimmed, never in exponent notation, and never -0.

A path of the commands given, in the order given (spec/02 §2.2, #414).

Horizontal line to absolute x.

Horizontal line to relative x.

Line to absolute position.

Line to relative position.

Move to absolute position.

Move to relative position.

Creates a new empty path.

Quadratic Bezier curve to absolute position.

Quadratic Bezier curve to relative position.

Smooth cubic Bezier curve to absolute position.

Smooth cubic Bezier curve to relative position.

Smooth quadratic Bezier curve to absolute position.

Smooth quadratic Bezier curve to relative position.

Converts the path to an SVG path data string.

Applies an affine transform to every coordinate of the path.

Vertical line to absolute y.

Vertical line to relative y.

Types

command()

@type command() ::
  {:M, number(), number()}
  | {:m, number(), number()}
  | {:L, number(), number()}
  | {:l, number(), number()}
  | {:H, number()}
  | {:h, number()}
  | {:V, number()}
  | {:v, number()}
  | {:C, number(), number(), number(), number(), number(), number()}
  | {:c, number(), number(), number(), number(), number(), number()}
  | {:S, number(), number(), number(), number()}
  | {:s, number(), number(), number(), number()}
  | {:Q, number(), number(), number(), number()}
  | {:q, number(), number(), number(), number()}
  | {:T, number(), number()}
  | {:t, number(), number()}
  | {:A, number(), number(), number(), integer(), integer(), number(), number()}
  | {:a, number(), number(), number(), integer(), integer(), number(), number()}
  | :Z

t()

@type t() :: %Visualize.IR.Path{commands: [command()], metadata: map()}

Functions

absolute(path)

@spec absolute(t()) :: t()

Rewrites every command as an absolute M, L, C, Q, A or Z.

Relative commands are resolved against the current point (a leading m is absolute, as SVG reads it), H/V become L, and the smooth commands are expanded as in expand_smooth/1. An arc keeps its radii, rotation and flags. The result draws the same figure and is what a target without relative commands, such as Canvas, replays.

Examples

iex> alias Visualize.IR.Path
iex> Path.new() |> Path.move_to(1, 1) |> Path.line_to_rel(2, 0) |> Path.vertical_to(5)
...> |> Path.close() |> Path.absolute()
%Visualize.IR.Path{commands: [{:M, 1, 1}, {:L, 3, 1}, {:L, 3, 5}, :Z]}

append(path1, path2)

@spec append(t(), t()) :: t()

Appends commands from another path.

arc_to(path, rx, ry, x_rotation, large_arc, sweep, x, y)

@spec arc_to(
  t(),
  number(),
  number(),
  number(),
  integer(),
  integer(),
  number(),
  number()
) :: t()

Arc to absolute position.

  • rx, ry: radii of the ellipse
  • x_rotation: rotation of the ellipse in degrees
  • large_arc: 0 or 1, whether to use the larger arc
  • sweep: 0 or 1, direction of the arc
  • x, y: end point

arc_to_rel(path, rx, ry, x_rotation, large_arc, sweep, dx, dy)

@spec arc_to_rel(
  t(),
  number(),
  number(),
  number(),
  integer(),
  integer(),
  number(),
  number()
) :: t()

Arc to relative position.

close(path)

@spec close(t()) :: t()

Close the path.

command_count(path)

@spec command_count(t()) :: non_neg_integer()

Returns the number of commands in the path.

concat(paths)

@spec concat([t()]) :: t()

Combines multiple paths into one.

curve_to(path, x1, y1, x2, y2, x, y)

@spec curve_to(t(), number(), number(), number(), number(), number(), number()) :: t()

Cubic Bezier curve to absolute position.

curve_to_rel(path, dx1, dy1, dx2, dy2, dx, dy)

@spec curve_to_rel(t(), number(), number(), number(), number(), number(), number()) ::
  t()

Cubic Bezier curve to relative position.

empty?(path)

@spec empty?(t()) :: boolean()

Returns whether the path is empty (has no commands).

expand_smooth(path)

@spec expand_smooth(t()) :: t()

Rewrites the smooth curve commands S/s/T/t as C/c/Q/q.

The first control point is the reflection of the previous command's last control point about the current point when that command was a cubic (for S) or a quadratic (for T), and the current point itself otherwise — SVG's rule. Every other command is kept as it is, so relative commands stay relative.

Examples

iex> alias Visualize.IR.Path
iex> Path.new() |> Path.move_to(0, 0) |> Path.curve_to(1, 1, 2, 1, 3, 0)
...> |> Path.smooth_curve_to(5, -1, 6, 0) |> Path.expand_smooth()
%Visualize.IR.Path{commands: [{:M, 0, 0}, {:C, 1, 1, 2, 1, 3, 0}, {:C, 4, -1, 5, -1, 6, 0}]}

format_number(n)

@spec format_number(number()) :: String.t()

The number formatter every serialiser shares (D-12, D-32): an integer verbatim; a float with at most four decimals, trailing zeros and point trimmed, never in exponent notation, and never -0.

Examples

iex> Visualize.IR.Path.format_number(1.5)
"1.5"
iex> Visualize.IR.Path.format_number(1.0e20)
"100000000000000000000"
iex> Visualize.IR.Path.format_number(-0.00001)
"0"

from_commands(commands)

@spec from_commands([command()]) :: t()

A path of the commands given, in the order given (spec/02 §2.2, #414).

What a builder drawing one command per datum ends with: the appenders put a command at the end of a list, which costs the list, so appending n commands one at a time costs a square of n — a 50,000-point line took six seconds to build and sixteen milliseconds to encode. A builder collects its commands and makes the path once.

iex> Visualize.IR.Path.from_commands([{:M, 0, 0}, {:L, 10, 5}])
%Visualize.IR.Path{commands: [{:M, 0, 0}, {:L, 10, 5}]}

horizontal_to(path, x)

@spec horizontal_to(t(), number()) :: t()

Horizontal line to absolute x.

horizontal_to_rel(path, dx)

@spec horizontal_to_rel(t(), number()) :: t()

Horizontal line to relative x.

line_to(path, x, y)

@spec line_to(t(), number(), number()) :: t()

Line to absolute position.

line_to_rel(path, dx, dy)

@spec line_to_rel(t(), number(), number()) :: t()

Line to relative position.

move_to(path, x, y)

@spec move_to(t(), number(), number()) :: t()

Move to absolute position.

move_to_rel(path, dx, dy)

@spec move_to_rel(t(), number(), number()) :: t()

Move to relative position.

new()

@spec new() :: t()

Creates a new empty path.

quad_to(path, x1, y1, x, y)

@spec quad_to(t(), number(), number(), number(), number()) :: t()

Quadratic Bezier curve to absolute position.

quad_to_rel(path, dx1, dy1, dx, dy)

@spec quad_to_rel(t(), number(), number(), number(), number()) :: t()

Quadratic Bezier curve to relative position.

smooth_curve_to(path, x2, y2, x, y)

@spec smooth_curve_to(t(), number(), number(), number(), number()) :: t()

Smooth cubic Bezier curve to absolute position.

smooth_curve_to_rel(path, dx2, dy2, dx, dy)

@spec smooth_curve_to_rel(t(), number(), number(), number(), number()) :: t()

Smooth cubic Bezier curve to relative position.

smooth_quad_to(path, x, y)

@spec smooth_quad_to(t(), number(), number()) :: t()

Smooth quadratic Bezier curve to absolute position.

smooth_quad_to_rel(path, dx, dy)

@spec smooth_quad_to_rel(t(), number(), number()) :: t()

Smooth quadratic Bezier curve to relative position.

to_string(path)

@spec to_string(t()) :: String.t()

Converts the path to an SVG path data string.

Examples

iex> alias Visualize.IR.Path
iex> Path.new() |> Path.move_to(10, 20) |> Path.line_to(100, 200) |> Path.to_string()
"M10,20L100,200"

transform(path, t)

Applies an affine transform to every coordinate of the path.

The transform is a matrix {a, b, c, d, e, f} (Visualize.IR.Transform.matrix/0) or a Visualize.IR.Transform, which is folded with Visualize.IR.Transform.to_matrix/1. Absolute end and control points get the full affine map; relative deltas get its linear part only. H/V/h/v are resolved against the current point and stay single-axis when the transform keeps that axis (no rotation or skew into it), otherwise they become L/l. An arc's end point is mapped, its radii and rotation are those of the exact image ellipse, and its sweep flag flips under a reflection. :Z is untouched.

Examples

iex> alias Visualize.IR.Path
iex> Path.new() |> Path.move_to(1, 2) |> Path.line_to_rel(3, 4) |> Path.horizontal_to(9)
...> |> Path.transform({1, 0, 0, 1, 10, 20})
%Visualize.IR.Path{commands: [{:M, 11, 22}, {:l, 3, 4}, {:H, 19}]}

vertical_to(path, y)

@spec vertical_to(t(), number()) :: t()

Vertical line to absolute y.

vertical_to_rel(path, dy)

@spec vertical_to_rel(t(), number()) :: t()

Vertical line to relative y.