Skip to main content

src/svg_path@ellipse.erl

-module(svg_path@ellipse).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/ellipse.gleam").
-export([endpoint_arc_data/6, center_arc_data/5, ellipse_affine/6, point/2, transformed_axes/3, collapsed_arc_line/7, collapsed_arc_subpath/7, arc_end_angle/1, angle_at/2, split_arc_inside_many/2, arc_to_cubics/6, endpoint_to_center/1, point_at_angle/2, arc_sweep/1, arc_large_arc/1, center_to_endpoint/1, arc_point/2, derivative_at_angle/2, arc_derivative/2, arc_bounding_box/1, split_arc/2, split_arc_inside/2, split_arc_many/2]).
-export_type([point/0, bounding_box/0, endpoint_arc_data/0, center_arc_data/0, cubic/0, affine/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(
    " Lower-level helpers for SVG elliptical arcs.\n"
    "\n"
    " Most users should work with `svg_path.Arc` values through the root module.\n"
    " This module is the more technical layer for users who want the ellipse math\n"
    " behind SVG arcs.\n"
    "\n"
    " SVG path data uses endpoint parameterization for elliptical arcs,\n"
    " represented here by `EndpointArcData`. An `A` command stores the current\n"
    " point, an end point, two radii, an `x_axis_rotation`, a `large_arc` flag,\n"
    " and a `sweep` flag. The radii are the ellipse's semi-axes.\n"
    " `x_axis_rotation` is the angle, in degrees, from the current coordinate\n"
    " system's x-axis to the ellipse's x-axis. The `large_arc` flag selects an\n"
    " arc spanning more than 180 degrees when it is `True`, and an arc spanning\n"
    " at most 180 degrees when it is `False`. The `sweep` flag selects increasing\n"
    " ellipse angles when it is `True`, and decreasing ellipse angles when it is\n"
    " `False`.\n"
    "\n"
    " This endpoint form is compact and fits SVG paths nicely, but it is not the\n"
    " most convenient form for evaluation, splitting, or analysis. SVG's\n"
    " implementation notes also define center parameterization, represented here\n"
    " by `CenterArcData`: an ellipse `center`, a corrected `radius`, the same\n"
    " `x_axis_rotation`, a `start_angle`, and a signed `delta_angle`.\n"
    "\n"
    " `endpoint_to_center` converts `EndpointArcData` into `CenterArcData`. It\n"
    " follows SVG's forgiving radius rules: radii are made\n"
    " positive, and if the requested ellipse is too small to connect the\n"
    " endpoints, both radii are scaled up uniformly until there is exactly one\n"
    " solution. `CenterArcData.radius` is therefore the corrected radius, not\n"
    " necessarily the input radius.\n"
    "\n"
    " Units are intentionally mixed to match their sources. `x_axis_rotation`\n"
    " remains in degrees because SVG path data uses degrees. `start_angle` and\n"
    " `delta_angle` are in radians because they are used with trigonometric\n"
    " functions. `start_angle` is the angle before the ellipse is stretched and\n"
    " rotated. `delta_angle` is the signed angular travel from the start point to\n"
    " the end point.\n"
    "\n"
    " For values returned by `endpoint_to_center`, these invariants hold:\n"
    "\n"
    " - `arc_sweep(arc)` is `True` when `delta_angle >= 0.0`.\n"
    " - `arc_large_arc(arc)` is `True` when `abs(delta_angle) > pi`.\n"
    " - `arc_end_angle(arc) == arc.start_angle + arc.delta_angle`.\n"
    " - `arc_point(arc, at: 0.0)` is the arc start point, modulo floating-point\n"
    "   roundoff.\n"
    " - `arc_point(arc, at: 1.0)` is the arc end point, modulo floating-point\n"
    "   roundoff.\n"
    " - `split_arc(arc, at: t)` preserves `center`, `radius`, and\n"
    "   `x_axis_rotation`, and divides `delta_angle` at angular progress `t`.\n"
    "\n"
    " The public `CenterArcData` constructor is intentionally available for\n"
    " advanced callers. The invariants above are guaranteed for values produced by\n"
    " `endpoint_to_center`; if you construct `CenterArcData` yourself, these\n"
    " helpers will use the values you provide without trying to repair them.\n"
    "\n"
    " Evaluation with `arc_point(arc, at: t)` uses angular progress through\n"
    " `CenterArcData`:\n"
    "\n"
    " ```gleam\n"
    " angle = arc.start_angle +. t *. arc.delta_angle\n"
    " ```\n"
    "\n"
    " This is not arc-length parameterization. Equal `t` steps correspond to\n"
    " equal angle steps in the unstretched ellipse coordinate system, not equal\n"
    " distances along the rendered curve. The `at` value is not clamped; values\n"
    " outside `0.0..1.0` extrapolate along the same ellipse. `split_arc` follows\n"
    " the same unclamped policy; use `split_arc_inside` when outside values should\n"
    " return an error. `split_arc_many` and `split_arc_inside_many` sort their\n"
    " split points, remove exact duplicates, and trim boundary `0.0` or `1.0`\n"
    " split points that would only create zero-length boundary arcs.\n"
).

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

-type bounding_box() :: {bounding_box, point(), point()}.

-type endpoint_arc_data() :: {endpoint_arc_data,
        point(),
        point(),
        float(),
        boolean(),
        boolean(),
        point()}.

-type center_arc_data() :: {center_arc_data,
        point(),
        point(),
        float(),
        float(),
        float()}.

-type cubic() :: {cubic, point(), point(), point(), point()}.

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

-type error() :: degenerate_input_arc |
    not_collapsed_to_line |
    split_outside_arc.

-file("src/svg_path/ellipse.gleam", 119).
?DOC(" Create endpoint arc data.\n").
-spec endpoint_arc_data(
    point(),
    point(),
    float(),
    boolean(),
    boolean(),
    point()
) -> endpoint_arc_data().
endpoint_arc_data(Start, Radius, X_axis_rotation, Large_arc, Sweep, End) ->
    {endpoint_arc_data, Start, Radius, X_axis_rotation, Large_arc, Sweep, End}.

-file("src/svg_path/ellipse.gleam", 133).
?DOC(
    " Create center arc data.\n"
    "\n"
    " This does not normalize or repair the given values.\n"
).
-spec center_arc_data(point(), point(), float(), float(), float()) -> center_arc_data().
center_arc_data(Center, Radius, X_axis_rotation, Start_angle, Delta_angle) ->
    {center_arc_data, Center, Radius, X_axis_rotation, Start_angle, Delta_angle}.

-file("src/svg_path/ellipse.gleam", 169).
?DOC(" Create an affine matrix for ellipse helpers.\n").
-spec ellipse_affine(float(), float(), float(), float(), float(), float()) -> affine().
ellipse_affine(A, B, C, D, E, F) ->
    {affine, A, B, C, D, E, F}.

-file("src/svg_path/ellipse.gleam", 181).
?DOC(" Transform a point by an affine matrix.\n").
-spec point(point(), affine()) -> point().
point(Point, Transform) ->
    {point,
        ((erlang:element(2, Transform) * erlang:element(2, Point)) + (erlang:element(
            4,
            Transform
        )
        * erlang:element(3, Point)))
        + erlang:element(6, Transform),
        ((erlang:element(3, Transform) * erlang:element(2, Point)) + (erlang:element(
            5,
            Transform
        )
        * erlang:element(3, Point)))
        + erlang:element(7, Transform)}.

-file("src/svg_path/ellipse.gleam", 1064).
-spec radians_to_degrees(float()) -> float().
radians_to_degrees(Radians) ->
    case gleam_community@maths:pi() of
        +0.0 -> +0.0;
        -0.0 -> -0.0;
        Gleam@denominator -> Radians * 180.0 / Gleam@denominator
    end.

-file("src/svg_path/ellipse.gleam", 1068).
-spec normalize_axis_rotation(float()) -> float().
normalize_axis_rotation(Degrees) ->
    case Degrees < +0.0 of
        true ->
            normalize_axis_rotation(Degrees + 180.0);

        false ->
            case Degrees >= 180.0 of
                true ->
                    normalize_axis_rotation(Degrees - 180.0);

                false ->
                    Degrees
            end
    end.

-file("src/svg_path/ellipse.gleam", 1080).
-spec square_root(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/ellipse"/utf8>>,
                        function => <<"square_root"/utf8>>,
                        line => 1081,
                        value => _assert_fail,
                        start => 32249,
                        'end' => 32295,
                        pattern_start => 32260,
                        pattern_end => 32268})
    end,
    Root@1.

-file("src/svg_path/ellipse.gleam", 1035).
-spec dot(point(), point()) -> float().
dot(A, B) ->
    (erlang:element(2, A) * erlang:element(2, B)) + (erlang:element(3, A) * erlang:element(
        3,
        B
    )).

-file("src/svg_path/ellipse.gleam", 1056).
-spec scale(point(), float()) -> point().
scale(Point, Factor) ->
    {point,
        erlang:element(2, Point) * Factor,
        erlang:element(3, Point) * Factor}.

-file("src/svg_path/ellipse.gleam", 1043).
-spec length(point()) -> float().
length(Point) ->
    square_root(
        (erlang:element(2, Point) * erlang:element(2, Point)) + (erlang:element(
            3,
            Point
        )
        * erlang:element(3, Point))
    ).

-file("src/svg_path/ellipse.gleam", 1047).
-spec normalize(point()) -> point().
normalize(Point) ->
    Point_length = length(Point),
    case Point_length =< 0.000000001 of
        true ->
            {point, 1.0, +0.0};

        false ->
            scale(Point, case Point_length of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator -> 1.0 / Gleam@denominator
                end)
    end.

-file("src/svg_path/ellipse.gleam", 1008).
-spec eigenvector(float(), float(), float(), float()) -> point().
eigenvector(Sxx, Sxy, Syy, Lambda) ->
    case gleam@float:absolute_value(Sxy) > 0.000000001 of
        true ->
            normalize({point, Sxy, Lambda - Sxx});

        false ->
            case Sxx >= Syy of
                true ->
                    {point, 1.0, +0.0};

                false ->
                    {point, +0.0, 1.0}
            end
    end.

-file("src/svg_path/ellipse.gleam", 965).
-spec extract_axes(point(), point()) -> {ok, {point(), float()}} |
    {error, error()}.
extract_axes(X_axis, Y_axis) ->
    Sxx = (erlang:element(2, X_axis) * erlang:element(2, X_axis)) + (erlang:element(
        2,
        Y_axis
    )
    * erlang:element(2, Y_axis)),
    Sxy = (erlang:element(2, X_axis) * erlang:element(3, X_axis)) + (erlang:element(
        2,
        Y_axis
    )
    * erlang:element(3, Y_axis)),
    Syy = (erlang:element(3, X_axis) * erlang:element(3, X_axis)) + (erlang:element(
        3,
        Y_axis
    )
    * erlang:element(3, Y_axis)),
    Discriminant = square_root(
        ((Sxx - Syy) * (Sxx - Syy)) + ((4.0 * Sxy) * Sxy)
    ),
    Lambda1 = ((Sxx + Syy) + Discriminant) / 2.0,
    Lambda2 = ((Sxx + Syy) - Discriminant) / 2.0,
    case (Lambda1 =< 0.000000001) orelse (Lambda2 =< 0.000000001) of
        true ->
            {error, degenerate_input_arc};

        false ->
            Axis1 = eigenvector(Sxx, Sxy, Syy, Lambda1),
            Axis2 = {point,
                +0.0 - erlang:element(3, Axis1),
                erlang:element(2, Axis1)},
            Choose_axis1 = gleam@float:absolute_value(dot(Axis1, X_axis)) >= gleam@float:absolute_value(
                dot(Axis2, X_axis)
            ),
            case Choose_axis1 of
                true ->
                    {ok,
                        {{point, square_root(Lambda1), square_root(Lambda2)},
                            normalize_axis_rotation(
                                radians_to_degrees(
                                    gleam_community@maths:atan2(
                                        erlang:element(3, Axis1),
                                        erlang:element(2, Axis1)
                                    )
                                )
                            )}};

                false ->
                    {ok,
                        {{point, square_root(Lambda2), square_root(Lambda1)},
                            normalize_axis_rotation(
                                radians_to_degrees(
                                    gleam_community@maths:atan2(
                                        erlang:element(3, Axis2),
                                        erlang:element(2, Axis2)
                                    )
                                )
                            )}}
            end
    end.

-file("src/svg_path/ellipse.gleam", 1028).
-spec linear_point(point(), affine()) -> point().
linear_point(Point, Transform) ->
    {point,
        (erlang:element(2, Transform) * erlang:element(2, Point)) + (erlang:element(
            4,
            Transform
        )
        * erlang:element(3, Point)),
        (erlang:element(3, Transform) * erlang:element(2, Point)) + (erlang:element(
            5,
            Transform
        )
        * erlang:element(3, Point))}.

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

-file("src/svg_path/ellipse.gleam", 943).
-spec arc_axes(point(), float()) -> {ok, {point(), point()}} | {error, error()}.
arc_axes(Radius, X_axis_rotation) ->
    Rx = gleam@float:absolute_value(erlang:element(2, Radius)),
    Ry = gleam@float:absolute_value(erlang:element(3, Radius)),
    case (Rx =< 0.000000001) orelse (Ry =< 0.000000001) of
        true ->
            {error, degenerate_input_arc};

        false ->
            Phi = degrees_to_radians(X_axis_rotation),
            Cos_phi = gleam_community@maths:cos(Phi),
            Sin_phi = gleam_community@maths:sin(Phi),
            {ok,
                {{point, Rx * Cos_phi, Rx * Sin_phi},
                    {point, +0.0 - (Ry * Sin_phi), Ry * Cos_phi}}}
    end.

-file("src/svg_path/ellipse.gleam", 191).
?DOC(
    " Transform an arc's radius and x-axis rotation.\n"
    "\n"
    " Returns the new radius and x-axis rotation for the transformed ellipse.\n"
).
-spec transformed_axes(point(), float(), affine()) -> {ok, {point(), float()}} |
    {error, error()}.
transformed_axes(Radius, X_axis_rotation, Transform) ->
    case arc_axes(Radius, X_axis_rotation) of
        {error, Error} ->
            {error, Error};

        {ok, {X_axis, Y_axis}} ->
            X_axis@1 = linear_point(X_axis, Transform),
            Y_axis@1 = linear_point(Y_axis, Transform),
            extract_axes(X_axis@1, Y_axis@1)
    end.

-file("src/svg_path/ellipse.gleam", 1024).
-spec offset(point(), point(), float()) -> point().
offset(Point, Direction, Distance) ->
    {point,
        erlang:element(2, Point) + (erlang:element(2, Direction) * Distance),
        erlang:element(3, Point) + (erlang:element(3, Direction) * Distance)}.

-file("src/svg_path/ellipse.gleam", 929).
-spec positive_remainder(float()) -> float().
positive_remainder(Angle) ->
    Turn = 2.0 * gleam_community@maths:pi(),
    case Angle < +0.0 of
        true ->
            positive_remainder(Angle + Turn);

        false ->
            case Angle >= Turn of
                true ->
                    positive_remainder(Angle - Turn);

                false ->
                    Angle
            end
    end.

-file("src/svg_path/ellipse.gleam", 916).
-spec angle_in_sweep(float(), float(), float()) -> boolean().
angle_in_sweep(Angle, Start_angle, Delta_angle) ->
    case Delta_angle >= +0.0 of
        true ->
            positive_remainder(Angle - Start_angle) =< (Delta_angle + 0.000000001);

        false ->
            positive_remainder(Start_angle - Angle) =< ((+0.0 - Delta_angle) + 0.000000001)
    end.

-file("src/svg_path/ellipse.gleam", 834).
-spec collapsed_candidate_angles(float(), float(), float(), float()) -> list(float()).
collapsed_candidate_angles(Start_angle, Delta_angle, Alpha, Beta) ->
    End_angle = Start_angle + Delta_angle,
    Maximum_angle = gleam_community@maths:atan2(Beta, Alpha),
    Minimum_angle = Maximum_angle + gleam_community@maths:pi(),
    _pipe = [Minimum_angle, Maximum_angle],
    _pipe@1 = gleam@list:filter(
        _pipe,
        fun(Angle) -> angle_in_sweep(Angle, Start_angle, Delta_angle) end
    ),
    lists:append(_pipe@1, [Start_angle, End_angle]).

-file("src/svg_path/ellipse.gleam", 1039).
-spec cross(point(), point()) -> float().
cross(A, B) ->
    (erlang:element(2, A) * erlang:element(3, B)) - (erlang:element(3, A) * erlang:element(
        2,
        B
    )).

-file("src/svg_path/ellipse.gleam", 810).
-spec collapsed_axis(point(), point()) -> {ok, point()} | {error, error()}.
collapsed_axis(X_axis, Y_axis) ->
    case gleam@float:absolute_value(cross(X_axis, Y_axis)) =< 0.000000001 of
        false ->
            {error, not_collapsed_to_line};

        true ->
            X_length = length(X_axis),
            Y_length = length(Y_axis),
            case (X_length > 0.000000001) orelse (Y_length > 0.000000001) of
                true ->
                    case X_length >= Y_length of
                        true ->
                            {ok, scale(X_axis, case X_length of
                                        +0.0 -> +0.0;
                                        -0.0 -> -0.0;
                                        Gleam@denominator -> 1.0 / Gleam@denominator
                                    end)};

                        false ->
                            {ok, scale(Y_axis, case Y_length of
                                        +0.0 -> +0.0;
                                        -0.0 -> -0.0;
                                        Gleam@denominator@1 -> 1.0 / Gleam@denominator@1
                                    end)}
                    end;

                false ->
                    {error, not_collapsed_to_line}
            end
    end.

-file("src/svg_path/ellipse.gleam", 830).
-spec fully_collapsed(point(), point()) -> boolean().
fully_collapsed(X_axis, Y_axis) ->
    (length(X_axis) =< 0.000000001) andalso (length(Y_axis) =< 0.000000001).

-file("src/svg_path/ellipse.gleam", 1020).
-spec vector_angle(point(), point()) -> float().
vector_angle(A, B) ->
    gleam_community@maths:atan2(cross(A, B), dot(A, B)).

-file("src/svg_path/ellipse.gleam", 787).
-spec swept_delta_angle(point(), point(), boolean()) -> float().
swept_delta_angle(Start_vector, End_vector, Sweep) ->
    Delta_angle = vector_angle(Start_vector, End_vector),
    case Sweep of
        true ->
            case Delta_angle < +0.0 of
                true ->
                    Delta_angle + (2.0 * gleam_community@maths:pi());

                false ->
                    Delta_angle
            end;

        false ->
            case Delta_angle > +0.0 of
                true ->
                    Delta_angle - (2.0 * gleam_community@maths:pi());

                false ->
                    Delta_angle
            end
    end.

-file("src/svg_path/ellipse.gleam", 766).
-spec center_prime(float(), float(), float(), float(), boolean(), boolean()) -> point().
center_prime(Rx, Ry, X1p, Y1p, Large_arc, Sweep) ->
    Numerator = ((((Rx * Rx) * Ry) * Ry) - (((Rx * Rx) * Y1p) * Y1p)) - (((Ry * Ry)
    * X1p)
    * X1p),
    Denominator = (((Rx * Rx) * Y1p) * Y1p) + (((Ry * Ry) * X1p) * X1p),
    Sign = case Large_arc =:= Sweep of
        true ->
            -1.0;

        false ->
            1.0
    end,
    Coefficient = Sign * square_root(gleam@float:max(+0.0, case Denominator of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> Numerator / Gleam@denominator
            end)),
    {point, case Ry of
            +0.0 -> +0.0;
            -0.0 -> -0.0;
            Gleam@denominator@1 -> (Coefficient * Rx) * Y1p / Gleam@denominator@1
        end, +0.0 - (case Rx of
            +0.0 -> +0.0;
            -0.0 -> -0.0;
            Gleam@denominator@2 -> (Coefficient * Ry) * X1p / Gleam@denominator@2
        end)}.

-file("src/svg_path/ellipse.gleam", 711).
-spec do_endpoint_to_center(
    point(),
    point(),
    float(),
    boolean(),
    boolean(),
    point()
) -> {ok, center_arc_data()} | {error, error()}.
do_endpoint_to_center(Start, Radius, X_axis_rotation, Large_arc, Sweep, End) ->
    Rx = gleam@float:absolute_value(erlang:element(2, Radius)),
    Ry = gleam@float:absolute_value(erlang:element(3, Radius)),
    case (Rx =< 0.000000001) orelse (Ry =< 0.000000001) of
        true ->
            {error, degenerate_input_arc};

        false ->
            Phi = degrees_to_radians(X_axis_rotation),
            Cos_phi = gleam_community@maths:cos(Phi),
            Sin_phi = gleam_community@maths:sin(Phi),
            Midpoint = {point,
                (erlang:element(2, Start) + erlang:element(2, End)) / 2.0,
                (erlang:element(3, Start) + erlang:element(3, End)) / 2.0},
            Half_delta = {point,
                (erlang:element(2, Start) - erlang:element(2, End)) / 2.0,
                (erlang:element(3, Start) - erlang:element(3, End)) / 2.0},
            X1p = (Cos_phi * erlang:element(2, Half_delta)) + (Sin_phi * erlang:element(
                3,
                Half_delta
            )),
            Y1p = (+0.0 - (Sin_phi * erlang:element(2, Half_delta))) + (Cos_phi
            * erlang:element(3, Half_delta)),
            Radius_scale = gleam@float:max(1.0, (case (Rx * Rx) of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator -> X1p * X1p / Gleam@denominator
                end) + (case (Ry * Ry) of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@1 -> Y1p * Y1p / Gleam@denominator@1
                end)),
            Scale = square_root(Radius_scale),
            Rx@1 = Rx * Scale,
            Ry@1 = Ry * Scale,
            Center_prime = center_prime(Rx@1, Ry@1, X1p, Y1p, Large_arc, Sweep),
            Center = {point,
                ((Cos_phi * erlang:element(2, Center_prime)) - (Sin_phi * erlang:element(
                    3,
                    Center_prime
                )))
                + erlang:element(2, Midpoint),
                ((Sin_phi * erlang:element(2, Center_prime)) + (Cos_phi * erlang:element(
                    3,
                    Center_prime
                )))
                + erlang:element(3, Midpoint)},
            Start_vector = {point, case Rx@1 of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@2 -> (X1p - erlang:element(
                        2,
                        Center_prime
                    ))
                    / Gleam@denominator@2
                end, case Ry@1 of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@3 -> (Y1p - erlang:element(
                        3,
                        Center_prime
                    ))
                    / Gleam@denominator@3
                end},
            End_vector = {point, case Rx@1 of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@4 -> ((+0.0 - X1p) - erlang:element(
                        2,
                        Center_prime
                    ))
                    / Gleam@denominator@4
                end, case Ry@1 of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@5 -> ((+0.0 - Y1p) - erlang:element(
                        3,
                        Center_prime
                    ))
                    / Gleam@denominator@5
                end},
            Start_angle = vector_angle({point, 1.0, +0.0}, Start_vector),
            Delta_angle = swept_delta_angle(Start_vector, End_vector, Sweep),
            {ok,
                {center_arc_data,
                    Center,
                    {point, Rx@1, Ry@1},
                    X_axis_rotation,
                    Start_angle,
                    Delta_angle}}
    end.

-file("src/svg_path/ellipse.gleam", 211).
?DOC(
    " Convert an arc collapsed by an affine transform into a single line segment.\n"
    "\n"
    " If the collapsed arc's extrema require more than one segment to preserve its\n"
    " out-and-back motion, use `collapsed_arc_subpath`.\n"
).
-spec collapsed_arc_line(
    point(),
    point(),
    float(),
    boolean(),
    boolean(),
    point(),
    affine()
) -> {ok, {point(), point()}} | {error, error()}.
collapsed_arc_line(
    Start,
    Radius,
    X_axis_rotation,
    Large_arc,
    Sweep,
    End,
    Transform
) ->
    case do_endpoint_to_center(
        Start,
        Radius,
        X_axis_rotation,
        Large_arc,
        Sweep,
        End
    ) of
        {error, Error} ->
            {error, Error};

        {ok, Arc} ->
            {X_axis@1, Y_axis@1} = case arc_axes(
                erlang:element(3, Arc),
                erlang:element(4, Arc)
            ) of
                {ok, {X_axis, Y_axis}} -> {X_axis, Y_axis};
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"svg_path/ellipse"/utf8>>,
                                function => <<"collapsed_arc_line"/utf8>>,
                                line => 225,
                                value => _assert_fail,
                                start => 8142,
                                'end' => 8226,
                                pattern_start => 8153,
                                pattern_end => 8174})
            end,
            X_axis@2 = linear_point(X_axis@1, Transform),
            Y_axis@2 = linear_point(Y_axis@1, Transform),
            case fully_collapsed(X_axis@2, Y_axis@2) of
                true ->
                    {ok, {point(Start, Transform), point(End, Transform)}};

                false ->
                    case collapsed_axis(X_axis@2, Y_axis@2) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Axis} ->
                            Center = point(erlang:element(2, Arc), Transform),
                            Alpha = dot(X_axis@2, Axis),
                            Beta = dot(Y_axis@2, Axis),
                            Angles = collapsed_candidate_angles(
                                erlang:element(5, Arc),
                                erlang:element(6, Arc),
                                Alpha,
                                Beta
                            ),
                            Scalars = gleam@list:map(
                                Angles,
                                fun(Angle) ->
                                    (Alpha * gleam_community@maths:cos(Angle)) + (Beta
                                    * gleam_community@maths:sin(Angle))
                                end
                            ),
                            First@1 = case gleam@list:first(Scalars) of
                                {ok, First} -> First;
                                _assert_fail@1 ->
                                    erlang:error(#{gleam_error => let_assert,
                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                file => <<?FILEPATH/utf8>>,
                                                module => <<"svg_path/ellipse"/utf8>>,
                                                function => <<"collapsed_arc_line"/utf8>>,
                                                line => 250,
                                                value => _assert_fail@1,
                                                start => 9120,
                                                'end' => 9162,
                                                pattern_start => 9131,
                                                pattern_end => 9140})
                            end,
                            {Low@1, High@1} = gleam@list:fold(
                                Scalars,
                                {First@1, First@1},
                                fun(Bounds, Scalar) ->
                                    {Low, High} = Bounds,
                                    {gleam@float:min(Low, Scalar),
                                        gleam@float:max(High, Scalar)}
                                end
                            ),
                            {ok,
                                {offset(Center, Axis, Low@1),
                                    offset(Center, Axis, High@1)}}
                    end
            end
    end.

