| /* Association between Unicode characters and their names. | |
| Copyright (C) 2000-2002, 2005-2007, 2009-2026 Free Software Foundation, Inc. | |
| This file is free software. | |
| It is dual-licensed under "the GNU LGPLv3+ or the GNU GPLv2+". | |
| You can redistribute it and/or modify it under either | |
| - the terms of the GNU Lesser General Public License as published | |
| by the Free Software Foundation, either version 3, or (at your | |
| option) any later version, or | |
| - the terms of the GNU General Public License as published by the | |
| Free Software Foundation; either version 2, or (at your option) | |
| any later version, or | |
| - the same dual license "the GNU LGPLv3+ or the GNU GPLv2+". | |
| This file is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| Lesser General Public License and the GNU General Public License | |
| for more details. | |
| You should have received a copy of the GNU Lesser General Public | |
| License and of the GNU General Public License along with this | |
| program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include "uniname.h" | |
| #include <assert.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include "attribute.h" | |
| #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) | |
| /* Table of Unicode character names, derived from UnicodeData.txt. | |
| This table is generated in a way to minimize the memory footprint: | |
| 1. its compiled size is small (less than 350 KB), | |
| 2. it resides entirely in the text or read-only data segment of the | |
| executable or shared library: the table contains only immediate | |
| integers, no pointers, and the functions don't do heap allocation. | |
| */ | |
| #include "uninames.h" | |
| /* It contains: | |
| static const char unicode_name_words[36303] = ...; | |
| #define UNICODE_CHARNAME_NUM_WORDS 6260 | |
| static const struct { uint16_t extra_offset; uint16_t ind_offset; } unicode_name_by_length[26] = ...; | |
| #define UNICODE_CHARNAME_WORD_HANGUL 3902 | |
| #define UNICODE_CHARNAME_WORD_SYLLABLE 4978 | |
| #define UNICODE_CHARNAME_WORD_CJK 417 | |
| #define UNICODE_CHARNAME_WORD_COMPATIBILITY 6107 | |
| static const uint16_t unicode_names[68940] = ...; | |
| static const struct { uint16_t index; uint32_t name:24; } unicode_name_to_index[16626] = ...; | |
| static const struct { uint16_t index; uint32_t name:24; } unicode_index_to_name[16626] = ...; | |
| #define UNICODE_CHARNAME_MAX_LENGTH 83 | |
| #define UNICODE_CHARNAME_MAX_WORDS 13 | |
| static const struct { uint32_t index; uint32_t gap; uint16_t length; } unicode_ranges[401] = ...; | |
| */ | |
| /* Returns the word with a given index. */ | |
| static const char * | |
| unicode_name_word (unsigned int index, unsigned int *lengthp) | |
| { | |
| assert (index < UNICODE_CHARNAME_NUM_WORDS); | |
| /* Binary search for i with | |
| unicode_name_by_length[i].ind_offset <= index | |
| and | |
| index < unicode_name_by_length[i+1].ind_offset | |
| */ | |
| unsigned int i1 = 0; | |
| unsigned int i2 = SIZEOF (unicode_name_by_length) - 1; | |
| while (i2 - i1 > 1) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| if (unicode_name_by_length[i].ind_offset <= index) | |
| i1 = i; | |
| else | |
| i2 = i; | |
| } | |
| unsigned int i = i1; | |
| assert (unicode_name_by_length[i].ind_offset <= index | |
| && index < unicode_name_by_length[i+1].ind_offset); | |
| *lengthp = i; | |
| return &unicode_name_words[unicode_name_by_length[i].extra_offset | |
| + (index-unicode_name_by_length[i].ind_offset)*i]; | |
| } | |
| /* Looks up the index of a word. */ | |
| static int | |
| unicode_name_word_lookup (const char *word, size_t length) | |
| { | |
| if (length > 0 && length < SIZEOF (unicode_name_by_length) - 1) | |
| { | |
| /* Binary search among the words of given length. */ | |
| unsigned int extra_offset = unicode_name_by_length[length].extra_offset; | |
| unsigned int i0 = unicode_name_by_length[length].ind_offset; | |
| unsigned int i1 = i0; | |
| unsigned int i2 = unicode_name_by_length[length+1].ind_offset; | |
| while (i2 - i1 > 0) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| const char *p = &unicode_name_words[extra_offset + (i-i0)*length]; | |
| const char *w = word; | |
| unsigned int n = length; | |
| for (;;) | |
| { | |
| if (*p < *w) | |
| { | |
| if (i1 == i) | |
| return -1; | |
| /* Note here: i1 < i < i2. */ | |
| i1 = i; | |
| break; | |
| } | |
| if (*p > *w) | |
| { | |
| /* Note here: i1 <= i < i2. */ | |
| i2 = i; | |
| break; | |
| } | |
| p++; w++; n--; | |
| if (n == 0) | |
| return i; | |
| } | |
| } | |
| } | |
| return -1; | |
| } | |
| #define UNINAME_INVALID_INDEX UINT16_MAX | |
| /* Looks up the internal index of a Unicode character. */ | |
| static uint16_t | |
| unicode_code_to_index (ucs4_t c) | |
| { | |
| /* Binary search in unicode_ranges. */ | |
| unsigned int i1 = 0; | |
| unsigned int i2 = SIZEOF (unicode_ranges); | |
| for (;;) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| ucs4_t start_code = | |
| unicode_ranges[i].index + unicode_ranges[i].gap; | |
| ucs4_t end_code = | |
| start_code + unicode_ranges[i].length - 1; | |
| if (start_code <= c && c <= end_code) | |
| return c - unicode_ranges[i].gap; | |
| if (end_code < c) | |
| { | |
| if (i1 == i) | |
| break; | |
| /* Note here: i1 < i < i2. */ | |
| i1 = i; | |
| } | |
| else if (c < start_code) | |
| { | |
| if (i2 == i) | |
| break; | |
| /* Note here: i1 <= i < i2. */ | |
| i2 = i; | |
| } | |
| } | |
| return UNINAME_INVALID_INDEX; | |
| } | |
| /* Looks up the codepoint of a Unicode character, from the given | |
| internal index. */ | |
| static ucs4_t | |
| unicode_index_to_code (uint16_t index) | |
| { | |
| /* Binary search in unicode_ranges. */ | |
| unsigned int i1 = 0; | |
| unsigned int i2 = SIZEOF (unicode_ranges); | |
| for (;;) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| uint16_t start_index = unicode_ranges[i].index; | |
| uint16_t end_index = start_index + unicode_ranges[i].length - 1; | |
| if (start_index <= index && index <= end_index) | |
| return index + unicode_ranges[i].gap; | |
| if (end_index < index) | |
| { | |
| if (i1 == i) | |
| break; | |
| /* Note here: i1 < i < i2. */ | |
| i1 = i; | |
| } | |
| else if (index < start_index) | |
| { | |
| if (i2 == i) | |
| break; | |
| /* Note here: i1 <= i < i2. */ | |
| i2 = i; | |
| } | |
| } | |
| return UNINAME_INVALID; | |
| } | |
| /* Auxiliary tables for Hangul syllable names, see the Unicode 3.0 book, | |
| sections 3.11 and 4.4. */ | |
| static const char jamo_initial_short_name[19][3] = | |
| { | |
| "G", "GG", "N", "D", "DD", "R", "M", "B", "BB", "S", "SS", "", "J", "JJ", | |
| "C", "K", "T", "P", "H" | |
| }; | |
| static const char jamo_medial_short_name[21][4] = | |
| { | |
| "A", "AE", "YA", "YAE", "EO", "E", "YEO", "YE", "O", "WA", "WAE", "OE", "YO", | |
| "U", "WEO", "WE", "WI", "YU", "EU", "YI", "I" | |
| }; | |
| static const char jamo_final_short_name[28][3] = | |
| { | |
| "", "G", "GG", "GS", "N", "NI", "NH", "D", "L", "LG", "LM", "LB", "LS", "LT", | |
| "LP", "LH", "M", "B", "BS", "S", "SS", "NG", "J", "C", "K", "T", "P", "H" | |
| }; | |
| /* Looks up the name of a Unicode character, in uppercase ASCII. | |
| Returns the filled buf, or NULL if the character does not have a name. */ | |
| char * | |
| unicode_character_name (ucs4_t c, char *buf) | |
| { | |
| if (c >= 0xAC00 && c <= 0xD7A3) | |
| { | |
| /* Special case for Hangul syllables. Keeps the tables small. */ | |
| /* buf needs to have at least 16 + 7 + 1 bytes here. */ | |
| memcpy (buf, "HANGUL SYLLABLE ", 16); | |
| char *ptr = buf + 16; | |
| unsigned int tmp = c - 0xAC00; | |
| unsigned int index3 = tmp % 28; tmp = tmp / 28; | |
| unsigned int index2 = tmp % 21; tmp = tmp / 21; | |
| unsigned int index1 = tmp; | |
| const char *q; | |
| q = jamo_initial_short_name[index1]; | |
| while (*q != '\0') | |
| *ptr++ = *q++; | |
| q = jamo_medial_short_name[index2]; | |
| while (*q != '\0') | |
| *ptr++ = *q++; | |
| q = jamo_final_short_name[index3]; | |
| while (*q != '\0') | |
| *ptr++ = *q++; | |
| *ptr = '\0'; | |
| return buf; | |
| } | |
| else if ((c >= 0xF900 && c <= 0xFA2D) || (c >= 0xFA30 && c <= 0xFA6A) | |
| || (c >= 0xFA70 && c <= 0xFAD9) || (c >= 0x2F800 && c <= 0x2FA1D)) | |
| { | |
| /* Special case for CJK compatibility ideographs. Keeps the tables | |
| small. */ | |
| /* buf needs to have at least 28 + 5 + 1 bytes here. */ | |
| memcpy (buf, "CJK COMPATIBILITY IDEOGRAPH-", 28); | |
| char *ptr = buf + 28; | |
| for (int i = (c < 0x10000 ? 12 : 16); i >= 0; i -= 4) | |
| { | |
| unsigned int x = (c >> i) & 0xf; | |
| *ptr++ = (x < 10 ? '0' : 'A' - 10) + x; | |
| } | |
| *ptr = '\0'; | |
| return buf; | |
| } | |
| else if ((c >= 0xFE00 && c <= 0xFE0F) || (c >= 0xE0100 && c <= 0xE01EF)) | |
| { | |
| /* Special case for variation selectors. Keeps the tables | |
| small. */ | |
| /* buf needs to have at least 19 + 3 + 1 bytes here. */ | |
| sprintf (buf, "VARIATION SELECTOR-%u", | |
| c <= 0xFE0F ? c - 0xFE00 + 1 : c - 0xE0100 + 17); | |
| return buf; | |
| } | |
| else | |
| { | |
| uint16_t index = unicode_code_to_index (c); | |
| const uint16_t *words = NULL; | |
| if (index != UNINAME_INVALID_INDEX) | |
| { | |
| /* Binary search in unicode_code_to_name. */ | |
| unsigned int i1 = 0; | |
| unsigned int i2 = SIZEOF (unicode_index_to_name); | |
| for (;;) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| if (unicode_index_to_name[i].index == index) | |
| { | |
| words = &unicode_names[unicode_index_to_name[i].name]; | |
| break; | |
| } | |
| else if (unicode_index_to_name[i].index < index) | |
| { | |
| if (i1 == i) | |
| { | |
| words = NULL; | |
| break; | |
| } | |
| /* Note here: i1 < i < i2. */ | |
| i1 = i; | |
| } | |
| else if (unicode_index_to_name[i].index > index) | |
| { | |
| if (i2 == i) | |
| { | |
| words = NULL; | |
| break; | |
| } | |
| /* Note here: i1 <= i < i2. */ | |
| i2 = i; | |
| } | |
| } | |
| } | |
| if (words != NULL) | |
| { | |
| /* Found it in unicode_index_to_name. Now concatenate the words. */ | |
| /* buf needs to have at least UNICODE_CHARNAME_MAX_LENGTH + 1 | |
| bytes. */ | |
| char *ptr = buf; | |
| for (;;) | |
| { | |
| unsigned int wordlen; | |
| const char *word = unicode_name_word (*words>>1, &wordlen); | |
| do | |
| *ptr++ = *word++; | |
| while (--wordlen > 0); | |
| if ((*words & 1) == 0) | |
| break; | |
| *ptr++ = ' '; | |
| words++; | |
| } | |
| *ptr = '\0'; | |
| return buf; | |
| } | |
| return NULL; | |
| } | |
| } | |
| /* Looks up the Unicode character with a given name, in upper- or lowercase | |
| ASCII. Returns the character if found, or UNINAME_INVALID if not found. */ | |
| ucs4_t | |
| unicode_name_character (const char *name) | |
| { | |
| size_t len = strlen (name); | |
| if (len > 1 && len <= UNICODE_CHARNAME_MAX_LENGTH) | |
| { | |
| /* Test for "word1 word2 ..." syntax. */ | |
| char buf[UNICODE_CHARNAME_MAX_LENGTH]; | |
| char *ptr = buf; | |
| for (;;) | |
| { | |
| char c = *name++; | |
| if (!(c >= ' ' && c <= '~')) | |
| break; | |
| *ptr++ = (c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c); | |
| if (--len == 0) | |
| goto filled_buf; | |
| } | |
| if (false) | |
| filled_buf: | |
| { | |
| { | |
| /* Special case for variation selector aliases. Keeps the | |
| tables small. */ | |
| const char *p1 = buf; | |
| if (ptr >= buf + 3 && *p1++ == 'V') | |
| { | |
| if (*p1++ == 'S') | |
| { | |
| if (*p1 != '0') | |
| { | |
| unsigned int c = 0; | |
| for (;;) | |
| { | |
| if (*p1 >= '0' && *p1 <= '9') | |
| c += (*p1 - '0'); | |
| p1++; | |
| if (p1 == ptr) | |
| { | |
| if (c >= 1 && c <= 16) | |
| return c - 1 + 0xFE00; | |
| else if (c >= 17 && c <= 256) | |
| return c - 17 + 0xE0100; | |
| else | |
| break; | |
| } | |
| c = c * 10; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| { | |
| /* Convert the constituents to uint16_t words. */ | |
| uint16_t words[UNICODE_CHARNAME_MAX_WORDS]; | |
| uint16_t *wordptr = words; | |
| { | |
| const char *p1 = buf; | |
| for (;;) | |
| { | |
| { | |
| const char *p2 = p1; | |
| while (p2 < ptr && *p2 != ' ') | |
| p2++; | |
| int word = unicode_name_word_lookup (p1, p2 - p1); | |
| if (word < 0) | |
| break; | |
| if (wordptr == &words[UNICODE_CHARNAME_MAX_WORDS]) | |
| break; | |
| *wordptr++ = word; | |
| if (p2 == ptr) | |
| goto filled_words; | |
| p1 = p2 + 1; | |
| } | |
| /* Special case for Hangul syllables. Keeps the tables small. */ | |
| if (wordptr == &words[2] | |
| && words[0] == UNICODE_CHARNAME_WORD_HANGUL | |
| && words[1] == UNICODE_CHARNAME_WORD_SYLLABLE) | |
| { | |
| /* Split the last word [p1..ptr) into three parts: | |
| 1) [BCDGHJKMNPRST] | |
| 2) [AEIOUWY] | |
| 3) [BCDGHIJKLMNPST] | |
| */ | |
| const char *p2 = p1; | |
| while (p2 < ptr | |
| && (*p2 == 'B' || *p2 == 'C' || *p2 == 'D' | |
| || *p2 == 'G' || *p2 == 'H' || *p2 == 'J' | |
| || *p2 == 'K' || *p2 == 'M' || *p2 == 'N' | |
| || *p2 == 'P' || *p2 == 'R' || *p2 == 'S' | |
| || *p2 == 'T')) | |
| p2++; | |
| const char *p3 = p2; | |
| while (p3 < ptr | |
| && (*p3 == 'A' || *p3 == 'E' || *p3 == 'I' | |
| || *p3 == 'O' || *p3 == 'U' || *p3 == 'W' | |
| || *p3 == 'Y')) | |
| p3++; | |
| const char *p4 = p3; | |
| while (p4 < ptr | |
| && (*p4 == 'B' || *p4 == 'C' || *p4 == 'D' | |
| || *p4 == 'G' || *p4 == 'H' || *p4 == 'I' | |
| || *p4 == 'J' || *p4 == 'K' || *p4 == 'L' | |
| || *p4 == 'M' || *p4 == 'N' || *p4 == 'P' | |
| || *p4 == 'S' || *p4 == 'T')) | |
| p4++; | |
| if (p4 == ptr) | |
| { | |
| size_t n1 = p2 - p1; | |
| size_t n2 = p3 - p2; | |
| size_t n3 = p4 - p3; | |
| if (n1 <= 2 && (n2 >= 1 && n2 <= 3) && n3 <= 2) | |
| { | |
| for (unsigned int index1 = 0; index1 < 19; index1++) | |
| if (memeq (jamo_initial_short_name[index1], p1, n1) | |
| && jamo_initial_short_name[index1][n1] == '\0') | |
| { | |
| for (unsigned int index2 = 0; index2 < 21; index2++) | |
| if (memeq (jamo_medial_short_name[index2], p2, n2) | |
| && jamo_medial_short_name[index2][n2] == '\0') | |
| { | |
| for (unsigned int index3 = 0; index3 < 28; index3++) | |
| if (memeq (jamo_final_short_name[index3], p3, n3) | |
| && jamo_final_short_name[index3][n3] == '\0') | |
| { | |
| return 0xAC00 + (index1 * 21 + index2) * 28 + index3; | |
| } | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| /* Special case for CJK compatibility ideographs. Keeps the | |
| tables small. */ | |
| if (wordptr == &words[2] | |
| && words[0] == UNICODE_CHARNAME_WORD_CJK | |
| && words[1] == UNICODE_CHARNAME_WORD_COMPATIBILITY | |
| && p1 + 14 <= ptr | |
| && p1 + 15 >= ptr | |
| && memeq (p1, "IDEOGRAPH-", 10)) | |
| { | |
| const char *p2 = p1 + 10; | |
| if (*p2 != '0') | |
| { | |
| unsigned int c = 0; | |
| for (;;) | |
| { | |
| if (*p2 >= '0' && *p2 <= '9') | |
| c += (*p2 - '0'); | |
| else if (*p2 >= 'A' && *p2 <= 'F') | |
| c += (*p2 - 'A' + 10); | |
| else | |
| break; | |
| p2++; | |
| if (p2 == ptr) | |
| { | |
| if ((c >= 0xF900 && c <= 0xFA2D) | |
| || (c >= 0xFA30 && c <= 0xFA6A) | |
| || (c >= 0xFA70 && c <= 0xFAD9) | |
| || (c >= 0x2F800 && c <= 0x2FA1D)) | |
| return c; | |
| else | |
| break; | |
| } | |
| c = c << 4; | |
| } | |
| } | |
| } | |
| /* Special case for variation selectors. Keeps the | |
| tables small. */ | |
| if (wordptr == &words[1] | |
| && words[0] == UNICODE_CHARNAME_WORD_VARIATION | |
| && p1 + 10 <= ptr | |
| && p1 + 12 >= ptr | |
| && memeq (p1, "SELECTOR-", 9)) | |
| { | |
| const char *p2 = p1 + 9; | |
| if (*p2 != '0') | |
| { | |
| unsigned int c = 0; | |
| for (;;) | |
| { | |
| if (*p2 >= '0' && *p2 <= '9') | |
| c += (*p2 - '0'); | |
| p2++; | |
| if (p2 == ptr) | |
| { | |
| if (c >= 1 && c <= 16) | |
| return c - 1 + 0xFE00; | |
| else if (c >= 17 && c <= 256) | |
| return c - 17 + 0xE0100; | |
| else | |
| break; | |
| } | |
| c = c * 10; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (false) | |
| filled_words: | |
| { | |
| /* Multiply by 2, to simplify later comparisons. */ | |
| size_t words_length = wordptr - words; | |
| { | |
| size_t i = words_length - 1; | |
| words[i] = 2 * words[i]; | |
| for (; i > 0; ) | |
| { | |
| --i; | |
| words[i] = 2 * words[i] + 1; | |
| } | |
| } | |
| /* Binary search in unicode_name_to_index. */ | |
| { | |
| unsigned int i1 = 0; | |
| unsigned int i2 = SIZEOF (unicode_name_to_index); | |
| for (;;) | |
| { | |
| unsigned int i = (i1 + i2) >> 1; | |
| const uint16_t *w = words; | |
| const uint16_t *p = &unicode_names[unicode_name_to_index[i].name]; | |
| size_t n = words_length; | |
| for (;;) | |
| { | |
| if (*p < *w) | |
| { | |
| if (i1 == i) | |
| goto name_not_found; | |
| /* Note here: i1 < i < i2. */ | |
| i1 = i; | |
| break; | |
| } | |
| else if (*p > *w) | |
| { | |
| if (i2 == i) | |
| goto name_not_found; | |
| /* Note here: i1 <= i < i2. */ | |
| i2 = i; | |
| break; | |
| } | |
| p++; w++; n--; | |
| if (n == 0) | |
| return unicode_index_to_code (unicode_name_to_index[i].index); | |
| } | |
| } | |
| } | |
| name_not_found: ; | |
| } | |
| } | |
| } | |
| } | |
| return UNINAME_INVALID; | |
| } |