Skip to main content

src/spruce@layout.erl

-module(spruce@layout).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spruce/layout.gleam").
-export([join_vertical/2, join_horizontal/2, place/5]).
-export_type([pos/0, block/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(" ANSI-aware layout helpers for composing multiline text blocks.\n").

-type pos() :: start | center | 'end'.

-type block() :: {block, integer(), integer(), list(binary())}.

-file("src/spruce/layout.gleam", 110).
-spec pad(binary(), integer(), pos()) -> binary().
pad(Text, Width, Pos) ->
    case Pos of
        start ->
            spruce@align:pad_right(Text, Width);

        center ->
            spruce@align:pad_center(Text, Width);

        'end' ->
            spruce@align:pad_left(Text, Width)
    end.

-file("src/spruce/layout.gleam", 138).
-spec max_visual_width(list(binary())) -> integer().
max_visual_width(Lines) ->
    _pipe = Lines,
    _pipe@1 = gleam@list:map(_pipe, fun spruce@align:visual_length/1),
    gleam@list:fold(_pipe@1, 0, fun gleam@int:max/2).

-file("src/spruce/layout.gleam", 131).
-spec prepend_reversed(list(binary()), list(binary())) -> list(binary()).
prepend_reversed(Lines, Acc) ->
    case Lines of
        [] ->
            Acc;

        [Line | Rest] ->
            prepend_reversed(Rest, [Line | Acc])
    end.

-file("src/spruce/layout.gleam", 123).
-spec flatten_lines_loop(list(binary()), list(binary())) -> list(binary()).
flatten_lines_loop(Blocks, Acc) ->
    case Blocks of
        [] ->
            Acc;

        [Block | Rest] ->
            flatten_lines_loop(
                Rest,
                prepend_reversed(gleam@string:split(Block, <<"\n"/utf8>>), Acc)
            )
    end.

-file("src/spruce/layout.gleam", 118).
-spec flatten_lines(list(binary())) -> list(binary()).
flatten_lines(Blocks) ->
    _pipe = flatten_lines_loop(Blocks, []),
    lists:reverse(_pipe).

-file("src/spruce/layout.gleam", 20).
?DOC(" Stack blocks vertically, padding every line to the widest visual width.\n").
-spec join_vertical(pos(), list(binary())) -> binary().
join_vertical(Pos, Blocks) ->
    Lines = flatten_lines(Blocks),
    Width = max_visual_width(Lines),
    _pipe = Lines,
    _pipe@1 = gleam@list:map(_pipe, fun(Line) -> pad(Line, Width, Pos) end),
    gleam@string:join(_pipe@1, <<"\n"/utf8>>).

-file("src/spruce/layout.gleam", 183).
-spec collect_row(list(list(binary())), binary(), list(list(binary()))) -> {ok,
        {binary(), list(list(binary()))}} |
    {error, nil}.
collect_row(Blocks, Row, Tails) ->
    case Blocks of
        [] ->
            {ok, {Row, lists:reverse(Tails)}};

        [[] | _] ->
            {error, nil};

        [[Line | Rest] | Remaining] ->
            collect_row(Remaining, <<Row/binary, Line/binary>>, [Rest | Tails])
    end.

-file("src/spruce/layout.gleam", 173).
-spec join_normalized_blocks(list(list(binary())), list(binary())) -> binary().
join_normalized_blocks(Blocks, Rows) ->
    case collect_row(Blocks, <<""/utf8>>, []) of
        {error, nil} ->
            _pipe = Rows,
            _pipe@1 = lists:reverse(_pipe),
            gleam@string:join(_pipe@1, <<"\n"/utf8>>);

        {ok, {Row, Tails}} ->
            join_normalized_blocks(Tails, [Row | Rows])
    end.

-file("src/spruce/layout.gleam", 162).
-spec repeat_line_loop(binary(), integer(), list(binary())) -> list(binary()).
repeat_line_loop(Line, Times, Acc) ->
    case Times =< 0 of
        true ->
            Acc;

        false ->
            repeat_line_loop(Line, Times - 1, [Line | Acc])
    end.

-file("src/spruce/layout.gleam", 156).
-spec repeat_line(binary(), integer()) -> list(binary()).
repeat_line(Line, Times) ->
    _pipe = repeat_line_loop(Line, Times, []),
    lists:reverse(_pipe).

-file("src/spruce/layout.gleam", 99).
-spec padding_counts(integer(), pos()) -> {integer(), integer()}.
padding_counts(Extra, Pos) ->
    case Pos of
        start ->
            {0, Extra};

        center ->
            Before = Extra div 2,
            {Before, Extra - Before};

        'end' ->
            {Extra, 0}
    end.

-file("src/spruce/layout.gleam", 77).
-spec position_lines(list(binary()), integer(), integer(), integer(), pos()) -> list(binary()).
position_lines(Lines, Content_height, Width, Target_height, Pos) ->
    Extra = Target_height - Content_height,
    case Extra =< 0 of
        true ->
            Lines;

        false ->
            {Before, After} = padding_counts(Extra, Pos),
            Blank = gleam@string:repeat(<<" "/utf8>>, Width),
            _pipe = repeat_line(Blank, Before),
            _pipe@1 = lists:append(_pipe, Lines),
            lists:append(_pipe@1, repeat_line(Blank, After))
    end.

-file("src/spruce/layout.gleam", 70).
-spec normalize_block(block(), integer(), pos()) -> list(binary()).
normalize_block(Block, Target_height, Pos) ->
    Padded_lines = gleam@list:map(
        erlang:element(4, Block),
        fun(Line) -> pad(Line, erlang:element(2, Block), start) end
    ),
    position_lines(
        Padded_lines,
        erlang:element(3, Block),
        erlang:element(2, Block),
        Target_height,
        Pos
    ).

-file("src/spruce/layout.gleam", 148).
-spec max_block_height_loop(list(block()), integer()) -> integer().
max_block_height_loop(Blocks, Acc) ->
    case Blocks of
        [] ->
            Acc;

        [{block, _, Height, _} | Rest] ->
            max_block_height_loop(Rest, gleam@int:max(Acc, Height))
    end.

-file("src/spruce/layout.gleam", 144).
-spec max_block_height(list(block())) -> integer().
max_block_height(Blocks) ->
    max_block_height_loop(Blocks, 0).

-file("src/spruce/layout.gleam", 64).
-spec block_info(binary()) -> block().
block_info(Block) ->
    {Width, _} = spruce@align:size(Block),
    Height = spruce@align:height(Block),
    {block, Width, Height, gleam@string:split(Block, <<"\n"/utf8>>)}.

-file("src/spruce/layout.gleam", 30).
?DOC(" Join blocks horizontally, aligning shorter blocks within the tallest block.\n").
-spec join_horizontal(pos(), list(binary())) -> binary().
join_horizontal(Pos, Blocks) ->
    case Blocks of
        [] ->
            <<""/utf8>>;

        _ ->
            Block_infos = gleam@list:map(Blocks, fun block_info/1),
            Target_height = max_block_height(Block_infos),
            _pipe = Block_infos,
            _pipe@1 = gleam@list:map(
                _pipe,
                fun(Block) -> normalize_block(Block, Target_height, Pos) end
            ),
            join_normalized_blocks(_pipe@1, [])
    end.

-file("src/spruce/layout.gleam", 45).
?DOC(" Place content in a region, preserving content larger than the requested size.\n").
-spec place(integer(), integer(), pos(), pos(), binary()) -> binary().
place(Width, Height, H, V, Content) ->
    {Content_width, Content_height} = spruce@align:size(Content),
    Region_width = gleam@int:max(Width, Content_width),
    Region_height = gleam@int:max(Height, Content_height),
    Lines = begin
        _pipe = Content,
        _pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
        gleam@list:map(_pipe@1, fun(Line) -> pad(Line, Region_width, H) end)
    end,
    _pipe@2 = position_lines(
        Lines,
        Content_height,
        Region_width,
        Region_height,
        V
    ),
    gleam@string:join(_pipe@2, <<"\n"/utf8>>).