-file("src/svg_path/ellipse.gleam", 887).
-spec insert_angle({float(), float()}, list({float(), float()})) -> list({float(),
    float()}).
insert_angle(Angle, Sorted) ->
    case Sorted of
        [] ->
            [Angle];

        [First | Rest] ->
            {Progress, _} = Angle,
            {First_progress, _} = First,
            case Progress =< First_progress of
                true ->
                    [Angle | Sorted];

                false ->
                    [First | insert_angle(Angle, Rest)]
            end
    end.

-file("src/svg_path/ellipse.gleam", 877).
-spec insert_sort_angles(list({float(), float()}), list({float(), float()})) -> list({float(),
    float()}).
insert_sort_angles(Angles, Sorted) ->
    case Angles of
        [] ->
            Sorted;

        [First | Rest] ->
            insert_sort_angles(Rest, insert_angle(First, Sorted))
    end.

-file("src/svg_path/ellipse.gleam", 905).
-spec angle_progress(float(), float(), float()) -> float().
angle_progress(Angle, Start_angle, Delta_angle) ->
    case Delta_angle >= +0.0 of
        true ->
            positive_remainder(Angle - Start_angle);

        false ->
            positive_remainder(Start_angle - Angle)
    end.

-file("src/svg_path/ellipse.gleam", 849).
-spec collapsed_ordered_angles(float(), float(), float(), float()) -> list(float()).
collapsed_ordered_angles(Start_angle, Delta_angle, Alpha, Beta) ->
    End_angle = Start_angle + Delta_angle,
    Maximum_angle = gleam_community@maths:atan2(Beta, Alpha),
    Minimum_angle = Maximum_angle + gleam_community@maths:pi(),
    Interior_extrema = begin
        _pipe = [Minimum_angle, Maximum_angle],
        _pipe@1 = gleam@list:filter(
            _pipe,
            fun(Angle) ->
                Progress = angle_progress(Angle, Start_angle, Delta_angle),
                (Progress > 0.000000001) andalso (Progress < (gleam@float:absolute_value(
                    Delta_angle
                )
                - 0.000000001))
            end
        ),
        _pipe@2 = gleam@list:map(
            _pipe@1,
            fun(Angle@1) ->
                {angle_progress(Angle@1, Start_angle, Delta_angle), Angle@1}
            end
        ),
        _pipe@3 = insert_sort_angles(_pipe@2, []),
        gleam@list:map(
            _pipe@3,
            fun(Pair) ->
                {_, Angle@2} = Pair,
                Angle@2
            end
        )
    end,
    [Start_angle | lists:append(Interior_extrema, [End_angle])].

