Line | Count | Source |
1 | | #include <stdio.h> |
2 | | #include <assert.h> |
3 | | #include "jv_unicode.h" |
4 | | #include "jv_utf8_tables.h" |
5 | | |
6 | | // jvp_utf8_backtrack returns the beginning of the last codepoint in the |
7 | | // string, assuming that start is the last byte in the string. |
8 | | // If the last codepoint is incomplete, returns the number of missing bytes via |
9 | | // *missing_bytes. If there are no leading bytes or an invalid byte is |
10 | | // encountered, NULL is returned and *missing_bytes is not altered. |
11 | 276 | const char* jvp_utf8_backtrack(const char* start, const char* min, int *missing_bytes) { |
12 | 276 | assert(min <= start); |
13 | 276 | if (min == start) { |
14 | 0 | return min; |
15 | 0 | } |
16 | 276 | int length = 0; |
17 | 276 | int seen = 1; |
18 | 276 | while ((length = utf8_coding_length[(unsigned char)*start]) == UTF8_CONTINUATION_BYTE) { |
19 | 0 | if (start == min) break; |
20 | 0 | start--; |
21 | 0 | seen++; |
22 | 0 | } |
23 | 276 | if (length == 0 || length == UTF8_CONTINUATION_BYTE || length - seen < 0) { |
24 | 0 | return NULL; |
25 | 0 | } |
26 | 276 | if (missing_bytes) *missing_bytes = length - seen; |
27 | 276 | return start; |
28 | 276 | } |
29 | | |
30 | 412M | const char* jvp_utf8_next(const char* in, const char* end, int* codepoint_ret) { |
31 | 412M | assert(in <= end); |
32 | 412M | if (in == end) { |
33 | 1.92M | return 0; |
34 | 1.92M | } |
35 | 410M | int codepoint = -1; |
36 | 410M | unsigned char first = (unsigned char)in[0]; |
37 | 410M | int length = utf8_coding_length[first]; |
38 | 410M | if ((first & 0x80) == 0) { |
39 | | /* Fast-path for ASCII */ |
40 | 398M | codepoint = first; |
41 | 398M | length = 1; |
42 | 398M | } else if (length == 0 || length == UTF8_CONTINUATION_BYTE) { |
43 | | /* Bad single byte - either an invalid byte or an out-of-place continuation byte */ |
44 | 5.31M | length = 1; |
45 | 6.29M | } else if (length > end - in) { |
46 | | /* String ends before UTF8 sequence ends */ |
47 | 338 | length = end - in; |
48 | 6.29M | } else { |
49 | 6.29M | codepoint = ((unsigned)in[0]) & utf8_coding_bits[first]; |
50 | 10.5M | for (int i=1; i<length; i++) { |
51 | 8.42M | unsigned ch = (unsigned char)in[i]; |
52 | 8.42M | if (utf8_coding_length[ch] != UTF8_CONTINUATION_BYTE){ |
53 | | /* Invalid UTF8 sequence - not followed by the right number of continuation bytes */ |
54 | 4.16M | codepoint = -1; |
55 | 4.16M | length = i; |
56 | 4.16M | break; |
57 | 4.16M | } |
58 | 4.25M | codepoint = (codepoint << 6) | (ch & 0x3f); |
59 | 4.25M | } |
60 | 6.29M | if (codepoint < utf8_first_codepoint[length]) { |
61 | | /* Overlong UTF8 sequence */ |
62 | 4.17M | codepoint = -1; |
63 | 4.17M | } |
64 | 6.29M | if (0xD800 <= codepoint && codepoint <= 0xDFFF) { |
65 | | /* Surrogate codepoints can't be encoded in UTF8 */ |
66 | 9 | codepoint = -1; |
67 | 9 | } |
68 | 6.29M | if (codepoint > 0x10FFFF) { |
69 | | /* Outside Unicode range */ |
70 | 1 | codepoint = -1; |
71 | 1 | } |
72 | 6.29M | } |
73 | 410M | assert(length > 0); |
74 | 410M | *codepoint_ret = codepoint; |
75 | 410M | return in + length; |
76 | 410M | } |
77 | | |
78 | 1.92M | int jvp_utf8_is_valid(const char* in, const char* end) { |
79 | 1.92M | int codepoint; |
80 | 105M | while ((in = jvp_utf8_next(in, end, &codepoint))) { |
81 | 103M | if (codepoint == -1) return 0; |
82 | 103M | } |
83 | 1.88M | return 1; |
84 | 1.92M | } |
85 | | |
86 | | /* Assumes startchar is the first byte of a valid character sequence */ |
87 | 0 | int jvp_utf8_decode_length(char startchar) { |
88 | 0 | if ((startchar & 0x80) == 0) return 1; // 0___ ____ |
89 | 0 | else if ((startchar & 0xE0) == 0xC0) return 2; // 110_ ____ |
90 | 0 | else if ((startchar & 0xF0) == 0xE0) return 3; // 1110 ____ |
91 | 0 | else return 4; // 1111 ____ |
92 | 0 | } |
93 | | |
94 | 306M | int jvp_utf8_encode_length(int codepoint) { |
95 | 306M | if (codepoint <= 0x7F) return 1; |
96 | 9.64M | else if (codepoint <= 0x7FF) return 2; |
97 | 9.62M | else if (codepoint <= 0xFFFF) return 3; |
98 | 7.69k | else return 4; |
99 | 306M | } |
100 | | |
101 | 306M | int jvp_utf8_encode(int codepoint, char* out) { |
102 | 306M | assert(codepoint >= 0 && codepoint <= 0x10FFFF); |
103 | 306M | char* start = out; |
104 | 306M | if (codepoint <= 0x7F) { |
105 | 297M | *out++ = codepoint; |
106 | 297M | } else if (codepoint <= 0x7FF) { |
107 | 23.7k | *out++ = 0xC0 + ((codepoint & 0x7C0) >> 6); |
108 | 23.7k | *out++ = 0x80 + ((codepoint & 0x03F)); |
109 | 9.62M | } else if(codepoint <= 0xFFFF) { |
110 | 9.61M | *out++ = 0xE0 + ((codepoint & 0xF000) >> 12); |
111 | 9.61M | *out++ = 0x80 + ((codepoint & 0x0FC0) >> 6); |
112 | 9.61M | *out++ = 0x80 + ((codepoint & 0x003F)); |
113 | 9.61M | } else { |
114 | 7.69k | *out++ = 0xF0 + ((codepoint & 0x1C0000) >> 18); |
115 | 7.69k | *out++ = 0x80 + ((codepoint & 0x03F000) >> 12); |
116 | 7.69k | *out++ = 0x80 + ((codepoint & 0x000FC0) >> 6); |
117 | 7.69k | *out++ = 0x80 + ((codepoint & 0x00003F)); |
118 | 7.69k | } |
119 | 306M | assert(out - start == jvp_utf8_encode_length(codepoint)); |
120 | 306M | return out - start; |
121 | 306M | } |
122 | | |
123 | | // characters with White_Space property in: |
124 | | // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt |
125 | 0 | int jvp_codepoint_is_whitespace(int c) { |
126 | 0 | return |
127 | 0 | (c >= 0x0009 && c <= 0x000D) || // <control-0009>..<control-000D> |
128 | 0 | c == 0x0020 || // SPACE |
129 | 0 | c == 0x0085 || // <control-0085> |
130 | 0 | c == 0x00A0 || // NO-BREAK SPACE |
131 | 0 | c == 0x1680 || // OGHAM SPACE MARK |
132 | 0 | (c >= 0x2000 && c <= 0x200A) || // EN QUAD..HAIR SPACE |
133 | 0 | c == 0x2028 || // LINE SEPARATOR |
134 | 0 | c == 0x2029 || // PARAGRAPH SEPARATOR |
135 | 0 | c == 0x202F || // NARROW NO-BREAK SPACE |
136 | 0 | c == 0x205F || // MEDIUM MATHEMATICAL SPACE |
137 | 0 | c == 0x3000 // IDEOGRAPHIC SPACE |
138 | 0 | ; |
139 | 0 | } |