-module(svg_path@transform@parse).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/transform/parse.gleam").
-export([attribute/1]).
-export_type([error/0, token/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 transform attribute parser.\n"
"\n"
" This module parses SVG transform lists such as\n"
" `translate(10 20)rotate(30)scale(2)`. Commas are accepted where SVG allows\n"
" separators, and adjacent signed numbers such as `translate(10-20)` are\n"
" handled.\n"
).
-type error() :: expected_close |
expected_open |
expected_transform |
{invalid_argument_count, binary(), integer()} |
{invalid_number, binary()} |
{unexpected_token, binary()} |
{unknown_transform, binary()}.
-type token() :: close | {name, binary()} | {number, float()} | open.
-file("src/svg_path/transform/parse.gleam", 173).
-spec skew_y_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
skew_y_transform(Name, Arguments) ->
case Arguments of
[Degrees] ->
{ok, svg_path@transform:skew_y(Degrees)};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 163).
-spec skew_x_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
skew_x_transform(Name, Arguments) ->
case Arguments of
[Degrees] ->
{ok, svg_path@transform:skew_x(Degrees)};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 142).
-spec rotate_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
rotate_transform(Name, Arguments) ->
case Arguments of
[Degrees] ->
{ok, svg_path@transform:rotate(Degrees)};
[Degrees@1, Cx, Cy] ->
Move_to_origin = svg_path@transform:translate(+0.0 - Cx, +0.0 - Cy),
Rotate = svg_path@transform:rotate(Degrees@1),
Move_back = svg_path@transform:translate(Cx, Cy),
{ok,
begin
_pipe = Move_to_origin,
_pipe@1 = svg_path@transform:chain(_pipe, Rotate),
svg_path@transform:chain(_pipe@1, Move_back)
end};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 131).
-spec scale_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
scale_transform(Name, Arguments) ->
case Arguments of
[Factor] ->
{ok, svg_path@transform:scale(Factor)};
[X, Y] ->
{ok, svg_path@transform:scale_xy(X, Y)};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 120).
-spec translate_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
translate_transform(Name, Arguments) ->
case Arguments of
[X] ->
{ok, svg_path@transform:translate(X, +0.0)};
[X@1, Y] ->
{ok, svg_path@transform:translate(X@1, Y)};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 110).
-spec matrix_transform(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
matrix_transform(Name, Arguments) ->
case Arguments of
[A, B, C, D, E, F] ->
{ok, svg_path@transform:matrix(A, B, C, D, E, F)};
_ ->
{error, {invalid_argument_count, Name, erlang:length(Arguments)}}
end.
-file("src/svg_path/transform/parse.gleam", 95).
-spec transform_from_arguments(binary(), list(float())) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
transform_from_arguments(Name, Arguments) ->
case Name of
<<"matrix"/utf8>> ->
matrix_transform(Name, Arguments);
<<"translate"/utf8>> ->
translate_transform(Name, Arguments);
<<"scale"/utf8>> ->
scale_transform(Name, Arguments);
<<"rotate"/utf8>> ->
rotate_transform(Name, Arguments);
<<"skewX"/utf8>> ->
skew_x_transform(Name, Arguments);
<<"skewY"/utf8>> ->
skew_y_transform(Name, Arguments);
_ ->
{error, {unknown_transform, Name}}
end.
-file("src/svg_path/transform/parse.gleam", 82).
-spec take_arguments(list(token()), list(float())) -> {ok,
{list(float()), list(token())}} |
{error, error()}.
take_arguments(Tokens, Arguments) ->
case Tokens of
[] ->
{error, expected_close};
[close | Rest] ->
{ok, {lists:reverse(Arguments), Rest}};
[{number, Number} | Rest@1] ->
take_arguments(Rest@1, [Number | Arguments]);
[{name, Name} | _] ->
{error, {unexpected_token, Name}};
[open | _] ->
{error, {unexpected_token, <<"("/utf8>>}}
end.
-file("src/svg_path/transform/parse.gleam", 55).
-spec parse_transforms(list(token()), svg_path@transform:matrix()) -> {ok,
svg_path@transform:matrix()} |
{error, error()}.
parse_transforms(Tokens, Accumulated) ->
case Tokens of
[] ->
{ok, Accumulated};
[{name, Name}, open | Rest] ->
case take_arguments(Rest, []) of
{error, Error} ->
{error, Error};
{ok, {Arguments, Rest@1}} ->
case transform_from_arguments(Name, Arguments) of
{error, Error@1} ->
{error, Error@1};
{ok, Next} ->
parse_transforms(
Rest@1,
svg_path@transform:chain(Next, Accumulated)
)
end
end;
[{name, _} | _] ->
{error, expected_open};
[open | _] ->
{error, expected_transform};
[close | _] ->
{error, expected_transform};
[{number, _} | _] ->
{error, expected_transform}
end.
-file("src/svg_path/transform/parse.gleam", 429).
-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/transform/parse.gleam", 397).
-spec normalize_decimal(binary()) -> binary().
normalize_decimal(Raw) ->
case gleam_stdlib:string_starts_with(Raw, <<"."/utf8>>) of
true ->
<<"0"/utf8, Raw/binary>>;
false ->
case gleam_stdlib:string_starts_with(Raw, <<"+."/utf8>>) of
true ->
<<"0"/utf8, (gleam@string:drop_start(Raw, 1))/binary>>;
false ->
case gleam_stdlib:string_starts_with(Raw, <<"-."/utf8>>) of
true ->
<<"-0"/utf8,
(gleam@string:drop_start(Raw, 1))/binary>>;
false ->
strip_leading_plus(Raw)
end
end
end.
-file("src/svg_path/transform/parse.gleam", 383).
-spec parse_decimal_number(binary()) -> {ok, float()} | {error, nil}.
parse_decimal_number(Raw) ->
Raw@1 = normalize_decimal(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/transform/parse.gleam", 436).
-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/transform/parse.gleam", 414).
-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/transform/parse.gleam", 371).
-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/transform/parse.gleam", 367).
-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/transform/parse.gleam", 247).
-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/transform/parse.gleam", 363).
-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/transform/parse.gleam", 303).
-spec is_ascii_letter(binary()) -> boolean().
is_ascii_letter(Grapheme) ->
gleam@list:contains(
[<<"A"/utf8>>,
<<"B"/utf8>>,
<<"C"/utf8>>,
<<"D"/utf8>>,
<<"E"/utf8>>,
<<"F"/utf8>>,
<<"G"/utf8>>,
<<"H"/utf8>>,
<<"I"/utf8>>,
<<"J"/utf8>>,
<<"K"/utf8>>,
<<"L"/utf8>>,
<<"M"/utf8>>,
<<"N"/utf8>>,
<<"O"/utf8>>,
<<"P"/utf8>>,
<<"Q"/utf8>>,
<<"R"/utf8>>,
<<"S"/utf8>>,
<<"T"/utf8>>,
<<"U"/utf8>>,
<<"V"/utf8>>,
<<"W"/utf8>>,
<<"X"/utf8>>,
<<"Y"/utf8>>,
<<"Z"/utf8>>,
<<"a"/utf8>>,
<<"b"/utf8>>,
<<"c"/utf8>>,
<<"d"/utf8>>,
<<"e"/utf8>>,
<<"f"/utf8>>,
<<"g"/utf8>>,
<<"h"/utf8>>,
<<"i"/utf8>>,
<<"j"/utf8>>,
<<"k"/utf8>>,
<<"l"/utf8>>,
<<"m"/utf8>>,
<<"n"/utf8>>,
<<"o"/utf8>>,
<<"p"/utf8>>,
<<"q"/utf8>>,
<<"r"/utf8>>,
<<"s"/utf8>>,
<<"t"/utf8>>,
<<"u"/utf8>>,
<<"v"/utf8>>,
<<"w"/utf8>>,
<<"x"/utf8>>,
<<"y"/utf8>>,
<<"z"/utf8>>],
Grapheme
).
-file("src/svg_path/transform/parse.gleam", 299).
-spec is_name_part(binary()) -> boolean().
is_name_part(Grapheme) ->
is_ascii_letter(Grapheme).
-file("src/svg_path/transform/parse.gleam", 232).
-spec read_name(list(binary()), list(binary())) -> {binary(), list(binary())}.
read_name(Graphemes, Name) ->
case Graphemes of
[] ->
{gleam@string:join(lists:reverse(Name), <<""/utf8>>), []};
[Grapheme | Rest] ->
case is_name_part(Grapheme) of
true ->
read_name(Rest, [Grapheme | Name]);
false ->
{gleam@string:join(lists:reverse(Name), <<""/utf8>>),
Graphemes}
end
end.
-file("src/svg_path/transform/parse.gleam", 295).
-spec is_name_start(binary()) -> boolean().
is_name_start(Grapheme) ->
is_ascii_letter(Grapheme).
-file("src/svg_path/transform/parse.gleam", 287).
-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/transform/parse.gleam", 189).
-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 Grapheme of
<<"("/utf8>> ->
tokenize_loop(Rest, [open | Tokens]);
<<")"/utf8>> ->
tokenize_loop(Rest, [close | Tokens]);
_ ->
case is_name_start(Grapheme) of
true ->
{Name, Rest@1} = read_name(Graphemes, []),
tokenize_loop(
Rest@1,
[{name, Name} | Tokens]
);
false ->
case is_number_start(Grapheme) of
true ->
{Raw, Rest@2} = read_number(
Graphemes,
[],
false
),
case parse_number(Raw) of
{ok, Number} ->
tokenize_loop(
Rest@2,
[{number, Number} |
Tokens]
);
{error, _} ->
{error,
{invalid_number, Raw}}
end;
false ->
{error,
{unexpected_token, Grapheme}}
end
end
end
end
end.
-file("src/svg_path/transform/parse.gleam", 183).
-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/transform/parse.gleam", 48).
?DOC(
" Parse an SVG transform attribute into a matrix.\n"
"\n"
" Empty strings parse as the identity matrix.\n"
).
-spec attribute(binary()) -> {ok, svg_path@transform:matrix()} |
{error, error()}.
attribute(Input) ->
case tokenize(Input) of
{error, Error} ->
{error, Error};
{ok, Tokens} ->
parse_transforms(Tokens, svg_path@transform:identity())
end.