-file("src/svg_path/ellipse.gleam", 551).
-spec collapsed_arc_points(
    point(),
    point(),
    float(),
    boolean(),
    boolean(),
    point(),
    affine()
) -> {ok, list(point())} | {error, error()}.
collapsed_arc_points(
    Start,
    Radius,
    X_axis_rotation,
    Large_arc,
    Sweep,
    End,
    Transform
) ->
    case do_endpoint_to_center(
        Start,
        Radius,
        X_axis_rotation,
        Large_arc,
        Sweep,
        End
    ) of
        {error, Error} ->
            {error, Error};

        {ok, Arc} ->
            {X_axis@1, Y_axis@1} = case arc_axes(
                erlang:element(3, Arc),
                erlang:element(4, Arc)
            ) of
                {ok, {X_axis, Y_axis}} -> {X_axis, Y_axis};
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"svg_path/ellipse"/utf8>>,
                                function => <<"collapsed_arc_points"/utf8>>,
                                line => 565,
                                value => _assert_fail,
                                start => 18212,
                                'end' => 18296,
                                pattern_start => 18223,
                                pattern_end => 18244})
            end,
            X_axis@2 = linear_point(X_axis@1, Transform),
            Y_axis@2 = linear_point(Y_axis@1, Transform),
            case fully_collapsed(X_axis@2, Y_axis@2) of
                true ->
                    {ok, [point(Start, Transform), point(End, Transform)]};

                false ->
                    case collapsed_axis(X_axis@2, Y_axis@2) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Axis} ->
                            Center = point(erlang:element(2, Arc), Transform),
                            Alpha = dot(X_axis@2, Axis),
                            Beta = dot(Y_axis@2, Axis),
                            Angles = collapsed_ordered_angles(
                                erlang:element(5, Arc),
                                erlang:element(6, Arc),
                                Alpha,
                                Beta
                            ),
                            {ok,
                                gleam@list:map(
                                    Angles,
                                    fun(Angle) ->
                                        offset(
                                            Center,
                                            Axis,
                                            (Alpha * gleam_community@maths:cos(
                                                Angle
                                            ))
                                            + (Beta * gleam_community@maths:sin(
                                                Angle
                                            ))
                                        )
                                    end
                                )}
                    end
            end
    end.

