text_fx/
presentation.rs

1#[inline]
2pub fn display_width(s: &str) -> usize {
3    s.chars().map(char_display_width).sum()
4}
5
6pub fn char_display_width(c: char) -> usize {
7    use std::cmp::Ordering::*;
8
9    match c {
10        '\u{0000}' => 0,                           // NULL
11        '\u{0001}'..='\u{001F}' | '\u{007F}' => 0, // Control characters
12
13        '\u{0300}'..='\u{036F}'
14        | '\u{1AB0}'..='\u{1AFF}'
15        | '\u{1DC0}'..='\u{1DFF}'
16        | '\u{20D0}'..='\u{20FF}'
17        | '\u{FE20}'..='\u{FE2F}' => 0, // Combining characters
18
19        _ => match unicode_width_hint(c) {
20            Less => 1,
21            Equal | Greater => 2,
22        },
23    }
24}
25
26// crude unicode width hinting: double-width for CJK, otherwise single
27fn unicode_width_hint(c: char) -> std::cmp::Ordering {
28    match c as u32 {
29        0x1100..=0x115F
30        | 0x2329
31        | 0x232A
32        | 0x2E80..=0xA4CF
33        | 0xAC00..=0xD7A3
34        | 0xF900..=0xFAFF
35        | 0xFE10..=0xFE19
36        | 0xFE30..=0xFE6F
37        | 0xFF00..=0xFF60
38        | 0xFFE0..=0xFFE6
39        | 0x1F300..=0x1F64F
40        | 0x1F900..=0x1F9FF => std::cmp::Ordering::Greater,
41        _ => std::cmp::Ordering::Less,
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_display_width() {
51        for (s, expected) in [
52            ("hello", 5), // ASCII → width 5
53            ("你好", 4),  // CJK → width 4
54            ("á", 1),     // 'a' + combining accent → width 1
55        ] {
56            assert_eq!(display_width(s), expected);
57        }
58    }
59}