2.59
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 int xs_is_diacritic(unsigned int cpoint);
13 unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2);
14 unsigned int xs_surrogate_enc(unsigned int cpoint);
15 unsigned int *_xs_unicode_upper_search(unsigned int cpoint);
16 unsigned int *_xs_unicode_lower_search(unsigned int cpoint);
17 #define xs_unicode_is_upper(cpoint) (!!_xs_unicode_upper_search(cpoint))
18 #define xs_unicode_is_lower(cpoint) (!!_xs_unicode_lower_search(cpoint))
19 unsigned int xs_unicode_to_upper(unsigned int cpoint);
20 unsigned int xs_unicode_to_lower(unsigned int cpoint);
21 int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac);
22 int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint);
23 int xs_unicode_is_alpha(unsigned int cpoint);
24
25#ifdef _XS_H
26 xs_str *xs_utf8_insert(xs_str *str, unsigned int cpoint, int *offset);
27 xs_str *xs_utf8_cat(xs_str *str, unsigned int cpoint);
28 xs_str *xs_utf8_to_upper(const char *str);
29 xs_str *xs_utf8_to_lower(const char *str);
30 xs_str *xs_utf8_to_nfd(const char *str);
31 xs_str *xs_utf8_to_nfc(const char *str);
32#endif
33
34#ifdef XS_IMPLEMENTATION
35
36#ifndef xs_countof
37#define xs_countof(a) (sizeof((a)) / sizeof((*a)))
38#endif
39
40int xs_utf8_enc(char buf[4], unsigned int cpoint)
41/* encodes an Unicode codepoint to utf-8 into buf and returns the size in bytes */
42{
43 char *p = buf;
44
45 if (cpoint < 0x80) /* 1 byte char */
46 *p++ = cpoint & 0xff;
47 else {
48 if (cpoint < 0x800) /* 2 byte char */
49 *p++ = 0xc0 | (cpoint >> 6);
50 else {
51 if (cpoint < 0x10000) /* 3 byte char */
52 *p++ = 0xe0 | (cpoint >> 12);
53 else { /* 4 byte char */
54 *p++ = 0xf0 | (cpoint >> 18);
55 *p++ = 0x80 | ((cpoint >> 12) & 0x3f);
56 }
57
58 *p++ = 0x80 | ((cpoint >> 6) & 0x3f);
59 }
60
61 *p++ = 0x80 | (cpoint & 0x3f);
62 }
63
64 return p - buf;
65}
66
67
68int xs_is_utf8_cont_byte(char c)
69/* returns true if c is an utf8 continuation byte */
70{
71 return ((c & 0xc0) == 0x80);
72}
73
74
75unsigned int xs_utf8_dec(const char **str)
76/* decodes an utf-8 char inside str and updates the pointer */
77{
78 const char *p = *str;
79 unsigned int cpoint = 0;
80 unsigned char c = *p++;
81 int cb = 0;
82
83 if ((c & 0x80) == 0) { /* 1 byte char */
84 cpoint = c;
85 }
86 else
87 if ((c & 0xe0) == 0xc0) { /* 2 byte char */
88 cpoint = (c & 0x1f) << 6;
89 cb = 1;
90 }
91 else
92 if ((c & 0xf0) == 0xe0) { /* 3 byte char */
93 cpoint = (c & 0x0f) << 12;
94 cb = 2;
95 }
96 else
97 if ((c & 0xf8) == 0xf0) { /* 4 byte char */
98 cpoint = (c & 0x07) << 18;
99 cb = 3;
100 }
101
102 /* process the continuation bytes */
103 while (cb > 0 && *p && xs_is_utf8_cont_byte(*p))
104 cpoint |= (*p++ & 0x3f) << (--cb * 6);
105
106 /* incomplete or broken? */
107 if (cb)
108 cpoint = 0xfffd;
109
110 *str = p;
111 return cpoint;
112}
113
114
115/** Unicode character width: intentionally dead simple **/
116
117static unsigned int xs_unicode_width_table[] = {
118 0x300, 0x36f, 0, /* diacritics */
119 0x1100, 0x11ff, 2, /* Hangul */
120 0x2e80, 0xa4cf, 2, /* CJK */
121 0xac00, 0xd7a3, 2, /* more Hangul */
122 0xe000, 0xf8ff, 0, /* private use */
123 0xf900, 0xfaff, 2, /* CJK compatibility */
124 0xff00, 0xff60, 2, /* full width things */
125 0xffdf, 0xffe6, 2, /* full width things */
126 0x1f200, 0x1ffff, 2, /* emojis */
127 0x20000, 0x2fffd, 2 /* more CJK */
128};
129
130int xs_unicode_width(unsigned int cpoint)
131/* returns the width in columns of a Unicode codepoint (somewhat simplified) */
132{
133 int b = 0;
134 int t = xs_countof(xs_unicode_width_table) / 3 - 1;
135
136 while (t >= b) {
137 int n = (b + t) / 2;
138 unsigned int *p = &xs_unicode_width_table[n * 3];
139
140 if (cpoint < p[0])
141 t = n - 1;
142 else
143 if (cpoint > p[1])
144 b = n + 1;
145 else
146 return p[2];
147 }
148
149 return 1;
150}
151
152
153int xs_is_diacritic(unsigned int cpoint)
154{
155 return cpoint >= 0x300 && cpoint <= 0x36f;
156}
157
158
159/** surrogate pairs **/
160
161int xs_is_surrogate(unsigned int cpoint)
162/* checks if cpoint is the first element of a Unicode surrogate pair */
163{
164 return cpoint >= 0xd800 && cpoint <= 0xdfff;
165}
166
167
168unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2)
169/* "decodes" a surrogate pair into a codepoint */
170{
171 return 0x10000 | ((p1 & 0x3ff) << 10) | (p2 & 0x3ff);
172}
173
174
175unsigned int xs_surrogate_enc(unsigned int cpoint)
176/* "encodes" a Unicode into a surrogate pair (p1 in the MSB word) */
177{
178 unsigned int p1 = 0xd7c0 + (cpoint >> 10);
179 unsigned int p2 = 0xdc00 + (cpoint & 0x3ff);
180
181 return (p1 << 16) | p2;
182}
183
184
185#ifdef _XS_H
186
187xs_str *xs_utf8_insert(xs_str *str, unsigned int cpoint, int *offset)
188/* encodes an Unicode codepoint to utf-8 into str */
189{
190 char tmp[4];
191
192 int c = xs_utf8_enc(tmp, cpoint);
193
194 str = xs_insert_m(str, *offset, tmp, c);
195
196 *offset += c;
197
198 return str;
199}
200
201
202xs_str *xs_utf8_cat(xs_str *str, unsigned int cpoint)
203/* encodes an Unicode codepoint to utf-8 into str */
204{
205 int offset = strlen(str);
206
207 return xs_utf8_insert(str, cpoint, &offset);
208}
209
210#endif /* _XS_H */
211
212
213#ifdef _XS_UNICODE_TBL_H
214
215/* include xs_unicode_tbl.h before this one to use these functions */
216
217unsigned int *_xs_unicode_upper_search(unsigned int cpoint)
218/* searches for an uppercase codepoint in the case fold table */
219{
220 int b = 0;
221 int t = xs_countof(xs_unicode_case_fold_table) / 2 + 1;
222
223 while (t >= b) {
224 int n = (b + t) / 2;
225 unsigned int *p = &xs_unicode_case_fold_table[n * 2];
226
227 if (cpoint < p[0])
228 t = n - 1;
229 else
230 if (cpoint > p[0])
231 b = n + 1;
232 else
233 return p;
234 }
235
236 return NULL;
237}
238
239
240unsigned int *_xs_unicode_lower_search(unsigned int cpoint)
241/* searches for a lowercase codepoint in the case fold table */
242{
243 unsigned int *p = xs_unicode_case_fold_table;
244 unsigned int *e = p + xs_countof(xs_unicode_case_fold_table);
245
246 while (p < e) {
247 if (cpoint == p[1])
248 return p;
249
250 p += 2;
251 }
252
253 return NULL;
254}
255
256
257unsigned int xs_unicode_to_lower(unsigned int cpoint)
258/* returns the cpoint to lowercase */
259{
260 if (cpoint < 0x80)
261 return tolower(cpoint);
262
263 unsigned int *p = _xs_unicode_upper_search(cpoint);
264
265 return p == NULL ? cpoint : p[1];
266}
267
268
269unsigned int xs_unicode_to_upper(unsigned int cpoint)
270/* returns the cpoint to uppercase */
271{
272 if (cpoint < 0x80)
273 return toupper(cpoint);
274
275 unsigned int *p = _xs_unicode_lower_search(cpoint);
276
277 return p == NULL ? cpoint : p[0];
278}
279
280
281int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac)
282/* applies unicode Normalization Form D */
283{
284 int b = 0;
285 int t = xs_countof(xs_unicode_nfd_table) / 3 - 1;
286
287 while (t >= b) {
288 int n = (b + t) / 2;
289 unsigned int *p = &xs_unicode_nfd_table[n * 3];
290
291 int c = cpoint - p[0];
292
293 if (c < 0)
294 t = n - 1;
295 else
296 if (c > 0)
297 b = n + 1;
298 else {
299 *base = p[1];
300 *diac = p[2];
301 return 1;
302 }
303 }
304
305 return 0;
306}
307
308
309int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint)
310/* applies unicode Normalization Form C */
311{
312 unsigned int *p = xs_unicode_nfd_table;
313 unsigned int *e = p + xs_countof(xs_unicode_nfd_table);
314
315 while (p < e) {
316 if (p[1] == base && p[2] == diac) {
317 *cpoint = p[0];
318 return 1;
319 }
320
321 p += 3;
322 }
323
324 return 0;
325}
326
327
328int xs_unicode_is_alpha(unsigned int cpoint)
329/* checks if a codepoint is an alpha (i.e. a letter) */
330{
331 int b = 0;
332 int t = xs_countof(xs_unicode_alpha_table) / 2 - 1;
333
334 while (t >= b) {
335 int n = (b + t) / 2;
336 unsigned int *p = &xs_unicode_alpha_table[n * 2];
337
338 if (cpoint < p[0])
339 t = n - 1;
340 else
341 if (cpoint > p[1])
342 b = n + 1;
343 else
344 return 1;
345 }
346
347 return 0;
348}
349
350
351#ifdef _XS_H
352
353xs_str *xs_utf8_to_upper(const char *str)
354{
355 xs_str *s = xs_str_new(NULL);
356 unsigned int cpoint;
357 int offset = 0;
358
359 while ((cpoint = xs_utf8_dec(&str))) {
360 cpoint = xs_unicode_to_upper(cpoint);
361 s = xs_utf8_insert(s, cpoint, &offset);
362 }
363
364 return s;
365}
366
367
368xs_str *xs_utf8_to_lower(const char *str)
369{
370 xs_str *s = xs_str_new(NULL);
371 unsigned int cpoint;
372 int offset = 0;
373
374 while ((cpoint = xs_utf8_dec(&str))) {
375 cpoint = xs_unicode_to_lower(cpoint);
376 s = xs_utf8_insert(s, cpoint, &offset);
377 }
378
379 return s;
380}
381
382
383xs_str *xs_utf8_to_nfd(const char *str)
384{
385 xs_str *s = xs_str_new(NULL);
386 unsigned int cpoint;
387 int offset = 0;
388
389 while ((cpoint = xs_utf8_dec(&str))) {
390 unsigned int base;
391 unsigned int diac;
392
393 if (xs_unicode_nfd(cpoint, &base, &diac)) {
394 s = xs_utf8_insert(s, base, &offset);
395 s = xs_utf8_insert(s, diac, &offset);
396 }
397 else
398 s = xs_utf8_insert(s, cpoint, &offset);
399 }
400
401 return s;
402}
403
404
405xs_str *xs_utf8_to_nfc(const char *str)
406{
407 xs_str *s = xs_str_new(NULL);
408 unsigned int cpoint;
409 unsigned int base = 0;
410 int offset = 0;
411
412 while ((cpoint = xs_utf8_dec(&str))) {
413 if (xs_is_diacritic(cpoint)) {
414 if (xs_unicode_nfc(base, cpoint, &base))
415 continue;
416 }
417
418 if (base)
419 s = xs_utf8_insert(s, base, &offset);
420
421 base = cpoint;
422 }
423
424 if (base)
425 s = xs_utf8_insert(s, base, &offset);
426
427 return s;
428}
429
430#endif /* _XS_H */
431
432#endif /* _XS_UNICODE_TBL_H */
433
434#endif /* XS_IMPLEMENTATION */
435
436#endif /* _XS_UNICODE_H */