-file("src/svg_path/ellipse.gleam", 267).
?DOC(" Convert an arc collapsed by an affine transform into a line-based subpath.\n").
-spec collapsed_arc_subpath(
    point(),
    point(),
    float(),
    boolean(),
    boolean(),
    point(),
    affine()
) -> {ok, list(point())} | {error, error()}.
collapsed_arc_subpath(
    Start,
    Radius,
    X_axis_rotation,
    Large_arc,
    Sweep,
    End,
    Transform
) ->
    collapsed_arc_points(
        Start,
        Radius,
        X_axis_rotation,
        Large_arc,
        Sweep,
        End,
        Transform
    ).

-file("src/svg_path/ellipse.gleam", 701).
-spec ellipse_derivative(center_arc_data(), float()) -> point().
ellipse_derivative(Arc, Angle) ->
    Phi = degrees_to_radians(erlang:element(4, Arc)),
    Cos_phi = gleam_community@maths:cos(Phi),
    Sin_phi = gleam_community@maths:sin(Phi),
    X = +0.0 - (erlang:element(2, erlang:element(3, Arc)) * gleam_community@maths:sin(
        Angle
    )),
    Y = erlang:element(3, erlang:element(3, Arc)) * gleam_community@maths:cos(
        Angle
    ),
    {point, (Cos_phi * X) - (Sin_phi * Y), (Sin_phi * X) + (Cos_phi * Y)}.

