Skip to main content

src/svg_path.erl

-module(svg_path).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path.gleam").
-export([bounding_box_width/1, bounding_box_height/1, point/2, bounding_box_center/1, bounding_box_diameter/1, default_crossing_options/0, default_minimize_options/0, default_distance_options/0, default_intersection_options/0, empty_path/0, subpaths/1, from_subpath/1, append_subpath/2, combine_paths/1, path_map_subpaths/2, path_filter_subpaths/2, as_subpath/1, empty_subpath/1, segment_start/1, segment_end/1, subpath_with/2, subpath/1, polyline/1, assert_polyline/1, 'end'/1, start/1, set_closed_with/3, set_closed/2, polygon/1, assert_polygon/1, assert_subpath_with/2, assert_subpath/1, segments/1, clean_subpath/1, splice_with/5, splice/4, assert_splice_with/5, assert_splice/4, segment_arcs_to_cubic_beziers/1, subpath_arcs_to_cubic_beziers/1, path_arcs_to_cubic_beziers/1, reverse_segment/1, reverse_subpath/1, reverse_path/1, map_segment_points/2, map_subpath_points/2, map_path_points/2, segment_to_cubic_beziers/1, subpath_to_cubic_beziers/1, path_to_cubic_beziers/1, is_closed/1, assert_set_closed_with/3, assert_set_closed/2, open_at/2, compare_subpath_parameters/2, segment_point/2, arc_from_endpoint_data/1, arc_from_center_data/1, split_segment/2, sub_segment/3, sub_segment_inside/3, split_subpath/2, sub_subpath/3, sub_subpaths/2, path_start/1, path_end/1, append_segment_with/3, append_segment/2, assert_append_segment_with/3, assert_append_segment/2, join_with/2, join/1, assert_join_with/2, assert_join/1, segment_derivative/2, segment_bounding_box/1, segment_crossings_with/3, segment_crossings/2, segment_minimize_with/3, segment_minimize/2, segment_distance_with/3, segment_distance/2, segment_intersections_with/3, segment_intersections/2, subpath_bounding_box/1, path_bounding_box/1, split_segment_inside/2, sub_segments/2, sub_segments_inside/2]).
-export_type([bounding_box/0, crossing_options/0, minimize_options/0, distance_options/0, intersection_options/0, segment_intersection/0, minimize_candidate/0, intersection_piece/0, path/0, subpath/0, subpath_parameter/0, canonical_subpath_parameter/0, endpoint_policy/0, segment/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(
    " Core SVG path data structures and constructors.\n"
    "\n"
    " This module models paths as a list of subpaths, and subpaths as continuous\n"
    " segment lists. Use `svg_path/parse` and `svg_path/serialize` when working\n"
    " directly with SVG path data strings.\n"
).

-type bounding_box() :: {bounding_box,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float())}.

-type crossing_options() :: {crossing_options, integer(), float(), integer()}.

-type minimize_options() :: {minimize_options, integer(), float(), integer()}.

-type distance_options() :: {distance_options, integer(), float(), integer()}.

-type intersection_options() :: {intersection_options, float(), integer()}.

-type segment_intersection() :: {segment_intersection,
        float(),
        float(),
        vec@vec2:vec2(float())}.

-type minimize_candidate() :: {minimize_candidate, float(), float()}.

-type intersection_piece() :: {intersection_piece, segment(), float(), float()}.

-type path() :: {path, list(subpath())}.

-opaque subpath() :: {subpath,
        vec@vec2:vec2(float()),
        list(segment()),
        boolean()}.

-type subpath_parameter() :: {subpath_parameter, integer(), float()}.

-type canonical_subpath_parameter() :: {canonical_subpath_parameter,
        integer(),
        float()}.

-type endpoint_policy() :: strict |
    wiggle |
    bridge |
    wiggle_then_bridge |
    {custom, fun((segment(), segment()) -> {segment(), segment()})}.

-type segment() :: {line, vec@vec2:vec2(float()), vec@vec2:vec2(float())} |
    {quadratic_bezier,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float())} |
    {cubic_bezier,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float())} |
    {arc,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float()),
        float(),
        boolean(),
        boolean(),
        vec@vec2:vec2(float())}.

-type error() :: already_closed |
    {discontinuous,
        integer(),
        integer(),
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float()),
        float()} |
    empty_subpath |
    not_closed |
    empty_path |
    empty_subpaths |
    degenerate_arc |
    cannot_map_arc_nonlinearly |
    {incompatible_horizontal_wiggle,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float())} |
    {incompatible_vertical_wiggle,
        vec@vec2:vec2(float()),
        vec@vec2:vec2(float())} |
    {invalid_splice, integer(), integer(), integer()} |
    {invalid_open_index, integer(), integer()} |
    {invalid_subpath_parameter, integer(), float(), integer()} |
    {invalid_subpath_interval, subpath_parameter(), subpath_parameter()} |
    {invalid_crossing_samples, integer()} |
    {invalid_crossing_tolerance, float()} |
    {invalid_crossing_max_iterations, integer()} |
    {crossing_max_iterations_reached, float(), float()} |
    {invalid_minimize_samples, integer()} |
    {invalid_minimize_tolerance, float()} |
    {invalid_minimize_max_iterations, integer()} |
    {minimize_max_iterations_reached, float(), float()} |
    {invalid_distance_samples, integer()} |
    {invalid_distance_tolerance, float()} |
    {invalid_distance_max_iterations, integer()} |
    {distance_max_iterations_reached, float(), float()} |
    {invalid_intersection_tolerance, float()} |
    {invalid_intersection_max_depth, integer()} |
    overlapping_segments |
    multiple_nonempty_subpaths |
    {not_close_enough, vec@vec2:vec2(float()), vec@vec2:vec2(float()), float()} |
    split_outside_segment.

-file("src/svg_path.gleam", 57).
?DOC(" Return the width of a bounding box.\n").
-spec bounding_box_width(bounding_box()) -> float().
bounding_box_width(Box) ->
    erlang:element(2, erlang:element(3, Box)) - erlang:element(
        2,
        erlang:element(2, Box)
    ).

-file("src/svg_path.gleam", 62).
?DOC(" Return the height of a bounding box.\n").
-spec bounding_box_height(bounding_box()) -> float().
bounding_box_height(Box) ->
    erlang:element(3, erlang:element(3, Box)) - erlang:element(
        3,
        erlang:element(2, Box)
    ).

-file("src/svg_path.gleam", 300).
?DOC(" Create a point from `x` and `y` coordinates.\n").
-spec point(float(), float()) -> vec@vec2:vec2(float()).
point(X, Y) ->
    {vec2, X, Y}.

-file("src/svg_path.gleam", 67).
?DOC(" Return the center point of a bounding box.\n").
-spec bounding_box_center(bounding_box()) -> vec@vec2:vec2(float()).
bounding_box_center(Box) ->
    point(
        erlang:element(2, erlang:element(2, Box)) + (bounding_box_width(Box) / 2.0),
        erlang:element(3, erlang:element(2, Box)) + (bounding_box_height(Box) / 2.0)
    ).

-file("src/svg_path.gleam", 77).
?DOC(
    " Return the taxicab diameter of a bounding box.\n"
    "\n"
    " This is the box width plus the box height.\n"
).
-spec bounding_box_diameter(bounding_box()) -> float().
bounding_box_diameter(Box) ->
    bounding_box_width(Box) + bounding_box_height(Box).

-file("src/svg_path.gleam", 305).
?DOC(" Return the default options for segment crossing detection.\n").
-spec default_crossing_options() -> crossing_options().
default_crossing_options() ->
    {crossing_options, 100, 0.000000001, 100}.

-file("src/svg_path.gleam", 314).
?DOC(" Return the default options for segment minimization.\n").
-spec default_minimize_options() -> minimize_options().
default_minimize_options() ->
    {minimize_options, 100, 0.000000001, 100}.

-file("src/svg_path.gleam", 323).
?DOC(" Return the default options for point-to-segment distance measurement.\n").
-spec default_distance_options() -> distance_options().
default_distance_options() ->
    {distance_options, 100, 0.000000001, 100}.

-file("src/svg_path.gleam", 332).
?DOC(" Return the default options for segment intersection detection.\n").
-spec default_intersection_options() -> intersection_options().
default_intersection_options() ->
    {intersection_options, 0.000000001, 48}.

-file("src/svg_path.gleam", 340).
?DOC(" Create an empty path.\n").
-spec empty_path() -> path().
empty_path() ->
    {path, []}.

-file("src/svg_path.gleam", 345).
?DOC(" Return the subpaths in a path.\n").
-spec subpaths(path()) -> list(subpath()).
subpaths(Path) ->
    erlang:element(2, Path).

-file("src/svg_path.gleam", 350).
?DOC(" Create a path containing a single subpath.\n").
-spec from_subpath(subpath()) -> path().
from_subpath(Subpath) ->
    {path, [Subpath]}.

-file("src/svg_path.gleam", 355).
?DOC(" Append a subpath to the end of a path.\n").
-spec append_subpath(path(), subpath()) -> path().
append_subpath(Path, Subpath) ->
    {path, lists:append(erlang:element(2, Path), [Subpath])}.

-file("src/svg_path.gleam", 360).
?DOC(" Combine paths by concatenating their subpaths.\n").
-spec combine_paths(list(path())) -> path().
combine_paths(Paths) ->
    _pipe = Paths,
    _pipe@1 = gleam@list:flat_map(_pipe, fun subpaths/1),
    {path, _pipe@1}.

-file("src/svg_path.gleam", 367).
?DOC(" Map over the subpaths in a path.\n").
-spec path_map_subpaths(path(), fun((subpath()) -> subpath())) -> path().
path_map_subpaths(Path, F) ->
    _pipe = erlang:element(2, Path),
    _pipe@1 = gleam@list:map(_pipe, F),
    {path, _pipe@1}.

-file("src/svg_path.gleam", 374).
?DOC(" Keep only the subpaths that satisfy a predicate.\n").
-spec path_filter_subpaths(path(), fun((subpath()) -> boolean())) -> path().
path_filter_subpaths(Path, Predicate) ->
    _pipe = erlang:element(2, Path),
    _pipe@1 = gleam@list:filter(_pipe, Predicate),
    {path, _pipe@1}.

-file("src/svg_path.gleam", 2947).
-spec first_subpath(list(subpath())) -> subpath().
first_subpath(Subpaths) ->
    case Subpaths of
        [First | _] ->
            First;

        [] ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.first_subpath received an empty list"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"first_subpath"/utf8>>,
                    line => 2950})
    end.

-file("src/svg_path.gleam", 3939).
-spec nonempty_subpaths(list(subpath())) -> list(subpath()).
nonempty_subpaths(Subpaths) ->
    _pipe = Subpaths,
    gleam@list:filter(
        _pipe,
        fun(Subpath) -> not gleam@list:is_empty(erlang:element(3, Subpath)) end
    ).

-file("src/svg_path.gleam", 388).
?DOC(
    " Convert a path with zero or one non-empty subpaths into a subpath.\n"
    "\n"
    " Empty subpaths are ignored. If more than one non-empty subpath is present,\n"
    " this returns `MultipleNonemptySubpaths`. If a path has only empty subpaths,\n"
    " the first empty subpath is returned.\n"
).
-spec as_subpath(path()) -> {ok, subpath()} | {error, error()}.
as_subpath(Path) ->
    case erlang:element(2, Path) of
        [] ->
            {error, empty_subpaths};

        Subpaths ->
            case nonempty_subpaths(Subpaths) of
                [] ->
                    {ok, first_subpath(Subpaths)};

                [Subpath] ->
                    {ok, Subpath};

                [_, _ | _] ->
                    {error, multiple_nonempty_subpaths}
            end
    end.

-file("src/svg_path.gleam", 404).
?DOC(
    " Create an empty open subpath at a start point.\n"
    "\n"
    " This represents a move-only subpath such as `M 0 0`.\n"
).
-spec empty_subpath(vec@vec2:vec2(float())) -> subpath().
empty_subpath(Start) ->
    {subpath, Start, [], false}.

-file("src/svg_path.gleam", 1038).
?DOC(" Return the start point of a segment.\n").
-spec segment_start(segment()) -> vec@vec2:vec2(float()).
segment_start(Segment) ->
    case Segment of
        {line, Start, _} ->
            Start;

        {quadratic_bezier, Start, _, _} ->
            Start;

        {cubic_bezier, Start, _, _, _} ->
            Start;

        {arc, Start, _, _, _, _, _} ->
            Start
    end.

-file("src/svg_path.gleam", 4210).
-spec float_square_root(float()) -> float().
float_square_root(Value) ->
    Root@1 = case gleam@float:square_root(Value) of
        {ok, Root} -> Root;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path"/utf8>>,
                        function => <<"float_square_root"/utf8>>,
                        line => 4211,
                        value => _assert_fail,
                        start => 118417,
                        'end' => 118463,
                        pattern_start => 118428,
                        pattern_end => 118436})
    end,
    Root@1.

