aboutsummaryrefslogtreecommitdiffstats
path: root/text/word.c
blob: e1f09774302d01c6c091df1f14098ad98c53d27b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* Word boundaries (UAX #29, Unicode 15.1): text/text.h. As with lines,
 * each boundary goes to the first rule that decides it; extends, formats
 * and joiners fold into the character before them (WB4), so the rules after
 * it see through them. */
#include "text.h"
#include <stdlib.h>

enum { OTHER = VEGA_WB_OTHER, CR = VEGA_WB_CR, LF = VEGA_WB_LF, NEWLINE = VEGA_WB_NEWLINE, EXTEND = VEGA_WB_EXTEND,
       ZWJ = VEGA_WB_ZWJ, RI = VEGA_WB_RI, FORMAT = VEGA_WB_FORMAT, KATAKANA = VEGA_WB_KATAKANA,
       HEBREW = VEGA_WB_HEBREW_LETTER, ALETTER = VEGA_WB_ALETTER, SINGLE_QUOTE = VEGA_WB_SINGLE_QUOTE,
       DOUBLE_QUOTE = VEGA_WB_DOUBLE_QUOTE, MIDNUMLET = VEGA_WB_MIDNUMLET, MIDLETTER = VEGA_WB_MIDLETTER,
       MIDNUM = VEGA_WB_MIDNUM, NUMERIC = VEGA_WB_NUMERIC, EXTENDNUMLET = VEGA_WB_EXTENDNUMLET,
       WSEGSPACE = VEGA_WB_WSEGSPACE };

static bool letter(unsigned c) { return c == ALETTER || c == HEBREW; }                       /* AHLetter */
static bool mid_letter(unsigned c) { return c == MIDLETTER || c == MIDNUMLET || c == SINGLE_QUOTE; }
static bool mid_number(unsigned c) { return c == MIDNUM || c == MIDNUMLET || c == SINGLE_QUOTE; }
static bool ignored(unsigned c) { return c == EXTEND || c == FORMAT || c == ZWJ; }
static bool line_end(unsigned c) { return c == CR || c == LF || c == NEWLINE; }

bool vega_words(const uint32_t *codes, size_t count, uint8_t *breaks) {
    breaks[0] = count > 0; /* WB1 */
    if (!count) return true;
    /* Per character: its class; its unit's (WB4); where its unit starts;
     * regional indicators ending at its unit. */
    uint8_t *raw = malloc(2 * count);
    size_t *start = malloc(2 * count * sizeof(size_t));
    if (!raw || !start) {
        free(raw);
        free(start);
        return false;
    }
    uint8_t *eff = raw + count;
    size_t *ri = start + count;
    for (size_t i = 0; i < count; ++i) {
        raw[i] = vega_word(codes[i]);
        bool folds = ignored(raw[i]) && i && !line_end(raw[start[i - 1]]);
        eff[i] = folds ? eff[i - 1] : raw[i];
        start[i] = folds ? start[i - 1] : i;
        ri[i] = folds ? ri[i - 1] : eff[i] == RI ? (i ? ri[i - 1] : 0) + 1 : 0;
    }
    for (size_t i = 1; i < count; ++i) {
        unsigned a = raw[i - 1], b = raw[i], A = eff[i - 1];
        size_t unit = start[i - 1], next = i + 1;
        while (next < count && start[next] != next) next++;
        unsigned after = next < count ? eff[next] : OTHER, earlier = unit ? eff[unit - 1] : OTHER;
        bool join;
        if (a == CR && b == LF) join = true;                                          /* WB3 */
        else if (line_end(a) || line_end(b)) join = false;                            /* WB3a, WB3b */
        else if (a == ZWJ && vega_extended_pictographic(codes[i])) join = true;      /* WB3c */
        else if (a == WSEGSPACE && b == WSEGSPACE) join = true;                       /* WB3d */
        else if (ignored(b)) join = true;                                             /* WB4 */
        else if (letter(A) && letter(b)) join = true;                                 /* WB5 */
        else if (letter(A) && (mid_letter(b)) && letter(after)) join = true;          /* WB6 */
        else if (mid_letter(A) && letter(b) && unit && letter(earlier)) join = true;  /* WB7 */
        else if (A == HEBREW && b == SINGLE_QUOTE) join = true;                       /* WB7a */
        else if (A == HEBREW && b == DOUBLE_QUOTE && after == HEBREW) join = true;    /* WB7b */
        else if (A == DOUBLE_QUOTE && b == HEBREW && unit && earlier == HEBREW) join = true; /* WB7c */
        else if (A == NUMERIC && b == NUMERIC) join = true;                           /* WB8 */
        else if (letter(A) && b == NUMERIC) join = true;                              /* WB9 */
        else if (A == NUMERIC && letter(b)) join = true;                              /* WB10 */
        else if (mid_number(A) && b == NUMERIC && unit && earlier == NUMERIC) join = true; /* WB11 */
        else if (A == NUMERIC && mid_number(b) && after == NUMERIC) join = true;      /* WB12 */
        else if (A == KATAKANA && b == KATAKANA) join = true;                         /* WB13 */
        else if ((letter(A) || A == NUMERIC || A == KATAKANA || A == EXTENDNUMLET) && b == EXTENDNUMLET) join = true; /* WB13a */
        else if (A == EXTENDNUMLET && (letter(b) || b == NUMERIC || b == KATAKANA)) join = true; /* WB13b */
        else if (A == RI && b == RI && ri[i - 1] % 2) join = true;                    /* WB15, WB16 */
        else join = false;                                                            /* WB999 */
        breaks[i] = !join;
    }
    breaks[count] = 1; /* WB2 */
    free(raw);
    free(start);
    return true;
}