==> src/grapheme.c <==1/* UAX #29 extended grapheme clusters, forwards: the pair rules plus the three that need context
2 * (GB9c conjuncts, GB11 emoji ZWJ sequences, GB12/13 regional indicator pairs), carried as small
3 * state machines while walking the cluster. */
4#include "tui/grapheme.h"
5
6#include "gc_tables.h"
7#include "tui/utf8.h"
8
9#include <stdbool.h>
10#include <stdint.h>
11
12static tui_gc_cat category(uint32_t c) {
13 size_t lo = 0, hi = sizeof TUI_GC_TABLE / sizeof TUI_GC_TABLE[0];
14 while (lo < hi) {
15 size_t mid = (lo + hi) / 2;
16 if (c < TUI_GC_TABLE[mid].lo) hi = mid;
17 else if (c > TUI_GC_TABLE[mid].hi) lo = mid + 1;
18 else return TUI_GC_TABLE[mid].cat;
19 }
20 return TUI_GC_Any;
21}
22
23static bool incb_extend(uint32_t c) {
24 size_t lo = 0, hi = sizeof TUI_GC_INCB_EXTEND / sizeof TUI_GC_INCB_EXTEND[0];
25 while (lo < hi) {
26 size_t mid = (lo + hi) / 2;
27 if (c < TUI_GC_INCB_EXTEND[mid][0]) hi = mid;
28 else if (c > TUI_GC_INCB_EXTEND[mid][1]) lo = mid + 1;
29 else return true;
30 }
31 return false;
32}
33
34static bool incb_linker(uint32_t c) {
35 switch (c) {
36 case 0x94D: case 0x9CD: case 0xACD: case 0xB4D: case 0xC4D: case 0xD4D: case 0x1039: case 0x17D2:
37 case 0x1A60: case 0x1B44: case 0x1BAB: case 0xA9C0: case 0xAAF6: case 0x10A3F: case 0x11133:
38 case 0x113D0: case 0x1193E: case 0x11A47: case 0x11A99: case 0x11F42:
39 return true;
40 default:
41 return false;
42 }
43}
44
45/* The context carried along the cluster. */
46typedef struct {
47 int emoji; /* 0 none, 1 ExtPict Extend*, 2 ExtPict Extend* ZWJ */
48 int incb; /* 0 none, 1 Consonant (Extend|Linker)* without a linker, 2 with one */
49 int ri; /* regional indicators in a row */
50} context;
51
52static void advance(context *x, uint32_t c, tui_gc_cat cat) {
53 if (cat == TUI_GC_Extended_Pictographic) x->emoji = 1;
54 else if (cat == TUI_GC_Extend) x->emoji = x->emoji == 1 ? 1 : 0;
55 else if (cat == TUI_GC_ZWJ) x->emoji = x->emoji == 1 ? 2 : 0;
56 else x->emoji = 0;
57
58 if (cat == TUI_GC_InCB_Consonant) x->incb = 1;
59 else if (incb_linker(c)) x->incb = x->incb ? 2 : 0;
60 else if (incb_extend(c)) x->incb = x->incb;
61 else x->incb = 0;
62
63 x->ri = cat == TUI_GC_Regional_Indicator ? x->ri + 1 : 0;
64}
65
66static bool is_break(tui_gc_cat a, tui_gc_cat b, const context *x) {
67 if (a == TUI_GC_CR && b == TUI_GC_LF) return false; /* GB3 */
68 if (a == TUI_GC_Control || a == TUI_GC_CR || a == TUI_GC_LF) return true; /* GB4 */
69 if (b == TUI_GC_Control || b == TUI_GC_CR || b == TUI_GC_LF) return true; /* GB5 */
70 if (a == TUI_GC_L && (b == TUI_GC_L || b == TUI_GC_V || b == TUI_GC_LV || b == TUI_GC_LVT)) /* GB6 */
71 return false;
72 if ((a == TUI_GC_LV || a == TUI_GC_V) && (b == TUI_GC_V || b == TUI_GC_T)) return false; /* GB7 */
73 if ((a == TUI_GC_LVT || a == TUI_GC_T) && b == TUI_GC_T) return false; /* GB8 */
74 if (b == TUI_GC_Extend || b == TUI_GC_ZWJ) return false; /* GB9 */
75 if (b == TUI_GC_SpacingMark) return false; /* GB9a */
76 if (a == TUI_GC_Prepend) return false; /* GB9b */
77 if (b == TUI_GC_InCB_Consonant && x->incb == 2) return false; /* GB9c */
78 if (a == TUI_GC_ZWJ && b == TUI_GC_Extended_Pictographic && x->emoji == 2) return false; /* GB11 */
79 if (a == TUI_GC_Regional_Indicator && b == TUI_GC_Regional_Indicator && x->ri % 2 == 1) /* GB12/13 */
80 return false;
81 return true; /* GB999 */
82}
83
84size_t tui_grapheme_next(const char *s, size_t len, size_t start) {
85 if (start >= len) return len;
86 size_t i = start;
87 uint32_t c = tui_utf8_next(s, len, &i);
88 tui_gc_cat a = category(c);
89 context x = {0, 0, 0};
90 advance(&x, c, a);
91 while (i < len) {
92 size_t at = i;
93 uint32_t d = tui_utf8_next(s, len, &i);
94 tui_gc_cat b = category(d);
95 if (is_break(a, b, &x)) return at;
96 advance(&x, d, b);
97 a = b;
98 }
99 return len;
100}