terminal_width/
terminal_width.rs

1//! Example: Calculate terminal display width of strings
2//!
3//! This example demonstrates how to calculate the display width of strings
4//! for terminal applications, handling mixed ASCII and CJK text.
5
6use east_asian_width::east_asian_width;
7
8/// Calculate the display width of a string for terminal rendering
9fn terminal_width(text: &str) -> usize {
10    text.chars()
11        .map(|c| east_asian_width(c as u32).as_usize())
12        .sum()
13}
14
15/// Calculate width with configurable ambiguous character handling
16fn terminal_width_with_config(text: &str, ambiguous_as_wide: bool) -> usize {
17    text.chars()
18        .map(|c| east_asian_width((c as u32, ambiguous_as_wide)).as_usize())
19        .sum()
20}
21
22/// Pad a string to a specific width for terminal alignment
23fn pad_to_width(text: &str, target_width: usize) -> String {
24    let current_width = terminal_width(text);
25    if current_width >= target_width {
26        text.to_string()
27    } else {
28        let padding = " ".repeat(target_width - current_width);
29        format!("{}{}", text, padding)
30    }
31}
32
33/// Truncate a string to fit within a specific width
34fn truncate_to_width(text: &str, max_width: usize) -> String {
35    let mut result = String::new();
36    let mut current_width = 0;
37
38    for ch in text.chars() {
39        let char_width = east_asian_width(ch as u32).as_usize();
40        if current_width + char_width > max_width {
41            break;
42        }
43        result.push(ch);
44        current_width += char_width;
45    }
46
47    result
48}
49
50fn main() {
51    // Example strings with different character types
52    let examples = vec![
53        "Hello World",       // ASCII only
54        "こんにちは",        // Japanese Hiragana
55        "你好世界",          // Chinese
56        "Hello 世界",        // Mixed ASCII and CJK
57        "Café naïve résumé", // ASCII with accents
58        "αβγδε",             // Greek (ambiguous)
59        "Abc123",      // Fullwidth ASCII
60    ];
61
62    println!("Terminal Width Examples");
63    println!("====================");
64    println!();
65
66    // Basic width calculation
67    println!("Basic Width Calculation:");
68    for text in &examples {
69        let width = terminal_width(text);
70        println!("  {:20} → {} columns", format!("\"{}\"", text), width);
71    }
72    println!();
73
74    // Ambiguous character handling
75    println!("Ambiguous Character Handling:");
76    let ambiguous_text = "αβγ→←";
77    println!("  Text: \"{}\"", ambiguous_text);
78    println!(
79        "  Narrow: {} columns",
80        terminal_width_with_config(ambiguous_text, false)
81    );
82    println!(
83        "  Wide:   {} columns",
84        terminal_width_with_config(ambiguous_text, true)
85    );
86    println!();
87
88    // Padding example
89    println!("Padding to Fixed Width (20 columns):");
90    for text in &examples {
91        let padded = pad_to_width(text, 20);
92        println!("  |{}|", padded);
93    }
94    println!();
95
96    // Truncation example
97    println!("Truncation to Fixed Width (10 columns):");
98    for text in &examples {
99        let truncated = truncate_to_width(text, 10);
100        let width = terminal_width(&truncated);
101        println!(
102            "  {:20} → \"{}\" ({} columns)",
103            format!("\"{}\"", text),
104            truncated,
105            width
106        );
107    }
108    println!();
109
110    // Table formatting example
111    println!("Table Formatting Example:");
112    let data = vec![
113        ("Name", "Language", "Greeting"),
114        ("Alice", "English", "Hello"),
115        ("田中", "日本語", "こんにちは"),
116        ("李明", "中文", "你好"),
117        ("김철수", "한국어", "안녕하세요"),
118    ];
119
120    // Calculate column widths
121    let col_widths = [12, 10, 15];
122
123    for (i, row) in data.iter().enumerate() {
124        if i == 1 {
125            // Print separator
126            println!("  {}", "-".repeat(col_widths.iter().sum::<usize>() + 6));
127        }
128
129        let formatted_row = format!(
130            "  {} | {} | {}",
131            pad_to_width(row.0, col_widths[0]),
132            pad_to_width(row.1, col_widths[1]),
133            pad_to_width(row.2, col_widths[2])
134        );
135        println!("{}", formatted_row);
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_terminal_width() {
145        assert_eq!(terminal_width("Hello"), 5);
146        assert_eq!(terminal_width("こんにちは"), 10); // 5 chars × 2 width
147        assert_eq!(terminal_width("Hello世界"), 9); // 5×1 + 2×2
148    }
149
150    #[test]
151    fn test_padding() {
152        assert_eq!(pad_to_width("Hi", 5), "Hi   ");
153        assert_eq!(pad_to_width("こんにちは", 10), "こんにちは");
154        assert_eq!(pad_to_width("Hi", 2), "Hi");
155    }
156
157    #[test]
158    fn test_truncation() {
159        assert_eq!(truncate_to_width("Hello World", 5), "Hello");
160        assert_eq!(truncate_to_width("こんにちは", 6), "こんに");
161        assert_eq!(truncate_to_width("Hello世界", 7), "Hello世");
162    }
163}