libghostty_vt/unicode.rs
1//! Unicode terminal-width helpers.
2
3use crate::ffi;
4
5/// Returns the terminal display width of a Unicode codepoint in terminal grid cells.
6///
7/// This is the same width table the terminal itself uses when laying out
8/// printed text, so callers can predict column layout that exactly matches what
9/// the terminal will do when the text is actually written to it.
10///
11/// Returns 0 for zero-width characters, 2 for wide characters, and 1 for
12/// everything else. This operates on a single character only and therefore
13/// cannot account for grapheme-cluster-level width rules. For cluster-accurate
14/// widths, use [`grapheme_width`]. Summing per-character widths is only correct
15/// when mode 2027, grapheme clustering, is disabled.
16///
17/// This function is pure, allocates nothing, and is thread-safe.
18#[must_use]
19pub fn codepoint_width(codepoint: char) -> u8 {
20 unsafe { ffi::ghostty_unicode_codepoint_width(u32::from(codepoint)) }
21}
22
23/// Measures the terminal display width of the first grapheme cluster in a sequence.
24///
25/// This uses the exact same grapheme segmentation and cluster width rules the
26/// terminal itself uses when printing text with grapheme clustering enabled,
27/// mode 2027, so callers can predict column layout that exactly matches what
28/// the terminal will do when the text is actually written to it. Unlike
29/// [`codepoint_width`], this accounts for cluster-level rules: emoji variation
30/// selectors, ZWJ sequences, combining marks, and skin tone modifiers.
31///
32/// Reads characters until the terminal would consider the grapheme cluster
33/// complete and returns the number of characters consumed with the cluster's
34/// total width in cells. Returns `(0, 0)` if and only if the input is empty.
35///
36/// This is not a streaming API. The provided sequence must contain a complete
37/// first grapheme cluster, or the logical end of the string. If input arrives in
38/// chunks, keep buffering while this function consumes all available characters
39/// and the stream may still continue; a later character could still extend the
40/// cluster and change its width.
41///
42/// Mode dependence: this models mode 2027, grapheme clustering, enabled. When
43/// mode 2027 is disabled, clusters never combine and variation selectors never
44/// change width; predict layout in that case by summing [`codepoint_width`] over
45/// each character instead.
46#[must_use]
47pub fn grapheme_width(chars: &[char]) -> (usize, u8) {
48 let codepoints = chars.iter().copied().map(u32::from).collect::<Vec<_>>();
49 let mut width = 0;
50 let consumed = unsafe {
51 ffi::ghostty_unicode_grapheme_width(codepoints.as_ptr(), codepoints.len(), &raw mut width)
52 };
53 (consumed, width)
54}