-file("src/svg_path.gleam", 4204).
-spec distance(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> float().
distance(A, B) ->
    Dx = erlang:element(2, A) - erlang:element(2, B),
    Dy = erlang:element(3, A) - erlang:element(3, B),
    _pipe = (Dx * Dx) + (Dy * Dy),
    float_square_root(_pipe).

-file("src/svg_path.gleam", 1048).
?DOC(" Return the end point of a segment.\n").
-spec segment_end(segment()) -> vec@vec2:vec2(float()).
segment_end(Segment) ->
    case Segment of
        {line, _, End} ->
            End;

        {quadratic_bezier, _, _, End} ->
            End;

        {cubic_bezier, _, _, _, End} ->
            End;

        {arc, _, _, _, _, _, End} ->
            End
    end.

-file("src/svg_path.gleam", 3948).
-spec continuous_from(list(segment()), integer()) -> {ok, nil} |
    {error, error()}.
continuous_from(Segments, Previous_index) ->
    case Segments of
        [] ->
            {ok, nil};

        [_] ->
            {ok, nil};

        [Left, Right | Rest] ->
            Left_end = segment_end(Left),
            Right_start = segment_start(Right),
            case Left_end =:= Right_start of
                true ->
                    continuous_from([Right | Rest], Previous_index + 1);

                false ->
                    {error,
                        {discontinuous,
                            Previous_index,
                            Previous_index + 1,
                            Left_end,
                            Right_start,
                            distance(Left_end, Right_start)}}
            end
    end.

-file("src/svg_path.gleam", 3944).
-spec continuous(list(segment())) -> {ok, nil} | {error, error()}.
continuous(Segments) ->
    continuous_from(Segments, 0).

-file("src/svg_path.gleam", 3566).
-spec starts_at(vec@vec2:vec2(float()), list(segment())) -> {ok, nil} |
    {error, error()}.
starts_at(Start, Segments) ->
    case Segments of
        [] ->
            {ok, nil};

        [First | _] ->
            Got = segment_start(First),
            case Got =:= Start of
                true ->
                    {ok, nil};

                false ->
                    {error,
                        {discontinuous, -1, 0, Start, Got, distance(Start, Got)}}
            end
    end.

-file("src/svg_path.gleam", 3551).
-spec strict_open_subpath_from(vec@vec2:vec2(float()), list(segment())) -> {ok,
        subpath()} |
    {error, error()}.
strict_open_subpath_from(Start, Segments) ->
    case starts_at(Start, Segments) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case continuous(Segments) of
                {ok, nil} ->
                    {ok, {subpath, Start, Segments, false}};

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

-file("src/svg_path.gleam", 3702).
-spec custom_reconcile_segments(
    list(segment()),
    segment(),
    list(segment()),
    fun((segment(), segment()) -> {segment(), segment()})
) -> list(segment()).
custom_reconcile_segments(Remaining, Previous, Reconciled, Reconcile) ->
    case Remaining of
        [] ->
            lists:reverse([Previous | Reconciled]);

        [Next | Rest] ->
            {Previous@1, Next@1} = case segment_end(Previous) =:= segment_start(
                Next
            ) of
                true ->
                    {Previous, Next};

                false ->
                    Reconcile(Previous, Next)
            end,
            custom_reconcile_segments(
                Rest,
                Next@1,
                [Previous@1 | Reconciled],
                Reconcile
            )
    end.

-file("src/svg_path.gleam", 3647).
-spec custom_open_subpath_from(
    vec@vec2:vec2(float()),
    list(segment()),
    fun((segment(), segment()) -> {segment(), segment()})
) -> {ok, subpath()} | {error, error()}.
custom_open_subpath_from(Start, Segments, Reconcile) ->
    case Segments of
        [] ->
            {ok, {subpath, Start, [], false}};

        [First | Rest] ->
            First@2 = case segment_start(First) =:= Start of
                true ->
                    First;

                false ->
                    Bridge = {line, Start, segment_start(First)},
                    {_, First@1} = Reconcile(Bridge, First),
                    First@1
            end,
            _pipe = custom_reconcile_segments(Rest, First@2, [], Reconcile),
            strict_open_subpath_from(Start, _pipe)
    end.

-file("src/svg_path.gleam", 3677).
-spec line_join_segments_loop(list(segment()), segment(), list(segment())) -> list(segment()).
line_join_segments_loop(Remaining, Previous, Joined) ->
    case Remaining of
        [] ->
            lists:reverse([Previous | Joined]);

        [Next | Rest] ->
            Previous_end = segment_end(Previous),
            Next_start = segment_start(Next),
            case Previous_end =:= Next_start of
                true ->
                    line_join_segments_loop(Rest, Next, [Previous | Joined]);

                false ->
                    line_join_segments_loop(
                        Rest,
                        Next,
                        [{line, Previous_end, Next_start}, Previous | Joined]
                    )
            end
    end.

-file("src/svg_path.gleam", 3670).
-spec line_join_segments(list(segment())) -> list(segment()).
line_join_segments(Segments) ->
    case Segments of
        [] ->
            [];

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

-file("src/svg_path.gleam", 3633).
-spec line_join_start(vec@vec2:vec2(float()), list(segment())) -> list(segment()).
line_join_start(Start, Segments) ->
    case Segments of
        [] ->
            [];

        [First | _] ->
            First_start = segment_start(First),
            case Start =:= First_start of
                true ->
                    line_join_segments(Segments);

                false ->
                    line_join_segments([{line, Start, First_start} | Segments])
            end
    end.

-file("src/svg_path.gleam", 4350).
-spec segment_with_end(segment(), vec@vec2:vec2(float())) -> segment().
segment_with_end(Segment, New_end) ->
    case Segment of
        {line, Start, _} ->
            {line, Start, New_end};

        {quadratic_bezier, Start@1, Control, _} ->
            {quadratic_bezier, Start@1, Control, New_end};

        {cubic_bezier, Start@2, Control1, Control2, _} ->
            {cubic_bezier, Start@2, Control1, Control2, New_end};

        {arc, Start@3, Radius, X_axis_rotation, Large_arc, Sweep, _} ->
            {arc, Start@3, Radius, X_axis_rotation, Large_arc, Sweep, New_end}
    end.

-file("src/svg_path.gleam", 4335).
-spec segment_with_start(segment(), vec@vec2:vec2(float())) -> segment().
segment_with_start(Segment, New_start) ->
    case Segment of
        {line, _, End} ->
            {line, New_start, End};

        {quadratic_bezier, _, Control, End@1} ->
            {quadratic_bezier, New_start, Control, End@1};

        {cubic_bezier, _, Control1, Control2, End@2} ->
            {cubic_bezier, New_start, Control1, Control2, End@2};

        {arc, _, Radius, X_axis_rotation, Large_arc, Sweep, End@3} ->
            {arc, New_start, Radius, X_axis_rotation, Large_arc, Sweep, End@3}
    end.

-file("src/svg_path.gleam", 4215).
-spec midpoint(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> vec@vec2:vec2(float()).
midpoint(A, B) ->
    point(
        (erlang:element(2, A) + erlang:element(2, B)) / 2.0,
        (erlang:element(3, A) + erlang:element(3, B)) / 2.0
    ).

-file("src/svg_path.gleam", 4288).
-spec segment_is_horizontal(segment()) -> boolean().
segment_is_horizontal(Segment) ->
    case Segment of
        {line, Start, End} ->
            erlang:element(3, Start) =:= erlang:element(3, End);

        _ ->
            false
    end.

-file("src/svg_path.gleam", 4264).
-spec wiggle_y(
    segment(),
    segment(),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> float().
wiggle_y(Previous, Next, Previous_end, Next_start) ->
    case segment_is_horizontal(Previous) of
        true ->
            erlang:element(3, Previous_end);

        false ->
            case segment_is_horizontal(Next) of
                true ->
                    erlang:element(3, Next_start);

                false ->
                    erlang:element(3, midpoint(Previous_end, Next_start))
            end
    end.

-file("src/svg_path.gleam", 4281).
-spec segment_is_vertical(segment()) -> boolean().
segment_is_vertical(Segment) ->
    case Segment of
        {line, Start, End} ->
            erlang:element(2, Start) =:= erlang:element(2, End);

        _ ->
            false
    end.

-file("src/svg_path.gleam", 4247).
-spec wiggle_x(
    segment(),
    segment(),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> float().
wiggle_x(Previous, Next, Previous_end, Next_start) ->
    case segment_is_vertical(Previous) of
        true ->
            erlang:element(2, Previous_end);

        false ->
            case segment_is_vertical(Next) of
                true ->
                    erlang:element(2, Next_start);

                false ->
                    erlang:element(2, midpoint(Previous_end, Next_start))
            end
    end.

-file("src/svg_path.gleam", 4219).
-spec wiggle_overlap(segment(), segment()) -> {ok, vec@vec2:vec2(float())} |
    {error, error()}.
wiggle_overlap(Previous, Next) ->
    Previous_end = segment_end(Previous),
    Next_start = segment_start(Next),
    Verticals_misaligned = (segment_is_vertical(Previous) andalso segment_is_vertical(
        Next
    ))
    andalso (erlang:element(2, Previous_end) /= erlang:element(2, Next_start)),
    Horizontals_misaligned = (segment_is_horizontal(Previous) andalso segment_is_horizontal(
        Next
    ))
    andalso (erlang:element(3, Previous_end) /= erlang:element(3, Next_start)),
    case Verticals_misaligned of
        true ->
            {error, {incompatible_vertical_wiggle, Previous_end, Next_start}};

        false ->
            case Horizontals_misaligned of
                true ->
                    {error,
                        {incompatible_horizontal_wiggle,
                            Previous_end,
                            Next_start}};

                false ->
                    {ok,
                        point(
                            wiggle_x(Previous, Next, Previous_end, Next_start),
                            wiggle_y(Previous, Next, Previous_end, Next_start)
                        )}
            end
    end.

-file("src/svg_path.gleam", 3973).
-spec wiggle_segments(list(segment()), segment(), list(segment()), integer()) -> {ok,
        list(segment())} |
    {error, error()}.
wiggle_segments(Remaining, Previous, Segments, Previous_index) ->
    case Remaining of
        [] ->
            {ok, lists:reverse([Previous | Segments])};

        [Next | Rest] ->
            Previous_end = segment_end(Previous),
            Next_start = segment_start(Next),
            case Previous_end =:= Next_start of
                true ->
                    wiggle_segments(
                        Rest,
                        Next,
                        [Previous | Segments],
                        Previous_index + 1
                    );

                false ->
                    case distance(Previous_end, Next_start) =< 0.000000001 of
                        false ->
                            {error,
                                {discontinuous,
                                    Previous_index,
                                    Previous_index + 1,
                                    Previous_end,
                                    Next_start,
                                    distance(Previous_end, Next_start)}};

                        true ->
                            case wiggle_overlap(Previous, Next) of
                                {error, Error} ->
                                    {error, Error};

                                {ok, Overlap} ->
                                    wiggle_segments(
                                        Rest,
                                        segment_with_start(Next, Overlap),
                                        [segment_with_end(Previous, Overlap) |
                                            Segments],
                                        Previous_index + 1
                                    )
                            end
                    end
            end
    end.

-file("src/svg_path.gleam", 3613).
-spec wiggle_start(vec@vec2:vec2(float()), segment()) -> {ok, segment()} |
    {error, error()}.
wiggle_start(Start, First) ->
    First_start = segment_start(First),
    case First_start =:= Start of
        true ->
            {ok, First};

        false ->
            case distance(Start, First_start) =< 0.000000001 of
                true ->
                    {ok, segment_with_start(First, Start)};

                false ->
                    {error,
                        {discontinuous,
                            -1,
                            0,
                            Start,
                            First_start,
                            distance(Start, First_start)}}
            end
    end.

-file("src/svg_path.gleam", 3593).
-spec wiggle_open_subpath_from(vec@vec2:vec2(float()), list(segment())) -> {ok,
        subpath()} |
    {error, error()}.
wiggle_open_subpath_from(Start, Segments) ->
    case Segments of
        [] ->
            {ok, {subpath, Start, [], false}};

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

                {ok, First@1} ->
                    case wiggle_segments(Rest, First@1, [], 0) of
                        {ok, Segments@1} ->
                            {ok, {subpath, Start, Segments@1, false}};

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

-file("src/svg_path.gleam", 3526).
-spec open_subpath_with_start(
    list(segment()),
    vec@vec2:vec2(float()),
    endpoint_policy()
) -> {ok, subpath()} | {error, error()}.
open_subpath_with_start(Segments, Start, Policy) ->
    case Policy of
        strict ->
            strict_open_subpath_from(Start, Segments);

        wiggle ->
            wiggle_open_subpath_from(Start, Segments);

        bridge ->
            Segments@1 = line_join_start(Start, Segments),
            {ok, {subpath, Start, Segments@1, false}};

        wiggle_then_bridge ->
            case wiggle_open_subpath_from(Start, Segments) of
                {ok, Subpath} ->
                    {ok, Subpath};

                {error, _} ->
                    Segments@2 = line_join_start(Start, Segments),
                    {ok, {subpath, Start, Segments@2, false}}
            end;

        {custom, Reconcile} ->
            custom_open_subpath_from(Start, Segments, Reconcile)
    end.

-file("src/svg_path.gleam", 3493).
-spec open_subpath_with_segments(list(segment()), endpoint_policy()) -> {ok,
        subpath()} |
    {error, error()}.
open_subpath_with_segments(Segments, Policy) ->
    case Segments of
        [] ->
            {error, empty_subpath};

        [First | _] ->
            open_subpath_with_start(Segments, segment_start(First), Policy)
    end.

-file("src/svg_path.gleam", 423).
?DOC(
    " Create an open subpath using the given endpoint reconciliation policy.\n"
    "\n"
    " Empty segment lists still return `EmptySubpath`.\n"
).
-spec subpath_with(list(segment()), endpoint_policy()) -> {ok, subpath()} |
    {error, error()}.
subpath_with(Segments, Endpoint_policy) ->
    open_subpath_with_segments(Segments, Endpoint_policy).

-file("src/svg_path.gleam", 416).
?DOC(
    " Create an open subpath from a non-empty continuous list of segments.\n"
    "\n"
    " Returns `EmptySubpath` if the segment list is empty. Use `empty_subpath`\n"
    " when you need to represent a move-only subpath.\n"
    "\n"
    " Returns `Discontinuous` if any segment starts somewhere other than the\n"
    " previous segment's end point. The error includes the two segment indices\n"
    " that failed to meet.\n"
).
-spec subpath(list(segment())) -> {ok, subpath()} | {error, error()}.
subpath(Segments) ->
    subpath_with(Segments, strict).

-file("src/svg_path.gleam", 3508).
-spec point_lines_loop(list(vec@vec2:vec2(float())), list(segment())) -> list(segment()).
point_lines_loop(Points, Segments) ->
    case Points of
        [] ->
            lists:reverse(Segments);

        [_] ->
            lists:reverse(Segments);

        [Start, End | Rest] ->
            point_lines_loop([End | Rest], [{line, Start, End} | Segments])
    end.

-file("src/svg_path.gleam", 3504).
-spec point_lines(list(vec@vec2:vec2(float()))) -> list(segment()).
point_lines(Points) ->
    point_lines_loop(Points, []).

-file("src/svg_path.gleam", 433).
?DOC(
    " Create an open subpath connecting the given points with line segments.\n"
    "\n"
    " The input must contain at least two points.\n"
).
-spec polyline(list(vec@vec2:vec2(float()))) -> {ok, subpath()} |
    {error, error()}.
polyline(Points) ->
    case point_lines(Points) of
        [] ->
            {error, empty_subpath};

        Segments ->
            subpath(Segments)
    end.

-file("src/svg_path.gleam", 441).
?DOC(" Create an open polyline subpath, panicking if the point list is invalid.\n").
-spec assert_polyline(list(vec@vec2:vec2(float()))) -> subpath().
assert_polyline(Points) ->
    case polyline(Points) of
        {ok, Subpath} ->
            Subpath;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_polyline received invalid points"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_polyline"/utf8>>,
                    line => 444})
    end.

-file("src/svg_path.gleam", 935).
?DOC(" Return the end point of a subpath.\n").
-spec 'end'(subpath()) -> {ok, vec@vec2:vec2(float())} | {error, error()}.
'end'(Subpath) ->
    case gleam@list:last(erlang:element(3, Subpath)) of
        {ok, Last} ->
            {ok, segment_end(Last)};

        {error, _} ->
            {ok, erlang:element(2, Subpath)}
    end.

-file("src/svg_path.gleam", 930).
?DOC(" Return the start point of a subpath.\n").
-spec start(subpath()) -> {ok, vec@vec2:vec2(float())} | {error, error()}.
start(Subpath) ->
    {ok, erlang:element(2, Subpath)}.

-file("src/svg_path.gleam", 4177).
-spec start_and_end(subpath()) -> {ok,
        {vec@vec2:vec2(float()), vec@vec2:vec2(float())}} |
    {error, error()}.
start_and_end(Subpath) ->
    case start(Subpath) of
        {error, Error} ->
            {error, Error};

        {ok, First} ->
            case 'end'(Subpath) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Last} ->
                    {ok, {First, Last}}
            end
    end.

-file("src/svg_path.gleam", 4059).
-spec strict_close_nonempty_subpath(subpath()) -> {ok, subpath()} |
    {error, error()}.
strict_close_nonempty_subpath(Subpath) ->
    case start_and_end(Subpath) of
        {error, Error} ->
            {error, Error};

        {ok, {First, Last}} when First =:= Last ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    true}};

        {ok, {First@1, Last@1}} ->
            Previous_index = erlang:length(erlang:element(3, Subpath)) - 1,
            {error,
                {discontinuous,
                    Previous_index,
                    0,
                    First@1,
                    Last@1,
                    distance(First@1, Last@1)}}
    end.

-file("src/svg_path.gleam", 4052).
-spec strict_close_open_subpath(subpath()) -> {ok, subpath()} | {error, error()}.
strict_close_open_subpath(Subpath) ->
    case erlang:element(3, Subpath) of
        [] ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    true}};

        _ ->
            strict_close_nonempty_subpath(Subpath)
    end.

-file("src/svg_path.gleam", 3586).
-spec strict_open_subpath(list(segment())) -> {ok, subpath()} | {error, error()}.
strict_open_subpath(Segments) ->
    case Segments of
        [] ->
            {error, empty_subpath};

        [First | _] ->
            strict_open_subpath_from(segment_start(First), Segments)
    end.

-file("src/svg_path.gleam", 4168).
-spec validate_custom_closed_segments(list(segment())) -> {ok, subpath()} |
    {error, error()}.
validate_custom_closed_segments(Segments) ->
    case strict_open_subpath(Segments) of
        {error, Error} ->
            {error, Error};

        {ok, Subpath} ->
            strict_close_open_subpath(Subpath)
    end.

-file("src/svg_path.gleam", 4322).
-spec split_last(list(JPQ)) -> {ok, {list(JPQ), JPQ}} | {error, error()}.
split_last(Items) ->
    case Items of
        [] ->
            {error, empty_subpath};

        [Only] ->
            {ok, {[], Only}};

        [First | Rest] ->
            case split_last(Rest) of
                {ok, {Middle, Last}} ->
                    {ok, {[First | Middle], Last}};

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

-file("src/svg_path.gleam", 4139).
-spec custom_close_open_subpath(
    subpath(),
    fun((segment(), segment()) -> {segment(), segment()})
) -> {ok, subpath()} | {error, error()}.
custom_close_open_subpath(Subpath, Reconcile) ->
    case erlang:element(3, Subpath) of
        [] ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    true}};

        [Only] ->
            case segment_end(Only) =:= segment_start(Only) of
                true ->
                    strict_close_open_subpath(Subpath);

                false ->
                    {Last, First} = Reconcile(Only, Only),
                    validate_custom_closed_segments([First, Last])
            end;

        [First@1 | Rest] ->
            {Middle@1, Last@2} = case split_last(Rest) of
                {ok, {Middle, Last@1}} -> {Middle, Last@1};
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"svg_path"/utf8>>,
                                function => <<"custom_close_open_subpath"/utf8>>,
                                line => 4155,
                                value => _assert_fail,
                                start => 117064,
                                'end' => 117113,
                                pattern_start => 117075,
                                pattern_end => 117094})
            end,
            case segment_end(Last@2) =:= segment_start(First@1) of
                true ->
                    strict_close_open_subpath(Subpath);

                false ->
                    {Last@3, First@2} = Reconcile(Last@2, First@1),
                    validate_custom_closed_segments(
                        [First@2 | lists:append(Middle@1, [Last@3])]
                    )
            end
    end.

-file("src/svg_path.gleam", 4123).
-spec line_close_nonempty_subpath(subpath()) -> {ok, subpath()} |
    {error, error()}.
line_close_nonempty_subpath(Subpath) ->
    case start_and_end(Subpath) of
        {error, Error} ->
            {error, Error};

        {ok, {First, Last}} when First =:= Last ->
            strict_close_open_subpath(Subpath);

        {ok, {First@1, Last@1}} ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    lists:append(
                        erlang:element(3, Subpath),
                        [{line, Last@1, First@1}]
                    ),
                    true}}
    end.

-file("src/svg_path.gleam", 4116).
-spec line_close_open_subpath(subpath()) -> {ok, subpath()} | {error, error()}.
line_close_open_subpath(Subpath) ->
    case erlang:element(3, Subpath) of
        [] ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    true}};

        _ ->
            line_close_nonempty_subpath(Subpath)
    end.

-file("src/svg_path.gleam", 4365).
-spec segment_with_start_and_end(
    segment(),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> segment().
segment_with_start_and_end(Segment, New_start, New_end) ->
    _pipe = Segment,
    _pipe@1 = segment_with_start(_pipe, New_start),
    segment_with_end(_pipe@1, New_end).

-file("src/svg_path.gleam", 4295).
-spec wiggle_ends_to(subpath(), vec@vec2:vec2(float())) -> subpath().
wiggle_ends_to(Subpath, Overlap) ->
    case erlang:element(3, Subpath) of
        [] ->
            Subpath;

        [Only] ->
            {subpath,
                Overlap,
                [segment_with_start_and_end(Only, Overlap, Overlap)],
                true};

        [First | Rest] ->
            {Middle@1, Last@1} = case split_last(Rest) of
                {ok, {Middle, Last}} -> {Middle, Last};
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"svg_path"/utf8>>,
                                function => <<"wiggle_ends_to"/utf8>>,
                                line => 4306,
                                value => _assert_fail,
                                start => 120694,
                                'end' => 120743,
                                pattern_start => 120705,
                                pattern_end => 120724})
            end,
            {subpath,
                Overlap,
                [segment_with_start(First, Overlap) |
                    lists:append(Middle@1, [segment_with_end(Last@1, Overlap)])],
                true}
    end.

-file("src/svg_path.gleam", 4189).
-spec first_and_last_segments(subpath()) -> {ok, {segment(), segment()}} |
    {error, error()}.
first_and_last_segments(Subpath) ->
    case erlang:element(3, Subpath) of
        [] ->
            {error, empty_subpath};

        [Only] ->
            {ok, {Only, Only}};

        [First | Rest] ->
            case gleam@list:last(Rest) of
                {ok, Last} ->
                    {ok, {First, Last}};

                {error, _} ->
                    {ok, {First, First}}
            end
    end.

-file("src/svg_path.gleam", 4086).
-spec wiggle_close_nonempty_subpath(subpath()) -> {ok, subpath()} |
    {error, error()}.
wiggle_close_nonempty_subpath(Subpath) ->
    case start_and_end(Subpath) of
        {error, Error} ->
            {error, Error};

        {ok, {First, Last}} ->
            case distance(First, Last) =< 0.000000001 of
                false ->
                    {error,
                        {discontinuous,
                            erlang:length(erlang:element(3, Subpath)) - 1,
                            0,
                            First,
                            Last,
                            distance(First, Last)}};

                true ->
                    case first_and_last_segments(Subpath) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, {First_segment, Last_segment}} ->
                            case wiggle_overlap(Last_segment, First_segment) of
                                {ok, Overlap} ->
                                    {ok, wiggle_ends_to(Subpath, Overlap)};

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

-file("src/svg_path.gleam", 4079).
-spec wiggle_close_open_subpath(subpath()) -> {ok, subpath()} | {error, error()}.
wiggle_close_open_subpath(Subpath) ->
    case erlang:element(3, Subpath) of
        [] ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    true}};

        _ ->
            wiggle_close_nonempty_subpath(Subpath)
    end.

-file("src/svg_path.gleam", 4034).
-spec close_open_subpath_with(subpath(), endpoint_policy()) -> {ok, subpath()} |
    {error, error()}.
close_open_subpath_with(Subpath, Policy) ->
    case Policy of
        strict ->
            strict_close_open_subpath(Subpath);

        wiggle ->
            wiggle_close_open_subpath(Subpath);

        bridge ->
            line_close_open_subpath(Subpath);

        wiggle_then_bridge ->
            case wiggle_close_open_subpath(Subpath) of
                {ok, Subpath@1} ->
                    {ok, Subpath@1};

                {error, _} ->
                    line_close_open_subpath(Subpath)
            end;

        {custom, Reconcile} ->
            custom_close_open_subpath(Subpath, Reconcile)
    end.

-file("src/svg_path.gleam", 4024).
-spec close_subpath_with(subpath(), endpoint_policy()) -> {ok, subpath()} |
    {error, error()}.
close_subpath_with(Subpath, Policy) ->
    case erlang:element(4, Subpath) of
        true ->
            {ok, Subpath};

        false ->
            close_open_subpath_with(Subpath, Policy)
    end.

-file("src/svg_path.gleam", 757).
?DOC(
    " Set a subpath's semantic closed state with an endpoint policy.\n"
    "\n"
    " Setting `closed` to `False` always succeeds. Setting it to `True` uses the\n"
    " given endpoint policy to reconcile a non-empty subpath's end point with its\n"
    " start point. Empty subpaths may be closed.\n"
).
-spec set_closed_with(subpath(), boolean(), endpoint_policy()) -> {ok,
        subpath()} |
    {error, error()}.
set_closed_with(Subpath, Closed, Endpoint_policy) ->
    case Closed of
        false ->
            {ok,
                {subpath,
                    erlang:element(2, Subpath),
                    erlang:element(3, Subpath),
                    false}};

        true ->
            close_subpath_with(Subpath, Endpoint_policy)
    end.

-file("src/svg_path.gleam", 745).
?DOC(
    " Set a subpath's semantic closed state.\n"
    "\n"
    " Setting `closed` to `False` always succeeds. Setting it to `True` requires a\n"
    " non-empty subpath's end point to exactly match its start point. Empty\n"
    " subpaths may be closed.\n"
).
-spec set_closed(subpath(), boolean()) -> {ok, subpath()} | {error, error()}.
set_closed(Subpath, Closed) ->
    set_closed_with(Subpath, Closed, strict).

