| 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 |
| 35 | extern "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 | */ |
| 69 | GHOSTTY_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 | */ |
| 144 | GHOSTTY_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 */ |