aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces/width.c
blob: 645e309c6e82a91f01df04fe68b5d863ec1125f4 (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
76
#include "vega.h"

typedef struct { uint32_t first, last; } Range;

/* Zero-width: combining marks, Hangul vowel and final jamo, joiners,
 * variation selectors, format characters. */
static const Range zero[] = {
    {0x0300, 0x036F}, {0x0483, 0x0489}, {0x0591, 0x05BD}, {0x0610, 0x061A}, {0x064B, 0x065F}, {0x0E31, 0x0E31},
    {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x1160, 0x11FF}, {0x1AB0, 0x1AFF}, {0x1DC0, 0x1DFF}, {0x200B, 0x200F},
    {0x202A, 0x202E}, {0x2060, 0x2064}, {0x20D0, 0x20FF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xD7B0, 0xD7FF},
    {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F}, {0xFEFF, 0xFEFF}, {0x1F3FB, 0x1F3FF}, {0xE0000, 0xE0FFF},
};

/* East Asian Wide and Fullwidth, and emoji shown as pictures. */
static const Range wide[] = {
    {0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A}, {0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
    {0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653}, {0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
    {0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5}, {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
    {0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA}, {0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
    {0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
    {0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x303E},
    {0x3041, 0x33FF}, {0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, {0xA000, 0xA4CF}, {0xA960, 0xA97F}, {0xAC00, 0xD7A3},
    {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE6F}, {0xFF00, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
    {0x17000, 0x18CFF}, {0x1B000, 0x1B2FF}, {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E},
    {0x1F191, 0x1F19A}, {0x1F200, 0x1F251}, {0x1F300, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F7E0, 0x1F7EB},
    {0x1F90C, 0x1F9FF}, {0x1FA70, 0x1FAFF}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
};

static bool in(const Range *ranges, size_t count, uint32_t codepoint) {
    size_t low = 0, high = count;
    while (low < high) {
        size_t middle = (low + high) / 2;
        if (codepoint < ranges[middle].first) high = middle;
        else if (codepoint > ranges[middle].last) low = middle + 1;
        else return true;
    }
    return false;
}

unsigned vega_width(uint32_t codepoint) {
    if (in(zero, sizeof(zero) / sizeof(*zero), codepoint)) return 0;
    return in(wide, sizeof(wide) / sizeof(*wide), codepoint) ? 2 : 1;
}

uint32_t vega_visible(uint32_t codepoint) {
    if (codepoint < 0x20) return 0x2400 + codepoint;
    if (codepoint == 0x7F) return 0x2421;
    if (codepoint >= 0x80 && codepoint < 0xA0) return 0xFFFD;
    if (codepoint == 0x2028 || codepoint == 0x2029) return 0x2424;
    return codepoint;
}

uint32_t vega_cell(uint32_t codepoint) {
    if ((codepoint >= 0x202A && codepoint <= 0x202E) || (codepoint >= 0x2066 && codepoint <= 0x2069)) return 0xFFFD;
    return vega_visible(codepoint);
}

size_t vega_decode(const char *text, size_t length, size_t at, uint32_t *codepoint) {
    const unsigned char *p = (const unsigned char *)text + at;
    size_t left = length - at, need = p[0] < 0x80 ? 1 : p[0] < 0xE0 ? 2 : p[0] < 0xF0 ? 3 : 4;
    if (need > left) { *codepoint = 0xFFFD; return left; }
    uint32_t value = need == 1 ? p[0] : need == 2 ? p[0] & 0x1Fu : need == 3 ? p[0] & 0x0Fu : p[0] & 0x07u;
    for (size_t i = 1; i < need; ++i) value = value << 6 | (p[i] & 0x3Fu);
    *codepoint = value;
    return need;
}

unsigned vega_text_width(const char *text, size_t length) {
    unsigned width = 0;
    for (size_t at = 0; at < length;) {
        uint32_t codepoint;
        at += vega_decode(text, length, at, &codepoint);
        width += vega_width(vega_cell(codepoint));
    }
    return width;
}