RasterExRatatui.Font behaviour (RasterExRatatui v0.4.0)

Copy Markdown View Source

Behaviour for a monospace bitmap font: a fixed cell size and one bitmap per codepoint.

A panel has no font engine, so every cell symbol is painted from a bitmap. The font decides the size of a cell in pixels (before the raster's integer scale:), which in turn decides how many cells fit on the panel.

Glyph layout

glyph/1 returns a bitstring of exactly width * height bits, row-major from the top-left pixel: the first width bits are the top row, and within a row the most significant bit is the leftmost pixel. A set bit is foreground ("ink"), a clear bit is background.

iex> alias RasterExRatatui.Font
iex> {w, h} = Font.Default6x8.cell_size()
iex> bits = Font.Default6x8.glyph(?T)
iex> bit_size(bits) == w * h
true
iex> Font.Art.render(bits, {w, h}) |> String.split("\n", trim: true) |> Enum.take(2)
["#####.", "..#..."]

Text glyphs usually leave a column and a row blank as inter-cell spacing; box-drawing, block, and braille glyphs fill the whole cell so they join across cell boundaries.

glyph/1 never fails: a codepoint the font does not cover returns a visible placeholder (RasterExRatatui.Font.Generated.hatched/1 is a good one), so unsupported characters show up in renderings instead of silently vanishing. has_glyph?/1 tells the two apart.

Implementing a font

RasterExRatatui.Font.Art parses ASCII art into glyph bitstrings at compile time and RasterExRatatui.Font.Generated derives braille, eighth blocks, and quadrants for any cell size, so a new font is mostly glyph data:

defmodule MyFont do
  @behaviour RasterExRatatui.Font

  alias RasterExRatatui.Font.{Art, Generated}

  @size {8, 12}
  @glyphs Map.merge(Generated.braille(@size), %{?A => Art.parse(@a_art, @size)})
  @missing Generated.hatched(@size)

  @impl true
  def cell_size, do: @size

  @impl true
  def glyph(codepoint), do: Map.get(@glyphs, codepoint, @missing)

  @impl true
  def has_glyph?(codepoint), do: Map.has_key?(@glyphs, codepoint)
end

Summary

Types

A glyph bitmap: width * height bits, row-major, most significant bit leftmost.

t()

A module implementing this behaviour.

Callbacks

Cell size in pixels, as {width, height}.

The bitmap for codepoint, or a placeholder when the font does not cover it.

Whether the font has a real glyph (not the placeholder) for codepoint.

Functions

The codepoint a cell symbol is painted with: its first codepoint, or a space for an empty symbol.

Types

glyph()

@type glyph() :: bitstring()

A glyph bitmap: width * height bits, row-major, most significant bit leftmost.

t()

@type t() :: module()

A module implementing this behaviour.

Callbacks

cell_size()

@callback cell_size() :: {pos_integer(), pos_integer()}

Cell size in pixels, as {width, height}.

glyph(codepoint)

@callback glyph(codepoint :: non_neg_integer()) :: glyph()

The bitmap for codepoint, or a placeholder when the font does not cover it.

has_glyph?(codepoint)

(optional)
@callback has_glyph?(codepoint :: non_neg_integer()) :: boolean()

Whether the font has a real glyph (not the placeholder) for codepoint.

Functions

codepoint(arg1)

@spec codepoint(String.t()) :: non_neg_integer()

The codepoint a cell symbol is painted with: its first codepoint, or a space for an empty symbol.

Cells carry grapheme clusters ("é" may arrive as e plus a combining accent); a bitmap font paints the base character.

Examples

iex> RasterExRatatui.Font.codepoint("A")
?A

iex> RasterExRatatui.Font.codepoint("e\u0301")
?e

iex> RasterExRatatui.Font.codepoint("")
?\s