-file("src/svg_path.gleam", 3519).
-spec close_polygon_points(list(vec@vec2:vec2(float())), vec@vec2:vec2(float())) -> list(vec@vec2:vec2(float())).
close_polygon_points(Points, First) ->
    case gleam@list:last(Points) of
        {ok, Last} when Last =:= First ->
            Points;

        _ ->
            lists:append(Points, [First])
    end.

-file("src/svg_path.gleam", 455).
?DOC(
    " Create a closed subpath connecting the given points with line segments.\n"
    "\n"
    " The input must contain at least two points. If the last point equals the\n"
    " first point, no extra zero-length closing line is added.\n"
    "\n"
    " This is equivalent to constructing a `polyline` from the same points and\n"
    " closing it with `set_closed_with(..., policy: Bridge)`.\n"
).
-spec polygon(list(vec@vec2:vec2(float()))) -> {ok, subpath()} |
    {error, error()}.
polygon(Points) ->
    case Points of
        [] ->
            {error, empty_subpath};

        [_] ->
            {error, empty_subpath};

        [First | _] ->
            Segments = point_lines(close_polygon_points(Points, First)),
            case subpath(Segments) of
                {error, Error} ->
                    {error, Error};

                {ok, Subpath} ->
                    set_closed(Subpath, true)
            end
    end.

-file("src/svg_path.gleam", 470).
?DOC(" Create a closed polygon subpath, panicking if the point list is invalid.\n").
-spec assert_polygon(list(vec@vec2:vec2(float()))) -> subpath().
assert_polygon(Points) ->
    case polygon(Points) of
        {ok, Subpath} ->
            Subpath;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_polygon received invalid points"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_polygon"/utf8>>,
                    line => 473})
    end.

-file("src/svg_path.gleam", 487).
?DOC(" Create an open subpath with an endpoint policy, panicking if construction fails.\n").
-spec assert_subpath_with(list(segment()), endpoint_policy()) -> subpath().
assert_subpath_with(Segments, Endpoint_policy) ->
    case subpath_with(Segments, Endpoint_policy) of
        {ok, Subpath} ->
            Subpath;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_subpath received invalid segments"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_subpath_with"/utf8>>,
                    line => 493})
    end.

-file("src/svg_path.gleam", 482).
?DOC(
    " Create an open subpath from a non-empty continuous list of segments,\n"
    " panicking if the segments are invalid.\n"
    "\n"
    " This is useful for hand-authored paths where invalid continuity would be a\n"
    " programmer error. Use `subpath` when you want to handle construction errors.\n"
).
-spec assert_subpath(list(segment())) -> subpath().
assert_subpath(Segments) ->
    assert_subpath_with(Segments, strict).

-file("src/svg_path.gleam", 498).
?DOC(" Return the segments in a subpath.\n").
-spec segments(subpath()) -> list(segment()).
segments(Subpath) ->
    erlang:element(3, Subpath).

-file("src/svg_path.gleam", 1583).
-spec is_zero_length_line(segment()) -> boolean().
is_zero_length_line(Segment) ->
    case Segment of
        {line, Start, End} ->
            Start =:= End;

        _ ->
            false
    end.

-file("src/svg_path.gleam", 506).
?DOC(
    " Remove zero-length line segments from a subpath.\n"
    "\n"
    " If cleanup would remove every segment, one zero-length line is preserved so\n"
    " a zero-length drawing subpath does not become a move-only subpath.\n"
).
-spec clean_subpath(subpath()) -> subpath().
clean_subpath(Subpath) ->
    Cleaned = begin
        _pipe = erlang:element(3, Subpath),
        gleam@list:filter(
            _pipe,
            fun(Segment) -> not is_zero_length_line(Segment) end
        )
    end,
    case Cleaned of
        [] ->
            case erlang:element(3, Subpath) of
                [] ->
                    Subpath;

                [First | _] ->
                    {subpath,
                        segment_start(First),
                        [First],
                        erlang:element(4, Subpath)}
            end;

        [First@1 | _] ->
            {subpath,
                segment_start(First@1),
                Cleaned,
                erlang:element(4, Subpath)}
    end.

-file("src/svg_path.gleam", 3471).
-spec validate_spliced_subpath(
    list(segment()),
    vec@vec2:vec2(float()),
    boolean(),
    endpoint_policy()
) -> {ok, subpath()} | {error, error()}.
validate_spliced_subpath(Segments, Start, Closed, Policy) ->
    Start@1 = case Segments of
        [] ->
            Start;

        [First | _] ->
            segment_start(First)
    end,
    case open_subpath_with_start(Segments, Start@1, Policy) of
        {ok, Subpath} ->
            case Closed of
                false ->
                    {ok, Subpath};

                true ->
                    close_subpath_with(Subpath, Policy)
            end;

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

-file("src/svg_path.gleam", 3008).
-spec drop(list(segment()), integer()) -> list(segment()).
drop(Segments, Count) ->
    case Count =< 0 of
        true ->
            Segments;

        false ->
            case Segments of
                [] ->
                    [];

                [_ | Rest] ->
                    drop(Rest, Count - 1)
            end
    end.

-file("src/svg_path.gleam", 2981).
-spec splice_segments_loop(
    list(segment()),
    integer(),
    integer(),
    list(segment()),
    integer(),
    list(segment())
) -> list(segment()).
splice_segments_loop(Segments, Start, Delete, Insert, Index, Before) ->
    case Segments of
        [] ->
            lists:append(lists:reverse(Before), Insert);

        [First | Rest] ->
            case Index < Start of
                true ->
                    splice_segments_loop(
                        Rest,
                        Start,
                        Delete,
                        Insert,
                        Index + 1,
                        [First | Before]
                    );

                false ->
                    lists:append(
                        lists:reverse(Before),
                        lists:append(Insert, drop(Segments, Delete))
                    )
            end
    end.

-file("src/svg_path.gleam", 2894).
-spec splice_segments(list(segment()), integer(), integer(), list(segment())) -> list(segment()).
splice_segments(Segments, Start, Delete, Insert) ->
    splice_segments_loop(Segments, Start, Delete, Insert, 0, []).

-file("src/svg_path.gleam", 554).
?DOC(" Replace a range of segments in a subpath using the given endpoint policy.\n").
-spec splice_with(
    subpath(),
    integer(),
    integer(),
    list(segment()),
    endpoint_policy()
) -> {ok, subpath()} | {error, error()}.
splice_with(Subpath, Start, Delete, Insert, Endpoint_policy) ->
    Length = erlang:length(erlang:element(3, Subpath)),
    case ((Start < 0) orelse (Delete < 0)) orelse (Start > Length) of
        true ->
            {error, {invalid_splice, Start, Delete, Length}};

        false ->
            Segments = splice_segments(
                erlang:element(3, Subpath),
                Start,
                Delete,
                Insert
            ),
            validate_spliced_subpath(
                Segments,
                erlang:element(2, Subpath),
                erlang:element(4, Subpath),
                Endpoint_policy
            )
    end.

-file("src/svg_path.gleam", 544).
?DOC(
    " Replace a range of segments in a subpath.\n"
    "\n"
    " `start` is a zero-based segment index and `delete` is the number of\n"
    " segments to remove. If `start + delete` extends past the end of the subpath,\n"
    " everything from `start` onward is deleted. Negative `start`, negative\n"
    " `delete`, and `start` greater than the subpath length return\n"
    " `InvalidSplice`.\n"
    "\n"
    " The edited subpath must remain continuous. Closed subpaths preserve their\n"
    " closed state. If the splice result is nonempty, the subpath start is updated\n"
    " to the first resulting segment's start point. If the splice result is empty,\n"
    " the previous start point is preserved.\n"
).
-spec splice(subpath(), integer(), integer(), list(segment())) -> {ok,
        subpath()} |
    {error, error()}.
splice(Subpath, Start, Delete, Insert) ->
    splice_with(Subpath, Start, Delete, Insert, strict).

-file("src/svg_path.gleam", 589).
?DOC(" Replace a range of segments with an endpoint policy, panicking if invalid.\n").
-spec assert_splice_with(
    subpath(),
    integer(),
    integer(),
    list(segment()),
    endpoint_policy()
) -> subpath().
assert_splice_with(Subpath, Start, Delete, Insert, Endpoint_policy) ->
    case splice_with(Subpath, Start, Delete, Insert, Endpoint_policy) of
        {ok, Subpath@1} ->
            Subpath@1;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_splice received an invalid splice"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_splice_with"/utf8>>,
                    line => 598})
    end.

-file("src/svg_path.gleam", 579).
?DOC(" Replace a range of segments, panicking if the splice is invalid.\n").
-spec assert_splice(subpath(), integer(), integer(), list(segment())) -> subpath().
assert_splice(Subpath, Start, Delete, Insert) ->
    assert_splice_with(Subpath, Start, Delete, Insert, strict).

