ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
utf8.c
Go to the documentation of this file.
1
13#include <ascii-chat-deps/utf8proc/utf8proc.h>
14#include <stdbool.h>
15#include <string.h>
16#include <stdio.h>
17
18int utf8_decode(const uint8_t *s, uint32_t *codepoint) {
19 if (s[0] < 0x80) {
20 *codepoint = s[0];
21 return 1;
22 }
23 if ((s[0] & 0xE0) == 0xC0) {
24 // Validate continuation byte
25 if ((s[1] & 0xC0) != 0x80)
26 return -1;
27 *codepoint = (((uint32_t)(s[0] & 0x1F) << 6) | (uint32_t)(s[1] & 0x3F));
28 return 2;
29 } else if ((s[0] & 0xF0) == 0xE0) {
30 // Validate continuation bytes
31 if ((s[1] & 0xC0) != 0x80 || (s[2] & 0xC0) != 0x80)
32 return -1;
33 *codepoint = (((uint32_t)(s[0] & 0x0F) << 12) | ((uint32_t)(s[1] & 0x3F) << 6) | (uint32_t)(s[2] & 0x3F));
34 return 3;
35 } else if ((s[0] & 0xF8) == 0xF0) {
36 // Validate continuation bytes
37 if ((s[1] & 0xC0) != 0x80 || (s[2] & 0xC0) != 0x80 || (s[3] & 0xC0) != 0x80)
38 return -1;
39 *codepoint = (((uint32_t)(s[0] & 0x07) << 18) | ((uint32_t)(s[1] & 0x3F) << 12) | ((uint32_t)(s[2] & 0x3F) << 6) |
40 (uint32_t)(s[3] & 0x3F));
41 return 4;
42 }
43 return -1; // Invalid
44}
45
46int utf8_display_width(const char *str) {
47 if (!str || !*str) {
48 return 0;
49 }
50
51 int width = 0;
52 const utf8proc_uint8_t *p = (const utf8proc_uint8_t *)str;
53
54 while (*p) {
55 // Skip ANSI escape sequences (ESC [ ... m)
56 // Pattern: \x1b [ [0-9;]* m
57 if (p[0] == 0x1b && p[1] == '[') {
58 // Found ANSI escape sequence start
59 p += 2; // Skip ESC[
60 // Skip digits and semicolons until we find 'm'
61 while (*p && *p != 'm') {
62 p++;
63 }
64 if (*p == 'm') {
65 p++; // Skip the 'm'
66 }
67 continue;
68 }
69
70 utf8proc_int32_t codepoint;
71 utf8proc_ssize_t len = utf8proc_iterate(p, -1, &codepoint);
72
73 if (len <= 0) {
74 // End of string or invalid UTF-8 sequence, stop processing
75 break;
76 }
77
78 // Get display width of this codepoint
79 int char_width = utf8proc_charwidth(codepoint);
80 if (char_width < 0) {
81 // Control character or unprintable - treat as 0 width
82 char_width = 0;
83 }
84 width += char_width;
85 p += len;
86 }
87
88 return width;
89}
90
91int utf8_display_width_n(const char *str, size_t max_bytes) {
92 if (!str || max_bytes == 0) {
93 return 0;
94 }
95
96 int width = 0;
97 const utf8proc_uint8_t *p = (const utf8proc_uint8_t *)str;
98 const utf8proc_uint8_t *end = p + max_bytes;
99
100 while (p < end && *p) {
101 // Skip ANSI escape sequences (ESC [ ... m)
102 if (p + 1 < end && p[0] == 0x1b && p[1] == '[') {
103 // Found ANSI escape sequence start
104 p += 2; // Skip ESC[
105 // Skip digits and semicolons until we find 'm'
106 while (p < end && *p && *p != 'm') {
107 p++;
108 }
109 if (p < end && *p == 'm') {
110 p++; // Skip the 'm'
111 }
112 continue;
113 }
114
115 utf8proc_int32_t codepoint;
116 utf8proc_ssize_t len = utf8proc_iterate(p, end - p, &codepoint);
117
118 if (len <= 0) {
119 // End of string or invalid UTF-8 sequence
120 break;
121 }
122
123 // Get display width of this codepoint
124 int char_width = utf8proc_charwidth(codepoint);
125 if (char_width < 0) {
126 // Control character or unprintable - treat as 0 width
127 char_width = 0;
128 }
129 width += char_width;
130 p += len;
131 }
132
133 return width;
134}
135
136size_t utf8_char_count(const char *str) {
137 if (!str) {
138 SET_ERRNO(ERROR_INVALID_PARAM, "str is NULL");
139 return -1; // SIZE_MAX
140 }
141
142 size_t count = 0;
143 const uint8_t *p = (const uint8_t *)str;
144 while (*p) {
145 uint32_t codepoint;
146 int decode_len = utf8_decode(p, &codepoint);
147 if (decode_len < 0) {
148 return -1; // SIZE_MAX - Invalid UTF-8
149 }
150 count++;
151 p += decode_len;
152 }
153 return count;
154}
155
156bool utf8_is_valid(const char *str) {
157 if (!str) {
158 SET_ERRNO(ERROR_INVALID_PARAM, "str is NULL");
159 return false;
160 }
161 // Reuse utf8_char_count to validate without duplicating loop
162 return utf8_char_count(str) != (size_t)-1;
163}
164
165bool utf8_is_ascii_only(const char *str) {
166 if (!str) {
167 SET_ERRNO(ERROR_INVALID_PARAM, "str is NULL");
168 return false;
169 }
170
171 // Fast path: Check if all bytes are in ASCII range (0x00-0x7F)
172 // ASCII characters are 0x00-0x7F (single byte, high bit clear)
173 // Multi-byte UTF-8 has continuation bytes with high bit set (10xxxxxx pattern)
174 // So if all bytes have high bit clear, it's guaranteed ASCII-only
175 const unsigned char *p = (const unsigned char *)str;
176 while (*p) {
177 if ((*p & ~0x7F) != 0) {
178 return false; // Non-ASCII byte found (high bit set)
179 }
180 p++;
181 }
182 return true;
183}
184
185size_t utf8_to_codepoints(const char *str, uint32_t *out_codepoints, size_t max_codepoints) {
186 if (!str || !out_codepoints || max_codepoints == 0) {
187 SET_ERRNO(ERROR_INVALID_PARAM, "str is NULL or out_codepoints is NULL or max_codepoints is 0");
188 return 0;
189 }
190
191 size_t count = 0;
192 const uint8_t *p = (const uint8_t *)str;
193 while (*p && count < max_codepoints) {
194 uint32_t codepoint;
195 int decode_len = utf8_decode(p, &codepoint);
196 if (decode_len < 0) {
197 return -1; // SIZE_MAX - Invalid UTF-8
198 }
199 out_codepoints[count++] = codepoint;
200 p += decode_len;
201 }
202 return count;
203}
204
205int utf8_next_char_bytes(const char *str, size_t max_bytes) {
206 if (!str || max_bytes == 0) {
207 SET_ERRNO(ERROR_INVALID_PARAM, "str is NULL or max_bytes is 0");
208 return -1;
209 }
210
211 // Use utf8proc_iterate to get byte length of next character
212 utf8proc_int32_t codepoint;
213 utf8proc_ssize_t len = utf8proc_iterate((const utf8proc_uint8_t *)str, (utf8proc_ssize_t)max_bytes, &codepoint);
214
215 if (len <= 0) {
216 return -1; // Invalid UTF-8 or end of string
217 }
218
219 return (int)len;
220}
221
222int utf8_continuation_bytes_needed(unsigned char first_byte) {
223 if ((first_byte & 0x80) == 0) {
224 return 0; // ASCII (0xxxxxxx) - single byte, no continuation needed
225 }
226 if ((first_byte & 0xE0) == 0xC0) {
227 return 1; // 110xxxxx - 2 byte sequence, 1 continuation byte needed
228 }
229 if ((first_byte & 0xF0) == 0xE0) {
230 return 2; // 1110xxxx - 3 byte sequence, 2 continuation bytes needed
231 }
232 if ((first_byte & 0xF8) == 0xF0) {
233 return 3; // 11110xxx - 4 byte sequence, 3 continuation bytes needed
234 }
235 return -1; // Invalid UTF-8 start byte
236}
237
238int utf8_read_and_insert_continuation_bytes(char *buffer, size_t *cursor, size_t *len, size_t max_len,
239 int continuation_bytes, int (*read_byte_fn)(void)) {
240 if (!buffer || !cursor || !len || continuation_bytes <= 0 || !read_byte_fn) {
241 SET_ERRNO(ERROR_INVALID_PARAM, "invalid params");
242 return -1;
243 }
244
245 for (int i = 0; i < continuation_bytes && *len < max_len - 1; i++) {
246 int next_byte = read_byte_fn();
247 if (next_byte == EOF) {
248 return -1; // EOF reached
249 }
250
251 // Shift characters right to make room
252 memmove(&buffer[*cursor + 1], &buffer[*cursor], *len - *cursor);
253 buffer[*cursor] = (char)next_byte;
254 (*len)++;
255 (*cursor)++;
256 }
257
258 return 0;
259}
260
261/* ============================================================================
262 * UTF-8 String Search Functions
263 * ========================================================================== */
264
272const char *utf8_strcasestr(const char *haystack, const char *needle) {
273 if (!haystack || !needle) {
274 SET_ERRNO(ERROR_INVALID_PARAM, "invalid params");
275 return NULL;
276 }
277
278 // Empty needle matches at start of haystack
279 if (needle[0] == '\0') {
280 return haystack;
281 }
282
283 // Get lengths
284 size_t haystack_len = strlen(haystack);
285 size_t needle_len = strlen(needle);
286
287 if (needle_len > haystack_len) {
288 return NULL;
289 }
290
291 // Case-fold both strings using utf8proc
292 // UTF8PROC_CASEFOLD performs Unicode case folding
293 // UTF8PROC_STABLE ensures stable output
294 // UTF8PROC_COMPOSE normalizes composed characters
295 utf8proc_option_t options = UTF8PROC_CASEFOLD | UTF8PROC_STABLE | UTF8PROC_COMPOSE;
296
297 // Case-fold the needle (pattern to search for)
298 utf8proc_uint8_t *needle_folded = NULL;
299 utf8proc_ssize_t needle_folded_len =
300 utf8proc_map((const utf8proc_uint8_t *)needle, (utf8proc_ssize_t)needle_len, &needle_folded, options);
301
302 if (needle_folded_len < 0 || !needle_folded) {
303 // Invalid UTF-8 in needle
304 if (needle_folded) {
305 free(needle_folded);
306 }
307 return NULL;
308 }
309
310 // Try each position in haystack
311 const char *haystack_pos = haystack;
312 while (*haystack_pos != '\0') {
313 // Calculate remaining haystack length
314 size_t remaining = haystack_len - (size_t)(haystack_pos - haystack);
315
316 if (remaining < needle_len) {
317 // Not enough characters left to match
318 break;
319 }
320
321 // Case-fold the current haystack window
322 utf8proc_uint8_t *haystack_folded = NULL;
323 utf8proc_ssize_t haystack_folded_len =
324 utf8proc_map((const utf8proc_uint8_t *)haystack_pos, (utf8proc_ssize_t)needle_len, &haystack_folded, options);
325
326 if (haystack_folded_len >= 0 && haystack_folded) {
327 // Compare case-folded strings
328 if ((size_t)haystack_folded_len == (size_t)needle_folded_len &&
329 memcmp(haystack_folded, needle_folded, (size_t)needle_folded_len) == 0) {
330 // Match found!
331 free(haystack_folded);
332 free(needle_folded);
333 return haystack_pos;
334 }
335 free(haystack_folded);
336 }
337
338 // Move to next UTF-8 character in haystack
339 utf8proc_int32_t codepoint;
340 utf8proc_ssize_t bytes = utf8proc_iterate((const utf8proc_uint8_t *)haystack_pos, -1, &codepoint);
341 if (bytes <= 0) {
342 // Invalid UTF-8, move by one byte
343 haystack_pos++;
344 } else {
345 haystack_pos += bytes;
346 }
347 }
348
349 free(needle_folded);
350 return NULL;
351}
352
360void utf8_write_lowercase(FILE *output, const char *text) {
361 if (!text) {
362 return;
363 }
364
365 for (const unsigned char *p = (const unsigned char *)text; *p; p++) {
366 // Check if this is the start of a multi-byte UTF-8 sequence
367 if (*p >= 0xC0) {
368 // Multi-byte UTF-8 character - copy as-is without modification
369 fputc(*p, output);
370 // Copy continuation bytes (0x80-0xBF)
371 while (p[1] >= 0x80 && p[1] < 0xC0) {
372 fputc(*++p, output);
373 }
374 } else if (*p >= 'A' && *p <= 'Z') {
375 // ASCII uppercase - convert to lowercase
376 fputc(*p + 32, output);
377 } else {
378 // ASCII lowercase or control character - copy as-is
379 fputc(*p, output);
380 }
381 }
382}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
Error and exit codes - unified status values (0-255)
unsigned int uint32_t
Definition common.h:58
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_PARAM
int utf8_display_width_n(const char *str, size_t max_bytes)
Calculate terminal display width of a UTF-8 string up to byte limit.
Definition utf8.c:91
bool utf8_is_ascii_only(const char *str)
Check if a string contains only ASCII characters.
Definition utf8.c:165
bool utf8_is_valid(const char *str)
Check if a string is valid UTF-8.
Definition utf8.c:156
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:18
size_t utf8_to_codepoints(const char *str, uint32_t *out_codepoints, size_t max_codepoints)
Convert UTF-8 string to array of Unicode codepoints.
Definition utf8.c:185
void utf8_write_lowercase(FILE *output, const char *text)
Write UTF-8 string to file with ASCII lowercase conversion.
Definition utf8.c:360
int utf8_display_width(const char *str)
Calculate terminal display width of a UTF-8 string.
Definition utf8.c:46
int utf8_read_and_insert_continuation_bytes(char *buffer, size_t *cursor, size_t *len, size_t max_len, int continuation_bytes, int(*read_byte_fn)(void))
Read continuation bytes and insert them into buffer at cursor position.
Definition utf8.c:238
int utf8_next_char_bytes(const char *str, size_t max_bytes)
Get byte length of next UTF-8 character.
Definition utf8.c:205
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.
Definition utf8.c:272
int utf8_continuation_bytes_needed(unsigned char first_byte)
Determine how many additional bytes are needed to complete a UTF-8 character.
Definition utf8.c:222
size_t utf8_char_count(const char *str)
Count UTF-8 characters (not bytes)
Definition utf8.c:136
ClangTool/LibTooling compatibility shim for stdbool.h.
🔤 UTF-8 Encoding and Decoding Utilities