-module(svg_path@parse).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/parse.gleam").
-export([path/1]).
-export_type([error/0, token/0, state/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(
" SVG path data parser.\n"
"\n"
" This module parses the `d` attribute syntax used by SVG paths. It supports\n"
" comma and whitespace separators, compact signed numbers, relative commands,\n"
" implicit repeated commands, smooth curves, and arcs.\n"
).
-type error() :: {core, svg_path:error()} |
expected_arc_flag |
expected_command |
expected_move |
expected_number |
{invalid_number, binary()} |
{unsupported_command, binary()}.
-type token() :: {command, binary()} | {number, float()}.
-type state() :: {state,
list(svg_path:subpath()),
svg_path:subpath(),
vec@vec2:vec2(float()),
boolean(),
boolean(),
gleam@option:option(vec@vec2:vec2(float())),
gleam@option:option(vec@vec2:vec2(float()))}.
-file("src/svg_path/parse.gleam", 67).
-spec initial_state() -> state().
initial_state() ->
{state,
[],
svg_path:empty_subpath(svg_path:point(+0.0, +0.0)),
svg_path:point(+0.0, +0.0),
false,
false,
none,
none}.
-file("src/svg_path/parse.gleam", 694).
-spec ensure_active(state()) -> {ok, nil} | {error, error()}.
ensure_active(State) ->
case erlang:element(6, State) andalso erlang:element(5, State) of
true ->
{ok, nil};
false ->
{error, expected_move}
end.
-file("src/svg_path/parse.gleam", 653).
-spec clear_curve_controls(state()) -> state().
clear_curve_controls(State) ->
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
none,
none}.
-file("src/svg_path/parse.gleam", 637).
-spec append_segment(state(), svg_path:segment(), vec@vec2:vec2(float())) -> {ok,
state()} |
{error, error()}.
append_segment(State, Segment, End) ->
case svg_path:append_segment(erlang:element(3, State), Segment) of
{error, Error} ->
{error, {core, Error}};
{ok, Subpath} ->
{ok,
begin
_pipe = {state,
erlang:element(2, State),
Subpath,
End,
erlang:element(5, State),
true,
erlang:element(7, State),
erlang:element(8, State)},
clear_curve_controls(_pipe)
end}
end.
-file("src/svg_path/parse.gleam", 626).
-spec append_line_to(state(), vec@vec2:vec2(float())) -> {ok, state()} |
{error, error()}.
append_line_to(State, Target) ->
append_segment(State, {line, erlang:element(4, State), Target}, Target).
-file("src/svg_path/parse.gleam", 713).
-spec offset(vec@vec2:vec2(float()), float(), float()) -> vec@vec2:vec2(float()).
offset(Point, X, Y) ->
svg_path:point(erlang:element(2, Point) + X, erlang:element(3, Point) + Y).
-file("src/svg_path/parse.gleam", 701).
-spec target_point(state(), float(), float(), boolean()) -> vec@vec2:vec2(float()).
target_point(State, X, Y, Relative) ->
case Relative of
true ->
offset(erlang:element(4, State), X, Y);
false ->
svg_path:point(X, Y)
end.
-file("src/svg_path/parse.gleam", 815).
-spec arc_flag(float()) -> {ok, boolean()} | {error, error()}.
arc_flag(Value) ->
case Value of
+0.0 ->
{ok, false};
1.0 ->
{ok, true};
_ ->
{error, expected_arc_flag}
end.
-file("src/svg_path/parse.gleam", 773).
-spec take_arc(list(token())) -> {ok,
{float(),
float(),
float(),
boolean(),
boolean(),
float(),
float(),
list(token())}} |
{error, error()}.
take_arc(Tokens) ->
case Tokens of
[{number, Radius_x},
{number, Radius_y},
{number, X_axis_rotation},
{number, Large_arc},
{number, Sweep},
{number, End_x},
{number, End_y} |
Rest] ->
case arc_flag(Large_arc) of
{error, Error} ->
{error, Error};
{ok, Large_arc@1} ->
case arc_flag(Sweep) of
{error, Error@1} ->
{error, Error@1};
{ok, Sweep@1} ->
{ok,
{Radius_x,
Radius_y,
X_axis_rotation,
Large_arc@1,
Sweep@1,
End_x,
End_y,
Rest}}
end
end;
_ ->
{error, expected_number}
end.
-file("src/svg_path/parse.gleam", 657).
-spec remember_cubic_control(state(), vec@vec2:vec2(float())) -> state().
remember_cubic_control(State, Control) ->
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
{some, Control},
none}.
-file("src/svg_path/parse.gleam", 687).
-spec reflect(vec@vec2:vec2(float()), vec@vec2:vec2(float())) -> vec@vec2:vec2(float()).
reflect(Point, Origin) ->
svg_path:point(
(erlang:element(2, Origin) * 2.0) - erlang:element(2, Point),
(erlang:element(3, Origin) * 2.0) - erlang:element(3, Point)
).
-file("src/svg_path/parse.gleam", 673).
-spec reflected_cubic_control(state()) -> vec@vec2:vec2(float()).
reflected_cubic_control(State) ->
case erlang:element(7, State) of
{some, Control} ->
reflect(Control, erlang:element(4, State));
none ->
erlang:element(4, State)
end.
-file("src/svg_path/parse.gleam", 756).
-spec take_smooth_cubic_bezier(list(token())) -> {ok,
{float(), float(), float(), float(), list(token())}} |
{error, error()}.
take_smooth_cubic_bezier(Tokens) ->
case Tokens of
[{number, Control2_x},
{number, Control2_y},
{number, End_x},
{number, End_y} |
Rest] ->
{ok, {Control2_x, Control2_y, End_x, End_y, Rest}};
_ ->
{error, expected_number}
end.
-file("src/svg_path/parse.gleam", 737).
-spec take_cubic_bezier(list(token())) -> {ok,
{float(), float(), float(), float(), float(), float(), list(token())}} |
{error, error()}.
take_cubic_bezier(Tokens) ->
case Tokens of
[{number, Control1_x},
{number, Control1_y},
{number, Control2_x},
{number, Control2_y},
{number, End_x},
{number, End_y} |
Rest] ->
{ok,
{Control1_x,
Control1_y,
Control2_x,
Control2_y,
End_x,
End_y,
Rest}};
_ ->
{error, expected_number}
end.
-file("src/svg_path/parse.gleam", 665).
-spec remember_quadratic_control(state(), vec@vec2:vec2(float())) -> state().
remember_quadratic_control(State, Control) ->
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
none,
{some, Control}}.
-file("src/svg_path/parse.gleam", 680).
-spec reflected_quadratic_control(state()) -> vec@vec2:vec2(float()).
reflected_quadratic_control(State) ->
case erlang:element(8, State) of
{some, Control} ->
reflect(Control, erlang:element(4, State));
none ->
erlang:element(4, State)
end.
-file("src/svg_path/parse.gleam", 717).
-spec take_pair(list(token())) -> {ok, {float(), float(), list(token())}} |
{error, error()}.
take_pair(Tokens) ->
case Tokens of
[{number, X}, {number, Y} | Rest] ->
{ok, {X, Y, Rest}};
_ ->
{error, expected_number}
end.
-file("src/svg_path/parse.gleam", 726).
-spec take_quadratic_bezier(list(token())) -> {ok,
{float(), float(), float(), float(), list(token())}} |
{error, error()}.
take_quadratic_bezier(Tokens) ->
case Tokens of
[{number, Control_x},
{number, Control_y},
{number, End_x},
{number, End_y} |
Rest] ->
{ok, {Control_x, Control_y, End_x, End_y, Rest}};
_ ->
{error, expected_number}
end.
-file("src/svg_path/parse.gleam", 611).
-spec finish_active_subpath(state()) -> {ok, state()} | {error, error()}.
finish_active_subpath(State) ->
case erlang:element(6, State) of
false ->
{ok, State};
true ->
{ok,
{state,
[erlang:element(3, State) | erlang:element(2, State)],
svg_path:empty_subpath(erlang:element(4, State)),
erlang:element(4, State),
erlang:element(5, State),
false,
erlang:element(7, State),
erlang:element(8, State)}}
end.
-file("src/svg_path/parse.gleam", 604).
-spec finish(state()) -> {ok, svg_path:path()} | {error, error()}.
finish(State) ->
case finish_active_subpath(State) of
{error, Error} ->
{error, Error};
{ok, State@1} ->
{ok, {path, lists:reverse(erlang:element(2, State@1))}}
end.
-file("src/svg_path/parse.gleam", 569).
-spec parse_close(list(token()), state()) -> {ok, svg_path:path()} |
{error, error()}.
parse_close(Tokens, State) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
Start@1 = case svg_path:start(erlang:element(3, State)) of
{ok, Start} -> Start;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"svg_path/parse"/utf8>>,
function => <<"parse_close"/utf8>>,
line => 576,
value => _assert_fail,
start => 15261,
'end' => 15313,
pattern_start => 15272,
pattern_end => 15281})
end,
case svg_path:set_closed_with(
erlang:element(3, State),
true,
bridge
) of
{error, Error@1} ->
{error, {core, Error@1}};
{ok, Subpath} ->
parse_tokens(
Tokens,
{state,
[Subpath | erlang:element(2, State)],
svg_path:empty_subpath(Start@1),
Start@1,
true,
false,
none,
none}
)
end
end.
-file("src/svg_path/parse.gleam", 263).
-spec parse_vertical_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_vertical_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, Y} | Rest] ->
Target = case Relative of
true ->
offset(erlang:element(4, State), +0.0, Y);
false ->
svg_path:point(
erlang:element(2, erlang:element(4, State)),
Y
)
end,
case append_line_to(State, Target) of
{error, Error} ->
{error, Error};
{ok, State@1} ->
parse_vertical_loop(Rest, State@1, Relative, true)
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 252).
-spec parse_vertical(list(token()), state(), boolean()) -> {ok, svg_path:path()} |
{error, error()}.
parse_vertical(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_vertical_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 224).
-spec parse_horizontal_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_horizontal_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, X} | Rest] ->
Target = case Relative of
true ->
offset(erlang:element(4, State), X, +0.0);
false ->
svg_path:point(
X,
erlang:element(3, erlang:element(4, State))
)
end,
case append_line_to(State, Target) of
{error, Error} ->
{error, Error};
{ok, State@1} ->
parse_horizontal_loop(Rest, State@1, Relative, true)
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 213).
-spec parse_horizontal(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_horizontal(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_horizontal_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 522).
-spec parse_arc_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_arc_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_arc(Tokens) of
{error, Error} ->
{error, Error};
{ok,
{Radius_x,
Radius_y,
X_axis_rotation,
Large_arc,
Sweep,
End_x,
End_y,
Rest}} ->
End = target_point(State, End_x, End_y, Relative),
Segment = {arc,
erlang:element(4, State),
svg_path:point(Radius_x, Radius_y),
X_axis_rotation,
Large_arc,
Sweep,
End},
case append_segment(State, Segment, End) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
parse_arc_loop(Rest, State@1, Relative, true)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 511).
-spec parse_arc(list(token()), state(), boolean()) -> {ok, svg_path:path()} |
{error, error()}.
parse_arc(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_arc_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 465).
-spec parse_smooth_cubic_bezier_loop(
list(token()),
state(),
boolean(),
boolean()
) -> {ok, svg_path:path()} | {error, error()}.
parse_smooth_cubic_bezier_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_smooth_cubic_bezier(Tokens) of
{error, Error} ->
{error, Error};
{ok, {Control2_x, Control2_y, End_x, End_y, Rest}} ->
Control1 = reflected_cubic_control(State),
Control2 = target_point(
State,
Control2_x,
Control2_y,
Relative
),
End = target_point(State, End_x, End_y, Relative),
Segment = {cubic_bezier,
erlang:element(4, State),
Control1,
Control2,
End},
case append_segment(State, Segment, End) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
State@2 = remember_cubic_control(State@1, Control2),
parse_smooth_cubic_bezier_loop(
Rest,
State@2,
Relative,
true
)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 453).
-spec parse_smooth_cubic_bezier(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_smooth_cubic_bezier(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_smooth_cubic_bezier_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 412).
-spec parse_cubic_bezier_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_cubic_bezier_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_cubic_bezier(Tokens) of
{error, Error} ->
{error, Error};
{ok,
{Control1_x,
Control1_y,
Control2_x,
Control2_y,
End_x,
End_y,
Rest}} ->
Control1 = target_point(
State,
Control1_x,
Control1_y,
Relative
),
Control2 = target_point(
State,
Control2_x,
Control2_y,
Relative
),
End = target_point(State, End_x, End_y, Relative),
Segment = {cubic_bezier,
erlang:element(4, State),
Control1,
Control2,
End},
case append_segment(State, Segment, End) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
State@2 = remember_cubic_control(State@1, Control2),
parse_cubic_bezier_loop(
Rest,
State@2,
Relative,
true
)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 400).
-spec parse_cubic_bezier(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_cubic_bezier(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_cubic_bezier_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 360).
-spec parse_smooth_quadratic_bezier_loop(
list(token()),
state(),
boolean(),
boolean()
) -> {ok, svg_path:path()} | {error, error()}.
parse_smooth_quadratic_bezier_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_pair(Tokens) of
{error, Error} ->
{error, Error};
{ok, {End_x, End_y, Rest}} ->
Control = reflected_quadratic_control(State),
End = target_point(State, End_x, End_y, Relative),
Segment = {quadratic_bezier,
erlang:element(4, State),
Control,
End},
case append_segment(State, Segment, End) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
State@2 = remember_quadratic_control(
State@1,
Control
),
parse_smooth_quadratic_bezier_loop(
Rest,
State@2,
Relative,
true
)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 343).
-spec parse_smooth_quadratic_bezier(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_smooth_quadratic_bezier(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_smooth_quadratic_bezier_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 303).
-spec parse_quadratic_bezier_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_quadratic_bezier_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_quadratic_bezier(Tokens) of
{error, Error} ->
{error, Error};
{ok, {Control_x, Control_y, End_x, End_y, Rest}} ->
Control = target_point(
State,
Control_x,
Control_y,
Relative
),
End = target_point(State, End_x, End_y, Relative),
Segment = {quadratic_bezier,
erlang:element(4, State),
Control,
End},
case append_segment(State, Segment, End) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
State@2 = remember_quadratic_control(
State@1,
Control
),
parse_quadratic_bezier_loop(
Rest,
State@2,
Relative,
true
)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 291).
-spec parse_quadratic_bezier(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_quadratic_bezier(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_quadratic_bezier_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 185).
-spec parse_line_loop(list(token()), state(), boolean(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_line_loop(Tokens, State, Relative, Parsed_any) ->
case Tokens of
[{number, _} | _] ->
case take_pair(Tokens) of
{error, Error} ->
{error, Error};
{ok, {X, Y, Rest}} ->
case append_line_to(
State,
target_point(State, X, Y, Relative)
) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
parse_line_loop(Rest, State@1, Relative, true)
end
end;
_ ->
case Parsed_any of
true ->
parse_tokens(Tokens, State);
false ->
{error, expected_number}
end
end.
-file("src/svg_path/parse.gleam", 174).
-spec parse_line(list(token()), state(), boolean()) -> {ok, svg_path:path()} |
{error, error()}.
parse_line(Tokens, State, Relative) ->
case ensure_active(State) of
{error, Error} ->
{error, Error};
{ok, nil} ->
parse_line_loop(Tokens, State, Relative, false)
end.
-file("src/svg_path/parse.gleam", 153).
-spec parse_implicit_lines(list(token()), state(), boolean()) -> {ok,
svg_path:path()} |
{error, error()}.
parse_implicit_lines(Tokens, State, Relative) ->
case Tokens of
[{number, _} | _] ->
case take_pair(Tokens) of
{error, Error} ->
{error, Error};
{ok, {X, Y, Rest}} ->
case append_line_to(
State,
target_point(State, X, Y, Relative)
) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
parse_implicit_lines(Rest, State@1, Relative)
end
end;
_ ->
parse_tokens(Tokens, State)
end.
-file("src/svg_path/parse.gleam", 119).
-spec parse_move(list(token()), state(), boolean()) -> {ok, svg_path:path()} |
{error, error()}.
parse_move(Tokens, State, Relative) ->
case take_pair(Tokens) of
{error, Error} ->
{error, Error};
{ok, {X, Y, Rest}} ->
case finish_active_subpath(State) of
{error, Error@1} ->
{error, Error@1};
{ok, State@1} ->
Base = case Relative andalso erlang:element(5, State@1) of
true ->
erlang:element(4, State@1);
false ->
svg_path:point(+0.0, +0.0)
end,
Target = offset(Base, X, Y),
State@2 = {state,
erlang:element(2, State@1),
svg_path:empty_subpath(Target),
Target,
true,
true,
none,
none},
parse_implicit_lines(Rest, State@2, Relative)
end
end.
-file("src/svg_path/parse.gleam", 90).
-spec parse_command(binary(), list(token()), state()) -> {ok, svg_path:path()} |
{error, error()}.
parse_command(Command, Tokens, State) ->
case Command of
<<"M"/utf8>> ->
parse_move(Tokens, State, false);
<<"m"/utf8>> ->
parse_move(Tokens, State, true);
<<"L"/utf8>> ->
parse_line(Tokens, State, false);
<<"l"/utf8>> ->
parse_line(Tokens, State, true);
<<"Q"/utf8>> ->
parse_quadratic_bezier(Tokens, State, false);
<<"q"/utf8>> ->
parse_quadratic_bezier(Tokens, State, true);
<<"T"/utf8>> ->
parse_smooth_quadratic_bezier(Tokens, State, false);
<<"t"/utf8>> ->
parse_smooth_quadratic_bezier(Tokens, State, true);
<<"C"/utf8>> ->
parse_cubic_bezier(Tokens, State, false);
<<"c"/utf8>> ->
parse_cubic_bezier(Tokens, State, true);
<<"S"/utf8>> ->
parse_smooth_cubic_bezier(Tokens, State, false);
<<"s"/utf8>> ->
parse_smooth_cubic_bezier(Tokens, State, true);
<<"A"/utf8>> ->
parse_arc(Tokens, State, false);
<<"a"/utf8>> ->
parse_arc(Tokens, State, true);
<<"H"/utf8>> ->
parse_horizontal(Tokens, State, false);
<<"h"/utf8>> ->
parse_horizontal(Tokens, State, true);
<<"V"/utf8>> ->
parse_vertical(Tokens, State, false);
<<"v"/utf8>> ->
parse_vertical(Tokens, State, true);
<<"Z"/utf8>> ->
parse_close(Tokens, State);
<<"z"/utf8>> ->
parse_close(Tokens, State);
_ ->
{error, {unsupported_command, Command}}
end.
-file("src/svg_path/parse.gleam", 79).
-spec parse_tokens(list(token()), state()) -> {ok, svg_path:path()} |
{error, error()}.
parse_tokens(Tokens, State) ->
case Tokens of
[] ->
finish(State);
[{command, Command} | Rest] ->
parse_command(Command, Rest, State);
[{number, _} | _] ->
{error, expected_command}
end.
-file("src/svg_path/parse.gleam", 988).
-spec strip_leading_plus(binary()) -> binary().
strip_leading_plus(Raw) ->
case gleam_stdlib:string_starts_with(Raw, <<"+"/utf8>>) of
true ->
gleam@string:drop_start(Raw, 1);
false ->
Raw
end.
-file("src/svg_path/parse.gleam", 959).
-spec parse_decimal_number(binary()) -> {ok, float()} | {error, nil}.
parse_decimal_number(Raw) ->
Raw@1 = strip_leading_plus(Raw),
case gleam_stdlib:parse_float(Raw@1) of
{ok, Number} ->
{ok, Number};
{error, _} ->
case gleam_stdlib:parse_int(Raw@1) of
{ok, Number@1} ->
{ok, erlang:float(Number@1)};
{error, _} ->
{error, nil}
end
end.
-file("src/svg_path/parse.gleam", 995).
-spec power_of_ten(integer()) -> float().
power_of_ten(Exponent) ->
case Exponent of
0 ->
1.0;
_ when Exponent > 0 ->
10.0 * power_of_ten(Exponent - 1);
_ ->
power_of_ten(Exponent + 1) / 10.0
end.
-file("src/svg_path/parse.gleam", 973).
-spec parse_exponent_number(binary(), binary()) -> {ok, float()} | {error, nil}.
parse_exponent_number(Mantissa, Exponent) ->
case parse_decimal_number(Mantissa) of
{error, _} ->
{error, nil};
{ok, Mantissa@1} ->
case begin
_pipe = Exponent,
_pipe@1 = strip_leading_plus(_pipe),
gleam_stdlib:parse_int(_pipe@1)
end of
{error, _} ->
{error, nil};
{ok, Exponent@1} ->
{ok, Mantissa@1 * power_of_ten(Exponent@1)}
end
end.
-file("src/svg_path/parse.gleam", 947).
-spec parse_number(binary()) -> {ok, float()} | {error, nil}.
parse_number(Raw) ->
case gleam@string:split_once(Raw, <<"e"/utf8>>) of
{ok, {Mantissa, Exponent}} ->
parse_exponent_number(Mantissa, Exponent);
{error, _} ->
case gleam@string:split_once(Raw, <<"E"/utf8>>) of
{ok, {Mantissa@1, Exponent@1}} ->
parse_exponent_number(Mantissa@1, Exponent@1);
{error, _} ->
parse_decimal_number(Raw)
end
end.
-file("src/svg_path/parse.gleam", 943).
-spec is_digit(binary()) -> boolean().
is_digit(Grapheme) ->
gleam@list:contains(
[<<"0"/utf8>>,
<<"1"/utf8>>,
<<"2"/utf8>>,
<<"3"/utf8>>,
<<"4"/utf8>>,
<<"5"/utf8>>,
<<"6"/utf8>>,
<<"7"/utf8>>,
<<"8"/utf8>>,
<<"9"/utf8>>],
Grapheme
).
-file("src/svg_path/parse.gleam", 863).
-spec read_number(list(binary()), list(binary()), boolean()) -> {binary(),
list(binary())}.
read_number(Graphemes, Number, Previous_was_exponent) ->
case Graphemes of
[] ->
{gleam@string:join(lists:reverse(Number), <<""/utf8>>), []};
[Grapheme | Rest] ->
case is_digit(Grapheme) orelse (Grapheme =:= <<"."/utf8>>) of
true ->
read_number(Rest, [Grapheme | Number], false);
false ->
case (Grapheme =:= <<"e"/utf8>>) orelse (Grapheme =:= <<"E"/utf8>>) of
true ->
read_number(Rest, [Grapheme | Number], true);
false ->
case (Previous_was_exponent orelse gleam@list:is_empty(
Number
))
andalso ((Grapheme =:= <<"+"/utf8>>) orelse (Grapheme
=:= <<"-"/utf8>>)) of
true ->
read_number(
Rest,
[Grapheme | Number],
false
);
false ->
{gleam@string:join(
lists:reverse(Number),
<<""/utf8>>
),
Graphemes}
end
end
end
end.
-file("src/svg_path/parse.gleam", 939).
-spec is_number_start(binary()) -> boolean().
is_number_start(Grapheme) ->
((is_digit(Grapheme) orelse (Grapheme =:= <<"+"/utf8>>)) orelse (Grapheme
=:= <<"-"/utf8>>))
orelse (Grapheme =:= <<"."/utf8>>).
-file("src/svg_path/parse.gleam", 911).
-spec is_command(binary()) -> boolean().
is_command(Grapheme) ->
gleam@list:contains(
[<<"M"/utf8>>,
<<"m"/utf8>>,
<<"L"/utf8>>,
<<"l"/utf8>>,
<<"Q"/utf8>>,
<<"q"/utf8>>,
<<"T"/utf8>>,
<<"t"/utf8>>,
<<"C"/utf8>>,
<<"c"/utf8>>,
<<"S"/utf8>>,
<<"s"/utf8>>,
<<"A"/utf8>>,
<<"a"/utf8>>,
<<"H"/utf8>>,
<<"h"/utf8>>,
<<"V"/utf8>>,
<<"v"/utf8>>,
<<"Z"/utf8>>,
<<"z"/utf8>>],
Grapheme
).
-file("src/svg_path/parse.gleam", 903).
-spec is_separator(binary()) -> boolean().
is_separator(Grapheme) ->
((((Grapheme =:= <<" "/utf8>>) orelse (Grapheme =:= <<"\n"/utf8>>)) orelse (Grapheme
=:= <<"\r"/utf8>>))
orelse (Grapheme =:= <<"\t"/utf8>>))
orelse (Grapheme =:= <<","/utf8>>).
-file("src/svg_path/parse.gleam", 829).
-spec tokenize_loop(list(binary()), list(token())) -> {ok, list(token())} |
{error, error()}.
tokenize_loop(Graphemes, Tokens) ->
case Graphemes of
[] ->
{ok, lists:reverse(Tokens)};
[Grapheme | Rest] ->
case is_separator(Grapheme) of
true ->
tokenize_loop(Rest, Tokens);
false ->
case is_command(Grapheme) of
true ->
tokenize_loop(Rest, [{command, Grapheme} | Tokens]);
false ->
case is_number_start(Grapheme) of
true ->
{Raw, Rest@1} = read_number(
Graphemes,
[],
false
),
case parse_number(Raw) of
{ok, Number} ->
tokenize_loop(
Rest@1,
[{number, Number} | Tokens]
);
{error, _} ->
{error, {invalid_number, Raw}}
end;
false ->
{error, {unsupported_command, Grapheme}}
end
end
end
end.
-file("src/svg_path/parse.gleam", 823).
-spec tokenize(binary()) -> {ok, list(token())} | {error, error()}.
tokenize(Input) ->
_pipe = Input,
_pipe@1 = gleam@string:to_graphemes(_pipe),
tokenize_loop(_pipe@1, []).
-file("src/svg_path/parse.gleam", 60).
?DOC(
" Parse an SVG path data string into a `Path`.\n"
"\n"
" Empty strings parse as an empty path. Move-only subpaths are preserved as\n"
" empty subpaths with start points. Closepath commands mark subpaths as\n"
" closed, inserting a straight line back to the subpath start when needed.\n"
).
-spec path(binary()) -> {ok, svg_path:path()} | {error, error()}.
path(Input) ->
case tokenize(Input) of
{error, Error} ->
{error, Error};
{ok, Tokens} ->
parse_tokens(Tokens, initial_state())
end.