aboutsummaryrefslogtreecommitdiffstats
path: root/text/normalize.c
blob: 302a4de27a9348c0d828b4ccd03f4c1a29878f2a (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
/* Normalization forms D and C (UAX #15, Unicode 15.1): text/text.h. */
#include "text.h"
#include <string.h>

/* Hangul syllables are made of jamo arithmetically (Unicode section 3.12). */
enum { SBASE = 0xAC00, LBASE = 0x1100, VBASE = 0x1161, TBASE = 0x11A7, LCOUNT = 19, VCOUNT = 21, TCOUNT = 28,
       NCOUNT = VCOUNT * TCOUNT, SCOUNT = LCOUNT * NCOUNT };

size_t vega_nfd(const uint32_t *in, size_t count, uint32_t *out) {
    size_t n = 0;
    for (size_t i = 0; i < count; ++i) {
        uint32_t code = in[i];
        if (code >= SBASE && code < SBASE + SCOUNT) {
            uint32_t s = code - SBASE;
            out[n++] = LBASE + s / NCOUNT;
            out[n++] = VBASE + s % NCOUNT / TCOUNT;
            if (s % TCOUNT) out[n++] = TBASE + s % TCOUNT;
            continue;
        }
        const uint32_t *parts;
        size_t length = vega_decomposition(code, &parts);
        if (length) {
            memcpy(out + n, parts, length * sizeof(*out));
            n += length;
        } else
            out[n++] = code;
    }
    /* Canonical order: each mark moves before the marks of a higher class
     * ahead of it, up to a starter (a stable sort of every run of marks). */
    for (size_t i = 1; i < n; ++i) {
        uint8_t class = vega_combining_class(out[i]);
        if (!class) continue;
        for (size_t j = i; j && vega_combining_class(out[j - 1]) > class; --j) {
            uint32_t swap = out[j - 1];
            out[j - 1] = out[j];
            out[j] = swap;
        }
    }
    return n;
}

static uint32_t compose(uint32_t first, uint32_t second) {
    if (first >= LBASE && first < LBASE + LCOUNT && second >= VBASE && second < VBASE + VCOUNT)
        return SBASE + ((first - LBASE) * VCOUNT + (second - VBASE)) * TCOUNT;
    if (first >= SBASE && first < SBASE + SCOUNT && (first - SBASE) % TCOUNT == 0 && second > TBASE &&
        second < TBASE + TCOUNT)
        return first + (second - TBASE);
    return vega_composition(first, second);
}

size_t vega_nfc(const uint32_t *in, size_t count, uint32_t *out) {
    size_t n = vega_nfd(in, count, out);
    if (!n) return 0;
    /* Each character after the last starter composes with it unless a
     * character between them blocks it (one of the same or a higher class,
     * or a starter). */
    size_t starter = 0, written = 1;
    int last = vega_combining_class(out[0]) ? 256 : 0;
    for (size_t i = 1; i < n; ++i) {
        uint32_t code = out[i];
        int class = vega_combining_class(code);
        uint32_t composite = compose(out[starter], code);
        if (composite && (last < class || last == 0)) {
            out[starter] = composite;
            continue;
        }
        if (!class) starter = written;
        last = class;
        out[written++] = code;
    }
    return written;
}