Skip to main content

src/svg_path@transform.erl

-module(svg_path@transform).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/transform.gleam").
-export([matrix/6, from_tuple/1, identity/0, multiply/2, chain/2, translate/2, about_point/2, scale_xy/2, scale/1, rotate/1, skew_x/1, skew_y/1, to_tuple/1, point/2, translate_point/3, scale_point/2, scale_xy_point/3, rotate_point/2, skew_x_point/2, skew_y_point/2, segment/2, segment_about_point/3, segment_about_anchor/3, translate_segment/3, scale_segment/2, scale_xy_segment/3, rotate_segment/2, skew_x_segment/2, skew_y_segment/2, segment_gracefully/2, segment_gracefully2/2, subpath/2, subpath_about_point/3, subpath_about_anchor/3, translate_subpath/3, scale_subpath/2, scale_xy_subpath/3, rotate_subpath/2, skew_x_subpath/2, skew_y_subpath/2, subpath_gracefully/2, path/2, path_about_point/3, path_about_anchor/3, translate_path/3, scale_path/2, scale_xy_path/3, rotate_path/2, skew_x_path/2, skew_y_path/2]).
-export_type([matrix/0, anchor/0, error/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Transform paths and path segments with 2D affine matrices.\n"
    "\n"
    " Matrices use SVG's six-value affine form `matrix(a b c d e f)` and are\n"
    " applied to column vectors. Use `chain(first:, then:)` when thinking in\n"
    " transform application order, and `multiply(left:, right:)` when thinking in\n"
    " algebraic matrix multiplication order.\n"
).

-opaque matrix() :: {matrix,
        float(),
        float(),
        float(),
        float(),
        float(),
        float()}.

-type anchor() :: top_left |
    top_center |
    top_right |
    center_left |
    center |
    center_right |
    bottom_left |
    bottom_center |
    bottom_right.

-type error() :: degenerate_arc_transform |
    invalid_matrix |
    {core, svg_path:error()}.

-file("src/svg_path/transform.gleam", 50).
?DOC(" Create an affine matrix from SVG's six matrix values.\n").
-spec matrix(float(), float(), float(), float(), float(), float()) -> matrix().
matrix(A, B, C, D, E, F) ->
    {matrix, A, B, C, D, E, F}.

-file("src/svg_path/transform.gleam", 62).
?DOC(" Create an affine matrix from SVG's six matrix values as a tuple.\n").
-spec from_tuple({float(), float(), float(), float(), float(), float()}) -> matrix().
from_tuple(Values) ->
    {A, B, C, D, E, F} = Values,
    matrix(A, B, C, D, E, F).

-file("src/svg_path/transform.gleam", 71).
?DOC(" The identity transform.\n").
-spec identity() -> matrix().
identity() ->
    matrix(1.0, +0.0, +0.0, 1.0, +0.0, +0.0).

-file("src/svg_path/transform.gleam", 86).
?DOC(
    " Multiply two matrices in algebraic order.\n"
    "\n"
    " `multiply(left: a, right: b)` returns `a * b`.\n"
).
-spec multiply(matrix(), matrix()) -> matrix().
multiply(Left, Right) ->
    matrix(
        (erlang:element(2, Left) * erlang:element(2, Right)) + (erlang:element(
            4,
            Left
        )
        * erlang:element(3, Right)),
        (erlang:element(3, Left) * erlang:element(2, Right)) + (erlang:element(
            5,
            Left
        )
        * erlang:element(3, Right)),
        (erlang:element(2, Left) * erlang:element(4, Right)) + (erlang:element(
            4,
            Left
        )
        * erlang:element(5, Right)),
        (erlang:element(3, Left) * erlang:element(4, Right)) + (erlang:element(
            5,
            Left
        )
        * erlang:element(5, Right)),
        ((erlang:element(2, Left) * erlang:element(6, Right)) + (erlang:element(
            4,
            Left
        )
        * erlang:element(7, Right)))
        + erlang:element(6, Left),
        ((erlang:element(3, Left) * erlang:element(6, Right)) + (erlang:element(
            5,
            Left
        )
        * erlang:element(7, Right)))
        + erlang:element(7, Left)
    ).

-file("src/svg_path/transform.gleam", 79).
?DOC(
    " Chain two transforms in application order.\n"
    "\n"
    " `chain(first: a, then: b)` creates a matrix that applies `a` first and `b`\n"
    " second. In algebraic matrix order, this is `b * a`.\n"
).
-spec chain(matrix(), matrix()) -> matrix().
chain(First, Second) ->
    multiply(Second, First).

-file("src/svg_path/transform.gleam", 108).
?DOC(" Create a translation matrix.\n").
-spec translate(float(), float()) -> matrix().
translate(X, Y) ->
    matrix(1.0, +0.0, +0.0, 1.0, X, Y).

-file("src/svg_path/transform.gleam", 98).
?DOC(" Create a matrix that applies a transform about a point.\n").
-spec about_point(matrix(), vec@vec2:vec2(float())) -> matrix().
about_point(Transform, Point) ->
    _pipe = translate(
        +0.0 - erlang:element(2, Point),
        +0.0 - erlang:element(3, Point)
    ),
    _pipe@1 = chain(_pipe, Transform),
    chain(
        _pipe@1,
        translate(erlang:element(2, Point), erlang:element(3, Point))
    ).

-file("src/svg_path/transform.gleam", 118).
?DOC(" Create a non-uniform scale matrix.\n").
-spec scale_xy(float(), float()) -> matrix().
scale_xy(X, Y) ->
    matrix(X, +0.0, +0.0, Y, +0.0, +0.0).

-file("src/svg_path/transform.gleam", 113).
?DOC(" Create a uniform scale matrix.\n").
-spec scale(float()) -> matrix().
scale(Factor) ->
    scale_xy(Factor, Factor).

-file("src/svg_path/transform.gleam", 884).
-spec degrees_to_radians(float()) -> float().
degrees_to_radians(Degrees) ->
    (Degrees * gleam_community@maths:pi()) / 180.0.

-file("src/svg_path/transform.gleam", 123).
?DOC(" Create a rotation matrix from an angle in degrees.\n").
-spec rotate(float()) -> matrix().
rotate(Degrees) ->
    Radians = degrees_to_radians(Degrees),
    Cosine = gleam_community@maths:cos(Radians),
    Sine = gleam_community@maths:sin(Radians),
    matrix(Cosine, Sine, +0.0 - Sine, Cosine, +0.0, +0.0).

-file("src/svg_path/transform.gleam", 132).
?DOC(" Create an x-axis skew matrix from an angle in degrees.\n").
-spec skew_x(float()) -> matrix().
skew_x(Degrees) ->
    matrix(
        1.0,
        +0.0,
        gleam_community@maths:tan(degrees_to_radians(Degrees)),
        1.0,
        +0.0,
        +0.0
    ).

-file("src/svg_path/transform.gleam", 144).
?DOC(" Create a y-axis skew matrix from an angle in degrees.\n").
-spec skew_y(float()) -> matrix().
skew_y(Degrees) ->
    matrix(
        1.0,
        gleam_community@maths:tan(degrees_to_radians(Degrees)),
        +0.0,
        1.0,
        +0.0,
        +0.0
    ).

-file("src/svg_path/transform.gleam", 156).
?DOC(" Return SVG's six matrix values as `#(a, b, c, d, e, f)`.\n").
-spec to_tuple(matrix()) -> {float(),
    float(),
    float(),
    float(),
    float(),
    float()}.
to_tuple(Transform) ->
    {erlang:element(2, Transform),
        erlang:element(3, Transform),
        erlang:element(4, Transform),
        erlang:element(5, Transform),
        erlang:element(6, Transform),
        erlang:element(7, Transform)}.

-file("src/svg_path/transform.gleam", 170).
?DOC(" Transform a point by a matrix.\n").
-spec point(vec@vec2:vec2(float()), matrix()) -> vec@vec2:vec2(float()).
point(Point, Transform) ->
    svg_path:point(
        ((erlang:element(2, Transform) * erlang:element(2, Point)) + (erlang:element(
            4,
            Transform
        )
        * erlang:element(3, Point)))
        + erlang:element(6, Transform),
        ((erlang:element(3, Transform) * erlang:element(2, Point)) + (erlang:element(
            5,
            Transform
        )
        * erlang:element(3, Point)))
        + erlang:element(7, Transform)
    ).

-file("src/svg_path/transform.gleam", 178).
?DOC(" Translate a point.\n").
-spec translate_point(vec@vec2:vec2(float()), float(), float()) -> vec@vec2:vec2(float()).
translate_point(Input, X, Y) ->
    point(Input, translate(X, Y)).

-file("src/svg_path/transform.gleam", 187).
?DOC(" Scale a point uniformly.\n").
-spec scale_point(vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
scale_point(Input, Factor) ->
    point(Input, scale(Factor)).

-file("src/svg_path/transform.gleam", 195).
?DOC(" Scale a point non-uniformly.\n").
-spec scale_xy_point(vec@vec2:vec2(float()), float(), float()) -> vec@vec2:vec2(float()).
scale_xy_point(Input, X, Y) ->
    point(Input, scale_xy(X, Y)).

-file("src/svg_path/transform.gleam", 204).
?DOC(" Rotate a point around the origin.\n").
-spec rotate_point(vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
rotate_point(Input, Degrees) ->
    point(Input, rotate(Degrees)).

-file("src/svg_path/transform.gleam", 212).
?DOC(" Skew a point along the x axis.\n").
-spec skew_x_point(vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
skew_x_point(Input, Degrees) ->
    point(Input, skew_x(Degrees)).

-file("src/svg_path/transform.gleam", 220).
?DOC(" Skew a point along the y axis.\n").
-spec skew_y_point(vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
skew_y_point(Input, Degrees) ->
    point(Input, skew_y(Degrees)).

-file("src/svg_path/transform.gleam", 880).
-spec determinant(matrix()) -> float().
determinant(Transform) ->
    (erlang:element(2, Transform) * erlang:element(5, Transform)) - (erlang:element(
        3,
        Transform
    )
    * erlang:element(4, Transform)).

-file("src/svg_path/transform.gleam", 873).
-spec transformed_sweep(boolean(), matrix()) -> boolean().
transformed_sweep(Sweep, Transform) ->
    case determinant(Transform) < +0.0 of
        true ->
            not Sweep;

        false ->
            Sweep
    end.

-file("src/svg_path/transform.gleam", 750).
-spec from_ellipse_point(svg_path@ellipse:point()) -> vec@vec2:vec2(float()).
from_ellipse_point(Point) ->
    svg_path:point(erlang:element(2, Point), erlang:element(3, Point)).

-file("src/svg_path/transform.gleam", 735).
-spec affine(matrix()) -> svg_path@ellipse:affine().
affine(Transform) ->
    svg_path@ellipse:ellipse_affine(
        erlang:element(2, Transform),
        erlang:element(3, Transform),
        erlang:element(4, Transform),
        erlang:element(5, Transform),
        erlang:element(6, Transform),
        erlang:element(7, Transform)
    ).

-file("src/svg_path/transform.gleam", 746).
-spec to_ellipse_point(vec@vec2:vec2(float())) -> svg_path@ellipse:point().
to_ellipse_point(Point) ->
    {point, erlang:element(2, Point), erlang:element(3, Point)}.

-file("src/svg_path/transform.gleam", 645).
-spec transform_valid_segment(svg_path:segment(), matrix()) -> {ok,
        svg_path:segment()} |
    {error, error()}.
transform_valid_segment(Segment, Transform) ->
    case Segment of
        {line, Start, End} ->
            {ok, {line, point(Start, Transform), point(End, Transform)}};

        {quadratic_bezier, Start@1, Control, End@1} ->
            {ok,
                {quadratic_bezier,
                    point(Start@1, Transform),
                    point(Control, Transform),
                    point(End@1, Transform)}};

        {cubic_bezier, Start@2, Control1, Control2, End@2} ->
            {ok,
                {cubic_bezier,
                    point(Start@2, Transform),
                    point(Control1, Transform),
                    point(Control2, Transform),
                    point(End@2, Transform)}};

        {arc, Start@3, Radius, X_axis_rotation, Large_arc, Sweep, End@3} ->
            case svg_path@ellipse:transformed_axes(
                to_ellipse_point(Radius),
                X_axis_rotation,
                affine(Transform)
            ) of
                {error, _} ->
                    {error, degenerate_arc_transform};

                {ok, {Radius@1, X_axis_rotation@1}} ->
                    {ok,
                        {arc,
                            point(Start@3, Transform),
                            from_ellipse_point(Radius@1),
                            X_axis_rotation@1,
                            Large_arc,
                            transformed_sweep(Sweep, Transform),
                            point(End@3, Transform)}}
            end
    end.

-file("src/svg_path/transform.gleam", 791).
-spec is_nan(float()) -> boolean().
is_nan(Value) ->
    not ((Value < +0.0) orelse (Value >= +0.0)).

-file("src/svg_path/transform.gleam", 787).
-spec is_finite(float()) -> boolean().
is_finite(Value) ->
    not is_nan(Value - Value).

-file("src/svg_path/transform.gleam", 695).
-spec validate_matrix(matrix()) -> {ok, nil} | {error, error()}.
validate_matrix(Transform) ->
    case ((((is_finite(erlang:element(2, Transform)) andalso is_finite(
        erlang:element(3, Transform)
    ))
    andalso is_finite(erlang:element(4, Transform)))
    andalso is_finite(erlang:element(5, Transform)))
    andalso is_finite(erlang:element(6, Transform)))
    andalso is_finite(erlang:element(7, Transform)) of
        true ->
            {ok, nil};

        false ->
            {error, invalid_matrix}
    end.

-file("src/svg_path/transform.gleam", 232).
?DOC(
    " Transform a segment by a matrix.\n"
    "\n"
    " Degenerate arc transforms return `DegenerateArcTransform`; use\n"
    " `segment_gracefully` or `segment_gracefully2` to convert collapsed arcs into\n"
    " line-based representations when possible.\n"
).
-spec segment(svg_path:segment(), matrix()) -> {ok, svg_path:segment()} |
    {error, error()}.
segment(Segment, Transform) ->
    case validate_matrix(Transform) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            transform_valid_segment(Segment, Transform)
    end.

-file("src/svg_path/transform.gleam", 243).
?DOC(" Transform a segment about a point.\n").
-spec segment_about_point(svg_path:segment(), matrix(), vec@vec2:vec2(float())) -> {ok,
        svg_path:segment()} |
    {error, error()}.
segment_about_point(Input, Transform, Point) ->
    segment(Input, about_point(Transform, Point)).

-file("src/svg_path/transform.gleam", 718).
-spec anchor_point(svg_path:bounding_box(), anchor()) -> vec@vec2:vec2(float()).
anchor_point(Box, Anchor) ->
    {bounding_box, Min, Max} = Box,
    Center = svg_path:bounding_box_center(Box),
    case Anchor of
        top_left ->
            Min;

        top_center ->
            svg_path:point(erlang:element(2, Center), erlang:element(3, Min));

        top_right ->
            svg_path:point(erlang:element(2, Max), erlang:element(3, Min));

        center_left ->
            svg_path:point(erlang:element(2, Min), erlang:element(3, Center));

        center ->
            Center;

        center_right ->
            svg_path:point(erlang:element(2, Max), erlang:element(3, Center));

        bottom_left ->
            svg_path:point(erlang:element(2, Min), erlang:element(3, Max));

        bottom_center ->
            svg_path:point(erlang:element(2, Center), erlang:element(3, Max));

        bottom_right ->
            Max
    end.

-file("src/svg_path/transform.gleam", 252).
?DOC(" Transform a segment about an anchor on its bounding box.\n").
-spec segment_about_anchor(svg_path:segment(), matrix(), anchor()) -> {ok,
        svg_path:segment()} |
    {error, error()}.
segment_about_anchor(Input, Transform, Anchor) ->
    case svg_path:segment_bounding_box(Input) of
        {error, Error} ->
            {error, {core, Error}};

        {ok, Box} ->
            segment_about_point(Input, Transform, anchor_point(Box, Anchor))
    end.

-file("src/svg_path/transform.gleam", 269).
?DOC(" Translate a segment.\n").
-spec translate_segment(svg_path:segment(), float(), float()) -> {ok,
        svg_path:segment()} |
    {error, error()}.
translate_segment(Input, X, Y) ->
    segment(Input, translate(X, Y)).

-file("src/svg_path/transform.gleam", 278).
?DOC(" Scale a segment uniformly.\n").
-spec scale_segment(svg_path:segment(), float()) -> {ok, svg_path:segment()} |
    {error, error()}.
scale_segment(Input, Factor) ->
    segment(Input, scale(Factor)).

-file("src/svg_path/transform.gleam", 286).
?DOC(" Scale a segment non-uniformly.\n").
-spec scale_xy_segment(svg_path:segment(), float(), float()) -> {ok,
        svg_path:segment()} |
    {error, error()}.
scale_xy_segment(Input, X, Y) ->
    segment(Input, scale_xy(X, Y)).

-file("src/svg_path/transform.gleam", 295).
?DOC(" Rotate a segment around the origin.\n").
-spec rotate_segment(svg_path:segment(), float()) -> {ok, svg_path:segment()} |
    {error, error()}.
rotate_segment(Input, Degrees) ->
    segment(Input, rotate(Degrees)).

-file("src/svg_path/transform.gleam", 303).
?DOC(" Skew a segment along the x axis.\n").
-spec skew_x_segment(svg_path:segment(), float()) -> {ok, svg_path:segment()} |
    {error, error()}.
skew_x_segment(Input, Degrees) ->
    segment(Input, skew_x(Degrees)).

-file("src/svg_path/transform.gleam", 311).
?DOC(" Skew a segment along the y axis.\n").
-spec skew_y_segment(svg_path:segment(), float()) -> {ok, svg_path:segment()} |
    {error, error()}.
skew_y_segment(Input, Degrees) ->
    segment(Input, skew_y(Degrees)).

-file("src/svg_path/transform.gleam", 322).
?DOC(
    " Transform a segment, converting collapsed arcs into lines when possible.\n"
    "\n"
    " This returns a single segment. If a collapsed arc needs multiple line\n"
    " segments to preserve its motion, use `segment_gracefully2`.\n"
).
-spec segment_gracefully(svg_path:segment(), matrix()) -> {ok,
        svg_path:segment()} |
    {error, error()}.
segment_gracefully(Input, Transform) ->
    case segment(Input, Transform) of
        {ok, Segment} ->
            {ok, Segment};

        {error, degenerate_arc_transform} ->
            case Input of
                {arc, Start, Radius, X_axis_rotation, Large_arc, Sweep, End} ->
                    case svg_path@ellipse:collapsed_arc_line(
                        to_ellipse_point(Start),
                        to_ellipse_point(Radius),
                        X_axis_rotation,
                        Large_arc,
                        Sweep,
                        to_ellipse_point(End),
                        affine(Transform)
                    ) of
                        {ok, Line} ->
                            {Start@1, End@1} = Line,
                            {ok,
                                {line,
                                    from_ellipse_point(Start@1),
                                    from_ellipse_point(End@1)}};

                        {error, _} ->
                            {error, degenerate_arc_transform}
                    end;

                _ ->
                    {error, degenerate_arc_transform}
            end;

        {error, Error} ->
            {error, Error}
    end.

-file("src/svg_path/transform.gleam", 709).
-spec map_core_error({ok, svg_path:subpath()} | {error, svg_path:error()}) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
map_core_error(Result) ->
    case Result of
        {ok, Subpath} ->
            {ok, Subpath};

        {error, Error} ->
            {error, {core, Error}}
    end.

-file("src/svg_path/transform.gleam", 768).
-spec lines_between_rest(
    svg_path@ellipse:point(),
    list(svg_path@ellipse:point()),
    list(svg_path:segment())
) -> list(svg_path:segment()).
lines_between_rest(Previous, Points, Lines) ->
    case Points of
        [] ->
            lists:reverse(Lines);

        [First | Rest] ->
            lines_between_rest(
                First,
                Rest,
                [{line, from_ellipse_point(Previous), from_ellipse_point(First)} |
                    Lines]
            )
    end.

-file("src/svg_path/transform.gleam", 754).
-spec lines_between(list(svg_path@ellipse:point())) -> list(svg_path:segment()).
lines_between(Points) ->
    case Points of
        [] ->
            [];

        [_] ->
            [];

        [First, Second | Rest] ->
            lines_between_rest(
                Second,
                Rest,
                [{line, from_ellipse_point(First), from_ellipse_point(Second)}]
            )
    end.

-file("src/svg_path/transform.gleam", 369).
?DOC(
    " Transform a segment, returning a subpath for graceful collapsed arc handling.\n"
    "\n"
    " This can represent collapsed arcs as multiple line segments when needed.\n"
).
-spec segment_gracefully2(svg_path:segment(), matrix()) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
segment_gracefully2(Input, Transform) ->
    case segment(Input, Transform) of
        {ok, Segment} ->
            _pipe = svg_path:subpath([Segment]),
            map_core_error(_pipe);

        {error, degenerate_arc_transform} ->
            case Input of
                {arc, Start, Radius, X_axis_rotation, Large_arc, Sweep, End} ->
                    case svg_path@ellipse:collapsed_arc_subpath(
                        to_ellipse_point(Start),
                        to_ellipse_point(Radius),
                        X_axis_rotation,
                        Large_arc,
                        Sweep,
                        to_ellipse_point(End),
                        affine(Transform)
                    ) of
                        {ok, Points} ->
                            _pipe@1 = svg_path:subpath(lines_between(Points)),
                            map_core_error(_pipe@1);

                        {error, _} ->
                            {error, degenerate_arc_transform}
                    end;

                _ ->
                    {error, degenerate_arc_transform}
            end;

        {error, Error} ->
            {error, Error}
    end.

-file("src/svg_path/transform.gleam", 857).
-spec close_transformed_subpath(svg_path:subpath()) -> {ok, svg_path:subpath()} |
    {error, error()}.
close_transformed_subpath(Subpath) ->
    case svg_path:set_closed(Subpath, true) of
        {ok, Subpath@1} ->
            {ok, Subpath@1};

        {error, _} ->
            case svg_path:set_closed_with(Subpath, true, wiggle) of
                {ok, Subpath@2} ->
                    {ok, Subpath@2};

                {error, Error} ->
                    {error, {core, Error}}
            end
    end.

-file("src/svg_path/transform.gleam", 527).
-spec finalize_transformed_subpath(
    svg_path:subpath(),
    list(svg_path:segment()),
    matrix()
) -> {ok, svg_path:subpath()} | {error, error()}.
finalize_transformed_subpath(Original, Segments, Transform) ->
    Start@1 = case svg_path:start(Original) of
        {ok, Start} -> Start;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path/transform"/utf8>>,
                        function => <<"finalize_transformed_subpath"/utf8>>,
                        line => 532,
                        value => _assert_fail,
                        start => 13739,
                        'end' => 13786,
                        pattern_start => 13750,
                        pattern_end => 13759})
    end,
    Start@2 = point(Start@1, Transform),
    case Segments of
        [] ->
            Subpath = svg_path:empty_subpath(Start@2),
            case svg_path:is_closed(Original) of
                true ->
                    _pipe = svg_path:set_closed(Subpath, true),
                    map_core_error(_pipe);

                false ->
                    {ok, Subpath}
            end;

        _ ->
            case svg_path:subpath(Segments) of
                {error, Error} ->
                    {error, {core, Error}};

                {ok, Transformed} ->
                    case svg_path:is_closed(Original) of
                        true ->
                            close_transformed_subpath(Transformed);

                        false ->
                            {ok, Transformed}
                    end
            end
    end.

-file("src/svg_path/transform.gleam", 795).
-spec transform_segments(
    list(svg_path:segment()),
    matrix(),
    list(svg_path:segment())
) -> {ok, list(svg_path:segment())} | {error, error()}.
transform_segments(Segments, Transform, Transformed) ->
    case Segments of
        [] ->
            {ok, lists:reverse(Transformed)};

        [First | Rest] ->
            case segment(First, Transform) of
                {error, Error} ->
                    {error, Error};

                {ok, First@1} ->
                    transform_segments(Rest, Transform, [First@1 | Transformed])
            end
    end.

-file("src/svg_path/transform.gleam", 413).
?DOC(
    " Transform a subpath by a matrix.\n"
    "\n"
    " Closed subpaths remain semantically closed when the transformed endpoints can\n"
    " be reconciled.\n"
).
-spec subpath(svg_path:subpath(), matrix()) -> {ok, svg_path:subpath()} |
    {error, error()}.
subpath(Subpath, Transform) ->
    case validate_matrix(Transform) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case transform_segments(svg_path:segments(Subpath), Transform, []) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Segments} ->
                    finalize_transformed_subpath(Subpath, Segments, Transform)
            end
    end.

-file("src/svg_path/transform.gleam", 430).
?DOC(" Transform a subpath about a point.\n").
-spec subpath_about_point(svg_path:subpath(), matrix(), vec@vec2:vec2(float())) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
subpath_about_point(Input, Transform, Point) ->
    subpath(Input, about_point(Transform, Point)).

-file("src/svg_path/transform.gleam", 439).
?DOC(" Transform a subpath about an anchor on its bounding box.\n").
-spec subpath_about_anchor(svg_path:subpath(), matrix(), anchor()) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
subpath_about_anchor(Input, Transform, Anchor) ->
    case svg_path:subpath_bounding_box(Input) of
        {error, Error} ->
            {error, {core, Error}};

        {ok, Box} ->
            subpath_about_point(Input, Transform, anchor_point(Box, Anchor))
    end.

-file("src/svg_path/transform.gleam", 456).
?DOC(" Translate a subpath.\n").
-spec translate_subpath(svg_path:subpath(), float(), float()) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
translate_subpath(Input, X, Y) ->
    subpath(Input, translate(X, Y)).

-file("src/svg_path/transform.gleam", 465).
?DOC(" Scale a subpath uniformly.\n").
-spec scale_subpath(svg_path:subpath(), float()) -> {ok, svg_path:subpath()} |
    {error, error()}.
scale_subpath(Input, Factor) ->
    subpath(Input, scale(Factor)).

-file("src/svg_path/transform.gleam", 473).
?DOC(" Scale a subpath non-uniformly.\n").
-spec scale_xy_subpath(svg_path:subpath(), float(), float()) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
scale_xy_subpath(Input, X, Y) ->
    subpath(Input, scale_xy(X, Y)).

-file("src/svg_path/transform.gleam", 482).
?DOC(" Rotate a subpath around the origin.\n").
-spec rotate_subpath(svg_path:subpath(), float()) -> {ok, svg_path:subpath()} |
    {error, error()}.
rotate_subpath(Input, Degrees) ->
    subpath(Input, rotate(Degrees)).

-file("src/svg_path/transform.gleam", 490).
?DOC(" Skew a subpath along the x axis.\n").
-spec skew_x_subpath(svg_path:subpath(), float()) -> {ok, svg_path:subpath()} |
    {error, error()}.
skew_x_subpath(Input, Degrees) ->
    subpath(Input, skew_x(Degrees)).

-file("src/svg_path/transform.gleam", 498).
?DOC(" Skew a subpath along the y axis.\n").
-spec skew_y_subpath(svg_path:subpath(), float()) -> {ok, svg_path:subpath()} |
    {error, error()}.
skew_y_subpath(Input, Degrees) ->
    subpath(Input, skew_y(Degrees)).

-file("src/svg_path/transform.gleam", 831).
-spec prepend_all(list(svg_path:segment()), list(svg_path:segment())) -> list(svg_path:segment()).
prepend_all(Segments, Transformed) ->
    case Segments of
        [] ->
            Transformed;

        [First | Rest] ->
            prepend_all(Rest, [First | Transformed])
    end.

-file("src/svg_path/transform.gleam", 811).
-spec transform_segments_gracefully(
    list(svg_path:segment()),
    matrix(),
    list(svg_path:segment())
) -> {ok, list(svg_path:segment())} | {error, error()}.
transform_segments_gracefully(Segments, Transform, Transformed) ->
    case Segments of
        [] ->
            {ok, lists:reverse(Transformed)};

        [First | Rest] ->
            case segment_gracefully2(First, Transform) of
                {error, Error} ->
                    {error, Error};

                {ok, First@1} ->
                    Transformed@1 = prepend_all(
                        svg_path:segments(First@1),
                        Transformed
                    ),
                    transform_segments_gracefully(
                        Rest,
                        Transform,
                        Transformed@1
                    )
            end
    end.

-file("src/svg_path/transform.gleam", 509).
?DOC(
    " Transform a subpath, gracefully converting collapsed arcs when possible.\n"
    "\n"
    " This uses the core path wiggle helpers to preserve continuity after\n"
    " collapsed arcs are converted to line-based subpaths.\n"
).
-spec subpath_gracefully(svg_path:subpath(), matrix()) -> {ok,
        svg_path:subpath()} |
    {error, error()}.
subpath_gracefully(Subpath, Transform) ->
    case validate_matrix(Transform) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case transform_segments_gracefully(
                svg_path:segments(Subpath),
                Transform,
                []
            ) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Segments} ->
                    finalize_transformed_subpath(Subpath, Segments, Transform)
            end
    end.

-file("src/svg_path/transform.gleam", 841).
-spec transform_subpaths(
    list(svg_path:subpath()),
    matrix(),
    list(svg_path:subpath())
) -> {ok, list(svg_path:subpath())} | {error, error()}.
transform_subpaths(Subpaths, Transform, Transformed) ->
    case Subpaths of
        [] ->
            {ok, lists:reverse(Transformed)};

        [First | Rest] ->
            case subpath(First, Transform) of
                {error, Error} ->
                    {error, Error};

                {ok, First@1} ->
                    transform_subpaths(Rest, Transform, [First@1 | Transformed])
            end
    end.

-file("src/svg_path/transform.gleam", 558).
?DOC(" Transform every subpath in a path by a matrix.\n").
-spec path(svg_path:path(), matrix()) -> {ok, svg_path:path()} |
    {error, error()}.
path(Path, Transform) ->
    case validate_matrix(Transform) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case transform_subpaths(svg_path:subpaths(Path), Transform, []) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Subpaths} ->
                    {ok, {path, Subpaths}}
            end
    end.

