/*!A cross-platform build utility based on Lua * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (C) 2015-present, Xmake Open Source Community. * * @author ruki * @file utf8.c * */ /* ////////////////////////////////////////////////////////////////////////////////////// * includes */ #include "utf8.h" /* ////////////////////////////////////////////////////////////////////////////////////// * implementation */ tb_char_t const* xm_utf8_decode(tb_char_t const* s, xm_utf8_int_t* val, tb_bool_t strict) { static const xm_utf8_int_t limits[] = {~(xm_utf8_int_t)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u}; tb_uint32_t c = (tb_byte_t)s[0]; xm_utf8_int_t res = 0; if (c < 0x80) { res = c; } else { if (xm_utf8_iscont(c)) { return tb_null; } tb_int_t count = 0; for (; c & 0x40; c <<= 1) { tb_uint32_t cc = (tb_byte_t)s[++count]; if (!xm_utf8_iscont(cc)) { return tb_null; } res = (res << 6) | (cc & 0x3F); } res |= ((xm_utf8_int_t)(c & 0x7F) << (count * 5)); if (count > 5 || res > XM_UTF8_MAXUTF || res < limits[count]) { return tb_null; } s += count; } if (strict) { if (res > XM_UTF8_MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) { return tb_null; } } if (val) { *val = res; } return s + 1; } tb_size_t xm_utf8_encode(tb_char_t* s, xm_utf8_int_t val) { tb_assert_and_check_return_val(s, 0); if (val < 0x80) { s[0] = (tb_char_t)val; return 1; } if (val < 0x800) { s[0] = (tb_char_t)(0xc0 | ((val >> 6) & 0x1f)); s[1] = (tb_char_t)(0x80 | (val & 0x3f)); return 2; } if (val < 0x10000) { s[0] = (tb_char_t)(0xe0 | ((val >> 12) & 0x0f)); s[1] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); s[2] = (tb_char_t)(0x80 | (val & 0x3f)); return 3; } if (val <= 0x10FFFF) { s[0] = (tb_char_t)(0xf0 | ((val >> 18) & 0x07)); s[1] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); s[2] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); s[3] = (tb_char_t)(0x80 | (val & 0x3f)); return 4; } if (val <= 0x3FFFFFF) { s[0] = (tb_char_t)(0xf8 | ((val >> 24) & 0x03)); s[1] = (tb_char_t)(0x80 | ((val >> 18) & 0x3f)); s[2] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); s[3] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); s[4] = (tb_char_t)(0x80 | (val & 0x3f)); return 5; } if (val <= 0x7FFFFFFF) { s[0] = (tb_char_t)(0xfc | ((val >> 30) & 0x01)); s[1] = (tb_char_t)(0x80 | ((val >> 24) & 0x3f)); s[2] = (tb_char_t)(0x80 | ((val >> 18) & 0x3f)); s[3] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); s[4] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); s[5] = (tb_char_t)(0x80 | (val & 0x3f)); return 6; } return 0; } tb_long_t xm_utf8_charpos(tb_char_t const* s, tb_size_t len, tb_long_t byte_pos) { if (byte_pos <= 0) return 0; if (byte_pos > len + 1) byte_pos = len + 1; // adjust byte_pos to the start of the character // // performance: // 0(1) complexity, because utf8 sequence is max 4 bytes while (byte_pos > 1 && xm_utf8_iscont(s[byte_pos - 1])) { byte_pos--; } // get character position tb_long_t count = xm_utf8_len_impl(s, len, 1, byte_pos - 1, tb_true, tb_null); return count >= 0? count + 1 : -1; } /* ////////////////////////////////////////////////////////////////////////////////////// * implementation interfaces */ static struct { xm_utf8_int_t first; xm_utf8_int_t last; } const g_non_spacing[] = { {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF} }; tb_long_t xm_utf8_charwidth(xm_utf8_int_t val) { // test for 8-bit control characters if (val == 0) return 0; if (val < 32 || (val >= 0x7f && val < 0xa0)) { if (val == 0x09) return 4; // TAB if (val == 0x08) return -1; // BS return 0; // other control chars } // binary search in table of non-spacing characters tb_long_t min = 0; tb_long_t max = tb_arrayn(g_non_spacing) - 1; if (val >= g_non_spacing[0].first && val <= g_non_spacing[max].last) { while (max >= min) { tb_long_t mid = (min + max) / 2; if (val > g_non_spacing[mid].last) { min = mid + 1; } else if (val < g_non_spacing[mid].first) { max = mid - 1; } else { return 0; } } } if (val >= 0x1100 && (val <= 0x115f || // Hangul Jamo init. consonants val == 0x2329 || val == 0x232a || (val >= 0x2e80 && val <= 0xa4cf && val != 0x303f) || // CJK ... Yi (val >= 0xac00 && val <= 0xd7a3) || // Hangul Syllables (val >= 0xf900 && val <= 0xfaff) || // CJK Compatibility Ideographs (val >= 0xfe10 && val <= 0xfe19) || // Vertical forms (val >= 0xfe30 && val <= 0xfe6f) || // CJK Compatibility Forms (val >= 0xff00 && val <= 0xff60) || // Fullwidth Forms (val >= 0xffe0 && val <= 0xffe6) || (val >= 0x20000 && val <= 0x2fffd) || (val >= 0x30000 && val <= 0x3fffd))) { return 2; } return 1; } tb_long_t xm_utf8_strwidth(tb_char_t const* s, tb_size_t len) { tb_assert_and_check_return_val(s, -1); tb_long_t width = 0; tb_char_t const* p = s; tb_char_t const* e = s + len; while (p < e) { xm_utf8_int_t val; tb_char_t const* next = xm_utf8_decode(p, &val, tb_true); if (next) { tb_long_t w = xm_utf8_charwidth(val); if (w < 0) return -1; width += w; p = next; } else { p++; // invalid byte, skip } } return width; } tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, tb_size_t* errpos) { tb_assert_and_check_return_val(s, -1); tb_long_t n = 0; while (posi <= posj) { tb_char_t const* s1 = xm_utf8_decode(s + posi - 1, tb_null, strict); if (s1 == tb_null) { if (errpos) { *errpos = posi; } return -1; } posi = s1 - s + 1; n++; } return n; } tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_long_t n, tb_long_t posi) { tb_assert_and_check_return_val(s, -1); // check if (1 > posi || --posi > (tb_long_t)len) { return -1; // error: position out of bounds } if (n == 0) { // find beginning of current byte sequence while (posi > 0 && xm_utf8_iscontp(s + posi)) { posi--; } } else { if (xm_utf8_iscontp(s + posi)) { return -2; // error: initial position is a continuation byte } if (n < 0) { while (n < 0 && posi > 0) { do { posi--; } while (posi > 0 && xm_utf8_iscontp(s + posi)); n++; } } else { n--; while (n > 0 && posi < (tb_long_t)len) { do { posi++; } while (xm_utf8_iscontp(s + posi)); n--; } } } if (n == 0) { return posi + 1; } return 0; // nil } tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, xm_utf8_codepoint_func_t func, tb_cpointer_t udata) { tb_assert_and_check_return_val(s, tb_false); if (posi > posj) { return tb_true; } tb_char_t const* se = s + posj; for (s += posi - 1; s < se;) { xm_utf8_int_t code; s = xm_utf8_decode(s, &code, strict); if (s == tb_null) { return tb_false; } if (func && !func(code, udata)) { return tb_false; } } return tb_true; } tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen, tb_long_t init, tb_long_t* pchar_end) { tb_assert_and_check_return_val(s && sub, 0); if (sublen == 0) { if (init > (tb_long_t)len + 1) init = len + 1; tb_long_t start_byte = 1; if (init > 0) { start_byte = xm_utf8_offset_impl(s, len, init, 1); } else if (init < 0) { start_byte = xm_utf8_offset_impl(s, len, init, len + 1); } if (start_byte <= 0) start_byte = 1; tb_long_t char_pos = 1; if (start_byte > 1) { tb_long_t c = xm_utf8_len_impl(s, len, 1, start_byte - 1, tb_true, tb_null); if (c >= 0) char_pos = c + 1; } if (pchar_end) *pchar_end = char_pos - 1; return char_pos; } tb_long_t start_byte = 1; if (init > 0) { start_byte = xm_utf8_offset_impl(s, len, init, 1); } else if (init < 0) { start_byte = xm_utf8_offset_impl(s, len, init, len + 1); } if (start_byte <= 0) return 0; tb_char_t const* p = tb_strstr(s + start_byte - 1, sub); if (!p) return 0; tb_long_t found_byte_start = p - s + 1; tb_long_t char_start = 1; if (found_byte_start > 1) { tb_long_t c = xm_utf8_len_impl(s, len, 1, found_byte_start - 1, tb_true, tb_null); if (c < 0) return 0; char_start = c + 1; } if (pchar_end) { tb_long_t match_len = xm_utf8_len_impl(s, len, found_byte_start, found_byte_start + sublen - 1, tb_true, tb_null); if (match_len < 0) return 0; *pchar_end = char_start + match_len - 1; } return char_start; } tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen) { tb_assert_and_check_return_val(s && sub, 0); tb_check_return_val(sublen, 0); // optimize for single character search if (sublen == 1) { tb_char_t const* p = tb_strrchr(s, sub[0]); return p ? (tb_long_t)(p - s + 1) : 0; } tb_char_t const* p = s; tb_char_t const* last = tb_null; while (1) { p = tb_strstr(p, sub); if (!p) break; last = p; p += 1; } if (last) { return (tb_long_t)(last - s + 1); } return 0; } tb_long_t xm_utf8_byte_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, xm_utf8_codepoint_func_t func, tb_cpointer_t udata) { tb_size_t sublen = 0; tb_char_t const* sub = xm_utf8_sub_impl(s, len, i, j, &sublen); if (sub && sublen > 0) { // decode and push codepoints tb_long_t n = 0; tb_char_t const* p = sub; tb_char_t const* e = sub + sublen; while (p < e) { xm_utf8_int_t val; tb_char_t const* next = xm_utf8_decode(p, &val, tb_true); if (next) { if (func && !func(val, udata)) break; n++; p = next; } else { p++; } } return n; } return 0; } tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen) { tb_assert_and_check_return_val(s && psublen, tb_null); *psublen = 0; // map i (char index) to byte offset tb_long_t start_byte = 0; if (i > 0) { start_byte = xm_utf8_offset_impl(s, len, i, 1); } else if (i < 0) { start_byte = xm_utf8_offset_impl(s, len, i, len + 1); } else { start_byte = 1; } if (start_byte == -1) { if (i > 0) { return ""; } else { start_byte = 1; } } else if (start_byte == 0) { if (i < 0) { start_byte = 1; } else { return ""; } } // map j (char index) to byte offset (end) tb_long_t end_byte = 0; if (j >= 0) { end_byte = xm_utf8_offset_impl(s, len, j + 1, 1); } else { end_byte = xm_utf8_offset_impl(s, len, j + 1, len + 1); } if (end_byte == -1) { if (j >= 0) end_byte = len + 1; else end_byte = 1; } else if (end_byte == 0) { if (j >= 0) end_byte = len + 1; else end_byte = 1; } if (end_byte <= start_byte) { return ""; } *psublen = end_byte - start_byte; return s + start_byte - 1; } tb_char_t* xm_utf8_reverse_impl(tb_char_t const* s, tb_size_t len, tb_char_t* buf) { tb_assert_and_check_return_val(s && len && buf, tb_null); tb_char_t const* p = s; tb_char_t const* e = s + len; tb_char_t* d = buf + len; while (p < e) { xm_utf8_int_t code; tb_char_t const* next = xm_utf8_decode(p, &code, tb_false); // invalid utf8? treat as 1 byte tb_size_t n = 1; if (next) { n = next - p; } // safety check if (p + n > e) n = e - p; d -= n; tb_memcpy(d, p, n); p += n; } buf[len] = '\0'; return buf; }