skip to content
==> src/utf8.c <==
1/* UTF-8 with Rust's error handling. */
2#include "tui/utf8.h"
3
4#include <stdlib.h>
5#include <string.h>
6
7uint32_t tui_utf8_next(const char *str, size_t len, size_t *i) {
8    const unsigned char *s = (const unsigned char *)str;
9    unsigned char b = s[*i];
10    if (b < 0x80) {
11        (*i)++;
12        return b;
13    }
14    int need;
15    unsigned char lo = 0x80, hi = 0xBF;
16    uint32_t cp;
17    if (b >= 0xC2 && b <= 0xDF) {
18        need = 1;
19        cp = b & 0x1F;
20    } else if (b >= 0xE0 && b <= 0xEF) {
21        need = 2;
22        cp = b & 0x0F;
23        if (b == 0xE0) lo = 0xA0;
24        if (b == 0xED) hi = 0x9F;
25    } else if (b >= 0xF0 && b <= 0xF4) {
26        need = 3;
27        cp = b & 0x07;
28        if (b == 0xF0) lo = 0x90;
29        if (b == 0xF4) hi = 0x8F;
30    } else {
31        (*i)++;
32        return TUI_UTF8_REPLACEMENT;
33    }
34    size_t j = *i + 1;
35    for (int k = 0; k < need; k++, j++) {
36        unsigned char c = j < len ? s[j] : 0;
37        if (j >= len || c < lo || c > hi) {
38            *i = j; /* the maximal subpart ends before the offending byte */
39            return TUI_UTF8_REPLACEMENT;
40        }
41        cp = cp << 6 | (c & 0x3F);
42        lo = 0x80;
43        hi = 0xBF;
44    }
45    *i = j;
46    return cp;
47}
48
49size_t tui_utf8_encode(uint32_t cp, char out[4]) {
50    if (cp < 0x80) {
51        out[0] = (char)cp;
52        return 1;
53    }
54    if (cp < 0x800) {
55        out[0] = (char)(0xC0 | cp >> 6);
56        out[1] = (char)(0x80 | (cp & 0x3F));
57        return 2;
58    }
59    if (cp < 0x10000) {
60        out[0] = (char)(0xE0 | cp >> 12);
61        out[1] = (char)(0x80 | (cp >> 6 & 0x3F));
62        out[2] = (char)(0x80 | (cp & 0x3F));
63        return 3;
64    }
65    out[0] = (char)(0xF0 | cp >> 18);
66    out[1] = (char)(0x80 | (cp >> 12 & 0x3F));
67    out[2] = (char)(0x80 | (cp >> 6 & 0x3F));
68    out[3] = (char)(0x80 | (cp & 0x3F));
69    return 4;
70}
71
72bool tui_utf8_valid(const char *s, size_t len) {
73    for (size_t i = 0; i < len;) {
74        size_t at = i;
75        uint32_t cp = tui_utf8_next(s, len, &i);
76        if (cp == TUI_UTF8_REPLACEMENT && !(i - at == 3 && (unsigned char)s[at] == 0xEF &&
77                                            (unsigned char)s[at + 1] == 0xBF && (unsigned char)s[at + 2] == 0xBD))
78            return false;
79    }
80    return true;
81}
82
83char *tui_utf8_lossy(const void *bytes, size_t len, size_t *out_len) {
84    const char *s = bytes;
85    /* Each byte becomes at most 3 (a lone bad byte -> U+FFFD). */
86    char *out = malloc(len * 3 + 1);
87    if (!out) abort();
88    size_t n = 0;
89    for (size_t i = 0; i < len;) {
90        size_t at = i;
91        uint32_t cp = tui_utf8_next(s, len, &i);
92        if (cp == TUI_UTF8_REPLACEMENT || cp >= 0x80) {
93            if (cp != TUI_UTF8_REPLACEMENT || i - at != 3 || memcmp(s + at, "\xEF\xBF\xBD", 3) != 0) {
94                n += tui_utf8_encode(cp, out + n);
95                continue;
96            }
97        }
98        memcpy(out + n, s + at, i - at);
99        n += i - at;
100    }
101    out[n] = 0;
102    if (out_len) *out_len = n;
103    return out;
104}
105
106size_t tui_utf8_count(const char *s, size_t len) {
107    size_t n = 0;
108    for (size_t i = 0; i < len; i++) n += ((unsigned char)s[i] & 0xC0) != 0x80;
109    return n;
110}