1/**
2 * @file unicode.h
3 *
4 * Unicode utilities - codepoint properties matching the terminal's
5 * text layout semantics.
6 */
7
8#ifndef GHOSTTY_VT_UNICODE_H
9#define GHOSTTY_VT_UNICODE_H
10
11/** @defgroup unicode Unicode Utilities
12 *
13 * Unicode codepoint properties matching the terminal's text layout
14 * semantics.
15 *
16 * ## Basic Usage
17 *
18 * Use ghostty_unicode_codepoint_width() to determine how many terminal
19 * grid cells a codepoint occupies, using the exact same width table the
20 * terminal itself uses when laying out printed text. Use
21 * ghostty_unicode_grapheme_width() to segment and measure full grapheme
22 * clusters with the same rules the terminal uses when mode 2027 is
23 * enabled. These functions are useful for predicting column layout of
24 * text that has not yet been written to the terminal, such as IME
25 * preedit (composition) overlays.
26 *
27 * @{
28 */
29
30#include <stddef.h>
31#include <stdint.h>
32#include <ghostty/vt/types.h>
33
34#ifdef __cplusplus
35extern "C" {
36#endif
37
38/**
39 * Returns the terminal display width of a Unicode codepoint in
40 * terminal grid cells: 0, 1, or 2.
41 *
42 * This is the same width table the terminal itself uses when laying
43 * out printed text, so callers can predict column layout (e.g. IME
44 * preedit overlays) that exactly matches what the terminal will do
45 * when the text is actually written to it.
46 *
47 * Semantics:
48 * - Returns 0 for zero-width codepoints: C0/C1 control characters,
49 * nonspacing and enclosing combining marks, default-ignorable
50 * codepoints (ZWJ, ZWNJ, variation selectors, etc.), and
51 * surrogate codepoints.
52 * - Returns 2 for wide codepoints: East Asian Wide/Fullwidth
53 * (including emoji with default emoji presentation) and regional
54 * indicators. Width is clamped to 2 (e.g. the three-em dash).
55 * - Returns 1 for everything else, including invalid codepoints
56 * beyond U+10FFFF (this function is total; it never fails).
57 *
58 * This operates on a single codepoint only and therefore cannot account
59 * for grapheme-cluster-level width rules (VS16 emoji presentation,
60 * combining sequences, etc.). For cluster-accurate widths, use
61 * ghostty_unicode_grapheme_width(). Summing per-codepoint widths is only
62 * correct when mode 2027 (grapheme clustering) is disabled.
63 *
64 * This function is pure, allocates nothing, and is thread-safe.
65 *
66 * @param cp The Unicode codepoint to measure
67 * @return Display width in cells: 0, 1, or 2
68 */
69GHOSTTY_API uint8_t ghostty_unicode_codepoint_width(uint32_t cp);
70
71/**
72 * Measures the terminal display width of the first grapheme cluster in a
73 * sequence of Unicode codepoints.
74 *
75 * This uses the exact same grapheme segmentation and cluster width rules
76 * the terminal itself uses when printing text with grapheme clustering
77 * enabled (mode 2027), so callers can predict column layout (e.g. IME
78 * preedit overlays) that exactly matches what the terminal will do when
79 * the text is actually written to it. Unlike
80 * ghostty_unicode_codepoint_width(), this accounts for cluster-level
81 * rules: emoji variation selectors, ZWJ sequences, combining marks, and
82 * skin tone modifiers.
83 *
84 * Reads codepoints from cps until the terminal would consider the
85 * grapheme cluster complete, stores the cluster's total width in cells
86 * (0, 1, or 2) into width (which may be NULL if only segmentation is
87 * desired), and returns the number of codepoints consumed. Returns 0 if
88 * and only if len is 0; otherwise consumes at least one codepoint. Measure
89 * a whole string by calling in a loop:
90 *
91 * @code
92 * size_t total = 0;
93 * for (size_t i = 0; i < len;) {
94 * uint8_t width;
95 * i += ghostty_unicode_grapheme_width(cps + i, len - i, &width);
96 * total += width;
97 * }
98 * @endcode
99 *
100 * This is not a streaming API. The provided sequence must contain a
101 * complete first grapheme cluster, or the logical end of the string. If
102 * input arrives in chunks, keep buffering while this function consumes all
103 * available codepoints (return value == len) and the stream may still
104 * continue; a later codepoint could still extend the cluster and change
105 * its width.
106 *
107 * Width semantics, matching the terminal with mode 2027 enabled:
108 * - The cluster starts at the width of its first codepoint, as returned by
109 * ghostty_unicode_codepoint_width().
110 * - VS16 (U+FE0F) forces the cluster wide (2) and VS15 (U+FE0E) forces it
111 * narrow (1), but only when the immediately preceding codepoint in the
112 * cluster is a valid emoji variation sequence base (per Unicode
113 * emoji-variation-sequences.txt). Invalid variation selectors are
114 * ignored entirely.
115 * - Any other continuation codepoint that contributes to grapheme width
116 * forces the cluster wide (2). Note this means cluster width is NOT the
117 * maximum of per-codepoint widths: some continuation marks have narrow
118 * codepoint width yet still widen the cluster.
119 *
120 * Mode dependence: this models mode 2027 (grapheme clustering) enabled,
121 * which is Ghostty's recommended configuration. When mode 2027 is
122 * disabled, clusters never combine and variation selectors never change
123 * width; predict layout in that case by summing
124 * ghostty_unicode_codepoint_width() over each codepoint instead.
125 *
126 * Edge cases:
127 * - Codepoints beyond U+10FFFF consume one codepoint, have width 1, and
128 * are always cluster boundaries. This function is total; it never fails.
129 * - Control characters (C0/C1, CR, LF) are never printed through the
130 * terminal's text path; passing them here returns an unspecified (but
131 * stable and bounded) result.
132 * - A cluster whose first codepoint is zero-width (e.g. a lone combining
133 * mark) is malformed at a cell start; the terminal may attach it to
134 * earlier screen content. This function reports the fold result for the
135 * sequence in isolation (typically 0).
136 *
137 * This function is pure, allocates nothing, and is thread-safe.
138 *
139 * @param cps Pointer to codepoints (may be NULL only when len is 0)
140 * @param len Number of codepoints available
141 * @param width Out: cluster display width in cells (0-2); may be NULL
142 * @return Number of codepoints in the first grapheme cluster
143 */
144GHOSTTY_API size_t ghostty_unicode_grapheme_width(const uint32_t *cps,
145 size_t len,
146 uint8_t *width);
147
148#ifdef __cplusplus
149}
150#endif
151
152/** @} */
153
154#endif /* GHOSTTY_VT_UNICODE_H */