distclean: Remove generated tests/Makefile
[jimtcl.git] / utf8.c
blob56a036b1ae39c2f4c57a066aff9fcc997f94225f
1 /**
2 * UTF-8 utility functions
4 * (c) 2010-2016 Steve Bennett <steveb@workware.net.au>
6 * See LICENCE for licence details.
7 */
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <assert.h>
14 #include "utf8.h"
16 /* This one is always implemented */
17 int utf8_fromunicode(char *p, unsigned uc)
19 if (uc <= 0x7f) {
20 *p = uc;
21 return 1;
23 else if (uc <= 0x7ff) {
24 *p++ = 0xc0 | ((uc & 0x7c0) >> 6);
25 *p = 0x80 | (uc & 0x3f);
26 return 2;
28 else if (uc <= 0xffff) {
29 *p++ = 0xe0 | ((uc & 0xf000) >> 12);
30 *p++ = 0x80 | ((uc & 0xfc0) >> 6);
31 *p = 0x80 | (uc & 0x3f);
32 return 3;
34 /* Note: We silently truncate to 21 bits here: 0x1fffff */
35 else {
36 *p++ = 0xf0 | ((uc & 0x1c0000) >> 18);
37 *p++ = 0x80 | ((uc & 0x3f000) >> 12);
38 *p++ = 0x80 | ((uc & 0xfc0) >> 6);
39 *p = 0x80 | (uc & 0x3f);
40 return 4;
44 #if defined(USE_UTF8) && !defined(JIM_BOOTSTRAP)
45 int utf8_charlen(int c)
47 if ((c & 0x80) == 0) {
48 return 1;
50 if ((c & 0xe0) == 0xc0) {
51 return 2;
53 if ((c & 0xf0) == 0xe0) {
54 return 3;
56 if ((c & 0xf8) == 0xf0) {
57 return 4;
59 /* Invalid sequence */
60 return -1;
63 int utf8_strlen(const char *str, int bytelen)
65 int charlen = 0;
66 if (bytelen < 0) {
67 bytelen = strlen(str);
69 while (bytelen > 0) {
70 int c;
71 int l = utf8_tounicode(str, &c);
72 charlen++;
73 str += l;
74 bytelen -= l;
76 return charlen;
79 int utf8_strwidth(const char *str, int charlen)
81 int width = 0;
82 while (charlen) {
83 int c;
84 int l = utf8_tounicode(str, &c);
85 width += utf8_width(c);
86 str += l;
87 charlen--;
89 return width;
92 int utf8_index(const char *str, int index)
94 const char *s = str;
95 while (index--) {
96 int c;
97 s += utf8_tounicode(s, &c);
99 return s - str;
102 int utf8_prev_len(const char *str, int len)
104 int n = 1;
106 assert(len > 0);
108 /* Look up to len chars backward for a start-of-char byte */
109 while (--len) {
110 if ((str[-n] & 0x80) == 0) {
111 /* Start of a 1-byte char */
112 break;
114 if ((str[-n] & 0xc0) == 0xc0) {
115 /* Start of a multi-byte char */
116 break;
118 n++;
120 return n;
123 int utf8_tounicode(const char *str, int *uc)
125 unsigned const char *s = (unsigned const char *)str;
127 if (s[0] < 0xc0) {
128 *uc = s[0];
129 return 1;
131 if (s[0] < 0xe0) {
132 if ((s[1] & 0xc0) == 0x80) {
133 *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
134 if (*uc >= 0x80) {
135 return 2;
137 /* Otherwise this is an invalid sequence */
140 else if (s[0] < 0xf0) {
141 if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
142 *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
143 if (*uc >= 0x800) {
144 return 3;
146 /* Otherwise this is an invalid sequence */
149 else if (s[0] < 0xf8) {
150 if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80) && ((str[3] & 0xc0) == 0x80)) {
151 *uc = ((s[0] & ~0xf0) << 18) | ((s[1] & ~0x80) << 12) | ((s[2] & ~0x80) << 6) | (s[3] & ~0x80);
152 if (*uc >= 0x10000) {
153 return 4;
155 /* Otherwise this is an invalid sequence */
159 /* Invalid sequence, so just return the byte */
160 *uc = *s;
161 return 1;
164 struct casemap {
165 unsigned short code; /* code point */
166 unsigned short altcode; /* alternate case code point */
169 struct utf8range {
170 unsigned lower; /* lower inclusive */
171 unsigned upper; /* upper exclusive */
175 /* Generated mapping tables */
176 #include "_unicode_mapping.c"
178 #define ARRAYSIZE(A) sizeof(A) / sizeof(*(A))
180 static int cmp_casemap(const void *key, const void *cm)
182 return *(int *)key - (int)((const struct casemap *)cm)->code;
185 static int utf8_map_case(const struct casemap *mapping, int num, int ch)
187 /* We only support 16 bit case mapping */
188 if (ch <= 0xffff) {
189 const struct casemap *cm =
190 bsearch(&ch, mapping, num, sizeof(*mapping), cmp_casemap);
192 if (cm) {
193 return cm->altcode;
196 return ch;
199 static int cmp_range(const void *key, const void *cm)
201 const struct utf8range *range = (const struct utf8range *)cm;
202 int ch = *(int *)key;
203 if (ch < range->lower) {
204 return -1;
206 if (ch >= range->upper) {
207 return 1;
209 return 0;
212 static int utf8_in_range(const struct utf8range *range, int num, int ch)
214 const struct utf8range *r =
215 bsearch(&ch, range, num, sizeof(*range), cmp_range);
217 if (r) {
218 return 1;
220 return 0;
223 int utf8_upper(int ch)
225 if (isascii(ch)) {
226 return toupper(ch);
228 return utf8_map_case(unicode_case_mapping_upper, ARRAYSIZE(unicode_case_mapping_upper), ch);
231 int utf8_lower(int ch)
233 if (isascii(ch)) {
234 return tolower(ch);
236 return utf8_map_case(unicode_case_mapping_lower, ARRAYSIZE(unicode_case_mapping_lower), ch);
239 int utf8_title(int ch)
241 if (!isascii(ch)) {
242 int newch = utf8_map_case(unicode_case_mapping_title, ARRAYSIZE(unicode_case_mapping_title), ch);
243 if (newch != ch) {
244 return newch ? newch : ch;
247 return utf8_upper(ch);
250 int utf8_width(int ch)
252 if (!isascii(ch)) {
253 if (utf8_in_range(unicode_range_combining, ARRAYSIZE(unicode_range_combining), ch)) {
254 return 0;
256 if (utf8_in_range(unicode_range_wide, ARRAYSIZE(unicode_range_wide), ch)) {
257 return 2;
260 return 1;
263 #endif /* JIM_BOOTSTRAP */