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