-file("src/svg_path/ellipse.gleam", 686).
-spec ellipse_point(center_arc_data(), float()) -> point().
ellipse_point(Arc, Angle) ->
    Phi = degrees_to_radians(erlang:element(4, Arc)),
    Cos_phi = gleam_community@maths:cos(Phi),
    Sin_phi = gleam_community@maths:sin(Phi),
    Cos_angle = gleam_community@maths:cos(Angle),
    Sin_angle = gleam_community@maths:sin(Angle),
    X = erlang:element(2, erlang:element(3, Arc)) * Cos_angle,
    Y = erlang:element(3, erlang:element(3, Arc)) * Sin_angle,
    {point,
        (erlang:element(2, erlang:element(2, Arc)) + (Cos_phi * X)) - (Sin_phi * Y),
        (erlang:element(3, erlang:element(2, Arc)) + (Sin_phi * X)) + (Cos_phi * Y)}.

-file("src/svg_path/ellipse.gleam", 446).
?DOC(" Return the arc's end angle in radians.\n").
-spec arc_end_angle(center_arc_data()) -> float().
arc_end_angle(Arc) ->
    erlang:element(5, Arc) + erlang:element(6, Arc).

-file("src/svg_path/ellipse.gleam", 634).
-spec cubic_for_arc(center_arc_data()) -> cubic().
cubic_for_arc(Arc) ->
    Start_angle = erlang:element(5, Arc),
    End_angle = arc_end_angle(Arc),
    Delta = erlang:element(6, Arc),
    Alpha = (4.0 / 3.0) * gleam_community@maths:tan(Delta / 4.0),
    Start = ellipse_point(Arc, Start_angle),
    End = ellipse_point(Arc, End_angle),
    Start_tangent = ellipse_derivative(Arc, Start_angle),
    End_tangent = ellipse_derivative(Arc, End_angle),
    {cubic,
        Start,
        offset(Start, Start_tangent, Alpha),
        offset(End, End_tangent, +0.0 - Alpha),
        End}.