-file("src/svg_path/transform.gleam", 574).
?DOC(" Transform a path about a point.\n").
-spec path_about_point(svg_path:path(), matrix(), vec@vec2:vec2(float())) -> {ok,
        svg_path:path()} |
    {error, error()}.
path_about_point(Input, Transform, Point) ->
    path(Input, about_point(Transform, Point)).

-file("src/svg_path/transform.gleam", 583).
?DOC(" Transform a path about an anchor on its bounding box.\n").
-spec path_about_anchor(svg_path:path(), matrix(), anchor()) -> {ok,
        svg_path:path()} |
    {error, error()}.
path_about_anchor(Input, Transform, Anchor) ->
    case svg_path:path_bounding_box(Input) of
        {error, Error} ->
            {error, {core, Error}};

        {ok, Box} ->
            path_about_point(Input, Transform, anchor_point(Box, Anchor))
    end.

-file("src/svg_path/transform.gleam", 596).
?DOC(" Translate a path.\n").
-spec translate_path(svg_path:path(), float(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
translate_path(Input, X, Y) ->
    path(Input, translate(X, Y)).

-file("src/svg_path/transform.gleam", 605).
?DOC(" Scale a path uniformly.\n").
-spec scale_path(svg_path:path(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
scale_path(Input, Factor) ->
    path(Input, scale(Factor)).

-file("src/svg_path/transform.gleam", 613).
?DOC(" Scale a path non-uniformly.\n").
-spec scale_xy_path(svg_path:path(), float(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
scale_xy_path(Input, X, Y) ->
    path(Input, scale_xy(X, Y)).

-file("src/svg_path/transform.gleam", 622).
?DOC(" Rotate a path around the origin.\n").
-spec rotate_path(svg_path:path(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
rotate_path(Input, Degrees) ->
    path(Input, rotate(Degrees)).

-file("src/svg_path/transform.gleam", 630).
?DOC(" Skew a path along the x axis.\n").
-spec skew_x_path(svg_path:path(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
skew_x_path(Input, Degrees) ->
    path(Input, skew_x(Degrees)).

-file("src/svg_path/transform.gleam", 638).
?DOC(" Skew a path along the y axis.\n").
-spec skew_y_path(svg_path:path(), float()) -> {ok, svg_path:path()} |
    {error, error()}.
skew_y_path(Input, Degrees) ->
    path(Input, skew_y(Degrees)).