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
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
Functions
@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.
@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.
@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.
@spec string_width(String.t()) :: non_neg_integer()
Total display width of a string — the sum of its grapheme widths.
Display width of a single grapheme. Returns 0 for empty input.