-file("src/svg_path/ellipse.gleam", 619).
-spec cubic_split_progresses_from(float(), float(), list(float())) -> list(float()).
cubic_split_progresses_from(Next, Step, Points) ->
    case Next >= (1.0 - 0.000000001) of
        true ->
            lists:reverse(Points);

        false ->
            cubic_split_progresses_from(Next + Step, Step, [Next | Points])
    end.

-file("src/svg_path/ellipse.gleam", 604).
-spec cubic_split_progresses(center_arc_data()) -> list(float()).
cubic_split_progresses(Arc) ->
    Quarter_turn = gleam_community@maths:pi() / 2.0,
    Delta = gleam@float:absolute_value(erlang:element(6, Arc)),
    case Delta =< (Quarter_turn + 0.000000001) of
        true ->
            [];

        false ->
            cubic_split_progresses_from(case Delta of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator -> Quarter_turn / Gleam@denominator
                end, case Delta of
                    +0.0 -> +0.0;
                    -0.0 -> -0.0;
                    Gleam@denominator@1 -> Quarter_turn / Gleam@denominator@1
                end, [])
    end.

-file("src/svg_path/ellipse.gleam", 441).
?DOC(" Return the angle at `t` using this module's angular-progress parameterization.\n").
-spec angle_at(center_arc_data(), float()) -> float().
angle_at(Arc, T) ->
    erlang:element(5, Arc) + (T * erlang:element(6, Arc)).

-file("src/svg_path/ellipse.gleam", 460).
-spec arc_between(center_arc_data(), float(), float()) -> center_arc_data().
arc_between(Arc, From, To) ->
    {center_arc_data,
        erlang:element(2, Arc),
        erlang:element(3, Arc),
        erlang:element(4, Arc),
        angle_at(Arc, From),
        erlang:element(6, Arc) * (To - From)}.

-file("src/svg_path/ellipse.gleam", 481).
-spec split_arc_between_progresses(
    center_arc_data(),
    float(),
    list(float()),
    list(center_arc_data())
) -> list(center_arc_data()).
split_arc_between_progresses(Arc, Previous, Points, Pieces) ->
    case Points of
        [] ->
            lists:reverse([arc_between(Arc, Previous, 1.0) | Pieces]);

        [Next | Rest] ->
            split_arc_between_progresses(
                Arc,
                Next,
                Rest,
                [arc_between(Arc, Previous, Next) | Pieces]
            )
    end.

-file("src/svg_path/ellipse.gleam", 474).
-spec split_arc_at_progresses(center_arc_data(), list(float())) -> list(center_arc_data()).
split_arc_at_progresses(Arc, Points) ->
    split_arc_between_progresses(Arc, +0.0, Points, []).

-file("src/svg_path/ellipse.gleam", 527).
-spec trim_reversed_end_progress(list(float())) -> list(float()).
trim_reversed_end_progress(Points) ->
    case Points of
        [1.0 | Rest] ->
            trim_reversed_end_progress(Rest);

        _ ->
            Points
    end.

