Text width
Terminal layout uses display cells, not UTF-8 bytes, Unicode scalar values, or Rust string length. Urushi segments text into grapheme clusters and keeps each cluster atomic during wrapping and clipping.
See width-sensitive behavior
Section titled “See width-sensitive behavior”8 cells 日本語 statusGrapheme-safe wrapping CJK, combining sequences, and emoji stay atomic at line edges.
a→ b 12345678 next rowTabs and finite width Configure tab stops and wrap against terminal-cell columns.
Run the complete Text width quickstart ↓
Quickstart
Section titled “Quickstart”cargo new width-democd width-democargo add urushiReplace src/main.rs with:
use std::error::Error;
use urushi::{ Available, PrintableText, RenderSettings, TextStyle, View, render, resolve,};
fn main() -> Result<(), Box<dyn Error>> { assert_eq!(PrintableText::new("日本語").width(), 6);
let view = View::text("日本語 status", TextStyle::new()); let resolved = resolve(&view, Available::columns(8))?; print!("{}", render(&resolved, &RenderSettings::default())); Ok(())}Run it with cargo run:
日本語statusThe three Japanese graphemes occupy six cells. Wrapping keeps every grapheme
whole and removes the break-space before status.
How terminal width works
Section titled “How terminal width works”One visible grapheme may contain several Unicode scalar values and occupy zero, one, or two terminal cells. This affects CJK text, combining marks, and emoji sequences.
Urushi applies one width model to wrapping, alignment, grids, padding, borders, dimensions, Canvas clipping, and Ratatui target rectangles. A wide grapheme is never split between rows or left as a half-cell at an edge.
str::len() counts bytes. Do not use it as a cursor column unless the input is
known to be ASCII. Use Urushi’s printable text and grapheme types for terminal
measurement.
Raw ANSI and cursor controls are not text. They break measurement and are rejected by plain-text constructors. Express appearance through Styles and terminal actions through the appropriate terminal API.