Boxart.Canvas (Boxart v0.3.2)

Copy Markdown View Source

2D character canvas for rendering diagrams.

Uses {col, row} coordinate maps for the grid. Supports direction-based junction merging when box-drawing characters overlap: each cell tracks a direction bitfield, and the correct junction character is derived from the combined directions.

Cells can be protected (node borders) — protected cells only accept junction merges that add a new direction, not plain overwrites.

Summary

Functions

Returns the character-to-directions mapping.

Clamp canvas to a maximum width. Cells beyond max_col are dropped.

Check if a horizontal range of cells on row contains only spaces. Returns true if every cell from col_start to col_end - 1 is empty.

Direction bitfield constant for down.

Direction bitfield constant for left.

Direction bitfield constant for right.

Direction bitfield constant for up.

Returns the direction-to-character mapping.

Draw a horizontal line of a character from col_start to col_end.

Draw a vertical line of a character from row_start to row_end.

Fill a horizontal span with a character from col_start to col_end (exclusive).

Flip the canvas horizontally (for RL direction).

Flip the canvas vertically (for BT direction).

Get the character at a position.

Get the full cell at a position, or a default empty cell.

Get the style key at a position.

Create a new canvas with the given dimensions.

Mark a cell as protected. Protected cells only accept junction merges that add a new direction, not plain overwrites from edge lines.

Check if a cell is protected.

Place a character on the canvas, optionally merging junctions.

Place text with per-segment style keys. Each segment is {text, style_key}.

Place a string of text starting at {col, row}. Each character is placed without junction merging. Wide (CJK) characters advance by 2 columns.

Render canvas to a string, trimming trailing whitespace per line and removing both leading and trailing empty lines.

Render canvas to an ANSI-colored string using the given theme.

Expand the canvas to at least the given dimensions.

Return {char, style_key} pairs for each cell as a list of rows.

Types

coord()

@type coord() :: {col :: integer(), row :: integer()}

style()

@type style() :: String.t()

t()

@type t() :: %Boxart.Canvas{
  cells: %{optional(coord()) => Boxart.Canvas.Cell.t()},
  height: non_neg_integer(),
  width: non_neg_integer()
}

Functions

char_to_directions()

@spec char_to_directions() :: %{required(String.t()) => non_neg_integer()}

Returns the character-to-directions mapping.

clamp_width(canvas, max_col)

@spec clamp_width(t(), pos_integer()) :: t()

Clamp canvas to a maximum width. Cells beyond max_col are dropped.

clear_range?(canvas, row, col_start, col_end)

@spec clear_range?(t(), integer(), integer(), integer()) :: boolean()

Check if a horizontal range of cells on row contains only spaces. Returns true if every cell from col_start to col_end - 1 is empty.

dir_down()

@spec dir_down() :: 2

Direction bitfield constant for down.

dir_left()

@spec dir_left() :: 4

Direction bitfield constant for left.

dir_right()

@spec dir_right() :: 8

Direction bitfield constant for right.

dir_up()

@spec dir_up() :: 1

Direction bitfield constant for up.

direction_to_char()

@spec direction_to_char() :: %{required(non_neg_integer()) => String.t()}

Returns the direction-to-character mapping.

draw_horizontal(canvas, row, col_start, col_end, ch, opts \\ [])

@spec draw_horizontal(t(), integer(), integer(), integer(), String.t(), keyword()) ::
  t()

Draw a horizontal line of a character from col_start to col_end.

draw_vertical(canvas, col, row_start, row_end, ch, opts \\ [])

@spec draw_vertical(t(), integer(), integer(), integer(), String.t(), keyword()) ::
  t()

Draw a vertical line of a character from row_start to row_end.

fill_horizontal(canvas, row, col_start, col_end, ch, opts \\ [])

Fill a horizontal span with a character from col_start to col_end (exclusive).

flip_horizontal(canvas)

@spec flip_horizontal(t()) :: t()

Flip the canvas horizontally (for RL direction).

flip_vertical(canvas)

@spec flip_vertical(t()) :: t()

Flip the canvas vertically (for BT direction).

get(canvas, col, row)

@spec get(t(), integer(), integer()) :: String.t()

Get the character at a position.

get_cell(canvas, col, row)

@spec get_cell(t(), integer(), integer()) :: Boxart.Canvas.Cell.t()

Get the full cell at a position, or a default empty cell.

get_style(canvas, col, row)

@spec get_style(t(), integer(), integer()) :: String.t()

Get the style key at a position.

new(width, height)

@spec new(non_neg_integer(), non_neg_integer()) :: t()

Create a new canvas with the given dimensions.

protect(canvas, col, row)

@spec protect(t(), integer(), integer()) :: t()

Mark a cell as protected. Protected cells only accept junction merges that add a new direction, not plain overwrites from edge lines.

protected?(canvas, col, row)

@spec protected?(t(), integer(), integer()) :: boolean()

Check if a cell is protected.

put(canvas, col, row, ch, opts \\ [])

@spec put(t(), integer(), integer(), String.t(), keyword()) :: t()

Place a character on the canvas, optionally merging junctions.

When merge is true and both the existing cell and the new character have directional information (from box-drawing characters), their direction bits are OR'd together and the correct junction character is derived from the combined bitfield.

Options

  • :merge - whether to merge box-drawing junctions (default: true)
  • :style - style key for the cell (default: "")

put_styled_text(canvas, col, row, segments)

@spec put_styled_text(t(), integer(), integer(), [{String.t(), String.t()}]) :: t()

Place text with per-segment style keys. Each segment is {text, style_key}.

put_text(canvas, col, row, text, opts \\ [])

@spec put_text(t(), integer(), integer(), String.t(), keyword()) :: t()

Place a string of text starting at {col, row}. Each character is placed without junction merging. Wide (CJK) characters advance by 2 columns.

render(canvas)

@spec render(t()) :: String.t()

Render canvas to a string, trimming trailing whitespace per line and removing both leading and trailing empty lines.

render_ansi(canvas, theme)

@spec render_ansi(t(), Boxart.Theme.t()) :: String.t()

Render canvas to an ANSI-colored string using the given theme.

resize(canvas, new_width, new_height)

@spec resize(t(), non_neg_integer(), non_neg_integer()) :: t()

Expand the canvas to at least the given dimensions.

to_styled_pairs(canvas)

@spec to_styled_pairs(t()) :: [[{String.t(), String.t()}]]

Return {char, style_key} pairs for each cell as a list of rows.