Files
cmus/uchar.c
2026-03-29 14:01:52 +03:00

674 lines
14 KiB
C

/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "uchar.h"
#include "compiler.h"
#include "gbuf.h"
#include "utils.h" /* N_ELEMENTS */
#include "ui_curses.h" /* using_utf8, charset */
#include "convert.h"
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
#include <ctype.h>
#include "unidecomp.h"
#include "wcwidth_uchar.h"
const char hex_tab[16] CMUS_NONSTRING = "0123456789abcdef";
/*
* Byte Sequence Min Min Max
* ----------------------------------------------------------------------------------
* 0xxxxxxx 0000000 0x00000 0x00007f
* 110xxxxx 10xxxxxx 000 10000000 0x00080 0x0007ff
* 1110xxxx 10xxxxxx 10xxxxxx 00001000 00000000 0x00800 0x00ffff
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 00001 00000000 00000000 0x10000 0x10ffff (not 0x1fffff)
*
* max: 100 001111 111111 111111 (0x10ffff)
*/
/* Length of UTF-8 byte sequence.
* Table index is the first byte of UTF-8 sequence.
*/
static const signed char len_tab[256] = {
/* 0-127 0xxxxxxx */
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,
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,
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,
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,
/* 128-191 10xxxxxx (invalid first byte) */
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,
/* 192-223 110xxxxx */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* 224-239 1110xxxx */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
/* 240-244 11110xxx (000 - 100) */
4, 4, 4, 4, 4,
/* 11110xxx (101 - 111) (always invalid) */
-1, -1, -1,
/* 11111xxx (always invalid) */
-1, -1, -1, -1, -1, -1, -1, -1
};
/* fault-tolerant equivalent to len_tab, from glib */
static const char utf8_skip_data[256] = {
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,
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,
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,
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,
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,
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,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};
const char * const utf8_skip = utf8_skip_data;
/* index is length of the UTF-8 sequence - 1 */
static int min_val[4] = { 0x000000, 0x000080, 0x000800, 0x010000 };
static int max_val[4] = { 0x00007f, 0x0007ff, 0x00ffff, 0x10ffff };
/* get value bits from the first UTF-8 sequence byte */
static unsigned int first_byte_mask[4] = { 0x7f, 0x1f, 0x0f, 0x07 };
int u_is_valid(const char *str)
{
const unsigned char *s = (const unsigned char *)str;
int i = 0;
while (s[i]) {
unsigned char ch = s[i++];
int len = len_tab[ch];
if (len <= 0)
return 0;
if (len > 1) {
/* len - 1 10xxxxxx bytes */
uchar u;
int c;
len--;
u = ch & first_byte_mask[len];
c = len;
do {
ch = s[i++];
if (len_tab[ch] != 0)
return 0;
u = (u << 6) | (ch & 0x3f);
} while (--c);
if (u < min_val[len] || u > max_val[len])
return 0;
}
}
return 1;
}
size_t u_strlen(const char *str)
{
size_t len;
for (len = 0; *str; len++)
str = u_next_char(str);
return len;
}
size_t u_strlen_safe(const char *str)
{
const unsigned char *s = (const unsigned char *)str;
size_t len = 0;
while (*s) {
int l = len_tab[*s];
if (unlikely(l > 1)) {
/* next l - 1 bytes must be 0x10xxxxxx */
int c = 1;
do {
if (len_tab[s[c]] != 0) {
/* invalid sequence */
goto single_char;
}
c++;
} while (c < l);
/* valid sequence */
s += l;
len++;
continue;
}
single_char:
/* l is -1, 0 or 1
* invalid chars counted as single characters */
s++;
len++;
}
return len;
}
int u_char_width(uchar u)
{
int w;
if (unlikely(u < 0x20))
goto control;
if (unlikely(!using_utf8))
return 1;
/* invalid bytes in unicode stream are rendered "<xx>" */
if (u & U_INVALID_MASK)
goto invalid;
w = wcwidth_uchar(u);
if (w >= 0)
return w;
else
return 1;
control:
/* special case */
if (u == 0)
return 1;
/* print control chars as <xx> */
invalid:
return 4;
}
int u_str_width(const char *str)
{
int idx = 0, w = 0;
while (str[idx]) {
uchar u = u_get_char(str, &idx);
w += u_char_width(u);
}
return w;
}
int u_str_nwidth(const char *str, int len)
{
int idx = 0;
int w = 0;
while (len > 0) {
uchar u = u_get_char(str, &idx);
if (u == 0)
break;
w += u_char_width(u);
len--;
}
return w;
}
char *u_strchr(const char *str, uchar uch)
{
int idx = 0;
while (str[idx]) {
uchar u = u_get_char(str, &idx);
if (uch == u)
return (char *) (str + idx);
}
return NULL;
}
void u_prev_char_pos(const char *str, int *idx)
{
const unsigned char *s = (const unsigned char *)str;
int c, len, i = *idx;
uchar ch;
ch = s[--i];
len = len_tab[ch];
if (len != 0) {
/* start of byte sequence or invalid uchar */
goto one;
}
c = 1;
while (1) {
if (i == 0) {
/* first byte of the sequence is missing */
break;
}
ch = s[--i];
len = len_tab[ch];
c++;
if (len == 0) {
if (c < 4)
continue;
/* too long sequence */
break;
}
if (len != c) {
/* incorrect length */
break;
}
/* ok */
*idx = i;
return;
}
one:
*idx = *idx - 1;
return;
}
uchar u_get_char(const char *str, int *idx)
{
const unsigned char *s = (const unsigned char *)str;
int len, i, x = 0;
uchar ch, u;
if (idx)
s += *idx;
else
idx = &x;
ch = s[0];
/* ASCII optimization */
if (ch < 128) {
*idx += 1;
return ch;
}
len = len_tab[ch];
if (unlikely(len < 1))
goto invalid;
u = ch & first_byte_mask[len - 1];
for (i = 1; i < len; i++) {
ch = s[i];
if (unlikely(len_tab[ch] != 0))
goto invalid;
u = (u << 6) | (ch & 0x3f);
}
*idx += len;
return u;
invalid:
*idx += 1;
u = s[0];
return u | U_INVALID_MASK;
}
void u_set_char_raw(char *str, int *idx, uchar uch)
{
int i = *idx;
if (uch <= 0x0000007fU) {
str[i++] = uch;
*idx = i;
} else if (uch <= 0x000007ffU) {
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000c0U;
i += 2;
*idx = i;
} else if (uch <= 0x0000ffffU) {
str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000e0U;
i += 3;
*idx = i;
} else if (uch <= 0x0010ffffU) {
str[i + 3] = (uch & 63) | 0x80; uch >>= 6;
str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000f0U;
i += 4;
*idx = i;
} else {
/* must be an invalid uchar */
str[i++] = uch & 0xff;
*idx = i;
}
}
/*
* Printing functions, these lose information
*/
void u_set_char(char *str, size_t *idx, uchar uch)
{
int i = *idx;
if (unlikely(uch <= 0x0000001fU))
goto invalid;
if (uch <= 0x0000007fU) {
str[i++] = uch;
*idx = i;
return;
} else if (uch <= 0x000007ffU) {
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000c0U;
i += 2;
*idx = i;
return;
} else if (uch <= 0x0000ffffU) {
str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000e0U;
i += 3;
*idx = i;
return;
} else if (uch <= 0x0010ffffU) {
str[i + 3] = (uch & 63) | 0x80; uch >>= 6;
str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
str[i + 0] = uch | 0x000000f0U;
i += 4;
*idx = i;
return;
}
invalid:
/* control character or invalid unicode */
if (uch == 0) {
/* handle this special case here to make the common case fast */
str[i++] = 0;
*idx = i;
} else {
str[i++] = '<';
str[i++] = hex_tab[(uch >> 4) & 0xf];
str[i++] = hex_tab[uch & 0xf];
str[i++] = '>';
*idx = i;
}
}
size_t u_copy_chars(char *dst, const char *src, int *width)
{
int w = *width;
int si = 0;
size_t di = 0;
int cw;
uchar u;
while (w >= 0) {
u = u_get_char(src, &si);
if (u == 0)
break;
cw = u_char_width(u);
w -= cw;
if (unlikely(w < 0)) {
if (cw == 4 && w >= -3) {
dst[di++] = '<';
if (w >= -2)
dst[di++] = hex_tab[(u >> 4) & 0xf];
if (w >= -1)
dst[di++] = hex_tab[u & 0xf];
w = 0;
} else
w += cw;
break;
}
u_set_char(dst, &di, u);
}
*width = w;
return di;
}
int u_to_ascii(char *dst, const char *src, int len)
{
int i, idx = 0;
for (i = 0; i < len && src[idx]; i++) {
uchar u = u_get_char(src, &idx);
dst[i] = (u < 128) ? u : '?';
}
return i;
}
void u_to_utf8(char *dst, const char *src)
{
int s = 0;
size_t d = 0;
uchar u;
do {
u = u_get_char(src, &s);
u_set_char(dst, &d, u);
} while (u!=0);
}
int u_print_size(uchar uch)
{
int s = u_char_size(uch);
/* control characters and invalid unicode set as <XX> */
if (uch < 0x0000001fU && uch != 0){
return 4;
}
return s;
}
int u_str_print_size(const char *str)
{
int l = 0;
int idx = 0;
uchar u;
do {
u = u_get_char(str, &idx);
l += u_print_size(u);
} while (u!=0);
return l;
}
int u_skip_chars(const char *str, int *width, bool overskip)
{
int w = *width;
int last_idx = 0, idx = 0;
uchar u = 0;
while (w > 0) {
last_idx = idx;
u = u_get_char(str, &idx);
w -= u_char_width(u);
}
/* undo last get if skipped 'too much' (the last char was double width or invalid (<xx>)) */
if (w < 0 && !overskip) {
w += u_char_width(u);
idx = last_idx;
} else while (1) {
/* consume any zero-width characters (e.g. combining marks) */
last_idx = idx;
u = u_get_char(str, &idx);
if (u_char_width(u) != 0) {
idx = last_idx;
break;
}
}
*width = w;
return idx;
}
/*
* Case-folding functions
*/
static inline uchar u_casefold_char(uchar ch)
{
/* faster lookup for for A-Z, rest of ASCII unaffected */
if (ch < 0x0041)
return ch;
if (ch <= 0x005A)
return ch + 0x20;
#if defined(_WIN32) || defined(__STDC_ISO_10646__) || defined(__APPLE__)
if (ch < 128)
return ch;
ch = towlower(ch);
#endif
return ch;
}
char *u_casefold(const char *str)
{
GBUF(out);
int i = 0;
while (str[i]) {
char buf[4];
int buflen = 0;
uchar ch = u_get_char(str, &i);
ch = u_casefold_char(ch);
u_set_char_raw(buf, &buflen, ch);
gbuf_add_bytes(&out, buf, buflen);
}
return gbuf_steal(&out);
}
/*
* Comparison functions
*/
int u_strcase_equal(const char *a, const char *b)
{
int ai = 0, bi = 0;
while (a[ai]) {
uchar au, bu;
au = u_get_char(a, &ai);
bu = u_get_char(b, &bi);
if (u_casefold_char(au) != u_casefold_char(bu))
return 0;
}
return b[bi] ? 0 : 1;
}
static uchar get_base_from_composed(uchar ch)
{
int begin = 0;
int end = N_ELEMENTS(unidecomp_map);
if (ch < unidecomp_map[begin].composed || ch > unidecomp_map[end - 1].composed)
return ch;
/* binary search */
while (1) {
int half = (begin + end) / 2;
if (ch == unidecomp_map[half].composed)
return unidecomp_map[half].base;
else if (half == begin)
break;
else if (ch > unidecomp_map[half].composed)
begin = half;
else
end = half;
}
return ch;
}
static inline int do_u_strncase_equal(const char *a, const char *b, size_t len, int only_base_chars)
{
int ai = 0, bi = 0;
size_t i;
for (i = 0; i < len; i++) {
uchar au, bu;
au = u_get_char(a, &ai);
bu = u_get_char(b, &bi);
if (only_base_chars) {
au = get_base_from_composed(au);
bu = get_base_from_composed(bu);
}
if (u_casefold_char(au) != u_casefold_char(bu))
return 0;
}
return 1;
}
int u_strncase_equal(const char *a, const char *b, size_t len)
{
return do_u_strncase_equal(a, b, len, 0);
}
int u_strncase_equal_base(const char *a, const char *b, size_t len)
{
return do_u_strncase_equal(a, b, len, 1);
}
static inline char *do_u_strcasestr(const char *haystack, const char *needle, int only_base_chars)
{
/* strlen is faster and works here */
int haystack_len = strlen(haystack);
int needle_len = u_strlen(needle);
do {
int idx;
if (haystack_len < needle_len)
return NULL;
if (do_u_strncase_equal(needle, haystack, needle_len, only_base_chars))
return (char *)haystack;
/* skip one char */
idx = 0;
u_get_char(haystack, &idx);
haystack += idx;
haystack_len -= idx;
} while (1);
}
char *u_strcasestr(const char *haystack, const char *needle)
{
return do_u_strcasestr(haystack, needle, 0);
}
char *u_strcasestr_base(const char *haystack, const char *needle)
{
return do_u_strcasestr(haystack, needle, 1);
}
char *u_strcasestr_filename(const char *haystack, const char *needle)
{
char *r = NULL, *ustr = NULL;
if (!using_utf8 && utf8_encode(haystack, charset, &ustr) == 0)
haystack = ustr;
r = u_strcasestr_base(haystack, needle);
free(ustr);
return r;
}