src/qrkit@render@svg.erl

-module(qrkit@render@svg).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/qrkit/render/svg.gleam").
-export([default_options/0, with_dark_color/2, with_light_color/2, with_background/2, to_string/2, with_module_size/2, with_margin/2]).
-export_type([svg_options/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 renderer for qrkit QR codes.\n").

-opaque svg_options() :: {svg_options,
        integer(),
        integer(),
        binary(),
        binary(),
        boolean()}.

-file("src/qrkit/render/svg.gleam", 19).
?DOC(" Default SVG rendering options.\n").
-spec default_options() -> svg_options().
default_options() ->
    {svg_options, 8, 4, <<"#111111"/utf8>>, <<"#ffffff"/utf8>>, true}.

-file("src/qrkit/render/svg.gleam", 52).
?DOC(" Set the dark module colour.\n").
-spec with_dark_color(svg_options(), binary()) -> svg_options().
with_dark_color(Options, Css_color) ->
    {svg_options, Module_size, Margin, _, Light_color, Background} = Options,
    {svg_options, Module_size, Margin, Css_color, Light_color, Background}.

-file("src/qrkit/render/svg.gleam", 58).
?DOC(" Set the light/background colour.\n").
-spec with_light_color(svg_options(), binary()) -> svg_options().
with_light_color(Options, Css_color) ->
    {svg_options, Module_size, Margin, Dark_color, _, Background} = Options,
    {svg_options, Module_size, Margin, Dark_color, Css_color, Background}.

-file("src/qrkit/render/svg.gleam", 64).
?DOC(" Toggle the background rectangle.\n").
-spec with_background(svg_options(), boolean()) -> svg_options().
with_background(Options, Draw) ->
    {svg_options, Module_size, Margin, Dark_color, Light_color, _} = Options,
    {svg_options, Module_size, Margin, Dark_color, Light_color, Draw}.

-file("src/qrkit/render/svg.gleam", 102).
?DOC(
    " Escape an attribute value to prevent breaking out of the surrounding\n"
    " `\"...\"` quote pair when callers pass user-controlled CSS strings.\n"
).
-spec escape_attribute(binary()) -> binary().
escape_attribute(Value) ->
    _pipe = Value,
    _pipe@1 = gleam@string:replace(_pipe, <<"&"/utf8>>, <<"&amp;"/utf8>>),
    _pipe@2 = gleam@string:replace(_pipe@1, <<"<"/utf8>>, <<"&lt;"/utf8>>),
    _pipe@3 = gleam@string:replace(_pipe@2, <<">"/utf8>>, <<"&gt;"/utf8>>),
    _pipe@4 = gleam@string:replace(_pipe@3, <<"\""/utf8>>, <<"&quot;"/utf8>>),
    gleam@string:replace(_pipe@4, <<"'"/utf8>>, <<"&#39;"/utf8>>).

-file("src/qrkit/render/svg.gleam", 169).
-spec dark_run_length(list(boolean()), integer()) -> integer().
dark_run_length(Row, Count) ->
    case Row of
        [true | Rest] ->
            dark_run_length(Rest, Count + 1);

        _ ->
            Count
    end.

-file("src/qrkit/render/svg.gleam", 128).
-spec row_path(
    list(boolean()),
    integer(),
    integer(),
    integer(),
    integer(),
    list(binary())
) -> binary().
row_path(Row, Row_index, Module_size, Margin, Col_index, Acc) ->
    case Row of
        [] ->
            _pipe = Acc,
            _pipe@1 = lists:reverse(_pipe),
            gleam@string:join(_pipe@1, <<""/utf8>>);

        [true | Rest] ->
            Run = dark_run_length(Rest, 1),
            X = (Col_index + Margin) * Module_size,
            Y = (Row_index + Margin) * Module_size,
            Width = Run * Module_size,
            Path = <<<<<<<<<<<<<<<<<<<<"M"/utf8,
                                                    (erlang:integer_to_binary(X))/binary>>/binary,
                                                " "/utf8>>/binary,
                                            (erlang:integer_to_binary(Y))/binary>>/binary,
                                        "h"/utf8>>/binary,
                                    (erlang:integer_to_binary(Width))/binary>>/binary,
                                "v"/utf8>>/binary,
                            (erlang:integer_to_binary(Module_size))/binary>>/binary,
                        "H"/utf8>>/binary,
                    (erlang:integer_to_binary(X))/binary>>/binary,
                "z"/utf8>>,
            row_path(
                gleam@list:drop(Row, Run),
                Row_index,
                Module_size,
                Margin,
                Col_index + Run,
                [Path | Acc]
            );

        [false | Rest@1] ->
            row_path(Rest@1, Row_index, Module_size, Margin, Col_index + 1, Acc)
    end.

-file("src/qrkit/render/svg.gleam", 111).
-spec path_data(
    list(list(boolean())),
    integer(),
    integer(),
    integer(),
    list(binary())
) -> binary().
path_data(Rows, Module_size, Margin, Row_index, Acc) ->
    case Rows of
        [] ->
            _pipe = Acc,
            _pipe@1 = lists:reverse(_pipe),
            gleam@string:join(_pipe@1, <<""/utf8>>);

        [Row | Rest] ->
            path_data(
                Rest,
                Module_size,
                Margin,
                Row_index + 1,
                [row_path(Row, Row_index, Module_size, Margin, 0, []) | Acc]
            )
    end.

-file("src/qrkit/render/svg.gleam", 70).
?DOC(" Render a QR code as an SVG document string.\n").
-spec to_string(qrkit:qr_code(), svg_options()) -> binary().
to_string(Qr, Options) ->
    {svg_options, Module_size, Margin, Dark_color, Light_color, Background} = Options,
    Total_width = (qrkit:width(Qr) + (Margin * 2)) * Module_size,
    Total_height = (qrkit:height(Qr) + (Margin * 2)) * Module_size,
    Background_tag = case Background of
        true ->
            <<<<<<<<<<<<"<rect width=\""/utf8,
                                    (erlang:integer_to_binary(Total_width))/binary>>/binary,
                                "\" height=\""/utf8>>/binary,
                            (erlang:integer_to_binary(Total_height))/binary>>/binary,
                        "\" fill=\""/utf8>>/binary,
                    (escape_attribute(Light_color))/binary>>/binary,
                "\"/>"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Path_data = path_data(qrkit:rows(Qr), Module_size, Margin, 0, []),
    <<<<<<<<<<<<<<<<<<<<"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 "/utf8,
                                            (erlang:integer_to_binary(
                                                Total_width
                                            ))/binary>>/binary,
                                        " "/utf8>>/binary,
                                    (erlang:integer_to_binary(Total_height))/binary>>/binary,
                                "\" shape-rendering=\"crispEdges\">"/utf8>>/binary,
                            Background_tag/binary>>/binary,
                        "<path fill=\""/utf8>>/binary,
                    (escape_attribute(Dark_color))/binary>>/binary,
                "\" d=\""/utf8>>/binary,
            Path_data/binary>>/binary,
        "\"/></svg>"/utf8>>.

-file("src/qrkit/render/svg.gleam", 176).
-spec positive_or(integer(), integer()) -> integer().
positive_or(Value, Default) ->
    case Value > 0 of
        true ->
            Value;

        false ->
            Default
    end.

-file("src/qrkit/render/svg.gleam", 26).
?DOC(
    " Set the SVG module size.\n"
    "\n"
    " Values less than 1 are normalised to 1 to keep the rendered output usable.\n"
).
-spec with_module_size(svg_options(), integer()) -> svg_options().
with_module_size(Options, Px) ->
    {svg_options, _, Margin, Dark_color, Light_color, Background} = Options,
    {svg_options,
        positive_or(Px, 1),
        Margin,
        Dark_color,
        Light_color,
        Background}.

-file("src/qrkit/render/svg.gleam", 183).
-spec non_negative_or(integer(), integer()) -> integer().
non_negative_or(Value, Default) ->
    case Value >= 0 of
        true ->
            Value;

        false ->
            Default
    end.

-file("src/qrkit/render/svg.gleam", 40).
?DOC(
    " Set the quiet-zone margin in modules.\n"
    "\n"
    " Negative values are normalised to 0.\n"
).
-spec with_margin(svg_options(), integer()) -> svg_options().
with_margin(Options, Modules) ->
    {svg_options, Module_size, _, Dark_color, Light_color, Background} = Options,
    {svg_options,
        Module_size,
        non_negative_or(Modules, 0),
        Dark_color,
        Light_color,
        Background}.