text_alignment/
text_alignment.rs

1//! Example: Text alignment for mixed-width characters
2//!
3//! This example shows how to align text containing both narrow and wide
4//! characters, which is common in internationalized applications.
5
6use east_asian_width::east_asian_width;
7
8/// Text alignment options
9#[derive(Debug, Clone, Copy)]
10enum Alignment {
11    Left,
12    Right,
13    Center,
14}
15
16/// Calculate the display width of a string
17fn display_width(text: &str) -> usize {
18    text.chars()
19        .map(|c| east_asian_width(c as u32).as_usize())
20        .sum()
21}
22
23/// Align text within a given width
24fn align_text(text: &str, width: usize, alignment: Alignment) -> String {
25    let text_width = display_width(text);
26
27    if text_width >= width {
28        return text.to_string();
29    }
30
31    let padding = width - text_width;
32
33    match alignment {
34        Alignment::Left => format!("{}{}", text, " ".repeat(padding)),
35        Alignment::Right => format!("{}{}", " ".repeat(padding), text),
36        Alignment::Center => {
37            let left_padding = padding / 2;
38            let right_padding = padding - left_padding;
39            format!(
40                "{}{}{}",
41                " ".repeat(left_padding),
42                text,
43                " ".repeat(right_padding)
44            )
45        }
46    }
47}
48
49/// Create a formatted table with proper alignment
50fn format_table(
51    headers: &[&str],
52    rows: &[Vec<&str>],
53    widths: &[usize],
54    alignments: &[Alignment],
55) -> String {
56    let mut result = String::new();
57
58    // Header
59    result.push('┌');
60    for (i, &width) in widths.iter().enumerate() {
61        result.push_str(&"─".repeat(width + 2));
62        if i < widths.len() - 1 {
63            result.push('┬');
64        }
65    }
66    result.push_str("┐\n");
67
68    // Header row
69    result.push('│');
70    for (&header, (&width, &alignment)) in headers.iter().zip(widths.iter().zip(alignments.iter()))
71    {
72        result.push(' ');
73        result.push_str(&align_text(header, width, alignment));
74        result.push_str(" │");
75    }
76    result.push('\n');
77
78    // Separator
79    result.push('├');
80    for (i, &width) in widths.iter().enumerate() {
81        result.push_str(&"─".repeat(width + 2));
82        if i < widths.len() - 1 {
83            result.push('┼');
84        }
85    }
86    result.push_str("┤\n");
87
88    // Data rows
89    for row in rows {
90        result.push('│');
91        for (&cell, (&width, &alignment)) in row.iter().zip(widths.iter().zip(alignments.iter())) {
92            result.push(' ');
93            result.push_str(&align_text(cell, width, alignment));
94            result.push_str(" │");
95        }
96        result.push('\n');
97    }
98
99    // Bottom border
100    result.push('└');
101    for (i, &width) in widths.iter().enumerate() {
102        result.push_str(&"─".repeat(width + 2));
103        if i < widths.len() - 1 {
104            result.push('┴');
105        }
106    }
107    result.push_str("┘\n");
108
109    result
110}
111
112/// Create a simple menu with aligned options
113fn create_menu(title: &str, options: &[(&str, &str)], width: usize) -> String {
114    let mut result = String::new();
115
116    // Title
117    result.push_str(&format!("┌{}┐\n", "─".repeat(width + 2)));
118    result.push_str(&format!(
119        "│ {} │\n",
120        align_text(title, width, Alignment::Center)
121    ));
122    result.push_str(&format!("├{}┤\n", "─".repeat(width + 2)));
123
124    // Options
125    for (key, description) in options {
126        let option_text = format!("{}: {}", key, description);
127        result.push_str(&format!(
128            "│ {} │\n",
129            align_text(&option_text, width, Alignment::Left)
130        ));
131    }
132
133    result.push_str(&format!("└{}┘\n", "─".repeat(width + 2)));
134    result
135}
136
137fn main() {
138    println!("Text Alignment Examples");
139    println!("======================");
140    println!();
141
142    // Basic alignment examples
143    let texts = vec![
144        "Hello",
145        "こんにちは",
146        "你好世界",
147        "Hello 世界",
148        "Café",
149        "αβγδε",
150    ];
151
152    println!("Basic Alignment (width: 20):");
153    println!("Left aligned:");
154    for text in &texts {
155        println!("  |{}|", align_text(text, 20, Alignment::Left));
156    }
157    println!();
158
159    println!("Right aligned:");
160    for text in &texts {
161        println!("  |{}|", align_text(text, 20, Alignment::Right));
162    }
163    println!();
164
165    println!("Center aligned:");
166    for text in &texts {
167        println!("  |{}|", align_text(text, 20, Alignment::Center));
168    }
169    println!();
170
171    // Table example
172    println!("Table Example:");
173    let headers = vec!["ID", "Name", "Language", "Greeting"];
174    let rows = vec![
175        vec!["1", "Alice", "English", "Hello"],
176        vec!["2", "田中太郎", "日本語", "こんにちは"],
177        vec!["3", "李小明", "中文", "你好"],
178        vec!["4", "김철수", "한국어", "안녕하세요"],
179        vec!["5", "José", "Español", "¡Hola!"],
180    ];
181    let widths = vec![3, 12, 10, 15];
182    let alignments = vec![
183        Alignment::Right,  // ID (numbers)
184        Alignment::Left,   // Name
185        Alignment::Center, // Language
186        Alignment::Left,   // Greeting
187    ];
188
189    let table = format_table(&headers, &rows, &widths, &alignments);
190    println!("{}", table);
191
192    // Menu example
193    println!("Menu Example:");
194    let menu_options = vec![
195        ("1", "新建文件 (New File)"),
196        ("2", "打开文件 (Open File)"),
197        ("3", "保存文件 (Save File)"),
198        ("4", "退出程序 (Exit)"),
199    ];
200    let menu = create_menu("文件菜单 (File Menu)", &menu_options, 30);
201    println!("{}", menu);
202
203    // Progress bar example
204    println!("Progress Bar Example:");
205    let progress_values = vec![0, 25, 50, 75, 100];
206    for progress in progress_values {
207        let bar_width = 30;
208        let filled = (progress * bar_width) / 100;
209        let empty = bar_width - filled;
210        let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
211        let label = format!("进度 (Progress): {}%", progress);
212        println!("  {} [{}]", align_text(&label, 20, Alignment::Left), bar);
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_display_width() {
222        assert_eq!(display_width("Hello"), 5);
223        assert_eq!(display_width("こんにちは"), 10);
224        assert_eq!(display_width("Hello世界"), 9);
225    }
226
227    #[test]
228    fn test_align_left() {
229        assert_eq!(align_text("Hi", 5, Alignment::Left), "Hi   ");
230        assert_eq!(
231            align_text("こんにちは", 12, Alignment::Left),
232            "こんにちは  "
233        );
234    }
235
236    #[test]
237    fn test_align_right() {
238        assert_eq!(align_text("Hi", 5, Alignment::Right), "   Hi");
239        assert_eq!(
240            align_text("こんにちは", 12, Alignment::Right),
241            "  こんにちは"
242        );
243    }
244
245    #[test]
246    fn test_align_center() {
247        assert_eq!(align_text("Hi", 6, Alignment::Center), "  Hi  ");
248        assert_eq!(align_text("Hi", 5, Alignment::Center), " Hi  ");
249        assert_eq!(
250            align_text("こんにちは", 12, Alignment::Center),
251            " こんにちは "
252        );
253    }
254}