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)] |
| 17 | pub 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 | |
| 26 | impl 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. |
| 41 | pub 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. |
| 56 | pub 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. |
| 65 | fn 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. |
| 83 | static 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. |
| 133 | static 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 | ]; |