==> src/width.c <==1/* unicode-width 0.2.2's str_width and lookup_width, ported: the width of each character depends on
2 * the one after it (WidthInfo), so the string is walked backwards, as the crate's rfold does. */
3#include "tui/unicode_width.h"
4
5#include "uw_tables.h"
6#include "tui/utf8.h"
7
8#include <stdbool.h>
9#include <stdlib.h>
10
11typedef uint16_t info;
12
13enum {
14 LIGATURE_TRANSPARENT_MASK = 0x2000,
15};
16
17#define I_DEFAULT 0x0000
18#define I_LINE_FEED 0x0001
19#define I_EMOJI_MODIFIER 0x0002
20#define I_REGIONAL_INDICATOR 0x0003
21#define I_SEVERAL_REGIONAL_INDICATOR 0x0004
22#define I_EMOJI_PRESENTATION 0x0005
23#define I_ZWJ_EMOJI_PRESENTATION 0x1006
24#define I_VS16_ZWJ_EMOJI_PRESENTATION 0x9006
25#define I_KEYCAP_ZWJ_EMOJI_PRESENTATION 0x1007
26#define I_VS16_KEYCAP_ZWJ_EMOJI_PRESENTATION 0x9007
27#define I_REGIONAL_INDICATOR_ZWJ_PRESENTATION 0x0009
28#define I_EVEN_REGIONAL_INDICATOR_ZWJ_PRESENTATION 0x000A
29#define I_ODD_REGIONAL_INDICATOR_ZWJ_PRESENTATION 0x000B
30#define I_TAG_END_ZWJ_EMOJI_PRESENTATION 0x0010
31#define I_TAG_D1_END_ZWJ_EMOJI_PRESENTATION 0x0011
32#define I_TAG_D2_END_ZWJ_EMOJI_PRESENTATION 0x0012
33#define I_TAG_D3_END_ZWJ_EMOJI_PRESENTATION 0x0013
34#define I_TAG_A1_END_ZWJ_EMOJI_PRESENTATION 0x0019
35#define I_TAG_A2_END_ZWJ_EMOJI_PRESENTATION 0x001A
36#define I_TAG_A3_END_ZWJ_EMOJI_PRESENTATION 0x001B
37#define I_TAG_A4_END_ZWJ_EMOJI_PRESENTATION 0x001C
38#define I_TAG_A5_END_ZWJ_EMOJI_PRESENTATION 0x001D
39#define I_TAG_A6_END_ZWJ_EMOJI_PRESENTATION 0x001E
40#define I_KIRAT_RAI_VOWEL_SIGN_E 0x0020
41#define I_KIRAT_RAI_VOWEL_SIGN_AI 0x0021
42#define I_VARIATION_SELECTOR_1_2_OR_3 0x0200
43#define I_VARIATION_SELECTOR_15 0x4000
44#define I_VARIATION_SELECTOR_16 0x8000
45#define I_JOINING_GROUP_ALEF 0x30FF
46#define I_HEBREW_LETTER_LAMED 0x3800
47#define I_ZWJ_HEBREW_LETTER_LAMED 0x3C00
48#define I_BUGINESE_LETTER_YA 0x3801
49#define I_ZWJ_BUGINESE_LETTER_YA 0x3C01
50#define I_BUGINESE_VOWEL_SIGN_I_ZWJ_LETTER_YA 0x3C02
51#define I_TIFINAGH_CONSONANT 0x3803
52#define I_ZWJ_TIFINAGH_CONSONANT 0x3C03
53#define I_TIFINAGH_JOINER_CONSONANT 0x3C04
54#define I_LISU_TONE_LETTER_MYA_NA_JEU 0x3C05
55#define I_OLD_TURKIC_LETTER_ORKHON_I 0x3806
56#define I_ZWJ_OLD_TURKIC_LETTER_ORKHON_I 0x3C06
57#define I_KHMER_COENG_ELIGIBLE_LETTER 0x3C07
58
59static bool is_ligature_transparent_info(info i) { return (i & 0x0800) == 0x0800; }
60static info set_zwj_bit(info i) { return (info)(i | 0x0400); }
61static bool is_emoji_presentation(info i) { return (i & I_VARIATION_SELECTOR_16) == I_VARIATION_SELECTOR_16; }
62static bool is_zwj_emoji_presentation(info i) { return (i & 0xB000) == 0x9000; }
63static info set_emoji_presentation(info i) {
64 if ((i & LIGATURE_TRANSPARENT_MASK) == LIGATURE_TRANSPARENT_MASK || (i & 0x9000) == 0x1000)
65 return (info)(i | (I_VARIATION_SELECTOR_16 & ~I_VARIATION_SELECTOR_15 & ~I_VARIATION_SELECTOR_1_2_OR_3));
66 return I_VARIATION_SELECTOR_16;
67}
68static info unset_emoji_presentation(info i) {
69 if ((i & LIGATURE_TRANSPARENT_MASK) == LIGATURE_TRANSPARENT_MASK) return (info)(i & ~I_VARIATION_SELECTOR_16);
70 return I_DEFAULT;
71}
72static bool is_text_presentation(info i) { return (i & I_VARIATION_SELECTOR_15) == I_VARIATION_SELECTOR_15; }
73static info set_text_presentation(info i) {
74 if ((i & LIGATURE_TRANSPARENT_MASK) == LIGATURE_TRANSPARENT_MASK)
75 return (info)(i | (I_VARIATION_SELECTOR_15 & ~I_VARIATION_SELECTOR_16 & ~I_VARIATION_SELECTOR_1_2_OR_3));
76 return I_VARIATION_SELECTOR_15;
77}
78static info unset_text_presentation(info i) { return (info)(i & ~I_VARIATION_SELECTOR_15); }
79static bool is_vs1_2_3(info i) { return (i & I_VARIATION_SELECTOR_1_2_OR_3) == I_VARIATION_SELECTOR_1_2_OR_3; }
80static info set_vs1_2_3(info i) {
81 if ((i & LIGATURE_TRANSPARENT_MASK) == LIGATURE_TRANSPARENT_MASK)
82 return (info)(i | (I_VARIATION_SELECTOR_1_2_OR_3 & ~I_VARIATION_SELECTOR_15 & ~I_VARIATION_SELECTOR_16));
83 return I_VARIATION_SELECTOR_1_2_OR_3;
84}
85static info unset_vs1_2_3(info i) { return (info)(i & ~I_VARIATION_SELECTOR_1_2_OR_3); }
86
87typedef struct {
88 uint8_t width;
89 info info;
90} lookup;
91
92static lookup lookup_width(uint32_t cp) {
93 uint8_t t1 = TUI_UW_WIDTH_ROOT[cp >> 13];
94 uint8_t t2 = TUI_UW_WIDTH_MIDDLE[t1][cp >> 7 & 0x3F];
95 uint8_t packed = TUI_UW_WIDTH_LEAVES[t2][cp >> 2 & 0x1F];
96 uint8_t width = packed >> (2 * (cp & 3)) & 3;
97 if (width < 3) return (lookup){width, I_DEFAULT};
98 if (cp == 0xA) return (lookup){1, I_LINE_FEED};
99 if (cp == 0x5DC) return (lookup){1, I_HEBREW_LETTER_LAMED};
100 if (cp >= 0x622 && cp <= 0x882) return (lookup){1, I_JOINING_GROUP_ALEF};
101 if (cp >= 0x1780 && cp <= 0x17AF) return (lookup){1, I_KHMER_COENG_ELIGIBLE_LETTER};
102 if (cp == 0x17D8) return (lookup){3, I_DEFAULT};
103 if (cp == 0x1A10) return (lookup){1, I_BUGINESE_LETTER_YA};
104 if (cp >= 0x2D31 && cp <= 0x2D6F) return (lookup){1, I_TIFINAGH_CONSONANT};
105 if (cp >= 0xA4FC && cp <= 0xA4FD) return (lookup){1, I_LISU_TONE_LETTER_MYA_NA_JEU};
106 if (cp == 0xFE01) return (lookup){0, I_VARIATION_SELECTOR_1_2_OR_3};
107 if (cp == 0xFE0E) return (lookup){0, I_VARIATION_SELECTOR_15};
108 if (cp == 0xFE0F) return (lookup){0, I_VARIATION_SELECTOR_16};
109 if (cp == 0x10C03) return (lookup){1, I_OLD_TURKIC_LETTER_ORKHON_I};
110 if (cp == 0x16D67) return (lookup){1, I_KIRAT_RAI_VOWEL_SIGN_E};
111 if (cp == 0x16D68) return (lookup){1, I_KIRAT_RAI_VOWEL_SIGN_AI};
112 if (cp >= 0x1F1E6 && cp <= 0x1F1FF) return (lookup){1, I_REGIONAL_INDICATOR};
113 if (cp >= 0x1F3FB && cp <= 0x1F3FF) return (lookup){2, I_EMOJI_MODIFIER};
114 return (lookup){2, I_EMOJI_PRESENTATION};
115}
116
117int tui_char_width(uint32_t cp) {
118 if (cp < 0x7F) return cp >= 0x20 ? 1 : -1;
119 if (cp >= 0xA0) return lookup_width(cp).width;
120 return -1;
121}
122
123static bool is_transparent_zero_width(uint32_t cp) {
124 if (lookup_width(cp).width != 0) return false;
125 size_t lo = 0, hi = sizeof TUI_UW_NON_TRANSPARENT_ZERO_WIDTHS / sizeof TUI_UW_NON_TRANSPARENT_ZERO_WIDTHS[0];
126 while (lo < hi) {
127 size_t mid = (lo + hi) / 2;
128 if (cp < TUI_UW_NON_TRANSPARENT_ZERO_WIDTHS[mid][0]) hi = mid;
129 else if (cp > TUI_UW_NON_TRANSPARENT_ZERO_WIDTHS[mid][1]) lo = mid + 1;
130 else return false; /* found: not transparent */
131 }
132 return true;
133}
134
135static bool is_ligature_transparent(uint32_t c) {
136 return c == 0x34F || (c >= 0x17B4 && c <= 0x17B5) || (c >= 0x180B && c <= 0x180D) || c == 0x180F ||
137 c == 0x200D || (c >= 0xFE00 && c <= 0xFE0F) || (c >= 0xE0100 && c <= 0xE01EF);
138}
139
140static bool starts_emoji_presentation_seq(uint32_t cp) {
141 int leaf;
142 switch (cp >> 10) {
143 case 0x0: leaf = 0; break;
144 case 0x8: leaf = 1; break;
145 case 0x9: leaf = 2; break;
146 case 0xA: leaf = 3; break;
147 case 0xC: leaf = 4; break;
148 case 0x7C: leaf = 5; break;
149 case 0x7D: leaf = 6; break;
150 default: return false;
151 }
152 uint8_t byte = TUI_UW_EMOJI_PRESENTATION_LEAVES[leaf][(cp >> 3) & 0x7F];
153 return ((byte >> (cp & 7)) & 1) == 1;
154}
155
156static bool in_pairs(const uint8_t (*leaf)[2], size_t n, uint8_t b) {
157 size_t lo = 0, hi = n;
158 while (lo < hi) {
159 size_t mid = (lo + hi) / 2;
160 if (b < leaf[mid][0]) hi = mid;
161 else if (b > leaf[mid][1]) lo = mid + 1;
162 else return true;
163 }
164 return false;
165}
166
167#define LEAF(t) (t), (sizeof(t) / sizeof((t)[0]))
168
169static bool starts_non_ideographic_text_presentation_seq(uint32_t cp) {
170 uint8_t b = (uint8_t)(cp & 0xFF);
171 switch (cp >> 8) {
172 case 0x23: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_0), b);
173 case 0x25: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_1), b);
174 case 0x26: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_2), b);
175 case 0x27: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_3), b);
176 case 0x2B: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_4), b);
177 case 0x1F0: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_5), b);
178 case 0x1F3: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_6), b);
179 case 0x1F4: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_7), b);
180 case 0x1F5: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_8), b);
181 case 0x1F6: return in_pairs(LEAF(TUI_UW_TEXT_PRESENTATION_LEAF_9), b);
182 default: return false;
183 }
184}
185
186static bool is_emoji_modifier_base(uint32_t cp) {
187 uint8_t b = (uint8_t)(cp & 0xFF);
188 switch (cp >> 8) {
189 case 0x26: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_0), b);
190 case 0x27: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_1), b);
191 case 0x1F3: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_2), b);
192 case 0x1F4: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_3), b);
193 case 0x1F5: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_4), b);
194 case 0x1F6: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_5), b);
195 case 0x1F9: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_6), b);
196 case 0x1FA: return in_pairs(LEAF(TUI_UW_EMOJI_MODIFIER_LEAF_7), b);
197 default: return false;
198 }
199}
200
201typedef struct {
202 int width;
203 info info;
204} step;
205
206static bool ri(uint32_t c) { return c >= 0x1F1E6 && c <= 0x1F1FF; }
207static bool tag_letter(uint32_t c) { return c >= 0xE0061 && c <= 0xE007A; }
208static bool tag_digit(uint32_t c) { return c >= 0xE0030 && c <= 0xE0039; }
209
210static step width_in_str(uint32_t c, info next) {
211 if (is_emoji_presentation(next)) {
212 if (starts_emoji_presentation_seq(c)) return (step){is_zwj_emoji_presentation(next) ? 0 : 2, I_EMOJI_PRESENTATION};
213 next = unset_emoji_presentation(next);
214 }
215 if (c <= 0xA0) {
216 if (c == '\n') return (step){1, I_LINE_FEED};
217 if (c == '\r' && next == I_LINE_FEED) return (step){0, I_DEFAULT};
218 return (step){1, I_DEFAULT};
219 }
220 if (next != I_DEFAULT) {
221 if (c == 0xFE0F) return (step){0, set_emoji_presentation(next)};
222 if (c == 0xFE01) return (step){0, set_vs1_2_3(next)};
223 if (c == 0xFE0E) return (step){0, set_text_presentation(next)};
224 if (is_text_presentation(next)) {
225 if (starts_non_ideographic_text_presentation_seq(c)) return (step){1, I_DEFAULT};
226 next = unset_text_presentation(next);
227 } else if (is_vs1_2_3(next)) {
228 if (c == 0x2018 || c == 0x2019 || c == 0x201C || c == 0x201D) return (step){2, I_DEFAULT};
229 next = unset_vs1_2_3(next);
230 }
231 if (is_ligature_transparent_info(next)) {
232 if (c == 0x200D) return (step){0, set_zwj_bit(next)};
233 if (is_ligature_transparent(c)) return (step){0, next};
234 }
235 switch (next) {
236 case I_JOINING_GROUP_ALEF:
237 if (c == 0x644 || (c >= 0x6B5 && c <= 0x6B8) || c == 0x76A || c == 0x8A6 || c == 0x8C7)
238 return (step){0, I_DEFAULT};
239 if (is_transparent_zero_width(c)) return (step){0, I_JOINING_GROUP_ALEF};
240 break;
241 case I_ZWJ_HEBREW_LETTER_LAMED:
242 if (c == 0x05D0) return (step){0, I_DEFAULT};
243 break;
244 case I_KHMER_COENG_ELIGIBLE_LETTER:
245 if (c == 0x17D2) return (step){-1, I_DEFAULT};
246 break;
247 case I_ZWJ_BUGINESE_LETTER_YA:
248 if (c == 0x1A17) return (step){0, I_BUGINESE_VOWEL_SIGN_I_ZWJ_LETTER_YA};
249 break;
250 case I_BUGINESE_VOWEL_SIGN_I_ZWJ_LETTER_YA:
251 if (c == 0x1A15) return (step){0, I_DEFAULT};
252 break;
253 default: break;
254 }
255 if ((next == I_TIFINAGH_CONSONANT || next == I_ZWJ_TIFINAGH_CONSONANT) && c == 0x2D7F)
256 return (step){1, I_TIFINAGH_JOINER_CONSONANT};
257 if (next == I_ZWJ_TIFINAGH_CONSONANT && ((c >= 0x2D31 && c <= 0x2D65) || c == 0x2D6F)) return (step){0, I_DEFAULT};
258 if (next == I_TIFINAGH_JOINER_CONSONANT && ((c >= 0x2D31 && c <= 0x2D65) || c == 0x2D6F))
259 return (step){-1, I_DEFAULT};
260 if (next == I_LISU_TONE_LETTER_MYA_NA_JEU && c >= 0xA4F8 && c <= 0xA4FB) return (step){0, I_DEFAULT};
261 if (next == I_ZWJ_OLD_TURKIC_LETTER_ORKHON_I && c == 0x10C32) return (step){0, I_DEFAULT};
262 if (next == I_EMOJI_MODIFIER && is_emoji_modifier_base(c)) return (step){0, I_EMOJI_PRESENTATION};
263 if ((next == I_REGIONAL_INDICATOR || next == I_SEVERAL_REGIONAL_INDICATOR) && ri(c))
264 return (step){1, I_SEVERAL_REGIONAL_INDICATOR};
265 if ((next == I_EMOJI_PRESENTATION || next == I_SEVERAL_REGIONAL_INDICATOR ||
266 next == I_EVEN_REGIONAL_INDICATOR_ZWJ_PRESENTATION || next == I_ODD_REGIONAL_INDICATOR_ZWJ_PRESENTATION ||
267 next == I_EMOJI_MODIFIER) &&
268 c == 0x200D)
269 return (step){0, I_ZWJ_EMOJI_PRESENTATION};
270 if (next == I_ZWJ_EMOJI_PRESENTATION && c == 0x20E3) return (step){0, I_KEYCAP_ZWJ_EMOJI_PRESENTATION};
271 if (next == I_VS16_ZWJ_EMOJI_PRESENTATION && starts_emoji_presentation_seq(c)) return (step){0, I_EMOJI_PRESENTATION};
272 if (next == I_VS16_KEYCAP_ZWJ_EMOJI_PRESENTATION && ((c >= '0' && c <= '9') || c == '#' || c == '*'))
273 return (step){0, I_EMOJI_PRESENTATION};
274 if (next == I_ZWJ_EMOJI_PRESENTATION && ri(c)) return (step){1, I_REGIONAL_INDICATOR_ZWJ_PRESENTATION};
275 if ((next == I_REGIONAL_INDICATOR_ZWJ_PRESENTATION || next == I_ODD_REGIONAL_INDICATOR_ZWJ_PRESENTATION) && ri(c))
276 return (step){-1, I_EVEN_REGIONAL_INDICATOR_ZWJ_PRESENTATION};
277 if (next == I_EVEN_REGIONAL_INDICATOR_ZWJ_PRESENTATION && ri(c))
278 return (step){3, I_ODD_REGIONAL_INDICATOR_ZWJ_PRESENTATION};
279 if (next == I_ZWJ_EMOJI_PRESENTATION && c >= 0x1F3FB && c <= 0x1F3FF) return (step){0, I_EMOJI_MODIFIER};
280 if (next == I_ZWJ_EMOJI_PRESENTATION && c == 0xE007F) return (step){0, I_TAG_END_ZWJ_EMOJI_PRESENTATION};
281 if (next == I_TAG_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A1_END_ZWJ_EMOJI_PRESENTATION};
282 if (next == I_TAG_A1_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A2_END_ZWJ_EMOJI_PRESENTATION};
283 if (next == I_TAG_A2_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A3_END_ZWJ_EMOJI_PRESENTATION};
284 if (next == I_TAG_A3_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A4_END_ZWJ_EMOJI_PRESENTATION};
285 if (next == I_TAG_A4_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A5_END_ZWJ_EMOJI_PRESENTATION};
286 if (next == I_TAG_A5_END_ZWJ_EMOJI_PRESENTATION && tag_letter(c)) return (step){0, I_TAG_A6_END_ZWJ_EMOJI_PRESENTATION};
287 if ((next == I_TAG_END_ZWJ_EMOJI_PRESENTATION || next == I_TAG_A1_END_ZWJ_EMOJI_PRESENTATION ||
288 next == I_TAG_A2_END_ZWJ_EMOJI_PRESENTATION || next == I_TAG_A3_END_ZWJ_EMOJI_PRESENTATION ||
289 next == I_TAG_A4_END_ZWJ_EMOJI_PRESENTATION) &&
290 tag_digit(c))
291 return (step){0, I_TAG_D1_END_ZWJ_EMOJI_PRESENTATION};
292 if (next == I_TAG_D1_END_ZWJ_EMOJI_PRESENTATION && tag_digit(c)) return (step){0, I_TAG_D2_END_ZWJ_EMOJI_PRESENTATION};
293 if (next == I_TAG_D2_END_ZWJ_EMOJI_PRESENTATION && tag_digit(c)) return (step){0, I_TAG_D3_END_ZWJ_EMOJI_PRESENTATION};
294 if ((next == I_TAG_A3_END_ZWJ_EMOJI_PRESENTATION || next == I_TAG_A4_END_ZWJ_EMOJI_PRESENTATION ||
295 next == I_TAG_A5_END_ZWJ_EMOJI_PRESENTATION || next == I_TAG_A6_END_ZWJ_EMOJI_PRESENTATION ||
296 next == I_TAG_D3_END_ZWJ_EMOJI_PRESENTATION) &&
297 c == 0x1F3F4)
298 return (step){0, I_EMOJI_PRESENTATION};
299 if (next == I_ZWJ_EMOJI_PRESENTATION && lookup_width(c).info == I_EMOJI_PRESENTATION)
300 return (step){0, I_EMOJI_PRESENTATION};
301 if (next == I_KIRAT_RAI_VOWEL_SIGN_E && c == 0x16D63) return (step){0, I_DEFAULT};
302 if (next == I_KIRAT_RAI_VOWEL_SIGN_E && c == 0x16D67) return (step){0, I_KIRAT_RAI_VOWEL_SIGN_AI};
303 if (next == I_KIRAT_RAI_VOWEL_SIGN_E && c == 0x16D68) return (step){1, I_KIRAT_RAI_VOWEL_SIGN_E};
304 if (next == I_KIRAT_RAI_VOWEL_SIGN_E && c == 0x16D69) return (step){0, I_DEFAULT};
305 if (next == I_KIRAT_RAI_VOWEL_SIGN_AI && c == 0x16D63) return (step){0, I_DEFAULT};
306 }
307 lookup l = lookup_width(c);
308 return (step){l.width, l.info};
309}
310
311size_t tui_str_width(const char *s, size_t len) {
312 /* Decode forwards, fold backwards. */
313 uint32_t small[256];
314 uint32_t *cps = small;
315 size_t n = 0, cap = 256;
316 for (size_t i = 0; i < len;) {
317 if (n == cap) {
318 cap *= 2;
319 uint32_t *bigger = malloc(cap * sizeof *bigger);
320 if (!bigger) abort();
321 for (size_t k = 0; k < n; k++) bigger[k] = cps[k];
322 if (cps != small) free(cps);
323 cps = bigger;
324 }
325 cps[n++] = tui_utf8_next(s, len, &i);
326 }
327 size_t sum = 0;
328 info next = I_DEFAULT;
329 for (size_t k = n; k-- > 0;) {
330 step st = width_in_str(cps[k], next);
331 sum += (size_t)(ptrdiff_t)st.width; /* wrapping_add_signed */
332 next = st.info;
333 }
334 if (cps != small) free(cps);
335 return sum;
336}
337
338size_t tui_cstr_width(const char *s) {
339 size_t len = 0;
340 while (s[len]) len++;
341 return tui_str_width(s, len);
342}