skip to content
==> 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}