-module(svg_path@number_format).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/number_format.gleam").
-export([raw_number/2, prepare/2, number/2, code_number/2]).
-export_type([left_padding_style/0, left_decimal_options/0, right_decimal_options/0, options/0, number_format/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(
" Shared numeric formatting helpers.\n"
"\n"
" This module keeps decimal rounding, fixed decimal formatting, and\n"
" left-padding behavior consistent across structural inspection and SVG path\n"
" serialization. Higher-level modules still decide which numbers should be\n"
" included in the lookahead list for automatic padding.\n"
).
-type left_padding_style() :: zero | space.
-type left_decimal_options() :: succinct |
{auto_left_padding, left_padding_style()} |
{left_padding, integer(), left_padding_style()}.
-type right_decimal_options() :: system |
{at_most, integer()} |
{fixed, integer()}.
-type options() :: {options, left_decimal_options(), right_decimal_options()}.
-opaque number_format() :: {number_format,
options(),
gleam@option:option({integer(), left_padding_style()})}.
-file("src/svg_path/number_format.gleam", 155).
-spec left_width(binary()) -> integer().
left_width(Number) ->
case gleam@string:split_once(Number, <<"."/utf8>>) of
{ok, {Whole, _}} ->
string:length(Whole);
{error, _} ->
string:length(Number)
end.
-file("src/svg_path/number_format.gleam", 210).
-spec strip_trailing_zeros(binary()) -> binary().
strip_trailing_zeros(String) ->
case gleam_stdlib:string_ends_with(String, <<"0"/utf8>>) of
true ->
_pipe = String,
_pipe@1 = gleam@string:drop_end(_pipe, 1),
strip_trailing_zeros(_pipe@1);
false ->
String
end.
-file("src/svg_path/number_format.gleam", 196).
-spec strip_trailing_decimal_zeros(binary()) -> binary().
strip_trailing_decimal_zeros(Number) ->
case gleam@string:split_once(Number, <<"."/utf8>>) of
{error, _} ->
Number;
{ok, {Whole, Fractional}} ->
Fractional@1 = strip_trailing_zeros(Fractional),
case Fractional@1 of
<<""/utf8>> ->
Whole;
_ ->
<<<<Whole/binary, "."/utf8>>/binary, Fractional@1/binary>>
end
end.
-file("src/svg_path/number_format.gleam", 225).
-spec power_of_ten_int(integer()) -> integer().
power_of_ten_int(Exponent) ->
case Exponent =< 0 of
true ->
1;
false ->
10 * power_of_ten_int(Exponent - 1)
end.
-file("src/svg_path/number_format.gleam", 221).
-spec power_of_ten(integer()) -> float().
power_of_ten(Exponent) ->
erlang:float(power_of_ten_int(Exponent)).
-file("src/svg_path/number_format.gleam", 171).
-spec fixed_decimal(float(), integer()) -> binary().
fixed_decimal(Number, Decimal_places) ->
Decimal_places@1 = gleam@int:max(Decimal_places, 0),
Scale = power_of_ten(Decimal_places@1),
Scaled = begin
_pipe = Number * Scale,
erlang:round(_pipe)
end,
Sign = case Scaled < 0 of
true ->
<<"-"/utf8>>;
false ->
<<""/utf8>>
end,
Absolute_scaled = gleam@int:absolute_value(Scaled),
case Decimal_places@1 of
0 ->
<<Sign/binary, (erlang:integer_to_binary(Absolute_scaled))/binary>>;
_ ->
Whole = case power_of_ten_int(Decimal_places@1) of
0 -> 0;
Gleam@denominator -> Absolute_scaled div Gleam@denominator
end,
Fractional = case power_of_ten_int(Decimal_places@1) of
0 -> 0;
Gleam@denominator@1 -> Absolute_scaled rem Gleam@denominator@1
end,
Fractional@1 = begin
_pipe@1 = Fractional,
_pipe@2 = erlang:integer_to_binary(_pipe@1),
gleam@string:pad_start(_pipe@2, Decimal_places@1, <<"0"/utf8>>)
end,
<<<<<<Sign/binary, (erlang:integer_to_binary(Whole))/binary>>/binary,
"."/utf8>>/binary,
Fractional@1/binary>>
end.
-file("src/svg_path/number_format.gleam", 162).
-spec decimal(float(), integer(), boolean()) -> binary().
decimal(Number, Decimal_places, Fixed_decimals) ->
Fixed = fixed_decimal(Number, Decimal_places),
case Fixed_decimals of
true ->
Fixed;
false ->
strip_trailing_decimal_zeros(Fixed)
end.
-file("src/svg_path/number_format.gleam", 106).
?DOC(
" Format a number without applying left-padding.\n"
"\n"
" This is useful when deciding which compact SVG command form a number would\n"
" use before padding is added.\n"
).
-spec raw_number(float(), options()) -> binary().
raw_number(Number, Options) ->
case erlang:element(3, Options) of
system ->
_pipe = Number,
_pipe@1 = gleam_stdlib:float_to_string(_pipe),
strip_trailing_decimal_zeros(_pipe@1);
{at_most, Decimal_places} ->
decimal(Number, Decimal_places, false);
{fixed, Decimal_places@1} ->
decimal(Number, Decimal_places@1, true)
end.
-file("src/svg_path/number_format.gleam", 114).
-spec auto_left_padding_width(list(float()), options()) -> integer().
auto_left_padding_width(Numbers, Options) ->
_pipe = Numbers,
_pipe@3 = gleam@list:map(_pipe, fun(Number) -> _pipe@1 = Number,
_pipe@2 = raw_number(_pipe@1, Options),
left_width(_pipe@2) end),
gleam@list:fold(_pipe@3, 0, fun gleam@int:max/2).
-file("src/svg_path/number_format.gleam", 66).
?DOC(" Prepare a formatter, using the supplied numbers to choose automatic padding.\n").
-spec prepare(options(), list(float())) -> number_format().
prepare(Options, Numbers) ->
Left_padding = case erlang:element(2, Options) of
succinct ->
none;
{left_padding, Width, Style} ->
{some, {gleam@int:max(Width, 0), Style}};
{auto_left_padding, Style@1} ->
{some, {auto_left_padding_width(Numbers, Options), Style@1}}
end,
{number_format, Options, Left_padding}.
-file("src/svg_path/number_format.gleam", 145).
-spec zero_pad_whole(binary(), integer()) -> binary().
zero_pad_whole(Whole, Width) ->
case gleam_stdlib:string_starts_with(Whole, <<"-"/utf8>>) of
true ->
Digits = gleam@string:drop_start(Whole, 1),
<<"-"/utf8,
(gleam@string:pad_start(
Digits,
gleam@int:max(Width - 1, 0),
<<"0"/utf8>>
))/binary>>;
false ->
gleam@string:pad_start(Whole, Width, <<"0"/utf8>>)
end.
-file("src/svg_path/number_format.gleam", 127).
-spec pad_left_side(binary(), integer(), left_padding_style()) -> binary().
pad_left_side(Number, Width, Style) ->
{Whole@1, Suffix} = case gleam@string:split_once(Number, <<"."/utf8>>) of
{ok, {Whole, Fractional}} ->
{Whole, <<"."/utf8, Fractional/binary>>};
{error, _} ->
{Number, <<""/utf8>>}
end,
Whole@2 = case Style of
space ->
gleam@string:pad_start(Whole@1, Width, <<" "/utf8>>);
zero ->
zero_pad_whole(Whole@1, Width)
end,
<<Whole@2/binary, Suffix/binary>>.
-file("src/svg_path/number_format.gleam", 120).
-spec left_pad(binary(), number_format()) -> binary().
left_pad(Number, Format) ->
case erlang:element(3, Format) of
none ->
Number;
{some, {Width, Style}} ->
pad_left_side(Number, Width, Style)
end.
-file("src/svg_path/number_format.gleam", 78).
?DOC(" Format a number.\n").
-spec number(float(), number_format()) -> binary().
number(Number, Format) ->
_pipe = Number,
_pipe@1 = raw_number(_pipe, erlang:element(2, Format)),
left_pad(_pipe@1, Format).
-file("src/svg_path/number_format.gleam", 88).
?DOC(
" Format a number for copy-pasteable Gleam code.\n"
"\n"
" Whole numbers are given an explicit `.0` suffix before left-padding is\n"
" applied.\n"
).
-spec code_number(float(), number_format()) -> binary().
code_number(Value, Format) ->
Number = raw_number(Value, erlang:element(2, Format)),
Number@1 = case (gleam_stdlib:contains_string(Number, <<"."/utf8>>) orelse gleam_stdlib:contains_string(
Number,
<<"e"/utf8>>
))
orelse gleam_stdlib:contains_string(Number, <<"E"/utf8>>) of
true ->
Number;
false ->
<<Number/binary, ".0"/utf8>>
end,
left_pad(Number@1, Format).