-file("src/svg_path/ellipse.gleam", 520).
-spec trim_end_progress(list(float())) -> list(float()).
trim_end_progress(Points) ->
    _pipe = Points,
    _pipe@1 = lists:reverse(_pipe),
    _pipe@2 = trim_reversed_end_progress(_pipe@1),
    lists:reverse(_pipe@2).

-file("src/svg_path/ellipse.gleam", 513).
-spec trim_start_progress(list(float())) -> list(float()).
trim_start_progress(Points) ->
    case Points of
        [+0.0 | Rest] ->
            trim_start_progress(Rest);

        _ ->
            Points
    end.

-file("src/svg_path/ellipse.gleam", 534).
-spec insert_unique_progress(list(float()), float()) -> list(float()).
insert_unique_progress(Sorted, Point) ->
    case Sorted of
        [] ->
            [Point];

        [First | Rest] ->
            case Point =:= First of
                true ->
                    Sorted;

                false ->
                    case Point =< First of
                        true ->
                            [Point | Sorted];

                        false ->
                            [First | insert_unique_progress(Rest, Point)]
                    end
            end
    end.

-file("src/svg_path/ellipse.gleam", 505).
-spec sort_unique_progresses(list(float())) -> list(float()).
sort_unique_progresses(Points) ->
    case Points of
        [] ->
            [];

        [First | Rest] ->
            _pipe = sort_unique_progresses(Rest),
            insert_unique_progress(_pipe, First)
    end.

-file("src/svg_path/ellipse.gleam", 498).
-spec normalized_progresses(list(float())) -> list(float()).
normalized_progresses(Points) ->
    _pipe = Points,
    _pipe@1 = sort_unique_progresses(_pipe),
    _pipe@2 = trim_start_progress(_pipe@1),
    trim_end_progress(_pipe@2).

-file("src/svg_path/ellipse.gleam", 418).
?DOC(
    " Split an arc at multiple angular progress values, erroring outside `0.0..1.0`.\n"
    "\n"
    " Split points are sorted, exact duplicates are removed, and boundary `0.0`\n"
    " or `1.0` split points are trimmed when they would only create zero-length\n"
    " boundary arcs. Values exactly at `0.0` or `1.0` are accepted.\n"
).
-spec split_arc_inside_many(center_arc_data(), list(float())) -> {ok,
        list(center_arc_data())} |
    {error, error()}.
split_arc_inside_many(Arc, Points) ->
    Points@1 = normalized_progresses(Points),
    case gleam@list:any(Points@1, fun(T) -> (T < +0.0) orelse (T > 1.0) end) of
        true ->
            {error, split_outside_arc};

        false ->
            {ok, split_arc_at_progresses(Arc, Points@1)}
    end.

-file("src/svg_path/ellipse.gleam", 292).
?DOC(
    " Convert an elliptical arc to one or more cubic Bezier curves.\n"
    "\n"
    " The arc is split into chunks of at most a quarter turn. This is the common\n"
    " deterministic SVG arc approximation strategy. This function does not accept\n"
    " a tolerance; use a higher-level helper if you want SVG path segments back.\n"
).
-spec arc_to_cubics(point(), point(), float(), boolean(), boolean(), point()) -> {ok,
        list(cubic())} |
    {error, error()}.
arc_to_cubics(Start, Radius, X_axis_rotation, Large_arc, Sweep, End) ->
    case do_endpoint_to_center(
        Start,
        Radius,
        X_axis_rotation,
        Large_arc,
        Sweep,
        End
    ) of
        {error, Error} ->
            {error, Error};

        {ok, Arc} ->
            case split_arc_inside_many(Arc, cubic_split_progresses(Arc)) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Chunks} ->
                    {ok, gleam@list:map(Chunks, fun cubic_for_arc/1)}
            end
    end.

-file("src/svg_path/ellipse.gleam", 318).
?DOC(
    " Convert endpoint arc data to center parameterization.\n"
    "\n"
    " Radii are corrected according to SVG's implementation notes: negative radii\n"
    " are made positive, and radii that are too small to reach between the\n"
    " endpoints are scaled up uniformly.\n"
).
-spec endpoint_to_center(endpoint_arc_data()) -> {ok, center_arc_data()} |
    {error, error()}.
endpoint_to_center(Data) ->
    do_endpoint_to_center(
        erlang:element(2, Data),
        erlang:element(3, Data),
        erlang:element(4, Data),
        erlang:element(5, Data),
        erlang:element(6, Data),
        erlang:element(7, Data)
    ).

-file("src/svg_path/ellipse.gleam", 431).
?DOC(" Evaluate an arc at a center-parameter angle in radians.\n").
-spec point_at_angle(center_arc_data(), float()) -> point().
point_at_angle(Arc, Angle) ->
    ellipse_point(Arc, Angle).

-file("src/svg_path/ellipse.gleam", 456).
?DOC(" Return whether the arc sweeps through increasing center-parameter angles.\n").
-spec arc_sweep(center_arc_data()) -> boolean().
arc_sweep(Arc) ->
    erlang:element(6, Arc) >= +0.0.

-file("src/svg_path/ellipse.gleam", 451).
?DOC(" Return whether the arc spans more than 180 degrees.\n").
-spec arc_large_arc(center_arc_data()) -> boolean().
arc_large_arc(Arc) ->
    gleam@float:absolute_value(erlang:element(6, Arc)) > gleam_community@maths:pi(
        
    ).

-file("src/svg_path/ellipse.gleam", 335).
?DOC(
    " Convert center arc data back to endpoint arc data.\n"
    "\n"
    " The returned endpoint data uses corrected radii from the center form, and\n"
    " derives `large_arc` and `sweep` from `delta_angle`.\n"
).
-spec center_to_endpoint(center_arc_data()) -> endpoint_arc_data().
center_to_endpoint(Data) ->
    {endpoint_arc_data,
        point_at_angle(Data, erlang:element(5, Data)),
        erlang:element(3, Data),
        erlang:element(4, Data),
        arc_large_arc(Data),
        arc_sweep(Data),
        point_at_angle(Data, arc_end_angle(Data))}.

