ref: 61e1bbc29155917b2f2a27d5286428cbf9c315fb
dir: /utf8.c/
/* Basic UTF-8 manipulation routines by Jeff Bezanson placed in the public domain Fall 2005 This code is designed to provide the utilities you need to manipulate UTF-8 as an internal string encoding. These functions do not perform the error checking normally needed when handling UTF-8 data, so if you happen to be from the Unicode Consortium you will want to flay me alive. I do this because error checking can be performed at the boundaries (I/O), with these routines reserved for higher performance on data known to be valid. A UTF-8 validation routine is included. */ #include "llt.h" static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; static const char trailingBytesForUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; // straight from musl int u8_iswprint(uint32_t c) { if(c < 0xff) return ((c+1) & 0x7f) >= 0x21; if(c < 0x2028 || c-0x202a < 0xd800-0x202a || c-0xe000 < 0xfff9-0xe000) return 1; return !(c-0xfffc > 0x10ffff-0xfffc || (c&0xfffe) == 0xfffe); } /* returns length of next utf-8 sequence */ size_t u8_seqlen(const char *s) { return trailingBytesForUTF8[(unsigned int)(uint8_t)s[0]] + 1; } /* returns the # of bytes needed to encode a certain character 0 means the character cannot (or should not) be encoded. */ size_t u8_charlen(uint32_t ch) { if(ch < 0x80) return 1; if(ch < 0x800) return 2; if(ch < 0x10000) return 3; if(ch < 0x110000) return 4; return 0; } size_t u8_codingsize(uint32_t *wcstr, size_t n) { size_t i, c = 0; for(i = 0; i < n; i++) c += u8_charlen(wcstr[i]); return c; } /* conversions without error checking only works for valid UTF-8, i.e. no 5- or 6-byte sequences srcsz = source size in bytes sz = dest size in # of wide characters returns # characters converted if sz == srcsz+1 (i.e. 4*srcsz+4 bytes), there will always be enough space. */ size_t u8_toucs(uint32_t *dest, size_t sz, const char *src, size_t srcsz) { uint32_t ch; const char *src_end = src + srcsz; size_t nb, i = 0; if(sz == 0 || srcsz == 0) return 0; while(i < sz){ if(!isutf(*src)){ // invalid sequence dest[i++] = 0xFFFD; src++; if(src >= src_end) break; continue; } nb = trailingBytesForUTF8[(uint8_t)*src]; if(src + nb >= src_end) break; ch = 0; switch(nb){ case 5: ch += (uint8_t)*src++; ch <<= 6; // fallthrough case 4: ch += (uint8_t)*src++; ch <<= 6; // fallthrough case 3: ch += (uint8_t)*src++; ch <<= 6; // fallthrough case 2: ch += (uint8_t)*src++; ch <<= 6; // fallthrough case 1: ch += (uint8_t)*src++; ch <<= 6; // fallthrough case 0: ch += (uint8_t)*src++; } ch -= offsetsFromUTF8[nb]; dest[i++] = ch; } return i; } /* * srcsz = number of source characters * sz = size of dest buffer in bytes * returns # bytes stored in dest * the destination string will never be bigger than the source string. */ size_t u8_toutf8(char *dest, size_t sz, const uint32_t *src, size_t srcsz) { uint32_t ch; size_t i = 0; char *dest0 = dest; char *dest_end = dest + sz; while(i < srcsz){ ch = src[i]; if(ch < 0x80){ if(dest >= dest_end) break; *dest++ = (char)ch; }else if(ch < 0x800){ if(dest >= dest_end-1) break; *dest++ = (ch>>6) | 0xC0; *dest++ = (ch & 0x3F) | 0x80; }else if(ch < 0x10000){ if(dest >= dest_end-2) break; *dest++ = (ch>>12) | 0xE0; *dest++ = ((ch>>6) & 0x3F) | 0x80; *dest++ = (ch & 0x3F) | 0x80; }else if(ch < 0x110000){ if(dest >= dest_end-3) break; *dest++ = (ch>>18) | 0xF0; *dest++ = ((ch>>12) & 0x3F) | 0x80; *dest++ = ((ch>>6) & 0x3F) | 0x80; *dest++ = (ch & 0x3F) | 0x80; } i++; } return dest-dest0; } size_t u8_wc_toutf8(char *dest, uint32_t ch) { if(ch < 0x80){ dest[0] = (char)ch; return 1; } if(ch < 0x800){ dest[0] = (ch>>6) | 0xC0; dest[1] = (ch & 0x3F) | 0x80; return 2; } if(ch < 0x10000){ dest[0] = (ch>>12) | 0xE0; dest[1] = ((ch>>6) & 0x3F) | 0x80; dest[2] = (ch & 0x3F) | 0x80; return 3; } if(ch < 0x110000){ dest[0] = (ch>>18) | 0xF0; dest[1] = ((ch>>12) & 0x3F) | 0x80; dest[2] = ((ch>>6) & 0x3F) | 0x80; dest[3] = (ch & 0x3F) | 0x80; return 4; } return 0; } /* byte offset => charnum */ size_t u8_charnum(const char *s, size_t offset) { size_t charnum = 0, i = 0; while(i < offset){ if((s[i++] & 0x80) != 0 && !isutf(s[++i]) && !isutf(s[++i])) i++; charnum++; } return charnum; } size_t u8_strwidth(const char *s) { uint32_t ch; size_t nb, tot = 0; int w; signed char sc; while((sc = (signed char)*s) != 0){ if(sc >= 0){ s++; if(sc) tot++; }else{ if(!isutf(sc)){ tot++; s++; continue; } nb = trailingBytesForUTF8[(uint8_t)sc]; ch = 0; switch(nb){ case 5: ch += (uint8_t)*s++; ch <<= 6; // fallthrough case 4: ch += (uint8_t)*s++; ch <<= 6; // fallthrough case 3: ch += (uint8_t)*s++; ch <<= 6; // fallthrough case 2: ch += (uint8_t)*s++; ch <<= 6; // fallthrough case 1: ch += (uint8_t)*s++; ch <<= 6; // fallthrough case 0: ch += (uint8_t)*s++; } ch -= offsetsFromUTF8[nb]; w = wcwidth(ch); // might return -1 if(w > 0) tot += w; } } return tot; } /* reads the next utf-8 sequence out of a string, updating an index */ uint32_t u8_nextchar(const char *s, size_t *i) { uint32_t ch = 0; size_t sz = 0; do{ ch <<= 6; ch += (uint8_t)s[(*i)]; sz++; }while(s[*i] && (++(*i)) && !isutf(s[*i])); return ch - offsetsFromUTF8[sz-1]; } /* next character without NUL character terminator */ uint32_t u8_nextmemchar(const char *s, size_t *i) { uint32_t ch = 0; size_t sz = 0; do{ ch <<= 6; ch += (uint8_t)s[(*i)++]; sz++; }while(!isutf(s[*i])); return ch - offsetsFromUTF8[sz-1]; } int octal_digit(char c) { return (c >= '0' && c <= '7'); } int hex_digit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } char read_escape_control_char(char c) { switch(c){ case 'n': return '\n'; case 't': return '\t'; case 'a': return '\a'; case 'b': return '\b'; case 'e': return '\e'; case 'f': return '\f'; case 'r': return '\r'; case 'v': return '\v'; } return c; } static inline int buf_put2c(char *buf, const char *src) { buf[0] = src[0]; buf[1] = src[1]; buf[2] = '\0'; return 2; } int u8_escape_wchar(char *buf, size_t sz, uint32_t ch) { assert(sz > 2); if(ch >= 0x20 && ch < 0x7f){ buf[0] = ch; buf[1] = '\0'; return 1; } if(ch > 0xffff) return snprintf(buf, sz, "\\U%.8x", ch); if(ch >= 0x80) return snprintf(buf, sz, "\\u%04x", ch); switch(ch){ case '\n': return buf_put2c(buf, "\\n"); case '\t': return buf_put2c(buf, "\\t"); case '\\': return buf_put2c(buf, "\\\\"); case '\a': return buf_put2c(buf, "\\a"); case '\b': return buf_put2c(buf, "\\b"); case '\e': return buf_put2c(buf, "\\e"); case '\f': return buf_put2c(buf, "\\f"); case '\r': return buf_put2c(buf, "\\r"); case '\v': return buf_put2c(buf, "\\v"); } return snprintf(buf, sz, "\\x%02x", ch); } size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, int escape_quotes, int ascii) { size_t i = *pi, i0; uint32_t ch; char *start = buf; char *blim = start + sz-11; assert(sz > 11); while(i < end && buf < blim){ // sz-11: leaves room for longest escape sequence if(escape_quotes && src[i] == '"'){ buf += buf_put2c(buf, "\\\""); i++; }else if(src[i] == '\\'){ buf += buf_put2c(buf, "\\\\"); i++; }else{ i0 = i; ch = u8_nextmemchar(src, &i); if(ascii || !u8_iswprint(ch)){ buf += u8_escape_wchar(buf, sz - (buf-start), ch); }else{ i = i0; do{ *buf++ = src[i++]; }while(!isutf(src[i])); } } } *buf++ = '\0'; *pi = i; return (buf-start); } char * u8_memchr(const char *s, uint32_t ch, size_t sz, size_t *charn) { size_t i = 0, lasti = 0; uint32_t c; int csz; *charn = 0; while(i < sz){ c = csz = 0; do{ c <<= 6; c += (uint8_t)s[i++]; csz++; }while(i < sz && !isutf(s[i])); c -= offsetsFromUTF8[csz-1]; if(c == ch) return (char*)&s[lasti]; lasti = i; (*charn)++; } return nil; } /* based on the valid_utf8 routine from the PCRE library by Philip Hazel length is in bytes, since without knowing whether the string is valid it's hard to know how many characters there are! */ int u8_isvalid(const char *str, int length) { const uint8_t *p, *pend = (uint8_t*)str + length; uint8_t c; int ab; for(p = (uint8_t*)str; p < pend; p++){ c = *p; if(c < 128) continue; if((c & 0xc0) != 0xc0) return 0; ab = trailingBytesForUTF8[c]; if(length < ab) return 0; length -= ab; p++; /* Check top bits in the second byte */ if((*p & 0xc0) != 0x80) return 0; /* Check for overlong sequences for each different length */ switch(ab){ /* Check for xx00 000x */ case 1: if((c & 0x3e) == 0) return 0; continue; /* We know there aren't any more bytes to check */ /* Check for 1110 0000, xx0x xxxx */ case 2: if(c == 0xe0 && (*p & 0x20) == 0) return 0; break; /* Check for 1111 0000, xx00 xxxx */ case 3: if(c == 0xf0 && (*p & 0x30) == 0) return 0; break; /* Check for 1111 1000, xx00 0xxx */ case 4: if(c == 0xf8 && (*p & 0x38) == 0) return 0; break; /* Check for leading 0xfe or 0xff and then for 1111 1100, xx00 00xx */ case 5: if(c == 0xfe || c == 0xff || (c == 0xfc && (*p & 0x3c) == 0)) return 0; break; } /* Check for valid bytes after the 2nd, if any; all must start 10 */ while(--ab > 0) if((*(++p) & 0xc0) != 0x80) return 0; } return 1; } int u8_reverse(char *dest, char * src, size_t len) { size_t si = 0, di = len; uint8_t c; dest[di] = '\0'; while(si < len){ c = (uint8_t)src[si]; if((~c) & 0x80){ di--; dest[di] = c; si++; }else{ switch(c>>4){ case 0xc: case 0xd: di -= 2; *((int16_t*)&dest[di]) = *((int16_t*)&src[si]); si += 2; break; case 0xe: di -= 3; dest[di] = src[si]; *((int16_t*)&dest[di+1]) = *((int16_t*)&src[si+1]); si += 3; break; case 0xf: di -= 4; *((int32_t*)&dest[di]) = *((int32_t*)&src[si]); si += 4; break; default: return 1; } } } return 0; }