-file("src/svg_path.gleam", 3913).
-spec interpolate(vec@vec2:vec2(float()), vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
interpolate(Start, End, T) ->
    point(
        erlang:element(2, Start) + ((erlang:element(2, End) - erlang:element(
            2,
            Start
        ))
        * T),
        erlang:element(3, Start) + ((erlang:element(3, End) - erlang:element(
            3,
            Start
        ))
        * T)
    ).

-file("src/svg_path.gleam", 3756).
-spec line_to_cubic(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> segment().
line_to_cubic(Start, End) ->
    {cubic_bezier,
        Start,
        interpolate(Start, End, 1.0 / 3.0),
        interpolate(Start, End, 2.0 / 3.0),
        End}.

-file("src/svg_path.gleam", 3813).
-spec force_cubic_end(list(segment()), vec@vec2:vec2(float())) -> list(segment()).
force_cubic_end(Segments, End) ->
    case Segments of
        [] ->
            [];

        [Only] ->
            [segment_with_end(Only, End)];

        [First | Rest] ->
            [First | force_cubic_end(Rest, End)]
    end.

-file("src/svg_path.gleam", 3802).
-spec force_cubic_start(list(segment()), vec@vec2:vec2(float())) -> list(segment()).
force_cubic_start(Segments, Start) ->
    case Segments of
        [] ->
            [];

        [{cubic_bezier, _, Control1, Control2, End} | Rest] ->
            [{cubic_bezier, Start, Control1, Control2, End} | Rest];

        [First | Rest@1] ->
            [First | Rest@1]
    end.

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

-file("src/svg_path.gleam", 3780).
-spec cubic_from_ellipse(svg_path@ellipse:cubic()) -> segment().
cubic_from_ellipse(Cubic) ->
    {cubic, Start, Control1, Control2, End} = Cubic,
    {cubic_bezier,
        from_ellipse_point(Start),
        from_ellipse_point(Control1),
        from_ellipse_point(Control2),
        from_ellipse_point(End)}.

-file("src/svg_path.gleam", 3791).
-spec cubic_segments_from_ellipse(
    list(svg_path@ellipse:cubic()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> list(segment()).
cubic_segments_from_ellipse(Cubics, Start, End) ->
    _pipe = Cubics,
    _pipe@1 = gleam@list:map(_pipe, fun cubic_from_ellipse/1),
    _pipe@2 = force_cubic_start(_pipe@1, Start),
    force_cubic_end(_pipe@2, End).

-file("src/svg_path.gleam", 3821).
-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.gleam", 679).
?DOC(
    " Convert an arc segment to cubic Bezier curves, preserving other segments.\n"
    "\n"
    " Non-arc segments are returned unchanged as a single-item list. An arc may\n"
    " become several cubic Bezier segments.\n"
).
-spec segment_arcs_to_cubic_beziers(segment()) -> list(segment()).
segment_arcs_to_cubic_beziers(Segment) ->
    case Segment of
        {line, _, _} ->
            [Segment];

        {quadratic_bezier, _, _, _} ->
            [Segment];

        {cubic_bezier, _, _, _, _} ->
            [Segment];

        {arc, Start, Radius, X_axis_rotation, Large_arc, Sweep, End} ->
            case svg_path@ellipse:arc_to_cubics(
                to_ellipse_point(Start),
                to_ellipse_point(Radius),
                X_axis_rotation,
                Large_arc,
                Sweep,
                to_ellipse_point(End)
            ) of
                {ok, Cubics} ->
                    cubic_segments_from_ellipse(Cubics, Start, End);

                {error, _} ->
                    [line_to_cubic(Start, End)]
            end
    end.

-file("src/svg_path.gleam", 3723).
-spec segments_arcs_to_cubic_beziers(list(segment()), list(segment())) -> list(segment()).
segments_arcs_to_cubic_beziers(Segments, Converted) ->
    case Segments of
        [] ->
            lists:reverse(Converted);

        [First | Rest] ->
            segments_arcs_to_cubic_beziers(
                Rest,
                lists:append(
                    lists:reverse(segment_arcs_to_cubic_beziers(First)),
                    Converted
                )
            )
    end.

-file("src/svg_path.gleam", 608).
?DOC(
    " Convert every arc in a subpath to cubic Bezier curves.\n"
    "\n"
    " Lines, quadratic Beziers, and cubic Beziers are preserved. Elliptical arcs\n"
    " are approximated with one or more cubic Beziers, split into chunks of at\n"
    " most a quarter turn. Degenerate arcs fall back to a straight-line cubic\n"
    " Bezier between their endpoints.\n"
).
-spec subpath_arcs_to_cubic_beziers(subpath()) -> subpath().
subpath_arcs_to_cubic_beziers(Subpath) ->
    {subpath,
        erlang:element(2, Subpath),
        segments_arcs_to_cubic_beziers(erlang:element(3, Subpath), []),
        erlang:element(4, Subpath)}.

-file("src/svg_path.gleam", 619).
?DOC(
    " Convert every arc in a path to cubic Bezier curves.\n"
    "\n"
    " This applies `subpath_arcs_to_cubic_beziers` to each subpath.\n"
).
-spec path_arcs_to_cubic_beziers(path()) -> path().
path_arcs_to_cubic_beziers(Path) ->
    {path,
        gleam@list:map(
            erlang:element(2, Path),
            fun subpath_arcs_to_cubic_beziers/1
        )}.

-file("src/svg_path.gleam", 2954).
-spec subpath_from_valid_segments(
    list(segment()),
    vec@vec2:vec2(float()),
    boolean()
) -> subpath().
subpath_from_valid_segments(Segments, Fallback_start, Closed) ->
    case Segments of
        [] ->
            {subpath, Fallback_start, [], Closed};

        [First | _] ->
            {subpath, segment_start(First), Segments, Closed}
    end.

-file("src/svg_path.gleam", 1058).
?DOC(" Reverse the traversal direction of a segment.\n").
-spec reverse_segment(segment()) -> segment().
reverse_segment(Segment) ->
    case Segment of
        {line, Start, End} ->
            {line, End, Start};

        {quadratic_bezier, Start@1, Control, End@1} ->
            {quadratic_bezier, End@1, Control, Start@1};

        {cubic_bezier, Start@2, Control1, Control2, End@2} ->
            {cubic_bezier, End@2, Control2, Control1, Start@2};

        {arc, Start@3, Radius, X_axis_rotation, Large_arc, Sweep, End@3} ->
            {arc, End@3, Radius, X_axis_rotation, Large_arc, not Sweep, Start@3}
    end.

-file("src/svg_path.gleam", 626).
?DOC(
    " Reverse the traversal direction of every segment in a subpath.\n"
    "\n"
    " The subpath's closed state is preserved.\n"
).
-spec reverse_subpath(subpath()) -> subpath().
reverse_subpath(Subpath) ->
    Segments = begin
        _pipe = erlang:element(3, Subpath),
        _pipe@1 = lists:reverse(_pipe),
        gleam@list:map(_pipe@1, fun reverse_segment/1)
    end,
    subpath_from_valid_segments(
        Segments,
        erlang:element(2, Subpath),
        erlang:element(4, Subpath)
    ).

-file("src/svg_path.gleam", 638).
?DOC(
    " Reverse the traversal direction of a path.\n"
    "\n"
    " This reverses each subpath and reverses the path's subpath order.\n"
).
-spec reverse_path(path()) -> path().
reverse_path(Path) ->
    {path,
        begin
            _pipe = erlang:element(2, Path),
            _pipe@1 = lists:reverse(_pipe),
            gleam@list:map(_pipe@1, fun reverse_subpath/1)
        end}.

-file("src/svg_path.gleam", 3833).
-spec from_bezier_point(svg_path@bezier:point()) -> vec@vec2:vec2(float()).
from_bezier_point(Point) ->
    {vec2, erlang:element(2, Point), erlang:element(3, Point)}.

-file("src/svg_path.gleam", 3864).
-spec segment_from_bezier_data(svg_path@bezier:bezier_data()) -> segment().
segment_from_bezier_data(Data) ->
    case Data of
        {linear_bezier_data, Start, End} ->
            {line, from_bezier_point(Start), from_bezier_point(End)};

        {quadratic_bezier_data, Start@1, Control, End@1} ->
            {quadratic_bezier,
                from_bezier_point(Start@1),
                from_bezier_point(Control),
                from_bezier_point(End@1)};

        {cubic_bezier_data, Start@2, Control1, Control2, End@2} ->
            {cubic_bezier,
                from_bezier_point(Start@2),
                from_bezier_point(Control1),
                from_bezier_point(Control2),
                from_bezier_point(End@2)}
    end.

-file("src/svg_path.gleam", 3829).
-spec to_bezier_point(vec@vec2:vec2(float())) -> svg_path@bezier:point().
to_bezier_point(Point) ->
    {point, erlang:element(2, Point), erlang:element(3, Point)}.

-file("src/svg_path.gleam", 3837).
-spec segment_to_bezier_data(segment()) -> svg_path@bezier:bezier_data().
segment_to_bezier_data(Segment) ->
    case Segment of
        {line, Start, End} ->
            svg_path@bezier:linear_bezier_data(
                to_bezier_point(Start),
                to_bezier_point(End)
            );

        {quadratic_bezier, Start@1, Control, End@1} ->
            svg_path@bezier:quadratic_bezier_data(
                to_bezier_point(Start@1),
                to_bezier_point(Control),
                to_bezier_point(End@1)
            );

        {cubic_bezier, Start@2, Control1, Control2, End@2} ->
            svg_path@bezier:cubic_bezier_data(
                to_bezier_point(Start@2),
                to_bezier_point(Control1),
                to_bezier_point(Control2),
                to_bezier_point(End@2)
            );

        {arc, _, _, _, _, _, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.segment_to_bezier_data received an arc"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"segment_to_bezier_data"/utf8>>,
                    line => 3860})
    end.

-file("src/svg_path.gleam", 1363).
?DOC(
    " Map the defining points of a segment.\n"
    "\n"
    " Lines, quadratic Beziers, and cubic Beziers are mapped by applying `f` to\n"
    " their endpoints and control points. For nonlinear functions, this is not the\n"
    " exact image of every point on the rendered curve. Arc segments return\n"
    " `CannotMapArcNonlinearly` because an arbitrary nonlinear mapping does not\n"
    " generally preserve SVG arc parameters.\n"
).
-spec map_segment_points(
    segment(),
    fun((vec@vec2:vec2(float())) -> vec@vec2:vec2(float()))
) -> {ok, segment()} | {error, error()}.
map_segment_points(Segment, F) ->
    case Segment of
        {line, _, _} ->
            {ok,
                begin
                    _pipe = Segment,
                    _pipe@1 = segment_to_bezier_data(_pipe),
                    _pipe@5 = svg_path@bezier:map_points(
                        _pipe@1,
                        fun(Point) -> _pipe@2 = Point,
                            _pipe@3 = from_bezier_point(_pipe@2),
                            _pipe@4 = F(_pipe@3),
                            to_bezier_point(_pipe@4) end
                    ),
                    segment_from_bezier_data(_pipe@5)
                end};

        {quadratic_bezier, _, _, _} ->
            {ok,
                begin
                    _pipe = Segment,
                    _pipe@1 = segment_to_bezier_data(_pipe),
                    _pipe@5 = svg_path@bezier:map_points(
                        _pipe@1,
                        fun(Point) -> _pipe@2 = Point,
                            _pipe@3 = from_bezier_point(_pipe@2),
                            _pipe@4 = F(_pipe@3),
                            to_bezier_point(_pipe@4) end
                    ),
                    segment_from_bezier_data(_pipe@5)
                end};

        {cubic_bezier, _, _, _, _} ->
            {ok,
                begin
                    _pipe = Segment,
                    _pipe@1 = segment_to_bezier_data(_pipe),
                    _pipe@5 = svg_path@bezier:map_points(
                        _pipe@1,
                        fun(Point) -> _pipe@2 = Point,
                            _pipe@3 = from_bezier_point(_pipe@2),
                            _pipe@4 = F(_pipe@3),
                            to_bezier_point(_pipe@4) end
                    ),
                    segment_from_bezier_data(_pipe@5)
                end};

        {arc, _, _, _, _, _, _} ->
            {error, cannot_map_arc_nonlinearly}
    end.

-file("src/svg_path.gleam", 2965).
-spec map_segments_points(
    list(segment()),
    fun((vec@vec2:vec2(float())) -> vec@vec2:vec2(float())),
    list(segment())
) -> {ok, list(segment())} | {error, error()}.
map_segments_points(Segments, F, Mapped) ->
    case Segments of
        [] ->
            {ok, lists:reverse(Mapped)};

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

                {ok, Segment} ->
                    map_segments_points(Rest, F, [Segment | Mapped])
            end
    end.

-file("src/svg_path.gleam", 648).
?DOC(
    " Map the defining points of every segment in a subpath.\n"
    "\n"
    " The subpath's closed state is preserved. For nonlinear functions, this maps\n"
    " endpoints and control points, not the exact image of every point on each\n"
    " rendered curve. If any segment is an arc, this returns\n"
    " `CannotMapArcNonlinearly`.\n"
).
-spec map_subpath_points(
    subpath(),
    fun((vec@vec2:vec2(float())) -> vec@vec2:vec2(float()))
) -> {ok, subpath()} | {error, error()}.
map_subpath_points(Subpath, F) ->
    case map_segments_points(erlang:element(3, Subpath), F, []) of
        {error, Error} ->
            {error, Error};

        {ok, Segments} ->
            {ok,
                {subpath,
                    F(erlang:element(2, Subpath)),
                    Segments,
                    erlang:element(4, Subpath)}}
    end.

-file("src/svg_path.gleam", 2931).
-spec map_subpaths_points(
    list(subpath()),
    fun((vec@vec2:vec2(float())) -> vec@vec2:vec2(float())),
    list(subpath())
) -> {ok, list(subpath())} | {error, error()}.
map_subpaths_points(Subpaths, F, Mapped) ->
    case Subpaths of
        [] ->
            {ok, lists:reverse(Mapped)};

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

                {ok, Subpath} ->
                    map_subpaths_points(Rest, F, [Subpath | Mapped])
            end
    end.

-file("src/svg_path.gleam", 665).
?DOC(
    " Map the defining points of every segment in a path.\n"
    "\n"
    " Each subpath's closed state is preserved. For nonlinear functions, this maps\n"
    " endpoints and control points, not the exact image of every point on each\n"
    " rendered curve. If any segment is an arc, this returns\n"
    " `CannotMapArcNonlinearly`.\n"
).
-spec map_path_points(
    path(),
    fun((vec@vec2:vec2(float())) -> vec@vec2:vec2(float()))
) -> {ok, path()} | {error, error()}.
map_path_points(Path, F) ->
    case map_subpaths_points(erlang:element(2, Path), F, []) of
        {error, Error} ->
            {error, Error};

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

-file("src/svg_path.gleam", 3765).
-spec quadratic_to_cubic(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> segment().
quadratic_to_cubic(Start, Control, End) ->
    {cubic_bezier,
        Start,
        point(
            erlang:element(2, Start) + ((2.0 / 3.0) * (erlang:element(
                2,
                Control
            )
            - erlang:element(2, Start))),
            erlang:element(3, Start) + ((2.0 / 3.0) * (erlang:element(
                3,
                Control
            )
            - erlang:element(3, Start)))
        ),
        point(
            erlang:element(2, End) + ((2.0 / 3.0) * (erlang:element(2, Control)
            - erlang:element(2, End))),
            erlang:element(3, End) + ((2.0 / 3.0) * (erlang:element(3, Control)
            - erlang:element(3, End)))
        ),
        End}.

-file("src/svg_path.gleam", 724).
?DOC(
    " Convert a segment to one or more cubic Bezier curves.\n"
    "\n"
    " Lines and quadratic Beziers are converted exactly. Cubic Beziers are\n"
    " returned unchanged. Arcs may become several cubic Bezier segments.\n"
).
-spec segment_to_cubic_beziers(segment()) -> list(segment()).
segment_to_cubic_beziers(Segment) ->
    case Segment of
        {line, Start, End} ->
            [line_to_cubic(Start, End)];

        {quadratic_bezier, Start@1, Control, End@1} ->
            [quadratic_to_cubic(Start@1, Control, End@1)];

        {cubic_bezier, _, _, _, _} ->
            [Segment];

        {arc, _, _, _, _, _, _} ->
            segment_arcs_to_cubic_beziers(Segment)
    end.

-file("src/svg_path.gleam", 3741).
-spec segments_to_cubic_beziers(list(segment()), list(segment())) -> list(segment()).
segments_to_cubic_beziers(Segments, Converted) ->
    case Segments of
        [] ->
            lists:reverse(Converted);

        [First | Rest] ->
            segments_to_cubic_beziers(
                Rest,
                lists:append(
                    lists:reverse(segment_to_cubic_beziers(First)),
                    Converted
                )
            )
    end.

-file("src/svg_path.gleam", 705).
?DOC(
    " Convert every segment in a subpath to cubic Bezier curves.\n"
    "\n"
    " Lines and quadratic Beziers are converted exactly. Cubic Beziers are\n"
    " preserved. Elliptical arcs are approximated with one or more cubic Beziers,\n"
    " split into chunks of at most a quarter turn.\n"
).
-spec subpath_to_cubic_beziers(subpath()) -> subpath().
subpath_to_cubic_beziers(Subpath) ->
    {subpath,
        erlang:element(2, Subpath),
        segments_to_cubic_beziers(erlang:element(3, Subpath), []),
        erlang:element(4, Subpath)}.

-file("src/svg_path.gleam", 716).
?DOC(
    " Convert every segment in a path to cubic Bezier curves.\n"
    "\n"
    " This applies `subpath_to_cubic_beziers` to each subpath.\n"
).
-spec path_to_cubic_beziers(path()) -> path().
path_to_cubic_beziers(Path) ->
    {path,
        gleam@list:map(erlang:element(2, Path), fun subpath_to_cubic_beziers/1)}.

-file("src/svg_path.gleam", 736).
?DOC(" Check whether a subpath is closed.\n").
-spec is_closed(subpath()) -> boolean().
is_closed(Subpath) ->
    erlang:element(4, Subpath).

-file("src/svg_path.gleam", 774).
?DOC(" Set a subpath's semantic closed state with an endpoint policy, panicking if invalid.\n").
-spec assert_set_closed_with(subpath(), boolean(), endpoint_policy()) -> subpath().
assert_set_closed_with(Subpath, Closed, Endpoint_policy) ->
    case set_closed_with(Subpath, Closed, Endpoint_policy) of
        {ok, Subpath@1} ->
            Subpath@1;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_set_closed received an invalid subpath"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_set_closed_with"/utf8>>,
                    line => 782})
    end.

-file("src/svg_path.gleam", 769).
?DOC(" Set a subpath's semantic closed state, panicking if invalid.\n").
-spec assert_set_closed(subpath(), boolean()) -> subpath().
assert_set_closed(Subpath, Closed) ->
    assert_set_closed_with(Subpath, Closed, strict).

-file("src/svg_path.gleam", 3020).
-spec take(list(segment()), integer()) -> list(segment()).
take(Segments, Count) ->
    case Count =< 0 of
        true ->
            [];

        false ->
            case Segments of
                [] ->
                    [];

                [First | Rest] ->
                    [First | take(Rest, Count - 1)]
            end
    end.

-file("src/svg_path.gleam", 3042).
-spec rotate_segments(list(segment()), integer()) -> list(segment()).
rotate_segments(Segments, Index) ->
    lists:append(drop(Segments, Index), take(Segments, Index)).

-file("src/svg_path.gleam", 3032).
-spec normalize_open_index(integer(), integer()) -> integer().
normalize_open_index(Index, Length) ->
    case Index of
        0 ->
            0;

        _ when Index =:= Length ->
            0;

        _ when Index =:= (0 - Length) ->
            0;

        _ when Index < 0 ->
            Index + Length;

        _ ->
            Index
    end.

-file("src/svg_path.gleam", 795).
?DOC(
    " Break open a closed subpath at the given segment index.\n"
    "\n"
    " The index denotes the segment that will become the first segment of the\n"
    " returned open subpath. Negative indices count from the end. `index` must be\n"
    " between `-length` and `length`, inclusive, where `length` is the number of\n"
    " segments in the subpath. After validation, the index is taken modulo the\n"
    " length, so `length`, `0`, and `-length` all open at the first segment.\n"
    " Opening a closed empty subpath at index `0` returns an open empty subpath\n"
    " with the same start point.\n"
).
-spec open_at(subpath(), integer()) -> {ok, subpath()} | {error, error()}.
open_at(Subpath, Index) ->
    Length = erlang:length(erlang:element(3, Subpath)),
    case erlang:element(4, Subpath) of
        false ->
            {error, not_closed};

        true ->
            case (Index < (0 - Length)) orelse (Index > Length) of
                true ->
                    {error, {invalid_open_index, Index, Length}};

                false ->
                    Index@1 = normalize_open_index(Index, Length),
                    Segments = rotate_segments(
                        erlang:element(3, Subpath),
                        Index@1
                    ),
                    {ok,
                        subpath_from_valid_segments(
                            Segments,
                            erlang:element(2, Subpath),
                            false
                        )}
            end
    end.

-file("src/svg_path.gleam", 818).
?DOC(" Compare two subpath parameters by segment index and then local `t`.\n").
-spec compare_subpath_parameters(subpath_parameter(), subpath_parameter()) -> gleam@order:order().
compare_subpath_parameters(A, B) ->
    {subpath_parameter, A_index, A_t} = A,
    {subpath_parameter, B_index, B_t} = B,
    case gleam@int:compare(A_index, B_index) of
        eq ->
            gleam@float:compare(A_t, B_t);

        Order ->
            Order
    end.

-file("src/svg_path.gleam", 3124).
-spec subpath_end_parameter(integer()) -> canonical_subpath_parameter().
subpath_end_parameter(Length) ->
    {canonical_subpath_parameter, Length - 1, 1.0}.

-file("src/svg_path.gleam", 3887).
-spec segment_to_center_arc_data(segment()) -> {ok,
        svg_path@ellipse:center_arc_data()} |
    {error, error()}.
segment_to_center_arc_data(Segment) ->
    case Segment of
        {arc, Start, Radius, X_axis_rotation, Large_arc, Sweep, End} ->
            Endpoint = svg_path@ellipse:endpoint_arc_data(
                to_ellipse_point(Start),
                to_ellipse_point(Radius),
                X_axis_rotation,
                Large_arc,
                Sweep,
                to_ellipse_point(End)
            ),
            case svg_path@ellipse:endpoint_to_center(Endpoint) of
                {error, _} ->
                    {error, degenerate_arc};

                {ok, Arc} ->
                    {ok, Arc}
            end;

        {line, _, _} ->
            {error, degenerate_arc};

        {quadratic_bezier, _, _, _} ->
            {error, degenerate_arc};

        {cubic_bezier, _, _, _, _} ->
            {error, degenerate_arc}
    end.

-file("src/svg_path.gleam", 1089).
?DOC(
    " Evaluate a segment at parameter `t`.\n"
    "\n"
    " `t` is not clamped. Values outside `0.0..1.0` extrapolate along the same\n"
    " segment.\n"
).
-spec segment_point(segment(), float()) -> {ok, vec@vec2:vec2(float())} |
    {error, error()}.
segment_point(Segment, T) ->
    case Segment of
        {line, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_point(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {quadratic_bezier, _, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_point(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {cubic_bezier, _, _, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_point(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {arc, _, _, _, _, _, _} ->
            case segment_to_center_arc_data(Segment) of
                {error, Error} ->
                    {error, Error};

                {ok, Arc} ->
                    {ok,
                        begin
                            _pipe@2 = svg_path@ellipse:arc_point(Arc, T),
                            from_ellipse_point(_pipe@2)
                        end}
            end
    end.

-file("src/svg_path.gleam", 3921).
?DOC(" Create an elliptical arc segment from endpoint-parameter arc data.\n").
-spec arc_from_endpoint_data(svg_path@ellipse:endpoint_arc_data()) -> segment().
arc_from_endpoint_data(Data) ->
    {arc,
        from_ellipse_point(erlang:element(2, Data)),
        from_ellipse_point(erlang:element(3, Data)),
        erlang:element(4, Data),
        erlang:element(5, Data),
        erlang:element(6, Data),
        from_ellipse_point(erlang:element(7, Data))}.

-file("src/svg_path.gleam", 3933).
?DOC(" Create an elliptical arc segment from center-parameter arc data.\n").
-spec arc_from_center_data(svg_path@ellipse:center_arc_data()) -> segment().
arc_from_center_data(Data) ->
    Endpoint = svg_path@ellipse:center_to_endpoint(Data),
    arc_from_endpoint_data(Endpoint).

-file("src/svg_path.gleam", 1386).
?DOC(
    " Split a segment at parameter `t`.\n"
    "\n"
    " `t` is not clamped. Values outside `0.0..1.0` extrapolate along the same\n"
    " segment.\n"
).
-spec split_segment(segment(), float()) -> {ok, {segment(), segment()}} |
    {error, error()}.
split_segment(Segment, T) ->
    case Segment of
        {line, _, _} ->
            {Left, Right} = begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier(_pipe, T)
            end,
            {ok,
                {segment_from_bezier_data(Left),
                    segment_from_bezier_data(Right)}};

        {quadratic_bezier, _, _, _} ->
            {Left, Right} = begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier(_pipe, T)
            end,
            {ok,
                {segment_from_bezier_data(Left),
                    segment_from_bezier_data(Right)}};

        {cubic_bezier, _, _, _, _} ->
            {Left, Right} = begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier(_pipe, T)
            end,
            {ok,
                {segment_from_bezier_data(Left),
                    segment_from_bezier_data(Right)}};

        {arc, _, _, _, _, _, _} ->
            case segment_to_center_arc_data(Segment) of
                {error, Error} ->
                    {error, Error};

                {ok, Arc} ->
                    {Left@1, Right@1} = svg_path@ellipse:split_arc(Arc, T),
                    {ok,
                        {arc_from_center_data(Left@1),
                            arc_from_center_data(Right@1)}}
            end
    end.

-file("src/svg_path.gleam", 1540).
-spec forward_sub_segment(segment(), float(), float()) -> {ok, segment()} |
    {error, error()}.
forward_sub_segment(Segment, From, To) ->
    case From =:= 1.0 of
        true ->
            case begin
                _pipe = reverse_segment(Segment),
                forward_sub_segment(_pipe, 1.0 - To, +0.0)
            end of
                {error, Error} ->
                    {error, Error};

                {ok, Segment@1} ->
                    {ok, reverse_segment(Segment@1)}
            end;

        false ->
            Local_to = case (1.0 - From) of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> (To - From) / Gleam@denominator
            end,
            case split_segment(Segment, From) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, {_, After_from}} ->
                    case split_segment(After_from, Local_to) of
                        {error, Error@2} ->
                            {error, Error@2};

                        {ok, {Between, _}} ->
                            case segment_point(Segment, From) of
                                {error, Error@3} ->
                                    {error, Error@3};

                                {ok, Start} ->
                                    case segment_point(Segment, To) of
                                        {error, Error@4} ->
                                            {error, Error@4};

                                        {ok, End} ->
                                            {ok,
                                                segment_with_start_and_end(
                                                    Between,
                                                    Start,
                                                    End
                                                )}
                                    end
                            end
                    end
            end
    end.

-file("src/svg_path.gleam", 1450).
?DOC(
    " Return the portion of a segment between two parameters.\n"
    "\n"
    " `from` and `to` are not clamped. Values outside `0.0..1.0` extrapolate\n"
    " along the same segment. If `from` is greater than `to`, the returned segment\n"
    " traverses the interval in reverse.\n"
).
-spec sub_segment(segment(), float(), float()) -> {ok, segment()} |
    {error, error()}.
sub_segment(Segment, From, To) ->
    case From =:= To of
        true ->
            case segment_point(Segment, From) of
                {error, Error} ->
                    {error, Error};

                {ok, Point} ->
                    {ok, {line, Point, Point}}
            end;

        false ->
            case From > To of
                true ->
                    case sub_segment(Segment, To, From) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Segment@1} ->
                            {ok, reverse_segment(Segment@1)}
                    end;

                false ->
                    forward_sub_segment(Segment, From, To)
            end
    end.

-file("src/svg_path.gleam", 1480).
?DOC(
    " Return the portion of a segment between two parameters.\n"
    "\n"
    " `from` and `to` must be inside `0.0..1.0`, inclusive. If `from` is greater\n"
    " than `to`, the returned segment traverses the interval in reverse.\n"
).
-spec sub_segment_inside(segment(), float(), float()) -> {ok, segment()} |
    {error, error()}.
sub_segment_inside(Segment, From, To) ->
    case (((From < +0.0) orelse (From > 1.0)) orelse (To < +0.0)) orelse (To > 1.0) of
        true ->
            {error, split_outside_segment};

        false ->
            sub_segment(Segment, From, To)
    end.

-file("src/svg_path.gleam", 3459).
-spec nth_segment(list(segment()), integer()) -> {ok, segment()} |
    {error, error()}.
nth_segment(Segments, Index) ->
    case Index < 0 of
        true ->
            {error, empty_subpath};

        false ->
            case {Segments, Index} of
                {[], _} ->
                    {error, empty_subpath};

                {[Segment | _], 0} ->
                    {ok, Segment};

                {[_ | Rest], _} ->
                    nth_segment(Rest, Index - 1)
            end
    end.

-file("src/svg_path.gleam", 3255).
-spec subpath_interval_end_piece(list(segment()), integer(), float()) -> {ok,
        list(segment())} |
    {error, error()}.
subpath_interval_end_piece(Segments, Index, T) ->
    case T =:= +0.0 of
        true ->
            {ok, []};

        false ->
            gleam@result:'try'(
                nth_segment(Segments, Index),
                fun(Segment) -> case T =:= 1.0 of
                        true ->
                            {ok, [Segment]};

                        false ->
                            gleam@result:'try'(
                                sub_segment_inside(Segment, +0.0, T),
                                fun(Piece) -> {ok, [Piece]} end
                            )
                    end end
            )
    end.

-file("src/svg_path.gleam", 3240).
-spec subpath_interval_start_piece(list(segment()), integer(), float()) -> {ok,
        list(segment())} |
    {error, error()}.
subpath_interval_start_piece(Segments, Index, T) ->
    gleam@result:'try'(
        nth_segment(Segments, Index),
        fun(Segment) -> case T =:= +0.0 of
                true ->
                    {ok, [Segment]};

                false ->
                    gleam@result:'try'(
                        sub_segment_inside(Segment, T, 1.0),
                        fun(Piece) -> {ok, [Piece]} end
                    )
            end end
    ).

-file("src/svg_path.gleam", 3100).
-spec canonical_to_subpath_parameter(canonical_subpath_parameter()) -> subpath_parameter().
canonical_to_subpath_parameter(Parameter) ->
    {canonical_subpath_parameter, Segment_index, T} = Parameter,
    {subpath_parameter, Segment_index, T}.

-file("src/svg_path.gleam", 3107).
-spec compare_canonical_subpath_parameters(
    canonical_subpath_parameter(),
    canonical_subpath_parameter()
) -> gleam@order:order().
compare_canonical_subpath_parameters(A, B) ->
    {canonical_subpath_parameter, A_index, A_t} = A,
    {canonical_subpath_parameter, B_index, B_t} = B,
    case gleam@int:compare(A_index, B_index) of
        eq ->
            gleam@float:compare(A_t, B_t);

        Order ->
            Order
    end.

-file("src/svg_path.gleam", 3192).
-spec subpath_interval_segments(
    subpath(),
    canonical_subpath_parameter(),
    canonical_subpath_parameter()
) -> {ok, list(segment())} | {error, error()}.
subpath_interval_segments(Subpath, From, To) ->
    case compare_canonical_subpath_parameters(From, To) of
        eq ->
            {ok, []};

        gt ->
            {error,
                {invalid_subpath_interval,
                    canonical_to_subpath_parameter(From),
                    canonical_to_subpath_parameter(To)}};

        lt ->
            {canonical_subpath_parameter, From_index, From_t} = From,
            {canonical_subpath_parameter, To_index, To_t} = To,
            case From_index =:= To_index of
                true ->
                    gleam@result:'try'(
                        nth_segment(erlang:element(3, Subpath), From_index),
                        fun(Segment) ->
                            gleam@result:'try'(
                                sub_segment_inside(Segment, From_t, To_t),
                                fun(Piece) -> {ok, [Piece]} end
                            )
                        end
                    );

                false ->
                    gleam@result:'try'(
                        subpath_interval_start_piece(
                            erlang:element(3, Subpath),
                            From_index,
                            From_t
                        ),
                        fun(Start) ->
                            Middle = begin
                                _pipe = erlang:element(3, Subpath),
                                _pipe@1 = drop(_pipe, From_index + 1),
                                take(_pipe@1, (To_index - From_index) - 1)
                            end,
                            gleam@result:'try'(
                                subpath_interval_end_piece(
                                    erlang:element(3, Subpath),
                                    To_index,
                                    To_t
                                ),
                                fun(End) ->
                                    {ok,
                                        lists:append(
                                            Start,
                                            lists:append(Middle, End)
                                        )}
                                end
                            )
                        end
                    )
            end
    end.

-file("src/svg_path.gleam", 3120).
-spec subpath_start_parameter() -> canonical_subpath_parameter().
subpath_start_parameter() ->
    {canonical_subpath_parameter, 0, +0.0}.

-file("src/svg_path.gleam", 3141).
-spec invalid_subpath_parameter(canonical_subpath_parameter(), integer()) -> {ok,
        any()} |
    {error, error()}.
invalid_subpath_parameter(Parameter, Length) ->
    {canonical_subpath_parameter, Segment_index, T} = Parameter,
    {error, {invalid_subpath_parameter, Segment_index, T, Length}}.

-file("src/svg_path.gleam", 3128).
-spec subpath_parameter_is_boundary(canonical_subpath_parameter(), integer()) -> boolean().
subpath_parameter_is_boundary(Parameter, Length) ->
    (compare_canonical_subpath_parameters(Parameter, subpath_start_parameter())
    =:= eq)
    orelse (compare_canonical_subpath_parameters(
        Parameter,
        subpath_end_parameter(Length)
    )
    =:= eq).

-file("src/svg_path.gleam", 3084).
-spec canonical_subpath_parameter(subpath_parameter(), integer(), boolean()) -> canonical_subpath_parameter().
canonical_subpath_parameter(Parameter, Length, Closed) ->
    {subpath_parameter, Segment_index, T} = Parameter,
    case (T =:= 1.0) andalso ((Segment_index + 1) < Length) of
        true ->
            {canonical_subpath_parameter, Segment_index + 1, +0.0};

        false ->
            case (Closed andalso (T =:= 1.0)) andalso (Segment_index =:= (Length
            - 1)) of
                true ->
                    {canonical_subpath_parameter, 0, +0.0};

                false ->
                    {canonical_subpath_parameter, Segment_index, T}
            end
    end.

-file("src/svg_path.gleam", 3046).
-spec validate_subpath_parameter(subpath(), subpath_parameter()) -> {ok,
        canonical_subpath_parameter()} |
    {error, error()}.
validate_subpath_parameter(Subpath, Parameter) ->
    Length = erlang:length(erlang:element(3, Subpath)),
    {subpath_parameter, Segment_index, T} = Parameter,
    case Length =:= 0 of
        true ->
            {error, empty_subpath};

        false ->
            case (((Segment_index < 0) orelse (Segment_index >= Length)) orelse (T
            < +0.0))
            orelse (T > 1.0) of
                true ->
                    {error,
                        {invalid_subpath_parameter, Segment_index, T, Length}};

                false ->
                    {ok,
                        canonical_subpath_parameter(
                            Parameter,
                            Length,
                            erlang:element(4, Subpath)
                        )}
            end
    end.

-file("src/svg_path.gleam", 836).
?DOC(
    " Split an open subpath at a subpath parameter.\n"
    "\n"
    " The split point must be inside the subpath: it cannot be the first point,\n"
    " the last point, outside the segment list, or outside the addressed segment's\n"
    " `0.0..1.0` parameter range. Closed and empty subpaths are rejected.\n"
).
-spec split_subpath(subpath(), subpath_parameter()) -> {ok,
        {subpath(), subpath()}} |
    {error, error()}.
split_subpath(Subpath, At) ->
    case erlang:element(4, Subpath) of
        true ->
            {error, already_closed};

        false ->
            gleam@result:'try'(
                validate_subpath_parameter(Subpath, At),
                fun(At@1) ->
                    case subpath_parameter_is_boundary(
                        At@1,
                        erlang:length(erlang:element(3, Subpath))
                    ) of
                        true ->
                            invalid_subpath_parameter(
                                At@1,
                                erlang:length(erlang:element(3, Subpath))
                            );

                        false ->
                            gleam@result:'try'(
                                subpath_interval_segments(
                                    Subpath,
                                    subpath_start_parameter(),
                                    At@1
                                ),
                                fun(Left_segments) ->
                                    gleam@result:'try'(
                                        subpath_interval_segments(
                                            Subpath,
                                            At@1,
                                            subpath_end_parameter(
                                                erlang:length(
                                                    erlang:element(3, Subpath)
                                                )
                                            )
                                        ),
                                        fun(Right_segments) ->
                                            gleam@result:'try'(
                                                open_subpath_with_segments(
                                                    Left_segments,
                                                    strict
                                                ),
                                                fun(Left) ->
                                                    gleam@result:'try'(
                                                        open_subpath_with_segments(
                                                            Right_segments,
                                                            strict
                                                        ),
                                                        fun(Right) ->
                                                            {ok, {Left, Right}}
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                    end
                end
            )
    end.

-file("src/svg_path.gleam", 3149).
-spec sub_subpath_between(
    subpath(),
    canonical_subpath_parameter(),
    canonical_subpath_parameter()
) -> {ok, subpath()} | {error, error()}.
sub_subpath_between(Subpath, From, To) ->
    case compare_canonical_subpath_parameters(From, To) of
        eq ->
            {error,
                {invalid_subpath_interval,
                    canonical_to_subpath_parameter(From),
                    canonical_to_subpath_parameter(To)}};

        lt ->
            gleam@result:'try'(
                subpath_interval_segments(Subpath, From, To),
                fun(Segments) ->
                    open_subpath_with_segments(Segments, strict)
                end
            );

        gt ->
            case erlang:element(4, Subpath) of
                false ->
                    {error,
                        {invalid_subpath_interval,
                            canonical_to_subpath_parameter(From),
                            canonical_to_subpath_parameter(To)}};

                true ->
                    Length = erlang:length(erlang:element(3, Subpath)),
                    gleam@result:'try'(
                        subpath_interval_segments(
                            Subpath,
                            From,
                            subpath_end_parameter(Length)
                        ),
                        fun(Before_wrap) ->
                            gleam@result:'try'(
                                subpath_interval_segments(
                                    Subpath,
                                    subpath_start_parameter(),
                                    To
                                ),
                                fun(After_wrap) ->
                                    open_subpath_with_segments(
                                        lists:append(Before_wrap, After_wrap),
                                        strict
                                    )
                                end
                            )
                        end
                    )
            end
    end.

-file("src/svg_path.gleam", 877).
?DOC(
    " Return the open subpath between two subpath parameters.\n"
    "\n"
    " Parameters must be valid for the subpath and must describe a positive-length\n"
    " interval. Open subpaths reject reversed intervals. Closed subpaths allow\n"
    " wrapped intervals, but equal parameters are still rejected.\n"
).
-spec sub_subpath(subpath(), subpath_parameter(), subpath_parameter()) -> {ok,
        subpath()} |
    {error, error()}.
sub_subpath(Subpath, From, To) ->
    gleam@result:'try'(
        validate_subpath_parameter(Subpath, From),
        fun(From@1) ->
            gleam@result:'try'(
                validate_subpath_parameter(Subpath, To),
                fun(To@1) -> sub_subpath_between(Subpath, From@1, To@1) end
            )
        end
    ).

-file("src/svg_path.gleam", 3443).
-spec cyclic_parameter_pairs_loop(
    list(canonical_subpath_parameter()),
    canonical_subpath_parameter(),
    list({canonical_subpath_parameter(), canonical_subpath_parameter()})
) -> list({canonical_subpath_parameter(), canonical_subpath_parameter()}).
cyclic_parameter_pairs_loop(Points, First, Pairs) ->
    case Points of
        [] ->
            lists:reverse(Pairs);

        [Last] ->
            lists:reverse([{Last, First} | Pairs]);

        [Left, Right | Rest] ->
            cyclic_parameter_pairs_loop(
                [Right | Rest],
                First,
                [{Left, Right} | Pairs]
            )
    end.

-file("src/svg_path.gleam", 3434).
-spec cyclic_parameter_pairs(list(canonical_subpath_parameter())) -> list({canonical_subpath_parameter(),
    canonical_subpath_parameter()}).
cyclic_parameter_pairs(Points) ->
    case Points of
        [] ->
            [];

        [_] ->
            [];

        [First | _] ->
            cyclic_parameter_pairs_loop(Points, First, [])
    end.

-file("src/svg_path.gleam", 3408).
-spec sub_subpaths_between_pairs(
    subpath(),
    list({canonical_subpath_parameter(), canonical_subpath_parameter()})
) -> {ok, list(subpath())} | {error, error()}.
sub_subpaths_between_pairs(Subpath, Pairs) ->
    _pipe = Pairs,
    _pipe@1 = gleam@list:fold(
        _pipe,
        {ok, []},
        fun(Subpaths, Pair) ->
            gleam@result:'try'(
                Subpaths,
                fun(Subpaths@1) ->
                    {From, To} = Pair,
                    gleam@result:'try'(
                        sub_subpath_between(Subpath, From, To),
                        fun(Subpath@1) -> {ok, [Subpath@1 | Subpaths@1]} end
                    )
                end
            )
        end
    ),
    gleam@result:map(_pipe@1, fun lists:reverse/1).

-file("src/svg_path.gleam", 3385).
-spec count_cyclic_descent(
    canonical_subpath_parameter(),
    canonical_subpath_parameter(),
    integer()
) -> {ok, integer()} | {error, error()}.
count_cyclic_descent(Previous, Point, Descents) ->
    case compare_canonical_subpath_parameters(Previous, Point) of
        eq ->
            {error,
                {invalid_subpath_interval,
                    canonical_to_subpath_parameter(Previous),
                    canonical_to_subpath_parameter(Point)}};

        gt ->
            {ok, Descents + 1};

        lt ->
            {ok, Descents}
    end.

-file("src/svg_path.gleam", 3339).
-spec validate_closed_subpath_split_points_loop(
    list(canonical_subpath_parameter()),
    canonical_subpath_parameter(),
    canonical_subpath_parameter(),
    integer()
) -> {ok, nil} | {error, error()}.
validate_closed_subpath_split_points_loop(Points, First, Previous, Descents) ->
    case Points of
        [] ->
            gleam@result:'try'(
                count_cyclic_descent(Previous, First, Descents),
                fun(Descents@1) -> case Descents@1 =:= 1 of
                        true ->
                            {ok, nil};

                        false ->
                            {error,
                                {invalid_subpath_interval,
                                    canonical_to_subpath_parameter(Previous),
                                    canonical_to_subpath_parameter(First)}}
                    end end
            );

        [Point | Rest] ->
            gleam@result:'try'(
                count_cyclic_descent(Previous, Point, Descents),
                fun(Descents@2) -> case Descents@2 > 1 of
                        true ->
                            {error,
                                {invalid_subpath_interval,
                                    canonical_to_subpath_parameter(Previous),
                                    canonical_to_subpath_parameter(Point)}};

                        false ->
                            validate_closed_subpath_split_points_loop(
                                Rest,
                                First,
                                Point,
                                Descents@2
                            )
                    end end
            )
    end.

-file("src/svg_path.gleam", 3324).
-spec validate_closed_subpath_split_points(list(canonical_subpath_parameter())) -> {ok,
        nil} |
    {error, error()}.
validate_closed_subpath_split_points(Points) ->
    case Points of
        [] ->
            {ok, nil};

        [_] ->
            {ok, nil};

        [First, Second | Rest] ->
            validate_closed_subpath_split_points_loop(
                [Second | Rest],
                First,
                First,
                0
            )
    end.

-file("src/svg_path.gleam", 3422).
-spec adjacent_parameter_pairs(list(canonical_subpath_parameter())) -> list({canonical_subpath_parameter(),
    canonical_subpath_parameter()}).
adjacent_parameter_pairs(Points) ->
    case Points of
        [] ->
            [];

        [_] ->
            [];

        [First, Second | Rest] ->
            [{First, Second} | adjacent_parameter_pairs([Second | Rest])]
    end.

-file("src/svg_path.gleam", 3401).
-spec sub_subpaths_between_points(
    subpath(),
    list(canonical_subpath_parameter())
) -> {ok, list(subpath())} | {error, error()}.
sub_subpaths_between_points(Subpath, Points) ->
    sub_subpaths_between_pairs(Subpath, adjacent_parameter_pairs(Points)).

-file("src/svg_path.gleam", 3295).
-spec validate_open_subpath_split_points_loop(
    list(canonical_subpath_parameter()),
    canonical_subpath_parameter(),
    integer()
) -> {ok, nil} | {error, error()}.
validate_open_subpath_split_points_loop(Points, Previous, Length) ->
    case Points of
        [] ->
            {ok, nil};

        [Point | Rest] ->
            case subpath_parameter_is_boundary(Point, Length) of
                true ->
                    invalid_subpath_parameter(Point, Length);

                false ->
                    case compare_canonical_subpath_parameters(Previous, Point) of
                        lt ->
                            validate_open_subpath_split_points_loop(
                                Rest,
                                Point,
                                Length
                            );

                        _ ->
                            {error,
                                {invalid_subpath_interval,
                                    canonical_to_subpath_parameter(Previous),
                                    canonical_to_subpath_parameter(Point)}}
                    end
            end
    end.

-file("src/svg_path.gleam", 3275).
-spec validate_open_subpath_split_points(
    list(canonical_subpath_parameter()),
    integer()
) -> {ok, nil} | {error, error()}.
validate_open_subpath_split_points(Points, Length) ->
    case Points of
        [] ->
            {ok, nil};

        [Point | Rest] ->
            case subpath_parameter_is_boundary(Point, Length) of
                true ->
                    invalid_subpath_parameter(Point, Length);

                false ->
                    validate_open_subpath_split_points_loop(Rest, Point, Length)
            end
    end.

-file("src/svg_path.gleam", 3071).
-spec validate_subpath_parameters(subpath(), list(subpath_parameter())) -> {ok,
        list(canonical_subpath_parameter())} |
    {error, error()}.
validate_subpath_parameters(Subpath, Parameters) ->
    _pipe = Parameters,
    _pipe@1 = gleam@list:fold(
        _pipe,
        {ok, []},
        fun(Validated, Parameter) ->
            gleam@result:'try'(
                Validated,
                fun(Validated@1) ->
                    gleam@result:'try'(
                        validate_subpath_parameter(Subpath, Parameter),
                        fun(Parameter@1) ->
                            {ok, [Parameter@1 | Validated@1]}
                        end
                    )
                end
            )
        end
    ),
    gleam@result:map(_pipe@1, fun lists:reverse/1).

-file("src/svg_path.gleam", 894).
?DOC(
    " Split a subpath at multiple subpath parameters.\n"
    "\n"
    " Open subpaths return the outer pieces as well as the pieces between split\n"
    " points, so an empty split list returns the original subpath. Open split\n"
    " points must be strictly increasing and cannot include the very start or very\n"
    " end. Closed split points must be cyclically increasing and distinct; empty\n"
    " split lists return an empty list.\n"
).
-spec sub_subpaths(subpath(), list(subpath_parameter())) -> {ok,
        list(subpath())} |
    {error, error()}.
sub_subpaths(Subpath, Points) ->
    Length = erlang:length(erlang:element(3, Subpath)),
    gleam@result:'try'(
        validate_subpath_parameters(Subpath, Points),
        fun(Points@1) -> case erlang:element(4, Subpath) of
                false ->
                    case Points@1 of
                        [] ->
                            {ok, [Subpath]};

                        _ ->
                            gleam@result:'try'(
                                validate_open_subpath_split_points(
                                    Points@1,
                                    Length
                                ),
                                fun(_) ->
                                    sub_subpaths_between_points(
                                        Subpath,
                                        [subpath_start_parameter() |
                                            lists:append(
                                                Points@1,
                                                [subpath_end_parameter(Length)]
                                            )]
                                    )
                                end
                            )
                    end;

                true ->
                    case Points@1 of
                        [] ->
                            {ok, []};

                        [Point] ->
                            {error,
                                {invalid_subpath_interval,
                                    canonical_to_subpath_parameter(Point),
                                    canonical_to_subpath_parameter(Point)}};

                        _ ->
                            gleam@result:'try'(
                                validate_closed_subpath_split_points(Points@1),
                                fun(_) ->
                                    sub_subpaths_between_pairs(
                                        Subpath,
                                        cyclic_parameter_pairs(Points@1)
                                    )
                                end
                            )
                    end
            end end
    ).

-file("src/svg_path.gleam", 2903).
-spec first_subpath_start(list(subpath())) -> {ok, vec@vec2:vec2(float())} |
    {error, error()}.
first_subpath_start(Subpaths) ->
    case Subpaths of
        [] ->
            {error, empty_subpaths};

        [Subpath | _] ->
            start(Subpath)
    end.

-file("src/svg_path.gleam", 943).
?DOC(" Return the start point of the first subpath in a path.\n").
-spec path_start(path()) -> {ok, vec@vec2:vec2(float())} | {error, error()}.
path_start(Path) ->
    case erlang:element(2, Path) of
        [] ->
            {error, empty_path};

        Subpaths ->
            first_subpath_start(Subpaths)
    end.

-file("src/svg_path.gleam", 2910).
-spec first_subpath_end(list(subpath())) -> {ok, vec@vec2:vec2(float())} |
    {error, error()}.
first_subpath_end(Subpaths) ->
    case Subpaths of
        [] ->
            {error, empty_subpaths};

        [Subpath | _] ->
            'end'(Subpath)
    end.

-file("src/svg_path.gleam", 951).
?DOC(" Return the end point of the last subpath in a path.\n").
-spec path_end(path()) -> {ok, vec@vec2:vec2(float())} | {error, error()}.
path_end(Path) ->
    case erlang:element(2, Path) of
        [] ->
            {error, empty_path};

        Subpaths ->
            first_subpath_end(lists:reverse(Subpaths))
    end.

-file("src/svg_path.gleam", 969).
?DOC(" Append a segment to an open subpath using the given endpoint policy.\n").
-spec append_segment_with(subpath(), segment(), endpoint_policy()) -> {ok,
        subpath()} |
    {error, error()}.
append_segment_with(Subpath, Segment, Endpoint_policy) ->
    case erlang:element(4, Subpath) of
        true ->
            {error, already_closed};

        false ->
            Segments = lists:append(erlang:element(3, Subpath), [Segment]),
            open_subpath_with_start(
                Segments,
                erlang:element(2, Subpath),
                Endpoint_policy
            )
    end.

-file("src/svg_path.gleam", 961).
?DOC(
    " Append a segment to an open subpath.\n"
    "\n"
    " The new segment must start exactly at the current end point.\n"
).
-spec append_segment(subpath(), segment()) -> {ok, subpath()} | {error, error()}.
append_segment(Subpath, Segment) ->
    append_segment_with(Subpath, Segment, strict).

-file("src/svg_path.gleam", 989).
?DOC(" Append a segment with an endpoint policy, panicking if invalid.\n").
-spec assert_append_segment_with(subpath(), segment(), endpoint_policy()) -> subpath().
assert_append_segment_with(Subpath, Segment, Endpoint_policy) ->
    case append_segment_with(Subpath, Segment, Endpoint_policy) of
        {ok, Subpath@1} ->
            Subpath@1;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_append_segment received an invalid segment"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_append_segment_with"/utf8>>,
                    line => 997})
    end.

-file("src/svg_path.gleam", 984).
?DOC(" Append a segment to an open subpath, panicking if invalid.\n").
-spec assert_append_segment(subpath(), segment()) -> subpath().
assert_append_segment(Subpath, Segment) ->
    assert_append_segment_with(Subpath, Segment, strict).

-file("src/svg_path.gleam", 2917).
-spec join_open_subpaths(list(subpath()), endpoint_policy()) -> {ok, subpath()} |
    {error, error()}.
join_open_subpaths(Subpaths, Policy) ->
    case Subpaths of
        [] ->
            {error, empty_subpath};

        [First | Rest] ->
            Start = erlang:element(2, First),
            Segments = gleam@list:flat_map([First | Rest], fun segments/1),
            open_subpath_with_start(Segments, Start, Policy)
    end.

-file("src/svg_path.gleam", 1011).
?DOC(" Join open subpaths using the given endpoint policy.\n").
-spec join_with(list(subpath()), endpoint_policy()) -> {ok, subpath()} |
    {error, error()}.
join_with(Subpaths, Endpoint_policy) ->
    case gleam@list:any(
        Subpaths,
        fun(Subpath) -> erlang:element(4, Subpath) end
    ) of
        true ->
            {error, already_closed};

        false ->
            join_open_subpaths(Subpaths, Endpoint_policy)
    end.

-file("src/svg_path.gleam", 1006).
?DOC(
    " Join open subpaths into one open subpath.\n"
    "\n"
    " Each subpath's end point must exactly match the next subpath's start point.\n"
    " Empty open subpaths can act as identity values when their start points line\n"
    " up with their neighbors.\n"
).
-spec join(list(subpath())) -> {ok, subpath()} | {error, error()}.
join(Subpaths) ->
    join_with(Subpaths, strict).

-file("src/svg_path.gleam", 1027).
?DOC(" Join open subpaths with an endpoint policy, panicking if invalid.\n").
-spec assert_join_with(list(subpath()), endpoint_policy()) -> subpath().
assert_join_with(Subpaths, Endpoint_policy) ->
    case join_with(Subpaths, Endpoint_policy) of
        {ok, Subpath} ->
            Subpath;

        {error, _} ->
            erlang:error(#{gleam_error => panic,
                    message => <<"svg_path.assert_join received invalid subpaths"/utf8>>,
                    file => <<?FILEPATH/utf8>>,
                    module => <<"svg_path"/utf8>>,
                    function => <<"assert_join_with"/utf8>>,
                    line => 1033})
    end.

-file("src/svg_path.gleam", 1022).
?DOC(" Join open subpaths, panicking if invalid.\n").
-spec assert_join(list(subpath())) -> subpath().
assert_join(Subpaths) ->
    assert_join_with(Subpaths, strict).

-file("src/svg_path.gleam", 1110).
?DOC(
    " Return a segment's derivative with respect to parameter `t`.\n"
    "\n"
    " `t` is not clamped.\n"
).
-spec segment_derivative(segment(), float()) -> {ok, vec@vec2:vec2(float())} |
    {error, error()}.
segment_derivative(Segment, T) ->
    case Segment of
        {line, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_derivative(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {quadratic_bezier, _, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_derivative(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {cubic_bezier, _, _, _, _} ->
            {ok,
                begin
                    _pipe = segment_to_bezier_data(Segment),
                    _pipe@1 = svg_path@bezier:bezier_derivative(_pipe, T),
                    from_bezier_point(_pipe@1)
                end};

        {arc, _, _, _, _, _, _} ->
            case segment_to_center_arc_data(Segment) of
                {error, Error} ->
                    {error, Error};

                {ok, Arc} ->
                    {ok,
                        begin
                            _pipe@2 = svg_path@ellipse:arc_derivative(Arc, T),
                            from_ellipse_point(_pipe@2)
                        end}
            end
    end.

-file("src/svg_path.gleam", 2890).
-spec max_point(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> vec@vec2:vec2(float()).
max_point(A, B) ->
    point(
        gleam@float:max(erlang:element(2, A), erlang:element(2, B)),
        gleam@float:max(erlang:element(3, A), erlang:element(3, B))
    ).

-file("src/svg_path.gleam", 2886).
-spec min_point(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> vec@vec2:vec2(float()).
min_point(A, B) ->
    point(
        gleam@float:min(erlang:element(2, A), erlang:element(2, B)),
        gleam@float:min(erlang:element(3, A), erlang:element(3, B))
    ).

-file("src/svg_path.gleam", 1132).
?DOC(" Return a segment's exact axis-aligned bounding box.\n").
-spec segment_bounding_box(segment()) -> {ok, bounding_box()} | {error, error()}.
segment_bounding_box(Segment) ->
    case Segment of
        {line, Start, End} ->
            {ok, {bounding_box, min_point(Start, End), max_point(Start, End)}};

        {quadratic_bezier, _, _, _} ->
            {bounding_box, Min, Max} = begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:bezier_bounding_box(_pipe)
            end,
            {ok, {bounding_box, from_bezier_point(Min), from_bezier_point(Max)}};

        {cubic_bezier, _, _, _, _} ->
            {bounding_box, Min, Max} = begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:bezier_bounding_box(_pipe)
            end,
            {ok, {bounding_box, from_bezier_point(Min), from_bezier_point(Max)}};

        {arc, _, _, _, _, _, _} ->
            case segment_to_center_arc_data(Segment) of
                {error, Error} ->
                    {error, Error};

                {ok, Arc} ->
                    {bounding_box, Min@1, Max@1} = svg_path@ellipse:arc_bounding_box(
                        Arc
                    ),
                    {ok,
                        {bounding_box,
                            from_ellipse_point(Min@1),
                            from_ellipse_point(Max@1)}}
            end
    end.

-file("src/svg_path.gleam", 1741).
-spec insert_near_unique(list(float()), float(), float()) -> list(float()).
insert_near_unique(Values, Value, Tolerance) ->
    case Values of
        [Previous | _] ->
            case gleam@float:absolute_value(Previous - Value) =< Tolerance of
                true ->
                    Values;

                false ->
                    [Value | Values]
            end;

        _ ->
            [Value | Values]
    end.

-file("src/svg_path.gleam", 1720).
-spec crossing_value(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    float()
) -> {ok, float()} | {error, error()}.
crossing_value(Segment, F, T) ->
    case segment_point(Segment, T) of
        {error, Error} ->
            {error, Error};

        {ok, Point} ->
            {ok, F(Point)}
    end.

-file("src/svg_path.gleam", 1731).
-spec crossing_value_unsafe(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    float()
) -> float().
crossing_value_unsafe(Segment, F, T) ->
    Value@1 = case crossing_value(Segment, F, T) of
        {ok, Value} -> Value;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path"/utf8>>,
                        function => <<"crossing_value_unsafe"/utf8>>,
                        line => 1736,
                        value => _assert_fail,
                        start => 52175,
                        'end' => 52227,
                        pattern_start => 52186,
                        pattern_end => 52195})
    end,
    Value@1.

-file("src/svg_path.gleam", 1692).
-spec refine_crossing(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    crossing_options(),
    float(),
    float()
) -> {ok, gleam@option:option(float())} | {error, error()}.
refine_crossing(Segment, F, Options, Previous_t, Next_t) ->
    Solver_options = {options,
        erlang:element(3, Options),
        erlang:element(4, Options)},
    case svg_path@root:bisect_with(
        fun(T) -> crossing_value_unsafe(Segment, F, T) end,
        Previous_t,
        Next_t,
        Solver_options
    ) of
        {ok, T@1} ->
            {ok, {some, T@1}};

        {error, {max_iterations_reached, Estimate, Value}} ->
            {error, {crossing_max_iterations_reached, Estimate, Value}};

        {error, _} ->
            {ok, none}
    end.

-file("src/svg_path.gleam", 1761).
-spec same_sign(float(), float()) -> boolean().
same_sign(A, B) ->
    ((A < +0.0) andalso (B < +0.0)) orelse ((A > +0.0) andalso (B > +0.0)).

-file("src/svg_path.gleam", 1757).
-spec is_close_to_zero(float(), float()) -> boolean().
is_close_to_zero(Value, Tolerance) ->
    gleam@float:absolute_value(Value) =< Tolerance.

-file("src/svg_path.gleam", 1667).
-spec crossing_for_window(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    crossing_options(),
    float(),
    float(),
    float(),
    float()
) -> {ok, gleam@option:option(float())} | {error, error()}.
crossing_for_window(
    Segment,
    F,
    Options,
    Previous_t,
    Previous_value,
    Next_t,
    Next_value
) ->
    case is_close_to_zero(Previous_value, erlang:element(3, Options)) of
        true ->
            {ok, {some, Previous_t}};

        false ->
            case is_close_to_zero(Next_value, erlang:element(3, Options)) of
                true ->
                    {ok, {some, Next_t}};

                false ->
                    case same_sign(Previous_value, Next_value) of
                        true ->
                            {ok, none};

                        false ->
                            refine_crossing(
                                Segment,
                                F,
                                Options,
                                Previous_t,
                                Next_t
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1607).
-spec scan_crossings(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    crossing_options(),
    integer(),
    float(),
    float(),
    list(float())
) -> {ok, list(float())} | {error, error()}.
scan_crossings(
    Segment,
    F,
    Options,
    Index,
    Previous_t,
    Previous_value,
    Crossings
) ->
    case Index > erlang:element(2, Options) of
        true ->
            {ok, lists:reverse(Crossings)};

        false ->
            Next_t = case erlang:float(erlang:element(2, Options)) of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> erlang:float(Index) / Gleam@denominator
            end,
            case crossing_value(Segment, F, Next_t) of
                {error, Error} ->
                    {error, Error};

                {ok, Next_value} ->
                    case crossing_for_window(
                        Segment,
                        F,
                        Options,
                        Previous_t,
                        Previous_value,
                        Next_t,
                        Next_value
                    ) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, none} ->
                            scan_crossings(
                                Segment,
                                F,
                                Options,
                                Index + 1,
                                Next_t,
                                Next_value,
                                Crossings
                            );

                        {ok, {some, Crossing}} ->
                            scan_crossings(
                                Segment,
                                F,
                                Options,
                                Index + 1,
                                Next_t,
                                Next_value,
                                insert_near_unique(
                                    Crossings,
                                    Crossing,
                                    erlang:element(3, Options)
                                )
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1590).
-spec validate_crossing_options(crossing_options()) -> {ok, nil} |
    {error, error()}.
validate_crossing_options(Options) ->
    case erlang:element(2, Options) =< 0 of
        true ->
            {error, {invalid_crossing_samples, erlang:element(2, Options)}};

        false ->
            case erlang:element(3, Options) =< +0.0 of
                true ->
                    {error,
                        {invalid_crossing_tolerance, erlang:element(3, Options)}};

                false ->
                    case erlang:element(4, Options) =< 0 of
                        true ->
                            {error,
                                {invalid_crossing_max_iterations,
                                    erlang:element(4, Options)}};

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

-file("src/svg_path.gleam", 1172).
?DOC(" Find scalar sign-change crossings along a segment using explicit options.\n").
-spec segment_crossings_with(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    crossing_options()
) -> {ok, list(float())} | {error, error()}.
segment_crossings_with(Segment, F, Options) ->
    case validate_crossing_options(Options) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case crossing_value(Segment, F, +0.0) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, First_value} ->
                    scan_crossings(
                        Segment,
                        F,
                        Options,
                        1,
                        +0.0,
                        First_value,
                        []
                    )
            end
    end.

-file("src/svg_path.gleam", 1164).
?DOC(
    " Find scalar sign-change crossings along a segment using default options.\n"
    "\n"
    " This samples `t` in `0.0..1.0`, detects sign changes of `f(segment_point(t))`,\n"
    " and refines each bracket with bisection. It finds crossings visible at the\n"
    " configured sampling resolution; tangent roots and pairs of crossings inside\n"
    " one sample window may be missed.\n"
).
-spec segment_crossings(segment(), fun((vec@vec2:vec2(float())) -> float())) -> {ok,
        list(float())} |
    {error, error()}.
segment_crossings(Segment, F) ->
    segment_crossings_with(Segment, F, default_crossing_options()).

-file("src/svg_path.gleam", 1947).
-spec best_candidate(minimize_candidate(), minimize_candidate()) -> minimize_candidate().
best_candidate(A, B) ->
    case erlang:element(3, A) =< erlang:element(3, B) of
        true ->
            A;

        false ->
            B
    end.

-file("src/svg_path.gleam", 1936).
-spec minimize_value(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    float()
) -> {ok, minimize_candidate()} | {error, error()}.
minimize_value(Segment, F, T) ->
    case segment_point(Segment, T) of
        {error, Error} ->
            {error, Error};

        {ok, Point} ->
            {ok, {minimize_candidate, T, F(Point)}}
    end.

-file("src/svg_path.gleam", 1858).
-spec golden_section_loop(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    float(),
    float(),
    float(),
    minimize_candidate(),
    float(),
    minimize_candidate(),
    float(),
    integer()
) -> {ok, minimize_candidate()} | {error, error()}.
golden_section_loop(
    Segment,
    F,
    Left,
    Right,
    Inner_left,
    Inner_left_candidate,
    Inner_right,
    Inner_right_candidate,
    Tolerance,
    Remaining_iterations
) ->
    case (Right - Left) =< Tolerance of
        true ->
            {ok, best_candidate(Inner_left_candidate, Inner_right_candidate)};

        false ->
            case Remaining_iterations =< 0 of
                true ->
                    Best = best_candidate(
                        Inner_left_candidate,
                        Inner_right_candidate
                    ),
                    {error,
                        {minimize_max_iterations_reached,
                            erlang:element(2, Best),
                            erlang:element(3, Best)}};

                false ->
                    case erlang:element(3, Inner_left_candidate) < erlang:element(
                        3,
                        Inner_right_candidate
                    ) of
                        true ->
                            Next_right = Inner_right,
                            Next_inner_right = Inner_left,
                            Next_inner_right_candidate = Inner_left_candidate,
                            Next_inner_left = Next_right - (0.6180339887498949 * (Next_right
                            - Left)),
                            case minimize_value(Segment, F, Next_inner_left) of
                                {error, Error} ->
                                    {error, Error};

                                {ok, Next_inner_left_candidate} ->
                                    golden_section_loop(
                                        Segment,
                                        F,
                                        Left,
                                        Next_right,
                                        Next_inner_left,
                                        Next_inner_left_candidate,
                                        Next_inner_right,
                                        Next_inner_right_candidate,
                                        Tolerance,
                                        Remaining_iterations - 1
                                    )
                            end;

                        false ->
                            Next_left = Inner_left,
                            Next_inner_left@1 = Inner_right,
                            Next_inner_left_candidate@1 = Inner_right_candidate,
                            Next_inner_right@1 = Next_left + (0.6180339887498949
                            * (Right - Next_left)),
                            case minimize_value(Segment, F, Next_inner_right@1) of
                                {error, Error@1} ->
                                    {error, Error@1};

                                {ok, Next_inner_right_candidate@1} ->
                                    golden_section_loop(
                                        Segment,
                                        F,
                                        Next_left,
                                        Right,
                                        Next_inner_left@1,
                                        Next_inner_left_candidate@1,
                                        Next_inner_right@1,
                                        Next_inner_right_candidate@1,
                                        Tolerance,
                                        Remaining_iterations - 1
                                    )
                            end
                    end
            end
    end.

-file("src/svg_path.gleam", 1824).
-spec golden_section_minimize(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    float(),
    float(),
    minimize_options()
) -> {ok, minimize_candidate()} | {error, error()}.
golden_section_minimize(Segment, F, Left, Right, Options) ->
    Span = Right - Left,
    Inner_left = Right - (0.6180339887498949 * Span),
    Inner_right = Left + (0.6180339887498949 * Span),
    case minimize_value(Segment, F, Inner_left) of
        {error, Error} ->
            {error, Error};

        {ok, Left_candidate} ->
            case minimize_value(Segment, F, Inner_right) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Right_candidate} ->
                    golden_section_loop(
                        Segment,
                        F,
                        Left,
                        Right,
                        Inner_left,
                        Left_candidate,
                        Inner_right,
                        Right_candidate,
                        erlang:element(3, Options),
                        erlang:element(4, Options)
                    )
            end
    end.

-file("src/svg_path.gleam", 1782).
-spec scan_minimize_windows(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    minimize_options(),
    integer(),
    float(),
    minimize_candidate()
) -> {ok, minimize_candidate()} | {error, error()}.
scan_minimize_windows(Segment, F, Options, Index, Previous_t, Best) ->
    case Index > erlang:element(2, Options) of
        true ->
            {ok, Best};

        false ->
            Next_t = case erlang:float(erlang:element(2, Options)) of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> erlang:float(Index) / Gleam@denominator
            end,
            case minimize_value(Segment, F, Next_t) of
                {error, Error} ->
                    {error, Error};

                {ok, Next} ->
                    case golden_section_minimize(
                        Segment,
                        F,
                        Previous_t,
                        Next_t,
                        Options
                    ) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Window_best} ->
                            scan_minimize_windows(
                                Segment,
                                F,
                                Options,
                                Index + 1,
                                Next_t,
                                begin
                                    _pipe = best_candidate(Best, Next),
                                    best_candidate(_pipe, Window_best)
                                end
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1765).
-spec validate_minimize_options(minimize_options()) -> {ok, nil} |
    {error, error()}.
validate_minimize_options(Options) ->
    case erlang:element(2, Options) =< 0 of
        true ->
            {error, {invalid_minimize_samples, erlang:element(2, Options)}};

        false ->
            case erlang:element(3, Options) =< +0.0 of
                true ->
                    {error,
                        {invalid_minimize_tolerance, erlang:element(3, Options)}};

                false ->
                    case erlang:element(4, Options) =< 0 of
                        true ->
                            {error,
                                {invalid_minimize_max_iterations,
                                    erlang:element(4, Options)}};

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

-file("src/svg_path.gleam", 1214).
?DOC(
    " Return the segment parameter where a scalar function is minimized using\n"
    " explicit options.\n"
).
-spec segment_minimize_with(
    segment(),
    fun((vec@vec2:vec2(float())) -> float()),
    minimize_options()
) -> {ok, float()} | {error, error()}.
segment_minimize_with(Segment, F, Options) ->
    case validate_minimize_options(Options) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case minimize_value(Segment, F, +0.0) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, First} ->
                    case scan_minimize_windows(
                        Segment,
                        F,
                        Options,
                        1,
                        +0.0,
                        First
                    ) of
                        {error, Error@2} ->
                            {error, Error@2};

                        {ok, Best} ->
                            {ok, erlang:element(2, Best)}
                    end
            end
    end.

-file("src/svg_path.gleam", 1201).
?DOC(
    " Return the segment parameter where a scalar function is minimized.\n"
    "\n"
    " This numerically minimizes `f(segment_point(t))` for `t` in `0.0..1.0`.\n"
).
-spec segment_minimize(segment(), fun((vec@vec2:vec2(float())) -> float())) -> {ok,
        float()} |
    {error, error()}.
segment_minimize(Segment, F) ->
    segment_minimize_with(Segment, F, default_minimize_options()).

-file("src/svg_path.gleam", 2232).
-spec squared_distance(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> float().
squared_distance(A, B) ->
    Dx = erlang:element(2, A) - erlang:element(2, B),
    Dy = erlang:element(3, A) - erlang:element(3, B),
    (Dx * Dx) + (Dy * Dy).

-file("src/svg_path.gleam", 2185).
-spec squared_segment_distance_at(vec@vec2:vec2(float()), segment(), float()) -> {ok,
        float()} |
    {error, error()}.
squared_segment_distance_at(Point, Segment, T) ->
    case segment_point(Segment, T) of
        {error, Error} ->
            {error, Error};

        {ok, Segment_point} ->
            {ok, squared_distance(Point, Segment_point)}
    end.

-file("src/svg_path.gleam", 2162).
-spec smallest_segment_distance_loop(
    vec@vec2:vec2(float()),
    segment(),
    list(float()),
    float()
) -> {ok, float()} | {error, error()}.
smallest_segment_distance_loop(Point, Segment, Candidates, Smallest) ->
    case Candidates of
        [] ->
            {ok, float_square_root(Smallest)};

        [First | Rest] ->
            case squared_segment_distance_at(Point, Segment, First) of
                {error, Error} ->
                    {error, Error};

                {ok, Distance} ->
                    smallest_segment_distance_loop(
                        Point,
                        Segment,
                        Rest,
                        gleam@float:min(Smallest, Distance)
                    )
            end
    end.

-file("src/svg_path.gleam", 2145).
-spec smallest_segment_distance(
    vec@vec2:vec2(float()),
    segment(),
    list(float())
) -> {ok, float()} | {error, error()}.
smallest_segment_distance(Point, Segment, Candidates) ->
    case Candidates of
        [] ->
            {ok, +0.0};

        [First | Rest] ->
            case squared_segment_distance_at(Point, Segment, First) of
                {error, Error} ->
                    {error, Error};

                {ok, First_distance} ->
                    smallest_segment_distance_loop(
                        Point,
                        Segment,
                        Rest,
                        First_distance
                    )
            end
    end.

-file("src/svg_path.gleam", 2228).
-spec dot(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> float().
dot(A, B) ->
    (erlang:element(2, A) * erlang:element(2, B)) + (erlang:element(3, A) * erlang:element(
        3,
        B
    )).

-file("src/svg_path.gleam", 2217).
-spec point_difference(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> vec@vec2:vec2(float()).
point_difference(A, B) ->
    point(
        erlang:element(2, A) - erlang:element(2, B),
        erlang:element(3, A) - erlang:element(3, B)
    ).

-file("src/svg_path.gleam", 2115).
-spec distance_stationary_value(vec@vec2:vec2(float()), segment(), float()) -> {ok,
        float()} |
    {error, error()}.
distance_stationary_value(Point, Segment, T) ->
    case segment_point(Segment, T) of
        {error, Error} ->
            {error, Error};

        {ok, Segment_point} ->
            case segment_derivative(Segment, T) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Derivative} ->
                    Offset = point_difference(Segment_point, Point),
                    {ok, dot(Offset, Derivative)}
            end
    end.

-file("src/svg_path.gleam", 2135).
-spec distance_stationary_value_unsafe(
    vec@vec2:vec2(float()),
    segment(),
    float()
) -> float().
distance_stationary_value_unsafe(Point, Segment, T) ->
    Value@1 = case distance_stationary_value(Point, Segment, T) of
        {ok, Value} -> Value;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path"/utf8>>,
                        function => <<"distance_stationary_value_unsafe"/utf8>>,
                        line => 2140,
                        value => _assert_fail,
                        start => 63140,
                        'end' => 63207,
                        pattern_start => 63151,
                        pattern_end => 63160})
    end,
    Value@1.

-file("src/svg_path.gleam", 2087).
-spec refine_distance_candidate(
    vec@vec2:vec2(float()),
    segment(),
    distance_options(),
    float(),
    float()
) -> {ok, gleam@option:option(float())} | {error, error()}.
refine_distance_candidate(Point, Segment, Options, Previous_t, Next_t) ->
    Solver_options = {options,
        erlang:element(3, Options),
        erlang:element(4, Options)},
    case svg_path@root:bisect_with(
        fun(T) -> distance_stationary_value_unsafe(Point, Segment, T) end,
        Previous_t,
        Next_t,
        Solver_options
    ) of
        {ok, T@1} ->
            {ok, {some, T@1}};

        {error, {max_iterations_reached, Estimate, Value}} ->
            {error, {distance_max_iterations_reached, Estimate, Value}};

        {error, _} ->
            {ok, none}
    end.

-file("src/svg_path.gleam", 2055).
-spec distance_candidate_for_window(
    vec@vec2:vec2(float()),
    segment(),
    distance_options(),
    float(),
    float(),
    float(),
    float()
) -> {ok, gleam@option:option(float())} | {error, error()}.
distance_candidate_for_window(
    Point,
    Segment,
    Options,
    Previous_t,
    Previous_value,
    Next_t,
    Next_value
) ->
    case is_close_to_zero(Previous_value, erlang:element(3, Options)) of
        true ->
            {ok, {some, Previous_t}};

        false ->
            case is_close_to_zero(Next_value, erlang:element(3, Options)) of
                true ->
                    {ok, {some, Next_t}};

                false ->
                    case same_sign(Previous_value, Next_value) of
                        true ->
                            {ok, none};

                        false ->
                            refine_distance_candidate(
                                Point,
                                Segment,
                                Options,
                                Previous_t,
                                Next_t
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1995).
-spec scan_distance_candidates(
    vec@vec2:vec2(float()),
    segment(),
    distance_options(),
    integer(),
    float(),
    float(),
    list(float())
) -> {ok, list(float())} | {error, error()}.
scan_distance_candidates(
    Point,
    Segment,
    Options,
    Index,
    Previous_t,
    Previous_value,
    Candidates
) ->
    case Index > erlang:element(2, Options) of
        true ->
            {ok, Candidates};

        false ->
            Next_t = case erlang:float(erlang:element(2, Options)) of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> erlang:float(Index) / Gleam@denominator
            end,
            case distance_stationary_value(Point, Segment, Next_t) of
                {error, Error} ->
                    {error, Error};

                {ok, Next_value} ->
                    case distance_candidate_for_window(
                        Point,
                        Segment,
                        Options,
                        Previous_t,
                        Previous_value,
                        Next_t,
                        Next_value
                    ) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, none} ->
                            scan_distance_candidates(
                                Point,
                                Segment,
                                Options,
                                Index + 1,
                                Next_t,
                                Next_value,
                                Candidates
                            );

                        {ok, {some, Candidate}} ->
                            scan_distance_candidates(
                                Point,
                                Segment,
                                Options,
                                Index + 1,
                                Next_t,
                                Next_value,
                                insert_near_unique(
                                    Candidates,
                                    Candidate,
                                    erlang:element(3, Options)
                                )
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1974).
-spec distance_candidates(vec@vec2:vec2(float()), segment(), distance_options()) -> {ok,
        list(float())} |
    {error, error()}.
distance_candidates(Point, Segment, Options) ->
    case distance_stationary_value(Point, Segment, +0.0) of
        {error, Error} ->
            {error, Error};

        {ok, First_value} ->
            scan_distance_candidates(
                Point,
                Segment,
                Options,
                1,
                +0.0,
                First_value,
                [1.0, +0.0]
            )
    end.

-file("src/svg_path.gleam", 2221).
-spec offset(vec@vec2:vec2(float()), vec@vec2:vec2(float()), float()) -> vec@vec2:vec2(float()).
offset(Origin, Direction, Distance) ->
    point(
        erlang:element(2, Origin) + (erlang:element(2, Direction) * Distance),
        erlang:element(3, Origin) + (erlang:element(3, Direction) * Distance)
    ).

-file("src/svg_path.gleam", 2213).
-spec clamp01(float()) -> float().
clamp01(Value) ->
    _pipe = Value,
    _pipe@1 = gleam@float:max(_pipe, +0.0),
    gleam@float:min(_pipe@1, 1.0).

-file("src/svg_path.gleam", 2196).
-spec point_to_line_distance(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> float().
point_to_line_distance(Point, Start, End) ->
    Line = point_difference(End, Start),
    Length_squared = dot(Line, Line),
    case Length_squared =:= +0.0 of
        true ->
            distance(Point, Start);

        false ->
            Progress = begin
                _pipe = case Length_squared of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator -> dot(
                        point_difference(Point, Start),
                        Line
                    )
                    / Gleam@denominator
                end,
                clamp01(_pipe)
            end,
            Projection = offset(Start, Line, Progress),
            distance(Point, Projection)
    end.

-file("src/svg_path.gleam", 1957).
-spec validate_distance_options(distance_options()) -> {ok, nil} |
    {error, error()}.
validate_distance_options(Options) ->
    case erlang:element(2, Options) =< 0 of
        true ->
            {error, {invalid_distance_samples, erlang:element(2, Options)}};

        false ->
            case erlang:element(3, Options) =< +0.0 of
                true ->
                    {error,
                        {invalid_distance_tolerance, erlang:element(3, Options)}};

                false ->
                    case erlang:element(4, Options) =< 0 of
                        true ->
                            {error,
                                {invalid_distance_max_iterations,
                                    erlang:element(4, Options)}};

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

-file("src/svg_path.gleam", 1256).
?DOC(" Return the shortest distance from a point to a segment using explicit options.\n").
-spec segment_distance_with(
    vec@vec2:vec2(float()),
    segment(),
    distance_options()
) -> {ok, float()} | {error, error()}.
segment_distance_with(Point, Segment, Options) ->
    case validate_distance_options(Options) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case Segment of
                {line, Start, End} ->
                    {ok, point_to_line_distance(Point, Start, End)};

                {quadratic_bezier, _, _, _} ->
                    case distance_candidates(Point, Segment, Options) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Candidates} ->
                            smallest_segment_distance(
                                Point,
                                Segment,
                                Candidates
                            )
                    end;

                {cubic_bezier, _, _, _, _} ->
                    case distance_candidates(Point, Segment, Options) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Candidates} ->
                            smallest_segment_distance(
                                Point,
                                Segment,
                                Candidates
                            )
                    end;

                {arc, _, _, _, _, _, _} ->
                    case distance_candidates(Point, Segment, Options) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Candidates} ->
                            smallest_segment_distance(
                                Point,
                                Segment,
                                Candidates
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 1248).
?DOC(
    " Return the shortest distance from a point to a segment.\n"
    "\n"
    " Lines are measured exactly. Quadratic Beziers, cubic Beziers, and arcs are\n"
    " measured by finding stationary points of squared distance in `0.0..1.0`.\n"
).
-spec segment_distance(vec@vec2:vec2(float()), segment()) -> {ok, float()} |
    {error, error()}.
segment_distance(Point, Segment) ->
    segment_distance_with(Point, Segment, default_distance_options()).

-file("src/svg_path.gleam", 2604).
-spec split_intersection_piece(intersection_piece()) -> {intersection_piece(),
    intersection_piece()}.
split_intersection_piece(Piece) ->
    {Left@1, Right@1} = case split_segment(erlang:element(2, Piece), 0.5) of
        {ok, {Left, Right}} -> {Left, Right};
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path"/utf8>>,
                        function => <<"split_intersection_piece"/utf8>>,
                        line => 2607,
                        value => _assert_fail,
                        start => 76231,
                        'end' => 76300,
                        pattern_start => 76242,
                        pattern_end => 76260})
    end,
    Middle = (erlang:element(3, Piece) + erlang:element(4, Piece)) / 2.0,
    {{intersection_piece, Left@1, erlang:element(3, Piece), Middle},
        {intersection_piece, Right@1, Middle, erlang:element(4, Piece)}}.

-file("src/svg_path.gleam", 2823).
-spec intersection_dedupe_tolerance(float()) -> float().
intersection_dedupe_tolerance(Tolerance) ->
    gleam@float:max(Tolerance * 1000000.0, 0.000001).

-file("src/svg_path.gleam", 2802).
-spec merge_intersections(segment_intersection(), segment_intersection()) -> segment_intersection().
merge_intersections(A, B) ->
    {segment_intersection,
        (erlang:element(2, A) + erlang:element(2, B)) / 2.0,
        (erlang:element(3, A) + erlang:element(3, B)) / 2.0,
        midpoint(erlang:element(4, A), erlang:element(4, B))}.

-file("src/svg_path.gleam", 2778).
-spec insert_intersection(
    list(segment_intersection()),
    segment_intersection(),
    float()
) -> list(segment_intersection()).
insert_intersection(Intersections, Intersection, Tolerance) ->
    case Intersections of
        [] ->
            [Intersection];

        [First | Rest] ->
            case (distance(
                erlang:element(4, First),
                erlang:element(4, Intersection)
            )
            =< Tolerance)
            orelse ((gleam@float:absolute_value(
                erlang:element(2, First) - erlang:element(2, Intersection)
            )
            =< Tolerance)
            andalso (gleam@float:absolute_value(
                erlang:element(3, First) - erlang:element(3, Intersection)
            )
            =< Tolerance)) of
                true ->
                    [merge_intersections(First, Intersection) | Rest];

                false ->
                    [First | insert_intersection(Rest, Intersection, Tolerance)]
            end
    end.

-file("src/svg_path.gleam", 2813).
-spec insert_intersections(
    list(segment_intersection()),
    list(segment_intersection()),
    float()
) -> list(segment_intersection()).
insert_intersections(Intersections, New_intersections, Tolerance) ->
    gleam@list:fold(
        New_intersections,
        Intersections,
        fun(Intersections@1, Intersection) ->
            insert_intersection(Intersections@1, Intersection, Tolerance)
        end
    ).

-file("src/svg_path.gleam", 2616).
-spec intersection_from_pieces(intersection_piece(), intersection_piece()) -> {ok,
        segment_intersection()} |
    {error, error()}.
intersection_from_pieces(Left, Right) ->
    Left_t = (erlang:element(3, Left) + erlang:element(4, Left)) / 2.0,
    Right_t = (erlang:element(3, Right) + erlang:element(4, Right)) / 2.0,
    case {segment_point(erlang:element(2, Left), 0.5),
        segment_point(erlang:element(2, Right), 0.5)} of
        {{error, Error}, _} ->
            {error, Error};

        {_, {error, Error}} ->
            {error, Error};

        {{ok, Left_point}, {ok, Right_point}} ->
            {ok,
                {segment_intersection,
                    Left_t,
                    Right_t,
                    midpoint(Left_point, Right_point)}}
    end.

-file("src/svg_path.gleam", 2831).
-spec interpolate_float(float(), float(), float()) -> float().
interpolate_float(Start, End, T) ->
    Start + ((End - Start) * T).

-file("src/svg_path.gleam", 2774).
-spec in_unit_range(float(), float()) -> boolean().
in_unit_range(Value, Tolerance) ->
    (Value >= (+0.0 - Tolerance)) andalso (Value =< (1.0 + Tolerance)).

-file("src/svg_path.gleam", 2827).
-spec cross(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> float().
cross(A, B) ->
    (erlang:element(2, A) * erlang:element(3, B)) - (erlang:element(3, A) * erlang:element(
        2,
        B
    )).

-file("src/svg_path.gleam", 2764).
-spec line_projection_t(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float())
) -> float().
line_projection_t(Point, Start, End) ->
    Direction = point_difference(End, Start),
    Length_squared = dot(Direction, Direction),
    case Length_squared =:= +0.0 of
        true ->
            +0.0;

        false ->
            case Length_squared of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> dot(
                    point_difference(Point, Start),
                    Direction
                )
                / Gleam@denominator
            end
    end.

-file("src/svg_path.gleam", 2358).
-spec collinear_line_intersections(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    float()
) -> {ok, list(segment_intersection())} | {error, error()}.
collinear_line_intersections(
    Left_start,
    Left_end,
    Right_start,
    Right_end,
    Tolerance
) ->
    Right_start_t = line_projection_t(Right_start, Left_start, Left_end),
    Right_end_t = line_projection_t(Right_end, Left_start, Left_end),
    Overlap_start = gleam@float:max(
        +0.0,
        gleam@float:min(Right_start_t, Right_end_t)
    ),
    Overlap_end = gleam@float:min(
        1.0,
        gleam@float:max(Right_start_t, Right_end_t)
    ),
    case Overlap_end < (Overlap_start - Tolerance) of
        true ->
            {ok, []};

        false ->
            case (Overlap_end - Overlap_start) =< Tolerance of
                true ->
                    Left_t = clamp01((Overlap_start + Overlap_end) / 2.0),
                    Point = interpolate(Left_start, Left_end, Left_t),
                    {ok,
                        [{segment_intersection,
                                Left_t,
                                begin
                                    _pipe = line_projection_t(
                                        Point,
                                        Right_start,
                                        Right_end
                                    ),
                                    clamp01(_pipe)
                                end,
                                Point}]};

                false ->
                    {error, overlapping_segments}
            end
    end.

-file("src/svg_path.gleam", 2752).
-spec point_on_line_segment(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    float()
) -> boolean().
point_on_line_segment(Point, Start, End, Tolerance) ->
    Direction = point_difference(End, Start),
    (gleam@float:absolute_value(
        cross(Direction, point_difference(Point, Start))
    )
    =< Tolerance)
    andalso in_unit_range(line_projection_t(Point, Start, End), Tolerance).

-file("src/svg_path.gleam", 2252).
-spec line_line_intersections(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    float()
) -> {ok, list(segment_intersection())} | {error, error()}.
line_line_intersections(Left_start, Left_end, Right_start, Right_end, Tolerance) ->
    Left_direction = point_difference(Left_end, Left_start),
    Right_direction = point_difference(Right_end, Right_start),
    Left_length_squared = dot(Left_direction, Left_direction),
    Right_length_squared = dot(Right_direction, Right_direction),
    case {Left_length_squared =< (Tolerance * Tolerance),
        Right_length_squared =< (Tolerance * Tolerance)} of
        {true, true} ->
            case distance(Left_start, Right_start) =< Tolerance of
                true ->
                    {ok,
                        [{segment_intersection,
                                +0.0,
                                +0.0,
                                midpoint(Left_start, Right_start)}]};

                false ->
                    {ok, []}
            end;

        {true, false} ->
            case point_on_line_segment(
                Left_start,
                Right_start,
                Right_end,
                Tolerance
            ) of
                true ->
                    {ok,
                        [{segment_intersection,
                                +0.0,
                                line_projection_t(
                                    Left_start,
                                    Right_start,
                                    Right_end
                                ),
                                Left_start}]};

                false ->
                    {ok, []}
            end;

        {false, true} ->
            case point_on_line_segment(
                Right_start,
                Left_start,
                Left_end,
                Tolerance
            ) of
                true ->
                    {ok,
                        [{segment_intersection,
                                line_projection_t(
                                    Right_start,
                                    Left_start,
                                    Left_end
                                ),
                                +0.0,
                                Right_start}]};

                false ->
                    {ok, []}
            end;

        {false, false} ->
            Start_difference = point_difference(Right_start, Left_start),
            Denominator = cross(Left_direction, Right_direction),
            case gleam@float:absolute_value(Denominator) =< Tolerance of
                true ->
                    case gleam@float:absolute_value(
                        cross(Start_difference, Left_direction)
                    )
                    =< Tolerance of
                        true ->
                            collinear_line_intersections(
                                Left_start,
                                Left_end,
                                Right_start,
                                Right_end,
                                Tolerance
                            );

                        false ->
                            {ok, []}
                    end;

                false ->
                    Left_t = case Denominator of
                        +0.0 -> +0.0;
                        -0.0 -> -0.0;
                        Gleam@denominator -> cross(
                            Start_difference,
                            Right_direction
                        )
                        / Gleam@denominator
                    end,
                    Right_t = case Denominator of
                        +0.0 -> +0.0;
                        -0.0 -> -0.0;
                        Gleam@denominator@1 -> cross(
                            Start_difference,
                            Left_direction
                        )
                        / Gleam@denominator@1
                    end,
                    case in_unit_range(Left_t, Tolerance) andalso in_unit_range(
                        Right_t,
                        Tolerance
                    ) of
                        true ->
                            Left_t@1 = clamp01(Left_t),
                            Right_t@1 = clamp01(Right_t),
                            {ok,
                                [{segment_intersection,
                                        Left_t@1,
                                        Right_t@1,
                                        interpolate(
                                            Left_start,
                                            Left_end,
                                            Left_t@1
                                        )}]};

                        false ->
                            {ok, []}
                    end
            end
    end.

-file("src/svg_path.gleam", 2637).
-spec chord_intersections_from_pieces(
    intersection_piece(),
    intersection_piece(),
    float()
) -> {ok, list(segment_intersection())} | {error, error()}.
chord_intersections_from_pieces(Left, Right, Tolerance) ->
    Left_start = segment_start(erlang:element(2, Left)),
    Left_end = segment_end(erlang:element(2, Left)),
    Right_start = segment_start(erlang:element(2, Right)),
    Right_end = segment_end(erlang:element(2, Right)),
    case line_line_intersections(
        Left_start,
        Left_end,
        Right_start,
        Right_end,
        Tolerance
    ) of
        {ok, Intersections} ->
            {ok,
                gleam@list:map(
                    Intersections,
                    fun(Intersection) ->
                        {segment_intersection,
                            interpolate_float(
                                erlang:element(3, Left),
                                erlang:element(4, Left),
                                erlang:element(2, Intersection)
                            ),
                            interpolate_float(
                                erlang:element(3, Right),
                                erlang:element(4, Right),
                                erlang:element(3, Intersection)
                            ),
                            erlang:element(4, Intersection)}
                    end
                )};

        {error, overlapping_segments} ->
            case intersection_from_pieces(Left, Right) of
                {error, Error} ->
                    {error, Error};

                {ok, Intersection@1} ->
                    {ok, [Intersection@1]}
            end;

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

-file("src/svg_path.gleam", 2680).
-spec boxes_overlap(bounding_box(), bounding_box(), float()) -> boolean().
boxes_overlap(Left, Right, Tolerance) ->
    (((erlang:element(2, erlang:element(2, Left)) =< (erlang:element(
        2,
        erlang:element(3, Right)
    )
    + Tolerance))
    andalso ((erlang:element(2, erlang:element(3, Left)) + Tolerance) >= erlang:element(
        2,
        erlang:element(2, Right)
    )))
    andalso (erlang:element(3, erlang:element(2, Left)) =< (erlang:element(
        3,
        erlang:element(3, Right)
    )
    + Tolerance)))
    andalso ((erlang:element(3, erlang:element(3, Left)) + Tolerance) >= erlang:element(
        3,
        erlang:element(2, Right)
    )).

-file("src/svg_path.gleam", 2510).
-spec collect_curve_curve_intersections(
    intersection_piece(),
    intersection_piece(),
    intersection_options(),
    integer(),
    list(segment_intersection())
) -> {ok, list(segment_intersection())} | {error, error()}.
collect_curve_curve_intersections(
    Left,
    Right,
    Options,
    Remaining_depth,
    Intersections
) ->
    case {segment_bounding_box(erlang:element(2, Left)),
        segment_bounding_box(erlang:element(2, Right))} of
        {{error, Error}, _} ->
            {error, Error};

        {_, {error, Error}} ->
            {error, Error};

        {{ok, Left_box}, {ok, Right_box}} ->
            case boxes_overlap(Left_box, Right_box, erlang:element(2, Options)) of
                false ->
                    {ok, Intersections};

                true ->
                    case (Remaining_depth =< 0) orelse ((bounding_box_diameter(
                        Left_box
                    )
                    =< erlang:element(2, Options))
                    andalso (bounding_box_diameter(Right_box) =< erlang:element(
                        2,
                        Options
                    ))) of
                        true ->
                            case chord_intersections_from_pieces(
                                Left,
                                Right,
                                erlang:element(2, Options)
                            ) of
                                {error, Error@1} ->
                                    {error, Error@1};

                                {ok, Found} ->
                                    {ok,
                                        insert_intersections(
                                            Intersections,
                                            Found,
                                            intersection_dedupe_tolerance(
                                                erlang:element(2, Options)
                                            )
                                        )}
                            end;

                        false ->
                            Split_left = bounding_box_diameter(Left_box) >= bounding_box_diameter(
                                Right_box
                            ),
                            case Split_left of
                                true ->
                                    {First, Second} = split_intersection_piece(
                                        Left
                                    ),
                                    case collect_curve_curve_intersections(
                                        First,
                                        Right,
                                        Options,
                                        Remaining_depth - 1,
                                        Intersections
                                    ) of
                                        {error, Error@2} ->
                                            {error, Error@2};

                                        {ok, Intersections@1} ->
                                            collect_curve_curve_intersections(
                                                Second,
                                                Right,
                                                Options,
                                                Remaining_depth - 1,
                                                Intersections@1
                                            )
                                    end;

                                false ->
                                    {First@1, Second@1} = split_intersection_piece(
                                        Right
                                    ),
                                    case collect_curve_curve_intersections(
                                        Left,
                                        First@1,
                                        Options,
                                        Remaining_depth - 1,
                                        Intersections
                                    ) of
                                        {error, Error@3} ->
                                            {error, Error@3};

                                        {ok, Intersections@2} ->
                                            collect_curve_curve_intersections(
                                                Left,
                                                Second@1,
                                                Options,
                                                Remaining_depth - 1,
                                                Intersections@2
                                            )
                                    end
                            end
                    end
            end
    end.

-file("src/svg_path.gleam", 2496).
-spec curve_curve_intersections(segment(), segment(), intersection_options()) -> {ok,
        list(segment_intersection())} |
    {error, error()}.
curve_curve_intersections(Left, Right, Options) ->
    collect_curve_curve_intersections(
        {intersection_piece, Left, +0.0, 1.0},
        {intersection_piece, Right, +0.0, 1.0},
        Options,
        erlang:element(3, Options),
        []
    ).

-file("src/svg_path.gleam", 2435).
-spec line_segment_intersections_from_ts(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    boolean(),
    segment(),
    list(float()),
    float(),
    list(segment_intersection())
) -> {ok, list(segment_intersection())} | {error, error()}.
line_segment_intersections_from_ts(
    Line_start,
    Line_end,
    Line_is_left,
    Segment,
    Segment_ts,
    Tolerance,
    Intersections
) ->
    case Segment_ts of
        [] ->
            {ok, Intersections};

        [Segment_t | Rest] ->
            case segment_point(Segment, Segment_t) of
                {error, Error} ->
                    {error, Error};

                {ok, Point} ->
                    Line_t = line_projection_t(Point, Line_start, Line_end),
                    case in_unit_range(Line_t, Tolerance) of
                        true ->
                            Intersection = case Line_is_left of
                                true ->
                                    {segment_intersection,
                                        clamp01(Line_t),
                                        clamp01(Segment_t),
                                        Point};

                                false ->
                                    {segment_intersection,
                                        clamp01(Segment_t),
                                        clamp01(Line_t),
                                        Point}
                            end,
                            line_segment_intersections_from_ts(
                                Line_start,
                                Line_end,
                                Line_is_left,
                                Segment,
                                Rest,
                                Tolerance,
                                insert_intersection(
                                    Intersections,
                                    Intersection,
                                    Tolerance
                                )
                            );

                        false ->
                            line_segment_intersections_from_ts(
                                Line_start,
                                Line_end,
                                Line_is_left,
                                Segment,
                                Rest,
                                Tolerance,
                                Intersections
                            )
                    end
            end
    end.

-file("src/svg_path.gleam", 2719).
-spec segment_projection_overlaps_line(
    list(vec@vec2:vec2(float())),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    float()
) -> boolean().
segment_projection_overlaps_line(Points, Line_start, Line_end, Tolerance) ->
    case Points of
        [] ->
            false;

        [First | Rest] ->
            First_t = line_projection_t(First, Line_start, Line_end),
            {Min_t@1, Max_t@1} = gleam@list:fold(
                Rest,
                {First_t, First_t},
                fun(Range, Point) ->
                    {Min_t, Max_t} = Range,
                    T = line_projection_t(Point, Line_start, Line_end),
                    {gleam@float:min(Min_t, T), gleam@float:max(Max_t, T)}
                end
            ),
            (gleam@float:min(1.0, Max_t@1) - gleam@float:max(+0.0, Min_t@1)) > Tolerance
    end.

-file("src/svg_path.gleam", 2742).
-spec segment_defining_points(segment()) -> gleam@option:option(list(vec@vec2:vec2(float()))).
segment_defining_points(Segment) ->
    case Segment of
        {line, Start, End} ->
            {some, [Start, End]};

        {quadratic_bezier, Start@1, Control, End@1} ->
            {some, [Start@1, Control, End@1]};

        {cubic_bezier, Start@2, Control1, Control2, End@2} ->
            {some, [Start@2, Control1, Control2, End@2]};

        {arc, _, _, _, _, _, _} ->
            none
    end.

-file("src/svg_path.gleam", 2691).
-spec segment_lies_on_line(
    segment(),
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    float()
) -> boolean().
segment_lies_on_line(Segment, Line_start, Line_end, Tolerance) ->
    Direction = point_difference(Line_end, Line_start),
    case segment_defining_points(Segment) of
        none ->
            false;

        {some, Points} ->
            gleam@list:all(
                Points,
                fun(Point) ->
                    gleam@float:absolute_value(
                        cross(Direction, point_difference(Point, Line_start))
                    )
                    =< Tolerance
                end
            )
            andalso segment_projection_overlaps_line(
                Points,
                Line_start,
                Line_end,
                Tolerance
            )
    end.

-file("src/svg_path.gleam", 2393).
-spec line_segment_intersections(
    vec@vec2:vec2(float()),
    vec@vec2:vec2(float()),
    boolean(),
    segment(),
    intersection_options()
) -> {ok, list(segment_intersection())} | {error, error()}.
line_segment_intersections(Line_start, Line_end, Line_is_left, Segment, Options) ->
    Line_direction = point_difference(Line_end, Line_start),
    case segment_lies_on_line(
        Segment,
        Line_start,
        Line_end,
        erlang:element(2, Options)
    ) of
        true ->
            {error, overlapping_segments};

        false ->
            case segment_crossings_with(
                Segment,
                fun(Point) ->
                    cross(Line_direction, point_difference(Point, Line_start))
                end,
                {crossing_options,
                    100,
                    erlang:element(2, Options),
                    erlang:element(3, Options) * 4}
            ) of
                {error, Error} ->
                    {error, Error};

                {ok, Segment_ts} ->
                    line_segment_intersections_from_ts(
                        Line_start,
                        Line_end,
                        Line_is_left,
                        Segment,
                        Segment_ts,
                        erlang:element(2, Options),
                        []
                    )
            end
    end.

-file("src/svg_path.gleam", 2238).
-spec validate_intersection_options(intersection_options()) -> {ok, nil} |
    {error, error()}.
validate_intersection_options(Options) ->
    case erlang:element(2, Options) =< +0.0 of
        true ->
            {error,
                {invalid_intersection_tolerance, erlang:element(2, Options)}};

        false ->
            case erlang:element(3, Options) =< 0 of
                true ->
                    {error,
                        {invalid_intersection_max_depth,
                            erlang:element(3, Options)}};

                false ->
                    {ok, nil}
            end
    end.

-file("src/svg_path.gleam", 1294).
?DOC(" Return point intersections between two segments using explicit options.\n").
-spec segment_intersections_with(segment(), segment(), intersection_options()) -> {ok,
        list(segment_intersection())} |
    {error, error()}.
segment_intersections_with(Left, Right, Options) ->
    case validate_intersection_options(Options) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            case {Left, Right} of
                {{line, Left_start, Left_end}, {line, Right_start, Right_end}} ->
                    line_line_intersections(
                        Left_start,
                        Left_end,
                        Right_start,
                        Right_end,
                        erlang:element(2, Options)
                    );

                {{line, Start, End}, _} ->
                    line_segment_intersections(Start, End, true, Right, Options);

                {_, {line, Start@1, End@1}} ->
                    line_segment_intersections(
                        Start@1,
                        End@1,
                        false,
                        Left,
                        Options
                    );

                {_, _} ->
                    curve_curve_intersections(Left, Right, Options)
            end
    end.

-file("src/svg_path.gleam", 1282).
?DOC(
    " Return point intersections between two segments.\n"
    "\n"
    " Overlapping segments return `OverlappingSegments`, since they have more than\n"
    " a finite list of point intersections.\n"
).
-spec segment_intersections(segment(), segment()) -> {ok,
        list(segment_intersection())} |
    {error, error()}.
segment_intersections(Left, Right) ->
    segment_intersections_with(Left, Right, default_intersection_options()).

-file("src/svg_path.gleam", 2879).
-spec combine_boxes(bounding_box(), bounding_box()) -> bounding_box().
combine_boxes(First, Second) ->
    {bounding_box,
        min_point(erlang:element(2, First), erlang:element(2, Second)),
        max_point(erlang:element(3, First), erlang:element(3, Second))}.

-file("src/svg_path.gleam", 2835).
-spec combine_segment_bounding_boxes(list(segment()), bounding_box()) -> {ok,
        bounding_box()} |
    {error, error()}.
combine_segment_bounding_boxes(Segments, Box) ->
    case Segments of
        [] ->
            {ok, Box};

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

                {ok, Next} ->
                    combine_segment_bounding_boxes(
                        Rest,
                        combine_boxes(Box, Next)
                    )
            end
    end.

-file("src/svg_path.gleam", 1336).
?DOC(" Return a non-empty subpath's exact axis-aligned bounding box.\n").
-spec subpath_bounding_box(subpath()) -> {ok, bounding_box()} | {error, error()}.
subpath_bounding_box(Subpath) ->
    case erlang:element(3, Subpath) of
        [] ->
            {error, empty_subpath};

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

                {ok, Box} ->
                    combine_segment_bounding_boxes(Rest, Box)
            end
    end.

-file("src/svg_path.gleam", 2851).
-spec combine_subpath_bounding_boxes(
    list(subpath()),
    gleam@option:option(bounding_box())
) -> {ok, bounding_box()} | {error, error()}.
combine_subpath_bounding_boxes(Subpaths, Box) ->
    case Subpaths of
        [] ->
            case Box of
                none ->
                    {error, empty_subpaths};

                {some, Box@1} ->
                    {ok, Box@1}
            end;

        [First | Rest] ->
            case subpath_bounding_box(First) of
                {error, empty_subpath} ->
                    combine_subpath_bounding_boxes(Rest, Box);

                {error, Error} ->
                    {error, Error};

                {ok, Next} ->
                    Box@3 = case Box of
                        none ->
                            Next;

                        {some, Box@2} ->
                            combine_boxes(Box@2, Next)
                    end,
                    combine_subpath_bounding_boxes(Rest, {some, Box@3})
            end
    end.

-file("src/svg_path.gleam", 1349).
?DOC(" Return the exact axis-aligned bounding box of all non-empty subpaths.\n").
-spec path_bounding_box(path()) -> {ok, bounding_box()} | {error, error()}.
path_bounding_box(Path) ->
    case erlang:element(2, Path) of
        [] ->
            {error, empty_path};

        Subpaths ->
            combine_subpath_bounding_boxes(Subpaths, none)
    end.

-file("src/svg_path.gleam", 1414).
?DOC(
    " Split a segment at parameter `t`, returning an error outside `0.0..1.0`.\n"
    "\n"
    " Values exactly at `0.0` or `1.0` are accepted and produce one zero-length\n"
    " segment.\n"
).
-spec split_segment_inside(segment(), float()) -> {ok, {segment(), segment()}} |
    {error, error()}.
split_segment_inside(Segment, T) ->
    case Segment of
        {line, _, _} ->
            case begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier_inside(_pipe, T)
            end of
                {error, _} ->
                    {error, split_outside_segment};

                {ok, {Left, Right}} ->
                    {ok,
                        {segment_from_bezier_data(Left),
                            segment_from_bezier_data(Right)}}
            end;

        {quadratic_bezier, _, _, _} ->
            case begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier_inside(_pipe, T)
            end of
                {error, _} ->
                    {error, split_outside_segment};

                {ok, {Left, Right}} ->
                    {ok,
                        {segment_from_bezier_data(Left),
                            segment_from_bezier_data(Right)}}
            end;

        {cubic_bezier, _, _, _, _} ->
            case begin
                _pipe = segment_to_bezier_data(Segment),
                svg_path@bezier:split_bezier_inside(_pipe, T)
            end of
                {error, _} ->
                    {error, split_outside_segment};

                {ok, {Left, Right}} ->
                    {ok,
                        {segment_from_bezier_data(Left),
                            segment_from_bezier_data(Right)}}
            end;

        {arc, _, _, _, _, _, _} ->
            case segment_to_center_arc_data(Segment) of
                {error, Error} ->
                    {error, Error};

                {ok, Arc} ->
                    case svg_path@ellipse:split_arc_inside(Arc, T) of
                        {error, _} ->
                            {error, split_outside_segment};

                        {ok, {Left@1, Right@1}} ->
                            {ok,
                                {arc_from_center_data(Left@1),
                                    arc_from_center_data(Right@1)}}
                    end
            end
    end.

-file("src/svg_path.gleam", 1516).
-spec sub_segments_loop(segment(), list(float()), list(segment())) -> {ok,
        list(segment())} |
    {error, error()}.
sub_segments_loop(Segment, Points, Segments) ->
    case Points of
        [] ->
            {ok, lists:reverse(Segments)};

        [_] ->
            {ok, lists:reverse(Segments)};

        [From, To | Rest] ->
            case sub_segment(Segment, From, To) of
                {error, Error} ->
                    {error, Error};

                {ok, Sub_segment} ->
                    sub_segments_loop(
                        Segment,
                        [To | Rest],
                        [Sub_segment | Segments]
                    )
            end
    end.

-file("src/svg_path.gleam", 1495).
?DOC(
    " Return segment portions between adjacent parameters.\n"
    "\n"
    " Parameters are not clamped. Values outside `0.0..1.0` extrapolate along the\n"
    " same segment. Empty and singleton lists return an empty list.\n"
).
-spec sub_segments(segment(), list(float())) -> {ok, list(segment())} |
    {error, error()}.
sub_segments(Segment, Points) ->
    sub_segments_loop(Segment, Points, []).

-file("src/svg_path.gleam", 1533).
-spec all_inside(list(float())) -> boolean().
all_inside(Points) ->
    case Points of
        [] ->
            true;

        [First | Rest] ->
            ((First >= +0.0) andalso (First =< 1.0)) andalso all_inside(Rest)
    end.

-file("src/svg_path.gleam", 1506).
?DOC(
    " Return segment portions between adjacent parameters.\n"
    "\n"
    " All parameters must be inside `0.0..1.0`, inclusive. Empty and singleton\n"
    " lists return an empty list.\n"
).
-spec sub_segments_inside(segment(), list(float())) -> {ok, list(segment())} |
    {error, error()}.
sub_segments_inside(Segment, Points) ->
    case all_inside(Points) of
        false ->
            {error, split_outside_segment};

        true ->
            sub_segments(Segment, Points)
    end.