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