-file("src/svg_path/ellipse.gleam", 351).
?DOC(
    " Evaluate an arc at angular progress `t`.\n"
    "\n"
    " `t` is not clamped. `0.0` evaluates the start of the arc, `1.0` evaluates\n"
    " the end of the arc, and values outside that range extrapolate along the\n"
    " same ellipse.\n"
).
-spec arc_point(center_arc_data(), float()) -> point().
arc_point(Arc, T) ->
    point_at_angle(Arc, angle_at(Arc, T)).

-file("src/svg_path/ellipse.gleam", 436).
?DOC(" Return the derivative with respect to the center-parameter angle.\n").
-spec derivative_at_angle(center_arc_data(), float()) -> point().
derivative_at_angle(Arc, Angle) ->
    ellipse_derivative(Arc, Angle).

-file("src/svg_path/ellipse.gleam", 360).
?DOC(
    " Return the derivative with respect to angular progress `t`.\n"
    "\n"
    " This is the tangent direction followed from the arc start to the arc end.\n"
    " For the raw derivative with respect to the ellipse angle, use\n"
    " `derivative_at_angle`.\n"
).
-spec arc_derivative(center_arc_data(), float()) -> point().
arc_derivative(Arc, T) ->
    scale(derivative_at_angle(Arc, angle_at(Arc, T)), erlang:element(6, Arc)).

-file("src/svg_path/ellipse.gleam", 679).
-spec include_point(bounding_box(), point()) -> bounding_box().
include_point(Box, Point) ->
    {bounding_box,
        {point,
            gleam@float:min(
                erlang:element(2, erlang:element(2, Box)),
                erlang:element(2, Point)
            ),
            gleam@float:min(
                erlang:element(3, erlang:element(2, Box)),
                erlang:element(3, Point)
            )},
        {point,
            gleam@float:max(
                erlang:element(2, erlang:element(3, Box)),
                erlang:element(2, Point)
            ),
            gleam@float:max(
                erlang:element(3, erlang:element(3, Box)),
                erlang:element(3, Point)
            )}}.

-file("src/svg_path/ellipse.gleam", 671).
-spec start_angle_extremum(float(), float()) -> float().
start_angle_extremum(Alpha, Beta) ->
    gleam_community@maths:atan2(Beta, Alpha).

-file("src/svg_path/ellipse.gleam", 675).
-spec opposite_angle_extremum(float(), float()) -> float().
opposite_angle_extremum(Alpha, Beta) ->
    start_angle_extremum(Alpha, Beta) + gleam_community@maths:pi().

-file("src/svg_path/ellipse.gleam", 652).
-spec arc_bounding_box_candidate_angles(center_arc_data()) -> list(float()).
arc_bounding_box_candidate_angles(Arc) ->
    Phi = degrees_to_radians(erlang:element(4, Arc)),
    X_alpha = erlang:element(2, erlang:element(3, Arc)) * gleam_community@maths:cos(
        Phi
    ),
    X_beta = +0.0 - (erlang:element(3, erlang:element(3, Arc)) * gleam_community@maths:sin(
        Phi
    )),
    Y_alpha = erlang:element(2, erlang:element(3, Arc)) * gleam_community@maths:sin(
        Phi
    ),
    Y_beta = erlang:element(3, erlang:element(3, Arc)) * gleam_community@maths:cos(
        Phi
    ),
    _pipe = [start_angle_extremum(X_alpha, X_beta),
        opposite_angle_extremum(X_alpha, X_beta),
        start_angle_extremum(Y_alpha, Y_beta),
        opposite_angle_extremum(Y_alpha, Y_beta)],
    _pipe@1 = gleam@list:filter(
        _pipe,
        fun(Angle) ->
            angle_in_sweep(
                Angle,
                erlang:element(5, Arc),
                erlang:element(6, Arc)
            )
        end
    ),
    lists:append(_pipe@1, [erlang:element(5, Arc), arc_end_angle(Arc)]).

-file("src/svg_path/ellipse.gleam", 365).
?DOC(" Return the arc's exact axis-aligned bounding box.\n").
-spec arc_bounding_box(center_arc_data()) -> bounding_box().
arc_bounding_box(Arc) ->
    Points = begin
        _pipe = arc_bounding_box_candidate_angles(Arc),
        gleam@list:map(_pipe, fun(Angle) -> point_at_angle(Arc, Angle) end)
    end,
    {First@1, Rest@1} = case Points of
        [First | Rest] -> {First, Rest};
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"svg_path/ellipse"/utf8>>,
                        function => <<"arc_bounding_box"/utf8>>,
                        line => 369,
                        value => _assert_fail,
                        start => 12794,
                        'end' => 12829,
                        pattern_start => 12805,
                        pattern_end => 12820})
    end,
    _pipe@1 = Rest@1,
    gleam@list:fold(
        _pipe@1,
        {bounding_box, First@1, First@1},
        fun include_point/2
    ).

-file("src/svg_path/ellipse.gleam", 379).
?DOC(
    " Split an arc at angular progress `t`.\n"
    "\n"
    " `t` is not clamped. Values outside `0.0..1.0` extrapolate along the same\n"
    " ellipse, matching `arc_point`.\n"
).
-spec split_arc(center_arc_data(), float()) -> {center_arc_data(),
    center_arc_data()}.
split_arc(Arc, T) ->
    {arc_between(Arc, +0.0, T), arc_between(Arc, T, 1.0)}.

-file("src/svg_path/ellipse.gleam", 390).
?DOC(
    " Split an arc at angular progress `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"
    " arc.\n"
).
-spec split_arc_inside(center_arc_data(), float()) -> {ok,
        {center_arc_data(), center_arc_data()}} |
    {error, error()}.
split_arc_inside(Arc, T) ->
    case (T < +0.0) orelse (T > 1.0) of
        true ->
            {error, split_outside_arc};

        false ->
            {ok, split_arc(Arc, T)}
    end.

-file("src/svg_path/ellipse.gleam", 406).
?DOC(
    " Split an arc at multiple angular progress values.\n"
    "\n"
    " Split points are sorted, exact duplicates are removed, and boundary `0.0`\n"
    " or `1.0` split points are trimmed when they would only create zero-length\n"
    " boundary arcs. Values outside `0.0..1.0` are allowed and extrapolate along\n"
    " the same ellipse, matching `split_arc`.\n"
).
-spec split_arc_many(center_arc_data(), list(float())) -> list(center_arc_data()).
split_arc_many(Arc, Points) ->
    split_arc_at_progresses(Arc, normalized_progresses(Points)).