Skip to main content

Text and width

A terminal cell is not a char. CJK and emoji occupy two columns, combining marks occupy none, a surrogate pair is one symbol made of two char values, and a flag emoji is one symbol made of four. string.Length answers none of the questions a layout asks.

So a cell on the surface holds a whole grapheme cluster — one symbol as a reader would count it — and every measurement the framework makes is in terminal columns. Every flow and absolute call clips, aligns and pads on that measure, which is why a box drawn around 日本語 closes where it should.

TextWidth

TextWidth is that measure, public for your own layout code.

Measuring

CallMeaning
Of(text)Width in columns
CountClusters(text)How many symbols the text is made of, as a reader would count them
OfCluster(span)Width of one grapheme cluster
OfRune(rune)Width of one code point, before combining marks are taken into account

Cutting

CallMeaning
Truncate(text, width)Cuts to a column width on a symbol boundary
TruncateStart(text, width)The same from the other end, keeping the tail — what a field scrolled to the right shows
Wrap(text, width)Breaks into lines that fit, at spaces where there is one and mid-word only when a single word is wider than the space; existing line breaks are kept

Padding

CallMeaning
PadRight(text, width)Pads on the right, left-aligning in that width
PadLeft(text, width)Pads on the left, right-aligning in that width

Walking

These are what a text field uses to move a caret without cutting a symbol in half.

CallMeaning
NextClusterLength(text, index)How many char values the next symbol occupies
NextClusterEnd(text, index)Where the symbol at an index ends — what a forward delete removes
PreviousClusterStart(text, index)Where the symbol before an index starts — what a backspace removes
SnapToCluster(text, index)Pulls a position back to the start of the symbol it lands in

Use them instead of the obvious thing

Instead ofUse
text.LengthTextWidth.Of(text)
text.PadRight(n)TextWidth.PadRight(text, n)
text[..n]TextWidth.Truncate(text, n)
caret - 1 on backspaceTextWidth.PreviousClusterStart(text, caret)

Anywhere the result lands on screen, the column measure is the one that matters. The framework's own widgets, modals and text editing are written against these calls, and TextEditing is the shared implementation they share.

What the surface does with a wide symbol

A wide symbol occupies two cells. Writing over either half clears the other, so a narrow character cannot be left sitting beside an orphaned half. One that would be split by the right edge is dropped rather than half-drawn.

Styles

TextStyle is a [Flags] enum carried by every style:

FlagNote
NoneNo attributes
BoldSome terminals render it as a brighter color instead
ItalicThe least widely supported of the four
Underline
DimThe opposite of Bold

They combine: TextStyle.Bold | TextStyle.Underline.