-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.