unicode_util — display widths and wrapping
Terminal cells are a fixed-width grid, but Unicode text isn’t: CJK
ideographs take 2 columns, emoji take 2, flag pairs and ZWJ sequences
collapse to one grapheme of width 2, and variation selectors are zero-width.
unicode_util measures text in terminal columns correctly and wraps text
to a width, so renderables don’t corrupt layout with wide or combining
characters.
The module uses finl_unicode::grapheme_clusters::Graphemes to split text
into grapheme clusters and unicode-width to measure each cluster, with
explicit handling for emoji presentation.
Measuring widths
pub fn grapheme_display_width(g: &str) -> u16 // 1 or 2 columns
pub fn char_display_width(c: char) -> u16 // simple per-char width (0/1/2)
pub fn str_display_width(s: &str) -> usize // sum of grapheme widths
pub fn graphemes_with_width(text: &str) -> impl Iterator<Item = (&str, u16)> + '_
The width rules, per grapheme:
| Grapheme | Width |
|---|---|
| ASCII, most Latin | 1 |
| CJK ideographs | 2 |
| Emoji | 2 |
| Flag pair (two Regional Indicators, 🇧🇷) | 2 |
| ZWJ sequence (🏳️🌈) | 2 (the whole sequence is one grapheme) |
| Grapheme containing U+FE0F (VS-16, emoji presentation) | forced 2 |
| Grapheme containing U+FE0E (VS-15, text presentation) | forced 1 |
| Zero-width (standalone combining mark) | forced 1 (avoids render-loop stalls) |
graphemes_with_width is the primary rendering primitive: instead of
iterating text.chars(), renderables iterate this to position each visual
unit, marking CellDiffOption::Skip on the extra columns of a wide grapheme.
Wrapping
pub fn word_wrap(text: &str, max_width: u16) -> Vec<String>
Wraps text to fit max_width columns, keeping words intact where possible.
Words longer than the whole line are broken at character level so no line
exceeds the width. Newlines are respected.
assert_eq!(word_wrap("a b c", 3), vec!["a b".to_string(), "c".to_string()]);
assert_eq!(word_wrap("中文字", 4), vec!["中文".to_string(), "字".to_string()]); // 2-col chars
Example — measuring mixed text
use cosh_tui::core::lib::unicode_util::{str_display_width, grapheme_display_width, word_wrap};
assert_eq!(str_display_width("abc"), 3);
assert_eq!(str_display_width("中文字"), 6); // three 2-col chars
assert_eq!(str_display_width("a中b"), 4);
assert_eq!(str_display_width("🇧🇷"), 2); // flag pair = one grapheme
assert_eq!(grapheme_display_width("☠\u{fe0f}"), 2); // skull + VS-16 → emoji
assert_eq!(grapheme_display_width("✊\u{fe0e}"), 1); // fist + VS-15 → text
for (g, w) in graphemes_with_width("a中") {
// ("a", 1), ("中", 2)
}
Next: detect_links — URL detection.