Harlock.Width (harlock v0.2.0)

Copy Markdown View Source

Display-column width of strings and graphemes for terminal rendering.

String.length/1 counts graphemes, not columns. For ASCII the two are the same; for CJK, emoji, and combining marks they aren't. Use this module wherever a width is meant for cursor positioning, padding, or truncation against the terminal grid.

Width rules (Unicode 15.1 East Asian Width + emoji presentation):

  • Control characters → 0
  • Combining marks, variation selectors, ZWJ, zero-width spaces → 0
  • East Asian Wide / Fullwidth → 2
  • Regional indicator pairs (flag emoji) → 2
  • Other emoji → 2 (we use coarse 0x1F000–0x1FAFF ranges; over-claim is safer than under-claim — alignment off by one column beats text bleeding into the next cell)
  • Everything else → 1

The grapheme width is the maximum width of any codepoint in the cluster: combining marks attach at width 0 to a base of 1 or 2, so the cluster width equals the base width. ZWJ sequences (e.g. 👨‍👩‍👧) take the width of any wide codepoint in the cluster (terminals render them as one glyph, which is 2 cells; we accept that as the cluster width).

Summary

Types

Width of a grapheme in terminal cells.

Functions

Pad a string on the left with pad so its display width equals target_cols. Returns the original string unchanged if it's already wider than the target.

Pad a string on the right with pad so its display width equals target_cols. Returns the original string unchanged if it's already wider than the target.

Truncate a string to at most max_cols display columns. A wide grapheme that wouldn't fit entirely in the remaining budget is dropped, not split.

Total display width of a string — the sum of its grapheme widths.

Display width of a single grapheme. Returns 0 for empty input.

Types

cells()

@type cells() :: 0 | 1 | 2

Width of a grapheme in terminal cells.

Functions

pad_leading(str, target_cols, pad \\ " ")

@spec pad_leading(String.t(), non_neg_integer(), String.t()) :: String.t()

Pad a string on the left with pad so its display width equals target_cols. Returns the original string unchanged if it's already wider than the target.

pad_trailing(str, target_cols, pad \\ " ")

@spec pad_trailing(String.t(), non_neg_integer(), String.t()) :: String.t()

Pad a string on the right with pad so its display width equals target_cols. Returns the original string unchanged if it's already wider than the target.

slice(str, max_cols)

@spec slice(String.t(), non_neg_integer()) :: String.t()

Truncate a string to at most max_cols display columns. A wide grapheme that wouldn't fit entirely in the remaining budget is dropped, not split.

string_width(str)

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

Total display width of a string — the sum of its grapheme widths.

width(grapheme)

@spec width(String.t()) :: cells()

Display width of a single grapheme. Returns 0 for empty input.