Measure the width of text in the terminal and build simple layouts!
5.0 kB
112 lines
1# Comparison
2
3Compared to many other libraries in this space, the main focus of this one is
4measuring the real width of things when printed to terminal emulators. This
5library does **not** try to guess what font shaping (especially ligatures) would
6do, and cares **less** about the way things look like in other monospace contexts,
7such as using a modern rendering pipeline (e.g. in a browser) but with a monospace
8font. I encourage you to use your font rendering library directly, or find a way
9to layout things without needing to know the size of your text.
10
11# Unicode version
12
13The data files are taken from the Unicode 16 release (Sep 2024).
14
15# Algorithm
16
17Components (codepoints, control sequences and grapheme clusters) are classified
18into 4 catgories:
19
20- **zero-width:** these do not advance the current column
21- **narrow:** these advance the cursor by one column
22- **wide:** these advance the cursor by two columns
23- **ambiguous:** these advance the cursor either by one or two columns, depending
24 on the `ambiguous_as_wide` option.
25
26Control characters and ansi escape sequences are ignored and reported as zero-width,
27**except**:
28
29 - Newlines (`\n` and `\r\n`) increase the current row, while resetting the column.
30 - Tabs (`\t`) increase the current column to the next tab stop.
31
32For the remaining string, 3 different modes are supported: `wcwidth`, `mode_2027`
33and `mode2027_ext`.
34
35## Default mode (`wcwidth`)
36
37By default, this library tries to match the output of the `wcwidth` function from
38glibc. This function is still widely used by the majority of terminal emulators.
39
40In this mode, the width of a string is the sum of the widths of its individual
41code points. (Extended) grapheme cluster boundaries are ignored.
42
43## (Extended) Mode 2027
44
45_Mode 2027_ is a proposed mode for terminal emulators that applications can
46request. When active and supported, the terminal emulator is supposed to follow
47the [terminal unicode core spec](https://github.com/contour-terminal/terminal-unicode-core).
48This mode is supported by some terminals, and even the default and only
49behaviour in some others.
50
51None of the terminals I tested implement the proposed spec fully, and the exact
52behaviour is subject to ongoing discussion in the freedesktop.org terminal
53working group. Support for this mode should therefore be considered best-effort
54at the moment, especially when support for Brahmic, Arabic, and some east asian
55scripts is required. If you just want your emoji family to be 2 columns wide,
56this mode works well enough right now.
57
58This library does the following:
59
60- Extended grapheme clusters are segmented based on the `pop_grapheme` function.
61 This function uses native target code, and is therefore not guaranteed to match
62 the unicode version of this library (at the time of writing,
63 Erlang uses 15.0, and Node.JS uses 15.1).
64- The width of a grapheme cluster is the _maximum_ width of all codepoints
65 that make it up.
66- If a grapheme cluster contains U+FE0F Variant Selector 16 in addition to
67 another non-zero-width codepoint, its width is wide.
68
69Additionally, in the extended mode, the following rules apply:
70
71- If a grapheme cluster contains two or more non-spacing non-zero-width
72 codepoints, its width is wide. "Spacing" codepoints in this context are all
73 codepoints with a `General_Category` of `Spacing_Mark`.
74
75This rule makes the width reported by this library across Erlang and Nodejs more
76stable (Unicode version differences, see above), as well as closer match the
77behaviour of actual terminals with mode 2027 support. Starting with Unicode 15.1,
78some sequences that ocupy multiple columns are now segmented into single
79grapheme clusters.
80
81
82## Width of a single codepoint
83
84The following codepoints are classified as **zero-with:**
85
86- All codepoints with the `Default_Ignorable_Code_Point` property, **except** for U+115F Hangul Choseong Filler.
87- All codepoints with a `General_Category` of `Control`, `Enclosing_Mark`, `Nonspacing_Mark`, `Paragraph_Separator`, or `Line_Separator`.
88- All codepoints with `Hangul_Syllable_Type` of Vowel or Trailing Jamo.
89- All codepoints marked as `Prepended_Concatenation_Mark`
90- `U+fff9..U+fffb`, interlinear annotation format characters
91- `U+13430..U+13440`, egyptian hieroglyph format characters
92
93The following codepoints are classified as **wide:**
94
95- All codepoints with the `Emoji_Presentation` property set.
96- All codepoints with an `East_Asian_Width` of Wide or Fullwidth.
97
98The following codepoints are classified as **ambiguous:**
99
100- All codepoints with an `East_Asian_Width` of Ambiguous.
101
102All other codepoints are **narrow**.
103
104# Testing
105
106I currently test against VTE (mainly gnome-terminal), Windows Terminal, Kitty,
107and foot for mode 2027 support. I also test on Contour since they originally
108proposed the mode 2027 spec; however, they use a custom Unicode library that I
109don't trust fully.
110
111When reporting a mismatch, please include which terminal and the escaped
112codepoints. Gitlab/Discord sometimes like to strip certain modifiers.