FTXUI 7.0.3
C++ functional terminal UI.
Loading...
Searching...
No Matches
string.cpp
Go to the documentation of this file.
1// Copyright 2020 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4//
5// Content of this file was created thanks to:
6// -
7// https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt
8// - Markus Kuhn -- 2007-05-26 (Unicode 5.0)
9// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
10// Thanks you!
11
13
14#include <array> // for array
15#include <cstddef> // for size_t
16#include <cstdint> // for uint32_t, uint8_t, uint16_t, int32_t
17#include <string> // for string, basic_string, wstring
18#include <string_view> // for string_view
19#include <tuple> // for _Swallow_assign, ignore
20#include <vector>
21
22#include "ftxui/screen/deprecated.hpp" // for wchar_width, wstring_width
23#include "ftxui/screen/string_internal.hpp" // for WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, GlyphCount, GlyphIterate, GlyphNext, GlyphPrevious, IsCombining, IsControl, IsFullWidth, Utf8ToWordBreakProperty
24
25namespace {
26
27struct Interval {
28 uint32_t first;
29 uint32_t last;
30};
31
32using WBP = ftxui::WordBreakProperty;
33struct WordBreakPropertyInterval {
34 uint32_t first;
35 uint32_t last;
36 WBP property;
37};
38
39// g_full_width_characters and g_word_break_intervals, generated from the
40// Unicode Character Database by tools/gen_unicode_tables.py.
42
43// Construct table of just WBP::Extend character intervals
44constexpr auto g_extend_characters{[]() constexpr {
45 // Compute number of extend character intervals
46 constexpr size_t size = []() constexpr {
47 size_t count = 0;
48 for (auto interval : g_word_break_intervals) {
49 if (interval.property == WBP::Extend) {
50 count++;
51 }
52 }
53 return count;
54 }();
55
56 // Create array of extend character intervals
57 std::array<Interval, size> result{};
58 size_t index = 0;
59 for (auto interval : g_word_break_intervals) {
60 if (interval.property == WBP::Extend) {
61 result[index++] = {interval.first, interval.last}; // NOLINT
62 }
63 }
64 return result;
65}()};
66
67// Find a codepoint inside a sorted list of Interval.
68template <size_t N>
69bool Bisearch(uint32_t ucs, const std::array<Interval, N>& table) {
70 if (ucs < table.front().first || ucs > table.back().last) { // NOLINT
71 return false;
72 }
73
74 int min = 0;
75 int max = N - 1;
76 while (max >= min) {
77 const int mid = (min + max) / 2;
78 if (ucs > table[mid].last) { // NOLINT
79 min = mid + 1;
80 } else if (ucs < table[mid].first) { // NOLINT
81 max = mid - 1;
82 } else {
83 return true;
84 }
85 }
86
87 return false;
88}
89
90// Find a value inside a sorted list of Interval + property.
91template <class C, size_t N>
92bool Bisearch(uint32_t ucs, const std::array<C, N>& table, C* out) {
93 if (ucs < table.front().first || ucs > table.back().last) { // NOLINT
94 return false;
95 }
96
97 int min = 0;
98 int max = N - 1;
99 while (max >= min) {
100 const int mid = (min + max) / 2;
101 if (ucs > table[mid].last) { // NOLINT
102 min = mid + 1;
103 } else if (ucs < table[mid].first) { // NOLINT
104 max = mid - 1;
105 } else {
106 *out = table[mid]; // NOLINT
107 return true;
108 }
109 }
110
111 return false;
112}
113
114int codepoint_width(uint32_t ucs) {
115 if (ftxui::IsControl(ucs)) {
116 return -1;
117 }
118
119 if (ftxui::IsCombining(ucs)) {
120 return 0;
121 }
122
123 if (ftxui::IsFullWidth(ucs)) {
124 return 2;
125 }
126
127 return 1;
128}
129
130} // namespace
131
132namespace ftxui {
133
134// From UTF8 encoded string |input|, eat in between 1 and 4 byte representing
135// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
136// |end| to represent the beginning of the next byte to eat for consecutive
137// executions.
138bool EatCodePoint(std::string_view input,
139 size_t start,
140 size_t* end,
141 uint32_t* ucs) {
142 if (start >= input.size()) {
143 *end = start + 1;
144 return false;
145 }
146 const uint8_t C0 = input[start];
147
148 // 1 byte string.
149 if ((C0 & 0b1000'0000) == 0b0000'0000) { // NOLINT
150 *ucs = C0 & 0b0111'1111; // NOLINT
151 *end = start + 1;
152 return true;
153 }
154
155 // 2 byte string.
156 if ((C0 & 0b1110'0000) == 0b1100'0000 && // NOLINT
157 start + 1 < input.size()) {
158 const uint8_t C1 = input[start + 1];
159 *ucs = 0;
160 *ucs += C0 & 0b0001'1111; // NOLINT
161 *ucs <<= 6; // NOLINT
162 *ucs += C1 & 0b0011'1111; // NOLINT
163 *end = start + 2;
164 return true;
165 }
166
167 // 3 byte string.
168 if ((C0 & 0b1111'0000) == 0b1110'0000 && // NOLINT
169 start + 2 < input.size()) {
170 const uint8_t C1 = input[start + 1];
171 const uint8_t C2 = input[start + 2];
172 *ucs = 0;
173 *ucs += C0 & 0b0000'1111; // NOLINT
174 *ucs <<= 6; // NOLINT
175 *ucs += C1 & 0b0011'1111; // NOLINT
176 *ucs <<= 6; // NOLINT
177 *ucs += C2 & 0b0011'1111; // NOLINT
178 *end = start + 3;
179 return true;
180 }
181
182 // 4 byte string.
183 if ((C0 & 0b1111'1000) == 0b1111'0000 && // NOLINT
184 start + 3 < input.size()) {
185 const uint8_t C1 = input[start + 1];
186 const uint8_t C2 = input[start + 2];
187 const uint8_t C3 = input[start + 3];
188 *ucs = 0;
189 *ucs += C0 & 0b0000'0111; // NOLINT
190 *ucs <<= 6; // NOLINT
191 *ucs += C1 & 0b0011'1111; // NOLINT
192 *ucs <<= 6; // NOLINT
193 *ucs += C2 & 0b0011'1111; // NOLINT
194 *ucs <<= 6; // NOLINT
195 *ucs += C3 & 0b0011'1111; // NOLINT
196 *end = start + 4;
197 return true;
198 }
199
200 *end = start + 1;
201 return false;
202}
203
204// From UTF16 encoded string |input|, eat in between 1 and 4 byte representing
205// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
206// |end| to represent the beginning of the next byte to eat for consecutive
207// executions.
208bool EatCodePoint(std::wstring_view input,
209 size_t start,
210 size_t* end,
211 uint32_t* ucs) {
212 if (start >= input.size()) {
213 *end = start + 1;
214 return false;
215 }
216
217 // On linux wstring uses the UTF32 encoding:
218 if constexpr (sizeof(wchar_t) == 4) {
219 *ucs = input[start]; // NOLINT
220 *end = start + 1;
221 return true;
222 }
223
224 // On windows, wstring uses the UTF16 encoding:
225 int32_t C0 = input[start]; // NOLINT
226
227 // 1 word size:
228 if (C0 < 0xd800 || C0 >= 0xdc00) { // NOLINT
229 *ucs = C0;
230 *end = start + 1;
231 return true;
232 }
233
234 // 2 word size:
235 if (start + 1 >= input.size()) {
236 *end = start + 2;
237 return false;
238 }
239
240 int32_t C1 = input[start + 1]; // NOLINT
241 *ucs = ((C0 & 0x3ff) << 10) + (C1 & 0x3ff) + 0x10000; // NOLINT
242 *end = start + 2;
243 return true;
244}
245
246bool IsCombining(uint32_t ucs) {
247 return Bisearch(ucs, g_extend_characters);
248}
249
250bool IsFullWidth(uint32_t ucs) {
251 if (ucs < 0x0300) { // Quick path: // NOLINT
252 return false;
253 }
254
255 return Bisearch(ucs, g_full_width_characters);
256}
257
258bool IsControl(uint32_t ucs) {
259 if (ucs == 0) {
260 return true;
261 }
262 if (ucs < 32) { // NOLINT
263 const uint32_t LINE_FEED = 10;
264 return ucs != LINE_FEED;
265 }
266 if (ucs >= 0x7f && ucs < 0xa0) { // NOLINT
267 return true;
268 }
269 return false;
270}
271
273 WordBreakPropertyInterval interval = {0, 0, WBP::ALetter};
274 std::ignore = Bisearch(codepoint, g_word_break_intervals, &interval);
275 return interval.property;
276}
277
278int wchar_width(wchar_t ucs) {
279 return codepoint_width(uint32_t(ucs));
280}
281
282int wstring_width(const std::wstring& text) {
283 int width = 0;
284
285 for (const wchar_t& it : text) {
286 const int w = wchar_width(it);
287 if (w < 0) {
288 return -1;
289 }
290 width += w;
291 }
292 return width;
293}
294
295// Return how many cells the UTF8 encoded string |input| is taking when printed.
296// Control characters are not taking any space, combining characters are
297// modifying the previous character and are not taking any space, fullwidth
298// characters are taking two cells and all the other characters are taking one
299// cell.
300int string_width(std::string_view input) {
301 // 1-byte optimization: This function is often called on a single ASCII
302 // character, so we can optimize this case by skipping the UTF8 decoding.
303 if (input.size() == 1) {
304 const char c = input[0];
305 if (c >= 32 && c < 127) { // NOLINT
306 return 1;
307 }
308 }
309
310 // ASCII optimization: If the string is pure ASCII, we can skip the UTF8
311 // decoding and just count the number of characters, ignoring control
312 // characters.
313 bool is_pure_ascii = true;
314 for (const char c : input) {
315 if (c < 31 || c >= 127) { // NOLINT
316 is_pure_ascii = false;
317 break;
318 }
319 }
320 if (is_pure_ascii) {
321 return static_cast<int>(input.size());
322 }
323
324 int width = 0;
325 size_t start = 0;
326 while (start < input.size()) {
327 uint32_t codepoint = 0;
328 if (!EatCodePoint(input, start, &start, &codepoint)) {
329 continue;
330 }
331
332 if (IsControl(codepoint)) {
333 continue;
334 }
335
336 if (IsCombining(codepoint)) {
337 continue;
338 }
339
340 if (IsFullWidth(codepoint)) {
341 width += 2;
342 continue;
343 }
344
345 width += 1;
346 }
347 return width;
348}
349
350std::vector<std::string> Utf8ToGlyphs(std::string_view input) {
351 std::vector<std::string> out;
352 out.reserve(input.size());
353 size_t start = 0;
354 size_t end = 0;
355 while (start < input.size()) {
356 uint32_t codepoint = 0;
357 if (!EatCodePoint(input, start, &end, &codepoint)) {
358 start = end;
359 continue;
360 }
361
362 const auto append = input.substr(start, end - start);
363 start = end;
364
365 // Ignore control characters.
366 if (IsControl(codepoint)) {
367 continue;
368 }
369
370 // Combining characters are put with the previous glyph they are modifying.
371 if (IsCombining(codepoint)) {
372 if (!out.empty()) {
373 out.back() += append;
374 }
375 continue;
376 }
377
378 // Fullwidth characters take two cells. The second is made of the empty
379 // string to reserve the space the first is taking.
380 if (IsFullWidth(codepoint)) {
381 out.emplace_back(append);
382 out.emplace_back("");
383 continue;
384 }
385
386 // Normal characters:
387 out.emplace_back(append);
388 }
389 return out;
390}
391
392size_t GlyphPrevious(std::string_view input, size_t start) {
393 while (true) {
394 if (start == 0) {
395 return 0;
396 }
397 start--;
398
399 // Skip the UTF8 continuation bytes.
400 if ((input[start] & 0b1100'0000) == 0b1000'0000) {
401 continue;
402 }
403
404 uint32_t codepoint = 0;
405 size_t end = 0;
406 const bool eaten = EatCodePoint(input, start, &end, &codepoint);
407
408 // Ignore invalid, control characters and combining characters.
409 if (!eaten || IsControl(codepoint) || IsCombining(codepoint)) {
410 continue;
411 }
412
413 return start;
414 }
415}
416
417size_t GlyphNext(std::string_view input, size_t start) {
418 bool glyph_found = false;
419 while (start < input.size()) {
420 size_t end = 0;
421 uint32_t codepoint = 0;
422 const bool eaten = EatCodePoint(input, start, &end, &codepoint);
423
424 // Ignore invalid, control characters and combining characters.
425 if (!eaten || IsControl(codepoint) || IsCombining(codepoint)) {
426 start = end;
427 continue;
428 }
429
430 // We eat the beginning of the next glyph. If we are eating the one
431 // requested, return its start position immediately.
432 if (glyph_found) {
433 return static_cast<int>(start);
434 }
435
436 // Otherwise, skip this glyph and iterate:
437 glyph_found = true;
438 start = end;
439 }
440 return static_cast<int>(input.size());
441}
442
443size_t GlyphIterate(std::string_view input, int glyph_offset, size_t start) {
444 if (glyph_offset >= 0) {
445 for (int i = 0; i < glyph_offset; ++i) {
446 start = GlyphNext(input, start);
447 }
448 return start;
449 } else {
450 for (int i = 0; i < -glyph_offset; ++i) {
451 start = GlyphPrevious(input, start);
452 }
453 return start;
454 }
455}
456
457std::vector<int> CellToGlyphIndex(std::string_view input) {
458 int x = -1;
459 std::vector<int> out;
460 out.reserve(input.size());
461 size_t start = 0;
462 size_t end = 0;
463 while (start < input.size()) {
464 uint32_t codepoint = 0;
465 const bool eaten = EatCodePoint(input, start, &end, &codepoint);
466 start = end;
467
468 // Ignore invalid / control characters.
469 if (!eaten || IsControl(codepoint)) {
470 continue;
471 }
472
473 // Combining characters are put with the previous glyph they are modifying.
474 if (IsCombining(codepoint)) {
475 if (x == -1) {
476 ++x;
477 out.push_back(x);
478 }
479 continue;
480 }
481
482 // Fullwidth characters take two cells. The second is made of the empty
483 // string to reserve the space the first is taking.
484 if (IsFullWidth(codepoint)) {
485 ++x;
486 out.push_back(x);
487 out.push_back(x);
488 continue;
489 }
490
491 // Normal characters:
492 ++x;
493 out.push_back(x);
494 }
495 return out;
496}
497
498int GlyphCount(std::string_view input) {
499 int size = 0;
500 size_t start = 0;
501 size_t end = 0;
502 while (start < input.size()) {
503 uint32_t codepoint = 0;
504 const bool eaten = EatCodePoint(input, start, &end, &codepoint);
505 start = end;
506
507 // Ignore invalid characters:
508 if (!eaten || IsControl(codepoint)) {
509 continue;
510 }
511
512 // Ignore combining characters, except when they don't have a preceding to
513 // combine with.
514 if (IsCombining(codepoint)) {
515 if (size == 0) {
516 size++;
517 }
518 continue;
519 }
520
521 size++;
522 }
523 return size;
524}
525
526std::vector<WordBreakProperty> Utf8ToWordBreakProperty(std::string_view input) {
527 std::vector<WordBreakProperty> out;
528 out.reserve(input.size());
529 size_t start = 0;
530 size_t end = 0;
531 while (start < input.size()) {
532 uint32_t codepoint = 0;
533 if (!EatCodePoint(input, start, &end, &codepoint)) {
534 start = end;
535 continue;
536 }
537 start = end;
538
539 // Ignore control characters.
540 if (IsControl(codepoint)) {
541 continue;
542 }
543
544 // Ignore combining characters.
545 if (IsCombining(codepoint)) {
546 continue;
547 }
548
549 WordBreakPropertyInterval interval = {0, 0, WBP::ALetter};
550 std::ignore = Bisearch(codepoint, g_word_break_intervals, &interval);
551 out.push_back(interval.property);
552 }
553 return out;
554}
555
556/// Convert a std::wstring into a UTF8 std::string.
557std::string to_string(std::wstring_view s) {
558 std::string out;
559
560 size_t i = 0;
561 uint32_t codepoint = 0;
562 while (EatCodePoint(s, i, &i, &codepoint)) {
563 // Code point <-> UTF-8 conversion
564 //
565 // ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
566 // ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃
567 // ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
568 // │0xxxxxxx│ │ │ │
569 // ├────────┼────────┼────────┼────────┤
570 // │110xxxxx│10xxxxxx│ │ │
571 // ├────────┼────────┼────────┼────────┤
572 // │1110xxxx│10xxxxxx│10xxxxxx│ │
573 // ├────────┼────────┼────────┼────────┤
574 // │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
575 // └────────┴────────┴────────┴────────┘
576
577 // 1 byte UTF8
578 if (codepoint <= 0b000'0000'0111'1111) { // NOLINT
579 const uint8_t p1 = codepoint;
580 out.push_back(p1); // NOLINT
581 continue;
582 }
583
584 // 2 bytes UTF8
585 if (codepoint <= 0b000'0111'1111'1111) { // NOLINT
586 uint8_t p2 = codepoint & 0b111111; // NOLINT
587 codepoint >>= 6; // NOLINT
588 uint8_t p1 = codepoint; // NOLINT
589 out.push_back(0b11000000 + p1); // NOLINT
590 out.push_back(0b10000000 + p2); // NOLINT
591 continue;
592 }
593
594 // 3 bytes UTF8
595 if (codepoint <= 0b1111'1111'1111'1111) { // NOLINT
596 uint8_t p3 = codepoint & 0b111111; // NOLINT
597 codepoint >>= 6; // NOLINT
598 uint8_t p2 = codepoint & 0b111111; // NOLINT
599 codepoint >>= 6; // NOLINT
600 uint8_t p1 = codepoint; // NOLINT
601 out.push_back(0b11100000 + p1); // NOLINT
602 out.push_back(0b10000000 + p2); // NOLINT
603 out.push_back(0b10000000 + p3); // NOLINT
604 continue;
605 }
606
607 // 4 bytes UTF8
608 if (codepoint <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
609 uint8_t p4 = codepoint & 0b111111; // NOLINT
610 codepoint >>= 6; // NOLINT
611 uint8_t p3 = codepoint & 0b111111; // NOLINT
612 codepoint >>= 6; // NOLINT
613 uint8_t p2 = codepoint & 0b111111; // NOLINT
614 codepoint >>= 6; // NOLINT
615 uint8_t p1 = codepoint; // NOLINT
616 out.push_back(0b11110000 + p1); // NOLINT
617 out.push_back(0b10000000 + p2); // NOLINT
618 out.push_back(0b10000000 + p3); // NOLINT
619 out.push_back(0b10000000 + p4); // NOLINT
620 continue;
621 }
622
623 // Something else?
624 }
625 return out;
626}
627
628/// Convert a UTF8 std::string into a std::wstring.
629std::wstring to_wstring(std::string_view s) {
630 std::wstring out;
631
632 size_t i = 0;
633 uint32_t codepoint = 0;
634 while (EatCodePoint(s, i, &i, &codepoint)) {
635 // On linux wstring are UTF32 encoded:
636 if constexpr (sizeof(wchar_t) == 4) {
637 out.push_back(codepoint); // NOLINT
638 continue;
639 }
640
641 // On Windows, wstring are UTF16 encoded:
642
643 // Codepoint encoded using 1 word:
644 // NOLINTNEXTLINE
645 if (codepoint < 0xD800 || (codepoint > 0xDFFF && codepoint < 0x10000)) {
646 uint16_t p0 = codepoint; // NOLINT
647 out.push_back(p0); // NOLINT
648 continue;
649 }
650
651 // Codepoint encoded using 2 words:
652 codepoint -= 0x010000; // NOLINT
653 uint16_t p0 = (((codepoint << 12) >> 22) + 0xD800); // NOLINT
654 uint16_t p1 = (((codepoint << 22) >> 22) + 0xDC00); // NOLINT
655 out.push_back(p0); // NOLINT
656 out.push_back(p1); // NOLINT
657 }
658 return out;
659}
660
661} // namespace ftxui
Decorator size(WidthOrHeight direction, Constraint constraint, int value)
Apply a constraint on the size of an element.
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
bool IsControl(uint32_t ucs)
Definition string.cpp:258
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Definition string.cpp:272
size_t GlyphPrevious(std::string_view input, size_t start)
Definition string.cpp:392
FTXUI_EXPORT(SCREEN) int string_width(std std::vector< std::string > Utf8ToGlyphs(std::string_view input)
Definition string.cpp:350
int string_width(std::string_view input)
Definition string.cpp:300
bool IsCombining(uint32_t ucs)
Definition string.cpp:246
int wchar_width(wchar_t ucs)
Definition string.cpp:278
bool EatCodePoint(std::string_view input, size_t start, size_t *end, uint32_t *ucs)
Definition string.cpp:138
std::string to_string(std::wstring_view s)
Convert a std::wstring into a UTF8 std::string.
Definition string.cpp:557
int GlyphCount(std::string_view input)
Definition string.cpp:498
std::vector< WordBreakProperty > Utf8ToWordBreakProperty(std::string_view input)
Definition string.cpp:526
int wstring_width(const std::wstring &text)
Definition string.cpp:282
std::vector< int > CellToGlyphIndex(std::string_view input)
Definition string.cpp:457
FTXUI_EXPORT(SCREEN) std FTXUI_EXPORT(SCREEN) std std::wstring to_wstring(T s)
Definition string.hpp:18
size_t GlyphIterate(std::string_view input, int glyph_offset, size_t start)
Definition string.cpp:443
bool IsFullWidth(uint32_t ucs)
Definition string.cpp:250
size_t GlyphNext(std::string_view input, size_t start)
Definition string.cpp:417
constexpr std::array< Interval, 123 > g_full_width_characters
constexpr std::array< WordBreakPropertyInterval, 1100 > g_word_break_intervals