| Crates.io | string-width |
| lib.rs | string-width |
| version | 0.1.0 |
| created_at | 2025-09-07 22:57:22.95121+00 |
| updated_at | 2025-09-07 22:57:22.95121+00 |
| description | Accurate Unicode string width calculation for terminal applications, handling emoji, East Asian characters, combining marks, and ANSI escape sequences |
| homepage | https://github.com/sabry-awad97/string-width |
| repository | https://github.com/sabry-awad97/string-width |
| max_upload_size | |
| id | 1828596 |
| size | 107,549 |
Accurate Unicode string width calculation for terminal applications. This library correctly handles:
Add this to your Cargo.toml:
[dependencies]
string-width = "0.1.0"
Minimum Supported Rust Version (MSRV): 1.85
use string_width::string_width;
// ASCII text
assert_eq!(string_width("Hello"), 5);
// East Asian characters (full-width)
assert_eq!(string_width("δ½ ε₯½"), 4);
// Emoji
assert_eq!(string_width("π"), 2);
assert_eq!(string_width("πΊπΈ"), 2); // Flag
assert_eq!(string_width("1οΈβ£"), 2); // Keycap sequence
// Mixed content
assert_eq!(string_width("Hello π δΈη"), 13);
// ANSI escape sequences are ignored by default
assert_eq!(string_width("\x1b[31mRed\x1b[0m"), 3);
use string_width::{string_width, string_width_with_options, StringWidthOptions, AmbiguousWidthTreatment};
// Direct configuration
let options = StringWidthOptions {
count_ansi: true, // Count ANSI escape sequences
ambiguous_width: AmbiguousWidthTreatment::Wide, // Treat ambiguous as wide
};
assert_eq!(string_width_with_options("Β±ΓΓ·", options.clone()), 6); // Ambiguous chars as wide
assert_eq!(string_width_with_options("\x1b[31mRed\x1b[0m", options), 12); // Count ANSI
// Using the builder pattern (recommended)
let options = StringWidthOptions::builder()
.count_ansi(true)
.ambiguous_as_wide()
.build();
assert_eq!(string_width_with_options("Β±ΓΓ·", options), 6);
use string_width::{string_width, StringWidthOptions, AmbiguousWidthTreatment};
// Fluent builder API for easy configuration
let options = StringWidthOptions::builder()
.count_ansi(true)
.ambiguous_as_wide()
.build();
let text = "\x1b[31mΒ±ΓΓ·\x1b[0m";
assert_eq!(string_width((text, options)), 14); // ANSI + wide ambiguous
// Convenience methods for common configurations
let narrow_options = StringWidthOptions::builder()
.ambiguous_as_narrow()
.build();
let wide_options = StringWidthOptions::builder()
.ambiguous_as_wide()
.build();
// Equivalent to direct construction but more readable
let manual_options = StringWidthOptions {
count_ansi: true,
ambiguous_width: AmbiguousWidthTreatment::Wide,
};
use string_width::{DisplayWidth, StringWidthOptions};
let text = "Hello π";
println!("Width: {}", text.display_width());
// With custom options using builder pattern
let options = StringWidthOptions::builder()
.count_ansi(false)
.ambiguous_as_narrow()
.build();
println!("Width: {}", text.display_width_with_options(options));
// Works with both &str and String
let owned_string = String::from("Hello π");
println!("Width: {}", owned_string.display_width());
Perfect for building CLI tools that need proper text alignment:
use string_width::DisplayWidth;
fn align_text(text: &str, width: usize) -> String {
let text_width = text.display_width();
let padding = width.saturating_sub(text_width);
format!("{}{}", text, " ".repeat(padding))
}
// Works correctly with Unicode content
println!("β{}β", align_text("Hello", 10)); // βHello β
println!("β{}β", align_text("δ½ ε₯½", 10)); // βδ½ ε₯½ β
println!("β{}β", align_text("πΊπΈ USA", 10)); // βπΊπΈ USA β
use string_width::DisplayWidth;
let data = vec![
("Name", "Country", "Greeting"),
("Alice", "πΊπΈ USA", "Hello!"),
("η°δΈ", "π―π΅ Japan", "γγγ«γ‘γ―"),
];
// Calculate column widths
let widths: Vec<usize> = (0..3)
.map(|col| data.iter().map(|row| {
match col {
0 => row.0.display_width(),
1 => row.1.display_width(),
2 => row.2.display_width(),
_ => 0,
}
}).max().unwrap_or(0))
.collect();
// Print aligned table
for (name, country, greeting) in data {
println!("β {:width0$} β {:width1$} β {:width2$} β",
name, country, greeting,
width0 = widths[0], width1 = widths[1], width2 = widths[2]
);
}
The library includes comprehensive examples demonstrating various use cases:
# Basic usage examples - demonstrates core functionality
cargo run --example basic_usage
# Terminal formatting and alignment - shows real-world usage
cargo run --example terminal_formatting
# CLI tool for measuring text width - interactive width analysis
cargo run --example cli_tool -- "Hello π World"
echo "Hello π World" | cargo run --example cli_tool
| Feature | Example | Width |
|---|---|---|
| ASCII | "Hello" |
5 |
| East Asian | "δ½ ε₯½" |
4 |
| Emoji | "π" |
2 |
| Emoji sequences | "π¨βπ©βπ§βπ¦" |
2 |
| Keycap sequences | "1οΈβ£" |
2 |
| Flag sequences | "πΊπΈ" |
2 |
| Combining marks | "Γ©" (e + Β΄) |
1 |
| Zero-width chars | "a\u{200B}b" |
2 |
| ANSI codes | "\x1b[31mRed\x1b[0m" |
3 |
The library is designed with a modular architecture:
width_calculation: Core width calculation logic and public APIcharacter_classification: Unicode character categorization and analysisemoji: Emoji sequence detection and handlingoptions: Configuration types and builder patternunicode_constants: Precomputed Unicode property tablesThe library is optimized for performance:
The library provides multiple ways to calculate string width:
| Function/Trait | Use Case | Example |
|---|---|---|
string_width() |
Simple width calculation | string_width("Hello") |
DisplayWidth |
Ergonomic trait-based API | "Hello".display_width() |
StringWidthInput |
Legacy compatibility | "Hello".calculate_width() |
| Builder Pattern | Complex configuration | StringWidthOptions::builder().build() |
| Library | Emoji Support | East Asian | ANSI Handling | Combining Marks | Builder API |
|---|---|---|---|---|---|
| string-width | β Full | β Yes | β Configurable | β Yes | β Yes |
| unicode-width | β Basic | β Yes | β No | β Yes | β No |
| textwrap | β No | β Yes | β No | β Yes | β No |
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under either of
at your option.