Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/term/width.rs

6.5 KiB, 1 run

created by r1870400018:20813, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The number of cells a character occupies on a terminal screen.
2//!
3//! A terminal grid is addressed in cells, not in characters, so before a character can be placed
4//! the model must know how many cells it will take. Three answers are possible: none, for a
5//! combining mark or a format control that hangs off the character before it; one, for the great
6//! majority; and two, for the East Asian wide and fullwidth forms and for most emoji.
7//!
8//! The ranges below are a hand maintained condensation of the Unicode `EastAsianWidth.txt`
9//! property values `W` and `F`, together with the common zero width blocks. They are deliberately
10//! kept small and self contained. The generated Unicode tables in `oxedyne_fe2o3_text::unicode`
11//! are the better long term home for this data, since its generator already downloads
12//! `EastAsianWidth.txt` in order to build the line breaking table; see the note in the module
13//! documentation of [`super`].
14
15/// How many cells a character occupies.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum CharWidth {
18 /// A combining mark or format control, which attaches to the preceding cell.
19 Zero,
20 /// One cell, the common case.
21 Narrow,
22 /// Two cells, for East Asian wide and fullwidth forms and most emoji.
23 Wide,
24}
25
26impl CharWidth {
27 /// The width as a cell count.
28 pub fn cells(&self) -> usize {
29 match self {
30 Self::Zero => 0,
31 Self::Narrow => 1,
32 Self::Wide => 2,
33 }
34 }
35}
36
37/// Returns the number of cells `c` occupies.
38///
39/// Control characters answer [`CharWidth::Zero`]; they never reach the grid, since the parser
40/// intercepts them, but a caller measuring an arbitrary string should not be told they are wide.
41pub fn char_width(c: char) -> CharWidth {
42 let u = c as u32;
43 if u < 0x20 || (0x7F..0xA0).contains(&u) {
44 return CharWidth::Zero;
45 }
46 if in_ranges(u, ZERO_WIDTH) {
47 return CharWidth::Zero;
48 }
49 if in_ranges(u, WIDE) {
50 return CharWidth::Wide;
51 }
52 CharWidth::Narrow
53}
54
55/// Returns the total cell width of a string.
56pub fn str_width(s: &str) -> usize {
57 let mut n = 0;
58 for c in s.chars() {
59 n += char_width(c).cells();
60 }
61 n
62}
63
64/// Binary searches a sorted, non overlapping range table.
65fn in_ranges(u: u32, table: &[(u32, u32)]) -> bool {
66 let mut lo = 0;
67 let mut hi = table.len();
68 while lo < hi {
69 let mid = (lo + hi) / 2;
70 let (a, b) = table[mid];
71 if u < a {
72 hi = mid;
73 } else if u > b {
74 lo = mid + 1;
75 } else {
76 return true;
77 }
78 }
79 false
80}
81
82/// Combining marks, variation selectors and the zero width format controls.
83static ZERO_WIDTH: &[(u32, u32)] = &[
84 (0x0300, 0x036F), // Combining diacritical marks
85 (0x0483, 0x0489), // Cyrillic
86 (0x0591, 0x05BD), // Hebrew points
87 (0x05BF, 0x05BF),
88 (0x05C1, 0x05C2),
89 (0x05C4, 0x05C5),
90 (0x05C7, 0x05C7),
91 (0x0610, 0x061A), // Arabic
92 (0x064B, 0x065F),
93 (0x0670, 0x0670),
94 (0x06D6, 0x06DC),
95 (0x06DF, 0x06E4),
96 (0x06E7, 0x06E8),
97 (0x06EA, 0x06ED),
98 (0x0711, 0x0711), // Syriac
99 (0x0730, 0x074A),
100 (0x07A6, 0x07B0), // Thaana
101 (0x07EB, 0x07F3),
102 (0x0816, 0x0819), // Samaritan
103 (0x081B, 0x0823),
104 (0x0825, 0x0827),
105 (0x0829, 0x082D),
106 (0x0859, 0x085B), // Mandaic
107 (0x08D3, 0x08E1), // Arabic extended
108 (0x08E3, 0x0902),
109 (0x093A, 0x093A), // Devanagari
110 (0x093C, 0x093C),
111 (0x0941, 0x0948),
112 (0x094D, 0x094D),
113 (0x0951, 0x0957),
114 (0x0962, 0x0963),
115 (0x0E31, 0x0E31), // Thai
116 (0x0E34, 0x0E3A),
117 (0x0E47, 0x0E4E),
118 (0x1AB0, 0x1AFF), // Combining diacritical marks extended
119 (0x1DC0, 0x1DFF), // Combining diacritical marks supplement
120 (0x200B, 0x200F), // Zero width space, joiners, directional marks
121 (0x202A, 0x202E), // Directional embedding and override
122 (0x2060, 0x2064), // Word joiner and invisible operators
123 (0x206A, 0x206F),
124 (0x20D0, 0x20F0), // Combining marks for symbols
125 (0xFE00, 0xFE0F), // Variation selectors
126 (0xFE20, 0xFE2F), // Combining half marks
127 (0xFEFF, 0xFEFF), // Zero width no break space
128 (0xFFF9, 0xFFFB), // Interlinear annotation
129 (0xE0100, 0xE01EF), // Variation selectors supplement
130];
131
132/// East Asian wide and fullwidth forms, and the emoji that render two cells wide.
133static WIDE: &[(u32, u32)] = &[
134 (0x1100, 0x115F), // Hangul jamo initial
135 (0x231A, 0x231B),
136 (0x2329, 0x232A),
137 (0x23E9, 0x23EC),
138 (0x23F0, 0x23F0),
139 (0x23F3, 0x23F3),
140 (0x25FD, 0x25FE),
141 (0x2614, 0x2615),
142 (0x2648, 0x2653),
143 (0x267F, 0x267F),
144 (0x2693, 0x2693),
145 (0x26A1, 0x26A1),
146 (0x26AA, 0x26AB),
147 (0x26BD, 0x26BE),
148 (0x26C4, 0x26C5),
149 (0x26CE, 0x26CE),
150 (0x26D4, 0x26D4),
151 (0x26EA, 0x26EA),
152 (0x26F2, 0x26F3),
153 (0x26F5, 0x26F5),
154 (0x26FA, 0x26FA),
155 (0x26FD, 0x26FD),
156 (0x2705, 0x2705),
157 (0x270A, 0x270B),
158 (0x2728, 0x2728),
159 (0x274C, 0x274C),
160 (0x274E, 0x274E),
161 (0x2753, 0x2755),
162 (0x2757, 0x2757),
163 (0x2795, 0x2797),
164 (0x27B0, 0x27B0),
165 (0x27BF, 0x27BF),
166 (0x2B1B, 0x2B1C),
167 (0x2B50, 0x2B50),
168 (0x2B55, 0x2B55),
169 (0x2E80, 0x2E99), // CJK radicals supplement
170 (0x2E9B, 0x2EF3),
171 (0x2F00, 0x2FD5), // Kangxi radicals
172 (0x2FF0, 0x2FFB), // Ideographic description
173 (0x3000, 0x303E), // CJK symbols and punctuation
174 (0x3041, 0x3096), // Hiragana
175 (0x3099, 0x30FF), // Katakana
176 (0x3105, 0x312F), // Bopomofo
177 (0x3131, 0x318E), // Hangul compatibility jamo
178 (0x3190, 0x31E3),
179 (0x31F0, 0x321E),
180 (0x3220, 0x3247),
181 (0x3250, 0x4DBF),
182 (0x4E00, 0xA48C), // CJK unified ideographs
183 (0xA490, 0xA4C6),
184 (0xA960, 0xA97C), // Hangul jamo extended A
185 (0xAC00, 0xD7A3), // Hangul syllables
186 (0xF900, 0xFAFF), // CJK compatibility ideographs
187 (0xFE10, 0xFE19), // Vertical forms
188 (0xFE30, 0xFE52), // CJK compatibility forms
189 (0xFE54, 0xFE66),
190 (0xFE68, 0xFE6B),
191 (0xFF01, 0xFF60), // Fullwidth forms
192 (0xFFE0, 0xFFE6),
193 (0x16FE0, 0x16FE4),
194 (0x16FF0, 0x16FF1),
195 (0x17000, 0x187F7), // Tangut
196 (0x18800, 0x18CD5),
197 (0x18D00, 0x18D08),
198 (0x1B000, 0x1B152), // Kana supplement
199 (0x1B164, 0x1B167),
200 (0x1B170, 0x1B2FB), // Nushu
201 (0x1F004, 0x1F004),
202 (0x1F0CF, 0x1F0CF),
203 (0x1F18E, 0x1F18E),
204 (0x1F191, 0x1F19A),
205 (0x1F200, 0x1F320),
206 (0x1F32D, 0x1F335),
207 (0x1F337, 0x1F37C),
208 (0x1F37E, 0x1F393),
209 (0x1F3A0, 0x1F3CA),
210 (0x1F3CF, 0x1F3D3),
211 (0x1F3E0, 0x1F3F0),
212 (0x1F3F4, 0x1F3F4),
213 (0x1F3F8, 0x1F43E),
214 (0x1F440, 0x1F440),
215 (0x1F442, 0x1F4FC),
216 (0x1F4FF, 0x1F53D),
217 (0x1F54B, 0x1F54E),
218 (0x1F550, 0x1F567),
219 (0x1F57A, 0x1F57A),
220 (0x1F595, 0x1F596),
221 (0x1F5A4, 0x1F5A4),
222 (0x1F5FB, 0x1F64F),
223 (0x1F680, 0x1F6C5),
224 (0x1F6CC, 0x1F6CC),
225 (0x1F6D0, 0x1F6D2),
226 (0x1F6D5, 0x1F6D7),
227 (0x1F6EB, 0x1F6EC),
228 (0x1F6F4, 0x1F6FC),
229 (0x1F7E0, 0x1F7EB),
230 (0x1F90C, 0x1F93A),
231 (0x1F93C, 0x1F945),
232 (0x1F947, 0x1F978),
233 (0x1F97A, 0x1F9CB),
234 (0x1F9CD, 0x1F9FF),
235 (0x1FA70, 0x1FA74),
236 (0x1FA78, 0x1FA7A),
237 (0x1FA80, 0x1FA86),
238 (0x1FA90, 0x1FAA8),
239 (0x1FAB0, 0x1FAB6),
240 (0x1FAC0, 0x1FAC2),
241 (0x1FAD0, 0x1FAD6),
242 (0x20000, 0x2FFFD), // CJK extension B onwards
243 (0x30000, 0x3FFFD),
244];