2.55
xs_unicode.h
1/* copyright (c) 2022 - 2024 grunfink et al. / MIT license */
2
3#ifndef _XS_UNICODE_H
4
5#define _XS_UNICODE_H
6
7 int _xs_utf8_enc(char buf[4], unsigned int cpoint);
8 int xs_is_utf8_cont_byte(char c);
9 unsigned int xs_utf8_dec(const char **str);
10 int xs_unicode_width(unsigned int cpoint);
11 int xs_is_surrogate(unsigned int cpoint);
12 unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2);
13 unsigned int xs_surrogate_enc(unsigned int cpoint);
14 unsigned int *_xs_unicode_upper_search(unsigned int cpoint);
15 unsigned int *_xs_unicode_lower_search(unsigned int cpoint);
16 #define xs_unicode_is_upper(cpoint) (!!_xs_unicode_upper_search(cpoint))
17 #define xs_unicode_is_lower(cpoint) (!!_xs_unicode_lower_search(cpoint))
18 unsigned int xs_unicode_to_upper(unsigned int cpoint);
19 unsigned int xs_unicode_to_lower(unsigned int cpoint);
20 int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac);
21 int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint);
22 int xs_unicode_is_alpha(unsigned int cpoint);
23
24#ifdef _XS_H
25 xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint);
26#endif
27
28#ifdef XS_IMPLEMENTATION
29
30#ifndef xs_countof
31#define xs_countof(a) (sizeof((a)) / sizeof((*a)))
32#endif
33
34int _xs_utf8_enc(char buf[4], unsigned int cpoint)
35/* encodes an Unicode codepoint to utf-8 into buf and returns the size in bytes */
36{
37 char *p = buf;
38
39 if (cpoint < 0x80) /* 1 byte char */
40 *p++ = cpoint & 0xff;
41 else {
42 if (cpoint < 0x800) /* 2 byte char */
43 *p++ = 0xc0 | (cpoint >> 6);
44 else {
45 if (cpoint < 0x10000) /* 3 byte char */
46 *p++ = 0xe0 | (cpoint >> 12);
47 else { /* 4 byte char */
48 *p++ = 0xf0 | (cpoint >> 18);
49 *p++ = 0x80 | ((cpoint >> 12) & 0x3f);
50 }
51
52 *p++ = 0x80 | ((cpoint >> 6) & 0x3f);
53 }
54
55 *p++ = 0x80 | (cpoint & 0x3f);
56 }
57
58 return p - buf;
59}
60
61
62int xs_is_utf8_cont_byte(char c)
63/* returns true if c is an utf8 continuation byte */
64{
65 return ((c & 0xc0) == 0x80);
66}
67
68
69unsigned int xs_utf8_dec(const char **str)
70/* decodes an utf-8 char inside str and updates the pointer */
71{
72 const char *p = *str;
73 unsigned int cpoint = 0;
74 unsigned char c = *p++;
75 int cb = 0;
76
77 if ((c & 0x80) == 0) { /* 1 byte char */
78 cpoint = c;
79 }
80 else
81 if ((c & 0xe0) == 0xc0) { /* 2 byte char */
82 cpoint = (c & 0x1f) << 6;
83 cb = 1;
84 }
85 else
86 if ((c & 0xf0) == 0xe0) { /* 3 byte char */
87 cpoint = (c & 0x0f) << 12;
88 cb = 2;
89 }
90 else
91 if ((c & 0xf8) == 0xf0) { /* 4 byte char */
92 cpoint = (c & 0x07) << 18;
93 cb = 3;
94 }
95
96 /* process the continuation bytes */
97 while (cb > 0 && *p && xs_is_utf8_cont_byte(*p))
98 cpoint |= (*p++ & 0x3f) << (--cb * 6);
99
100 /* incomplete or broken? */
101 if (cb)
102 cpoint = 0xfffd;
103
104 *str = p;
105 return cpoint;
106}
107
108
109/** Unicode character width: intentionally dead simple **/
110
111static unsigned int xs_unicode_width_table[] = {
112 0x300, 0x36f, 0, /* diacritics */
113 0x1100, 0x11ff, 2, /* Hangul */
114 0x2e80, 0xa4cf, 2, /* CJK */
115 0xac00, 0xd7a3, 2, /* more Hangul */
116 0xe000, 0xf8ff, 0, /* private use */
117 0xf900, 0xfaff, 2, /* CJK compatibility */
118 0xff00, 0xff60, 2, /* full width things */
119 0xffdf, 0xffe6, 2, /* full width things */
120 0x1f200, 0x1ffff, 2, /* emojis */
121 0x20000, 0x2fffd, 2 /* more CJK */
122};
123
124int xs_unicode_width(unsigned int cpoint)
125/* returns the width in columns of a Unicode codepoint (somewhat simplified) */
126{
127 int b = 0;
128 int t = xs_countof(xs_unicode_width_table) / 3 - 1;
129
130 while (t >= b) {
131 int n = (b + t) / 2;
132 unsigned int *p = &xs_unicode_width_table[n * 3];
133
134 if (cpoint < p[0])
135 t = n - 1;
136 else
137 if (cpoint > p[1])
138 b = n + 1;
139 else
140 return p[2];
141 }
142
143 return 1;
144}
145
146
147/** surrogate pairs **/
148
149int xs_is_surrogate(unsigned int cpoint)
150/* checks if cpoint is the first element of a Unicode surrogate pair */
151{
152 return cpoint >= 0xd800 && cpoint <= 0xdfff;
153}
154
155
156unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2)
157/* "decodes" a surrogate pair into a codepoint */
158{
159 return 0x10000 | ((p1 & 0x3ff) << 10) | (p2 & 0x3ff);
160}
161
162
163unsigned int xs_surrogate_enc(unsigned int cpoint)
164/* "encodes" a Unicode into a surrogate pair (p1 in the MSB word) */
165{
166 unsigned int p1 = 0xd7c0 + (cpoint >> 10);
167 unsigned int p2 = 0xdc00 + (cpoint & 0x3ff);
168
169 return (p1 << 16) | p2;
170}
171
172
173#ifdef _XS_H
174
175xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint)
176/* encodes an Unicode codepoint to utf-8 into str */
177{
178 char tmp[4];
179
180 int c = _xs_utf8_enc(tmp, cpoint);
181
182 return xs_append_m(str, tmp, c);
183}
184
185#endif /* _XS_H */
186
187
188#ifdef _XS_UNICODE_TBL_H
189
190/* include xs_unicode_tbl.h before this one to use these functions */
191
192unsigned int *_xs_unicode_upper_search(unsigned int cpoint)
193/* searches for an uppercase codepoint in the case fold table */
194{
195 int b = 0;
196 int t = xs_countof(xs_unicode_case_fold_table) / 2 + 1;
197
198 while (t >= b) {
199 int n = (b + t) / 2;
200 unsigned int *p = &xs_unicode_case_fold_table[n * 2];
201
202 if (cpoint < p[0])
203 t = n - 1;
204 else
205 if (cpoint > p[0])
206 b = n + 1;
207 else
208 return p;
209 }
210
211 return NULL;
212}
213
214
215unsigned int *_xs_unicode_lower_search(unsigned int cpoint)
216/* searches for a lowercase codepoint in the case fold table */
217{
218 unsigned int *p = xs_unicode_case_fold_table;
219 unsigned int *e = p + xs_countof(xs_unicode_case_fold_table);
220
221 while (p < e) {
222 if (cpoint == p[1])
223 return p;
224
225 p += 2;
226 }
227
228 return NULL;
229}
230
231
232unsigned int xs_unicode_to_lower(unsigned int cpoint)
233/* returns the cpoint to lowercase */
234{
235 unsigned int *p = _xs_unicode_upper_search(cpoint);
236
237 return p == NULL ? cpoint : p[1];
238}
239
240
241unsigned int xs_unicode_to_upper(unsigned int cpoint)
242/* returns the cpoint to uppercase */
243{
244 unsigned int *p = _xs_unicode_lower_search(cpoint);
245
246 return p == NULL ? cpoint : p[0];
247}
248
249
250int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac)
251/* applies unicode Normalization Form D */
252{
253 int b = 0;
254 int t = xs_countof(xs_unicode_nfd_table) / 3 - 1;
255
256 while (t >= b) {
257 int n = (b + t) / 2;
258 unsigned int *p = &xs_unicode_nfd_table[n * 3];
259
260 int c = cpoint - p[0];
261
262 if (c < 0)
263 t = n - 1;
264 else
265 if (c > 0)
266 b = n + 1;
267 else {
268 *base = p[1];
269 *diac = p[2];
270 return 1;
271 }
272 }
273
274 return 0;
275}
276
277
278int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint)
279/* applies unicode Normalization Form C */
280{
281 unsigned int *p = xs_unicode_nfd_table;
282 unsigned int *e = p + xs_countof(xs_unicode_nfd_table);
283
284 while (p < e) {
285 if (p[1] == base && p[2] == diac) {
286 *cpoint = p[0];
287 return 1;
288 }
289
290 p += 3;
291 }
292
293 return 0;
294}
295
296
297int xs_unicode_is_alpha(unsigned int cpoint)
298/* checks if a codepoint is an alpha (i.e. a letter) */
299{
300 int b = 0;
301 int t = xs_countof(xs_unicode_alpha_table) / 2 - 1;
302
303 while (t >= b) {
304 int n = (b + t) / 2;
305 unsigned int *p = &xs_unicode_alpha_table[n * 2];
306
307 if (cpoint < p[0])
308 t = n - 1;
309 else
310 if (cpoint > p[1])
311 b = n + 1;
312 else
313 return 1;
314 }
315
316 return 0;
317}
318
319
320#endif /* _XS_UNICODE_TBL_H */
321
322#endif /* XS_IMPLEMENTATION */
323
324#endif /* _XS_UNICODE_H */