2005-09-15 09:58:31 -04:00
|
|
|
/* Charsets convertor */
|
|
|
|
|
2006-10-12 17:43:49 -04:00
|
|
|
#ifndef _GNU_SOURCE
|
|
|
|
#define _GNU_SOURCE /* strcasecmp() */
|
|
|
|
#endif
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if HAVE_LANGINFO_CODESET
|
|
|
|
#include <langinfo.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdlib.h>
|
2006-08-05 12:45:53 -04:00
|
|
|
#if HAVE_WCTYPE_H
|
|
|
|
#include <wctype.h>
|
|
|
|
#endif
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2010-07-23 13:59:59 -04:00
|
|
|
#ifdef HAVE_ICONV
|
|
|
|
#include <errno.h>
|
|
|
|
#include <iconv.h>
|
|
|
|
#endif
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "document/options.h"
|
|
|
|
#include "intl/charsets.h"
|
|
|
|
#include "util/conv.h"
|
|
|
|
#include "util/error.h"
|
|
|
|
#include "util/fastfind.h"
|
2008-01-03 06:57:24 -05:00
|
|
|
#include "util/hash.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "util/memory.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Fix namespace clash on MacOS. */
|
|
|
|
#define table table_elinks
|
|
|
|
|
|
|
|
struct table_entry {
|
|
|
|
unsigned char c;
|
2006-09-24 05:47:00 -04:00
|
|
|
/* This should in principle be unicode_val_T, but because all
|
|
|
|
* the values currently in codepage.inc fit in 16 bits, we can
|
|
|
|
* as well use uint16_t and halve sizeof(struct table_entry)
|
|
|
|
* from 8 bytes to 4. Should other characters ever be needed,
|
|
|
|
* unicode_val_T u : 24 might be a possibility, although it
|
|
|
|
* seems a little unportable as bitfields are in principle
|
|
|
|
* restricted to int, which may be 16-bit. */
|
|
|
|
uint16_t u;
|
2005-09-15 09:58:31 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
struct codepage_desc {
|
2021-01-02 10:20:27 -05:00
|
|
|
char *name;
|
|
|
|
char *const *aliases;
|
2006-09-28 18:07:54 -04:00
|
|
|
|
2006-09-24 09:55:29 -04:00
|
|
|
/* The Unicode mappings of codepage bytes 0x80...0xFF.
|
|
|
|
* (0x00...0x7F are assumed to be ASCII in all codepages.)
|
|
|
|
* Because all current values fit in 16 bits, we store them as
|
|
|
|
* uint16_t rather than unicode_val_T. If the codepage does
|
|
|
|
* not use some byte, then @highhalf maps that byte to 0xFFFF,
|
|
|
|
* which C code converts to UCS_REPLACEMENT_CHARACTER where
|
|
|
|
* appropriate. (U+FFFF is reserved and will never be
|
|
|
|
* assigned as a character.) */
|
|
|
|
const uint16_t *highhalf;
|
2006-09-28 18:07:54 -04:00
|
|
|
|
2006-09-24 09:55:29 -04:00
|
|
|
/* If some byte in the codepage corresponds to multiple Unicode
|
|
|
|
* characters, then the preferred character is in @highhalf
|
2007-01-03 00:32:00 -05:00
|
|
|
* above, and the rest are listed here in @table. This table
|
2006-09-24 09:55:29 -04:00
|
|
|
* is not used for translating from the codepage to Unicode. */
|
2006-09-24 04:59:23 -04:00
|
|
|
const struct table_entry *table;
|
2010-07-23 09:44:12 -04:00
|
|
|
|
|
|
|
/* Whether use iconv for translation */
|
|
|
|
unsigned int iconv:1;
|
2005-09-15 09:58:31 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
#include "intl/codepage.inc"
|
|
|
|
#include "intl/uni_7b.inc"
|
|
|
|
#include "intl/entity.inc"
|
|
|
|
|
2009-03-28 14:15:08 -04:00
|
|
|
/* Declare the external-linkage inline functions defined in this file.
|
|
|
|
* Avoid the GCC 4.3.1 warning: `foo' declared inline after being
|
|
|
|
* called. The functions are not declared inline in charsets.h
|
|
|
|
* because C99 6.7.4p6 says that every external-linkage function
|
|
|
|
* declared inline shall be defined in the same translation unit.
|
|
|
|
* The non-inline declarations in charsets.h also make sure that the
|
|
|
|
* compiler emits global definitions for the symbols so that the
|
|
|
|
* functions can be called from other translation units. */
|
2021-01-02 10:20:27 -05:00
|
|
|
NONSTATIC_INLINE char *encode_utf8(unicode_val_T u);
|
|
|
|
NONSTATIC_INLINE int utf8charlen(const char *p);
|
|
|
|
NONSTATIC_INLINE unicode_val_T utf8_to_unicode(char **string,
|
|
|
|
const char *end);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2007-01-02 14:40:14 -05:00
|
|
|
static const char strings[256][2] = {
|
2005-09-15 09:58:31 -04:00
|
|
|
"\000", "\001", "\002", "\003", "\004", "\005", "\006", "\007",
|
|
|
|
"\010", "\011", "\012", "\013", "\014", "\015", "\016", "\017",
|
|
|
|
"\020", "\021", "\022", "\023", "\024", "\025", "\026", "\033",
|
|
|
|
"\030", "\031", "\032", "\033", "\034", "\035", "\036", "\033",
|
|
|
|
"\040", "\041", "\042", "\043", "\044", "\045", "\046", "\047",
|
|
|
|
"\050", "\051", "\052", "\053", "\054", "\055", "\056", "\057",
|
|
|
|
"\060", "\061", "\062", "\063", "\064", "\065", "\066", "\067",
|
|
|
|
"\070", "\071", "\072", "\073", "\074", "\075", "\076", "\077",
|
|
|
|
"\100", "\101", "\102", "\103", "\104", "\105", "\106", "\107",
|
|
|
|
"\110", "\111", "\112", "\113", "\114", "\115", "\116", "\117",
|
|
|
|
"\120", "\121", "\122", "\123", "\124", "\125", "\126", "\127",
|
|
|
|
"\130", "\131", "\132", "\133", "\134", "\135", "\136", "\137",
|
|
|
|
"\140", "\141", "\142", "\143", "\144", "\145", "\146", "\147",
|
|
|
|
"\150", "\151", "\152", "\153", "\154", "\155", "\156", "\157",
|
|
|
|
"\160", "\161", "\162", "\163", "\164", "\165", "\166", "\167",
|
|
|
|
"\170", "\171", "\172", "\173", "\174", "\175", "\176", "\177",
|
|
|
|
"\200", "\201", "\202", "\203", "\204", "\205", "\206", "\207",
|
|
|
|
"\210", "\211", "\212", "\213", "\214", "\215", "\216", "\217",
|
|
|
|
"\220", "\221", "\222", "\223", "\224", "\225", "\226", "\227",
|
|
|
|
"\230", "\231", "\232", "\233", "\234", "\235", "\236", "\237",
|
|
|
|
"\240", "\241", "\242", "\243", "\244", "\245", "\246", "\247",
|
|
|
|
"\250", "\251", "\252", "\253", "\254", "\255", "\256", "\257",
|
|
|
|
"\260", "\261", "\262", "\263", "\264", "\265", "\266", "\267",
|
|
|
|
"\270", "\271", "\272", "\273", "\274", "\275", "\276", "\277",
|
|
|
|
"\300", "\301", "\302", "\303", "\304", "\305", "\306", "\307",
|
|
|
|
"\310", "\311", "\312", "\313", "\314", "\315", "\316", "\317",
|
|
|
|
"\320", "\321", "\322", "\323", "\324", "\325", "\326", "\327",
|
|
|
|
"\330", "\331", "\332", "\333", "\334", "\335", "\336", "\337",
|
|
|
|
"\340", "\341", "\342", "\343", "\344", "\345", "\346", "\347",
|
|
|
|
"\350", "\351", "\352", "\353", "\354", "\355", "\356", "\357",
|
|
|
|
"\360", "\361", "\362", "\363", "\364", "\365", "\366", "\367",
|
|
|
|
"\370", "\371", "\372", "\373", "\374", "\375", "\376", "\377",
|
|
|
|
};
|
|
|
|
|
2010-07-23 13:59:59 -04:00
|
|
|
#ifdef HAVE_ICONV
|
|
|
|
static iconv_t iconv_cd = (iconv_t)-1;
|
|
|
|
#endif
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
free_translation_table(struct conv_table *p)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 256; i++)
|
|
|
|
if (p[i].t)
|
|
|
|
free_translation_table(p[i].u.tbl);
|
|
|
|
|
|
|
|
mem_free(p);
|
|
|
|
}
|
|
|
|
|
2007-01-01 17:54:14 -05:00
|
|
|
/* A string used in conversion tables when there is no correct
|
2007-01-01 18:07:57 -05:00
|
|
|
* conversion. This is compared by address and therefore should be a
|
|
|
|
* named array rather than a pointer so that it won't share storage
|
|
|
|
* with any other string literal that happens to have the same
|
|
|
|
* characters. */
|
2021-01-02 10:20:27 -05:00
|
|
|
static const char no_str[] = "*";
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
static void
|
|
|
|
new_translation_table(struct conv_table *p)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 256; i++)
|
|
|
|
if (p[i].t)
|
|
|
|
free_translation_table(p[i].u.tbl);
|
|
|
|
for (i = 0; i < 128; i++) {
|
|
|
|
p[i].t = 0;
|
|
|
|
p[i].u.str = strings[i];
|
|
|
|
}
|
|
|
|
for (; i < 256; i++) {
|
|
|
|
p[i].t = 0;
|
|
|
|
p[i].u.str = no_str;
|
|
|
|
}
|
2010-07-23 13:59:59 -04:00
|
|
|
p->iconv_cp = -1;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#define BIN_SEARCH(table, entry, entries, key, result) \
|
|
|
|
{ \
|
|
|
|
long _s = 0, _e = (entries) - 1; \
|
|
|
|
\
|
|
|
|
while (_s <= _e || !((result) = -1)) { \
|
|
|
|
long _m = (_s + _e) / 2; \
|
|
|
|
\
|
|
|
|
if ((table)[_m].entry == (key)) { \
|
|
|
|
(result) = _m; \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
if ((table)[_m].entry > (key)) _e = _m - 1; \
|
|
|
|
if ((table)[_m].entry < (key)) _s = _m + 1; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
|
|
|
|
static const unicode_val_T strange_chars[32] = {
|
|
|
|
0x20ac, 0x0000, 0x002a, 0x0000, 0x201e, 0x2026, 0x2020, 0x2021,
|
|
|
|
0x005e, 0x2030, 0x0160, 0x003c, 0x0152, 0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0060, 0x0027, 0x0022, 0x0022, 0x002a, 0x2013, 0x2014,
|
|
|
|
0x007e, 0x2122, 0x0161, 0x003e, 0x0153, 0x0000, 0x0000, 0x0000,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define SYSTEM_CHARSET_FLAG 128
|
2006-09-24 09:55:29 -04:00
|
|
|
#define is_cp_ptr_utf8(cp_ptr) ((cp_ptr)->aliases == aliases_utf8)
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2021-01-02 10:20:27 -05:00
|
|
|
const char *
|
2006-10-01 18:33:41 -04:00
|
|
|
u2cp_(unicode_val_T u, int to, enum nbsp_mode nbsp_mode)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
int j;
|
|
|
|
int s;
|
|
|
|
|
|
|
|
if (u < 128) return strings[u];
|
2006-01-14 16:44:00 -05:00
|
|
|
|
2011-04-17 11:09:29 -04:00
|
|
|
if (u < 0xa0) {
|
|
|
|
u = strange_chars[u - 0x80];
|
|
|
|
if (!u) return NULL;
|
|
|
|
}
|
|
|
|
|
2006-01-14 16:44:00 -05:00
|
|
|
to &= ~SYSTEM_CHARSET_FLAG;
|
|
|
|
|
2006-09-24 06:33:58 -04:00
|
|
|
if (is_cp_ptr_utf8(&codepages[to]))
|
2006-09-17 09:06:22 -04:00
|
|
|
return encode_utf8(u);
|
2006-01-14 16:44:00 -05:00
|
|
|
|
2007-01-29 13:57:37 -05:00
|
|
|
/* To mark non breaking spaces in non-UTF-8 strings, we use a
|
|
|
|
* special char NBSP_CHAR. */
|
2007-04-22 15:37:12 -04:00
|
|
|
if (u == UCS_NO_BREAK_SPACE) {
|
2006-10-01 18:33:41 -04:00
|
|
|
if (nbsp_mode == NBSP_MODE_HACK) return NBSP_CHAR_STRING;
|
|
|
|
else /* NBSP_MODE_ASCII */ return " ";
|
|
|
|
}
|
2007-04-22 15:38:40 -04:00
|
|
|
if (u == UCS_SOFT_HYPHEN) return "";
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2006-09-24 09:55:29 -04:00
|
|
|
if (u < 0xFFFF)
|
|
|
|
for (j = 0; j < 0x80; j++)
|
|
|
|
if (codepages[to].highhalf[j] == u)
|
|
|
|
return strings[0x80 + j];
|
2005-09-15 09:58:31 -04:00
|
|
|
for (j = 0; codepages[to].table[j].c; j++)
|
|
|
|
if (codepages[to].table[j].u == u)
|
|
|
|
return strings[codepages[to].table[j].c];
|
|
|
|
|
|
|
|
BIN_SEARCH(unicode_7b, x, N_UNICODE_7B, u, s);
|
|
|
|
if (s != -1) return unicode_7b[s].s;
|
|
|
|
|
|
|
|
return no_str;
|
|
|
|
}
|
|
|
|
|
2021-01-02 10:20:27 -05:00
|
|
|
static char utf_buffer[7];
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2021-01-02 10:20:27 -05:00
|
|
|
NONSTATIC_INLINE char *
|
2006-09-17 09:06:22 -04:00
|
|
|
encode_utf8(unicode_val_T u)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
memset(utf_buffer, 0, 7);
|
|
|
|
|
|
|
|
if (u < 0x80)
|
|
|
|
utf_buffer[0] = u;
|
|
|
|
else if (u < 0x800)
|
|
|
|
utf_buffer[0] = 0xc0 | ((u >> 6) & 0x1f),
|
|
|
|
utf_buffer[1] = 0x80 | (u & 0x3f);
|
|
|
|
else if (u < 0x10000)
|
|
|
|
utf_buffer[0] = 0xe0 | ((u >> 12) & 0x0f),
|
|
|
|
utf_buffer[1] = 0x80 | ((u >> 6) & 0x3f),
|
|
|
|
utf_buffer[2] = 0x80 | (u & 0x3f);
|
|
|
|
else if (u < 0x200000)
|
|
|
|
utf_buffer[0] = 0xf0 | ((u >> 18) & 0x0f),
|
|
|
|
utf_buffer[1] = 0x80 | ((u >> 12) & 0x3f),
|
|
|
|
utf_buffer[2] = 0x80 | ((u >> 6) & 0x3f),
|
|
|
|
utf_buffer[3] = 0x80 | (u & 0x3f);
|
|
|
|
else if (u < 0x4000000)
|
|
|
|
utf_buffer[0] = 0xf8 | ((u >> 24) & 0x0f),
|
|
|
|
utf_buffer[1] = 0x80 | ((u >> 18) & 0x3f),
|
|
|
|
utf_buffer[2] = 0x80 | ((u >> 12) & 0x3f),
|
|
|
|
utf_buffer[3] = 0x80 | ((u >> 6) & 0x3f),
|
|
|
|
utf_buffer[4] = 0x80 | (u & 0x3f);
|
|
|
|
else utf_buffer[0] = 0xfc | ((u >> 30) & 0x01),
|
|
|
|
utf_buffer[1] = 0x80 | ((u >> 24) & 0x3f),
|
|
|
|
utf_buffer[2] = 0x80 | ((u >> 18) & 0x3f),
|
|
|
|
utf_buffer[3] = 0x80 | ((u >> 12) & 0x3f),
|
|
|
|
utf_buffer[4] = 0x80 | ((u >> 6) & 0x3f),
|
|
|
|
utf_buffer[5] = 0x80 | (u & 0x3f);
|
|
|
|
|
|
|
|
return utf_buffer;
|
|
|
|
}
|
|
|
|
|
2006-02-02 18:27:01 -05:00
|
|
|
/* Number of bytes utf8 character indexed by first byte. Illegal bytes are
|
|
|
|
* equal ones and handled different. */
|
2007-01-01 10:18:05 -05:00
|
|
|
static const char utf8char_len_tab[256] = {
|
2006-02-02 18:27:01 -05:00
|
|
|
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
|
|
|
|
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
|
|
|
|
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
|
|
|
|
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
|
|
|
|
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
|
|
|
|
1,1,1,1,1,1,1,1, 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,
|
|
|
|
};
|
|
|
|
|
2008-10-18 06:51:04 -04:00
|
|
|
#ifdef CONFIG_UTF8
|
2009-03-28 14:15:08 -04:00
|
|
|
NONSTATIC_INLINE int
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8charlen(const char *p)
|
2006-01-30 19:09:49 -05:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
return p ? utf8char_len_tab[(unsigned char)*p] : 0;
|
2006-01-30 19:09:49 -05:00
|
|
|
}
|
|
|
|
|
2009-03-28 14:15:08 -04:00
|
|
|
int
|
2021-01-02 10:20:27 -05:00
|
|
|
strlen_utf8(char **str)
|
2006-01-14 16:44:00 -05:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *s = *str;
|
|
|
|
char *end = strchr((const char *)s, '\0');
|
2006-01-14 16:44:00 -05:00
|
|
|
int x;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
for (x = 0;; x++, s += len) {
|
2006-01-30 19:09:49 -05:00
|
|
|
len = utf8charlen(s);
|
2006-01-14 16:44:00 -05:00
|
|
|
if (s + len > end) break;
|
|
|
|
}
|
|
|
|
*str = s;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2006-05-01 16:58:51 -04:00
|
|
|
#define utf8_issingle(p) (((p) & 0x80) == 0)
|
|
|
|
#define utf8_islead(p) (utf8_issingle(p) || ((p) & 0xc0) == 0xc0)
|
|
|
|
|
|
|
|
/* Start from @current and move back to @pos char. This pointer return. The
|
|
|
|
* most left pointer is @start. */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *
|
|
|
|
utf8_prevchar(char *current, int pos, char *start)
|
2006-05-01 16:58:51 -04:00
|
|
|
{
|
2006-07-25 03:59:12 -04:00
|
|
|
if (current == NULL || start == NULL || pos < 0)
|
|
|
|
return NULL;
|
|
|
|
while (pos > 0 && current != start) {
|
|
|
|
current--;
|
|
|
|
if (utf8_islead(*current))
|
|
|
|
pos--;
|
|
|
|
}
|
|
|
|
return current;
|
2006-05-01 16:58:51 -04:00
|
|
|
}
|
|
|
|
|
2006-03-04 18:10:33 -05:00
|
|
|
/* Count number of standard terminal cells needed for displaying UTF-8
|
|
|
|
* character. */
|
|
|
|
int
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8_char2cells(char *utf8_char, char *end)
|
2006-03-04 18:10:33 -05:00
|
|
|
{
|
|
|
|
unicode_val_T u;
|
|
|
|
|
|
|
|
if (end == NULL)
|
2016-04-20 13:43:37 -04:00
|
|
|
end = strchr((const char *)utf8_char, '\0');
|
2006-03-04 18:10:33 -05:00
|
|
|
|
|
|
|
if(!utf8_char || !end)
|
|
|
|
return -1;
|
|
|
|
|
2006-09-17 09:06:22 -04:00
|
|
|
u = utf8_to_unicode(&utf8_char, end);
|
2006-03-04 18:10:33 -05:00
|
|
|
|
|
|
|
return unicode_to_cell(u);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Count number of standard terminal cells needed for displaying string
|
|
|
|
* with UTF-8 characters. */
|
|
|
|
int
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8_ptr2cells(char *string, char *end)
|
2006-03-04 18:10:33 -05:00
|
|
|
{
|
|
|
|
int charlen, cell, cells = 0;
|
|
|
|
|
|
|
|
if (end == NULL)
|
2016-04-20 13:43:37 -04:00
|
|
|
end = strchr((const char *)string, '\0');
|
2006-03-04 18:10:33 -05:00
|
|
|
|
|
|
|
if(!string || !end)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
charlen = utf8charlen(string);
|
2006-07-27 03:51:10 -04:00
|
|
|
if (string + charlen > end)
|
2006-03-04 18:10:33 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
cell = utf8_char2cells(string, end);
|
|
|
|
if (cell < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
cells += cell;
|
|
|
|
string += charlen;
|
|
|
|
} while (1);
|
|
|
|
|
|
|
|
return cells;
|
|
|
|
}
|
|
|
|
|
2006-04-07 16:06:17 -04:00
|
|
|
/* Count number of characters in string. */
|
|
|
|
int
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8_ptr2chars(char *string, char *end)
|
2006-04-07 16:06:17 -04:00
|
|
|
{
|
|
|
|
int charlen, chars = 0;
|
|
|
|
|
|
|
|
if (end == NULL)
|
2016-04-20 13:43:37 -04:00
|
|
|
end = strchr((const char *)string, '\0');
|
2006-04-07 16:06:17 -04:00
|
|
|
|
|
|
|
if(!string || !end)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
charlen = utf8charlen(string);
|
2006-07-27 03:51:10 -04:00
|
|
|
if (string + charlen > end)
|
2006-04-07 16:06:17 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
chars++;
|
|
|
|
string += charlen;
|
|
|
|
} while (1);
|
|
|
|
|
|
|
|
return chars;
|
|
|
|
}
|
|
|
|
|
2006-03-04 18:10:33 -05:00
|
|
|
/*
|
|
|
|
* Count number of bytes from begining of the string needed for displaying
|
|
|
|
* specified number of cells.
|
|
|
|
*/
|
|
|
|
int
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8_cells2bytes(char *string, int max_cells, char *end)
|
2006-03-04 18:10:33 -05:00
|
|
|
{
|
|
|
|
unsigned int bytes = 0, cells = 0;
|
|
|
|
|
|
|
|
assert(max_cells>=0);
|
|
|
|
|
|
|
|
if (end == NULL)
|
2016-04-20 13:43:37 -04:00
|
|
|
end = strchr((const char *)string, '\0');
|
2006-03-04 18:10:33 -05:00
|
|
|
|
|
|
|
if(!string || !end)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
int cell = utf8_char2cells(&string[bytes], end);
|
|
|
|
if (cell < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
cells += cell;
|
|
|
|
if (cells > max_cells)
|
|
|
|
break;
|
|
|
|
|
|
|
|
bytes += utf8charlen(&string[bytes]);
|
|
|
|
|
|
|
|
if (string + bytes > end) {
|
|
|
|
bytes = end - string;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while(1);
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
2006-02-07 19:42:39 -05:00
|
|
|
|
2006-09-02 11:28:31 -04:00
|
|
|
/* Take @max steps forward from @string in the specified @way, but
|
|
|
|
* not going past @end. Return the resulting address. Store the
|
|
|
|
* number of steps taken to *@count, unless @count is NULL.
|
|
|
|
*
|
|
|
|
* This assumes the text is valid UTF-8, and @string and @end point to
|
|
|
|
* character boundaries. If not, it doesn't crash but the results may
|
|
|
|
* be inconsistent.
|
|
|
|
*
|
|
|
|
* This function can do some of the same jobs as utf8charlen(),
|
|
|
|
* utf8_cells2bytes(), and strlen_utf8(). */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *
|
|
|
|
utf8_step_forward(char *string, char *end,
|
2006-09-02 11:28:31 -04:00
|
|
|
int max, enum utf8_step way, int *count)
|
|
|
|
{
|
|
|
|
int steps = 0;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *current = string;
|
2006-09-02 11:28:31 -04:00
|
|
|
|
|
|
|
assert(string);
|
|
|
|
assert(max >= 0);
|
2006-09-02 20:08:56 -04:00
|
|
|
if_assert_failed goto invalid_arg;
|
2006-09-02 11:28:31 -04:00
|
|
|
if (end == NULL)
|
2016-04-20 13:43:37 -04:00
|
|
|
end = strchr((const char *)string, '\0');
|
2006-09-02 11:28:31 -04:00
|
|
|
|
|
|
|
switch (way) {
|
2006-11-12 07:51:18 -05:00
|
|
|
case UTF8_STEP_CHARACTERS:
|
2006-09-02 11:28:31 -04:00
|
|
|
while (steps < max && current < end) {
|
|
|
|
++current;
|
|
|
|
if (utf8_islead(*current))
|
|
|
|
++steps;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2006-11-12 07:51:18 -05:00
|
|
|
case UTF8_STEP_CELLS_FEWER:
|
|
|
|
case UTF8_STEP_CELLS_MORE:
|
2009-05-26 17:50:57 -04:00
|
|
|
while (steps < max && current < end) {
|
2006-09-02 11:28:31 -04:00
|
|
|
unicode_val_T u;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *prev = current;
|
2006-09-02 11:28:31 -04:00
|
|
|
int width;
|
|
|
|
|
2006-09-17 09:06:22 -04:00
|
|
|
u = utf8_to_unicode(¤t, end);
|
2006-09-02 11:28:31 -04:00
|
|
|
if (u == UCS_NO_CHAR) {
|
|
|
|
/* Assume the incomplete sequence
|
|
|
|
* costs one cell. */
|
|
|
|
current = end;
|
|
|
|
++steps;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
width = unicode_to_cell(u);
|
2006-11-12 07:51:18 -05:00
|
|
|
if (way == UTF8_STEP_CELLS_FEWER
|
2006-09-02 11:28:31 -04:00
|
|
|
&& steps + width > max) {
|
|
|
|
/* Back off. */
|
|
|
|
current = prev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
steps += width;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
INTERNAL("impossible enum utf8_step");
|
|
|
|
}
|
|
|
|
|
2006-09-02 20:08:56 -04:00
|
|
|
invalid_arg:
|
2006-09-02 11:28:31 -04:00
|
|
|
if (count)
|
|
|
|
*count = steps;
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Take @max steps backward from @string in the specified @way, but
|
|
|
|
* not going past @start. Return the resulting address. Store the
|
|
|
|
* number of steps taken to *@count, unless @count is NULL.
|
|
|
|
*
|
|
|
|
* This assumes the text is valid UTF-8, and @string and @start point
|
|
|
|
* to character boundaries. If not, it doesn't crash but the results
|
|
|
|
* may be inconsistent.
|
|
|
|
*
|
|
|
|
* This function can do some of the same jobs as utf8_prevchar(). */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *
|
|
|
|
utf8_step_backward(char *string, char *start,
|
2006-09-02 11:28:31 -04:00
|
|
|
int max, enum utf8_step way, int *count)
|
|
|
|
{
|
|
|
|
int steps = 0;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *current = string;
|
2006-09-02 11:28:31 -04:00
|
|
|
|
|
|
|
assert(string);
|
|
|
|
assert(start);
|
|
|
|
assert(max >= 0);
|
2006-09-02 20:08:56 -04:00
|
|
|
if_assert_failed goto invalid_arg;
|
2006-09-02 11:28:31 -04:00
|
|
|
|
|
|
|
switch (way) {
|
2006-11-12 07:51:18 -05:00
|
|
|
case UTF8_STEP_CHARACTERS:
|
2006-09-02 11:28:31 -04:00
|
|
|
while (steps < max && current > start) {
|
|
|
|
--current;
|
|
|
|
if (utf8_islead(*current))
|
|
|
|
++steps;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2006-11-12 07:51:18 -05:00
|
|
|
case UTF8_STEP_CELLS_FEWER:
|
|
|
|
case UTF8_STEP_CELLS_MORE:
|
2006-09-02 11:28:31 -04:00
|
|
|
while (steps < max) {
|
2021-01-02 10:20:27 -05:00
|
|
|
char *prev = current;
|
|
|
|
char *look;
|
2006-09-02 11:28:31 -04:00
|
|
|
unicode_val_T u;
|
|
|
|
int width;
|
|
|
|
|
|
|
|
if (current <= start)
|
|
|
|
break;
|
|
|
|
do {
|
|
|
|
--current;
|
|
|
|
} while (current > start && !utf8_islead(*current));
|
|
|
|
|
|
|
|
look = current;
|
2006-09-17 09:06:22 -04:00
|
|
|
u = utf8_to_unicode(&look, prev);
|
2006-09-02 11:28:31 -04:00
|
|
|
if (u == UCS_NO_CHAR) {
|
|
|
|
/* Assume the incomplete sequence
|
|
|
|
* costs one cell. */
|
|
|
|
width = 1;
|
|
|
|
} else
|
|
|
|
width = unicode_to_cell(u);
|
|
|
|
|
2006-11-12 07:51:18 -05:00
|
|
|
if (way == UTF8_STEP_CELLS_FEWER
|
2006-09-02 11:28:31 -04:00
|
|
|
&& steps + width > max) {
|
|
|
|
/* Back off. */
|
|
|
|
current = prev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
steps += width;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
INTERNAL("impossible enum utf8_step");
|
|
|
|
}
|
|
|
|
|
2006-09-02 20:08:56 -04:00
|
|
|
invalid_arg:
|
2006-09-02 11:28:31 -04:00
|
|
|
if (count)
|
|
|
|
*count = steps;
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
|
2006-07-27 03:51:10 -04:00
|
|
|
/*
|
2006-02-07 19:42:39 -05:00
|
|
|
* Find out number of standard terminal collumns needed for displaying symbol
|
|
|
|
* (glyph) which represents Unicode character c.
|
|
|
|
*
|
2006-10-21 17:05:37 -04:00
|
|
|
* TODO: Use wcwidth when it is available. This seems to require:
|
|
|
|
* - Make the configure script check whether <wchar.h> and wcwidth exist.
|
|
|
|
* - Define _XOPEN_SOURCE and include <wchar.h>.
|
|
|
|
* - Test that __STDC_ISO_10646__ is defined. (This macro means wchar_t
|
|
|
|
* matches ISO 10646 in all locales.)
|
|
|
|
* However, these do not suffice, because wcwidth depends on LC_CTYPE
|
|
|
|
* in glibc-2.3.6. For instance, wcwidth(0xff20) is -1 when LC_CTYPE
|
|
|
|
* is "fi_FI.ISO-8859-1" or "C", but 2 when LC_CTYPE is "fi_FI.UTF-8".
|
|
|
|
* <features.h> defines __STDC_ISO_10646__ as 200009L, so 0xff20 means
|
|
|
|
* U+FF20 FULLWIDTH COMMERCIAL AT regardless of LC_CTYPE; but this
|
|
|
|
* character is apparently not supported in all locales. Why is that?
|
|
|
|
* - Perhaps there is standardese that requires supported characters
|
|
|
|
* to be convertable to multibyte form. Then ELinks could just pick
|
|
|
|
* some UTF-8 locale for its wcwidth purposes.
|
|
|
|
* - Perhaps wcwidth can even return different nonnegative values for
|
|
|
|
* the same ISO 10646 character in different locales. Then ELinks
|
|
|
|
* would have to set LC_CTYPE to match at least the terminal's
|
|
|
|
* charset (which may differ from the LC_CTYPE environment variable,
|
|
|
|
* especially when the master process is serving a slave terminal).
|
|
|
|
* But there is no guarantee that the libc supports all the same
|
|
|
|
* charsets as ELinks does.
|
|
|
|
* For now, it seems safest to avoid the potentially locale-dependent
|
|
|
|
* libc version of wcwidth, and instead use a hardcoded mapping.
|
|
|
|
*
|
2006-02-07 19:42:39 -05:00
|
|
|
* @return 2 for double-width glyph, 1 for others.
|
2017-05-16 12:37:29 -04:00
|
|
|
* 0 for unprintable glyphs (like 0x200e: "LEFT-TO-RIGHT MARK")
|
2006-02-07 19:42:39 -05:00
|
|
|
*/
|
2020-08-03 17:16:43 -04:00
|
|
|
|
|
|
|
#if 0
|
2009-03-28 14:15:08 -04:00
|
|
|
NONSTATIC_INLINE int
|
2006-02-07 19:42:39 -05:00
|
|
|
unicode_to_cell(unicode_val_T c)
|
|
|
|
{
|
2017-05-16 12:37:29 -04:00
|
|
|
if (c == 0x200e || c == 0x200f)
|
|
|
|
return 0;
|
2006-02-07 19:42:39 -05:00
|
|
|
if (c >= 0x1100
|
|
|
|
&& (c <= 0x115f /* Hangul Jamo */
|
|
|
|
|| c == 0x2329
|
|
|
|
|| c == 0x232a
|
|
|
|
|| (c >= 0x2e80 && c <= 0xa4cf
|
|
|
|
&& c != 0x303f) /* CJK ... Yi */
|
|
|
|
|| (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
|
|
|
|
|| (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility
|
|
|
|
Ideographs */
|
|
|
|
|| (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
|
|
|
|
|| (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
|
|
|
|
|| (c >= 0xffe0 && c <= 0xffe6)
|
|
|
|
|| (c >= 0x20000 && c <= 0x2fffd)
|
|
|
|
|| (c >= 0x30000 && c <= 0x3fffd)))
|
|
|
|
return 2;
|
|
|
|
|
2006-07-27 03:51:10 -04:00
|
|
|
return 1;
|
2006-02-07 19:42:39 -05:00
|
|
|
}
|
2020-08-03 17:16:43 -04:00
|
|
|
#endif
|
2006-02-07 19:42:39 -05:00
|
|
|
|
2006-08-05 12:45:53 -04:00
|
|
|
/* Fold the case of a Unicode character, so that hotkeys in labels can
|
2006-08-13 16:41:48 -04:00
|
|
|
* be compared case-insensitively. It is unspecified whether the
|
2006-08-05 12:45:53 -04:00
|
|
|
* result will be in upper or lower case. */
|
|
|
|
unicode_val_T
|
|
|
|
unicode_fold_label_case(unicode_val_T c)
|
|
|
|
{
|
|
|
|
#if __STDC_ISO_10646__ && HAVE_WCTYPE_H
|
|
|
|
return towlower(c);
|
|
|
|
#else /* !(__STDC_ISO_10646__ && HAVE_WCTYPE_H) */
|
|
|
|
/* For now, this supports only ASCII. It would be possible to
|
|
|
|
* use code generated from CaseFolding.txt of Unicode if the
|
|
|
|
* acknowledgements required by http://www.unicode.org/copyright.html
|
|
|
|
* were added to associated documentation of ELinks. */
|
|
|
|
if (c >= 0x41 && c <= 0x5A)
|
|
|
|
return c + 0x20;
|
|
|
|
else
|
|
|
|
return c;
|
|
|
|
#endif /* !(__STDC_ISO_10646__ && HAVE_WCTYPE_H) */
|
|
|
|
}
|
2008-10-18 06:51:04 -04:00
|
|
|
#endif /* CONFIG_UTF8 */
|
2006-08-05 12:45:53 -04:00
|
|
|
|
2009-03-28 14:15:08 -04:00
|
|
|
NONSTATIC_INLINE unicode_val_T
|
2021-01-02 10:20:27 -05:00
|
|
|
utf8_to_unicode(char **string, const char *end)
|
2006-01-14 16:44:00 -05:00
|
|
|
{
|
2021-01-17 15:56:40 -05:00
|
|
|
unsigned char *str = (unsigned char *)*string;
|
2006-01-14 16:44:00 -05:00
|
|
|
unicode_val_T u;
|
|
|
|
int length;
|
|
|
|
|
2021-01-17 15:56:40 -05:00
|
|
|
length = utf8char_len_tab[str[0]];
|
2006-01-14 16:44:00 -05:00
|
|
|
|
2021-01-17 15:56:40 -05:00
|
|
|
if (str + length > (const unsigned char *)end) {
|
2006-01-14 16:44:00 -05:00
|
|
|
return UCS_NO_CHAR;
|
2006-07-27 03:51:10 -04:00
|
|
|
}
|
2006-01-14 16:44:00 -05:00
|
|
|
|
|
|
|
switch (length) {
|
2006-12-22 18:48:07 -05:00
|
|
|
case 1: /* U+0000 to U+007F */
|
2006-12-19 02:31:55 -05:00
|
|
|
if (str[0] >= 0x80) {
|
|
|
|
invalid_utf8:
|
|
|
|
++*string;
|
|
|
|
return UCS_REPLACEMENT_CHARACTER;
|
|
|
|
}
|
2006-01-14 16:44:00 -05:00
|
|
|
u = str[0];
|
|
|
|
break;
|
2006-12-22 18:48:07 -05:00
|
|
|
case 2: /* U+0080 to U+07FF */
|
2006-12-19 02:31:55 -05:00
|
|
|
if ((str[1] & 0xc0) != 0x80)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
u = (str[0] & 0x1f) << 6;
|
|
|
|
u += (str[1] & 0x3f);
|
2006-12-19 02:31:55 -05:00
|
|
|
if (u < 0x80)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
break;
|
2006-12-22 18:48:07 -05:00
|
|
|
case 3: /* U+0800 to U+FFFF, except surrogates */
|
2006-12-19 02:31:55 -05:00
|
|
|
if ((str[1] & 0xc0) != 0x80 || (str[2] & 0xc0) != 0x80)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
u = (str[0] & 0x0f) << 12;
|
|
|
|
u += ((str[1] & 0x3f) << 6);
|
|
|
|
u += (str[2] & 0x3f);
|
2006-12-22 18:48:07 -05:00
|
|
|
if (u < 0x800 || is_utf16_surrogate(u))
|
2006-12-19 02:31:55 -05:00
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
break;
|
2006-12-22 18:48:07 -05:00
|
|
|
case 4: /* U+10000 to U+1FFFFF */
|
2006-12-19 02:31:55 -05:00
|
|
|
if ((str[1] & 0xc0) != 0x80 || (str[2] & 0xc0) != 0x80
|
|
|
|
|| (str[3] & 0xc0) != 0x80)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
u = (str[0] & 0x0f) << 18;
|
|
|
|
u += ((str[1] & 0x3f) << 12);
|
|
|
|
u += ((str[2] & 0x3f) << 6);
|
|
|
|
u += (str[3] & 0x3f);
|
2006-12-19 02:31:55 -05:00
|
|
|
if (u < 0x10000)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
break;
|
2006-12-22 18:48:07 -05:00
|
|
|
case 5: /* U+200000 to U+3FFFFFF */
|
2006-12-19 02:31:55 -05:00
|
|
|
if ((str[1] & 0xc0) != 0x80 || (str[2] & 0xc0) != 0x80
|
|
|
|
|| (str[3] & 0xc0) != 0x80 || (str[4] & 0xc0) != 0x80)
|
|
|
|
goto invalid_utf8;
|
2006-01-14 16:44:00 -05:00
|
|
|
u = (str[0] & 0x0f) << 24;
|
|
|
|
u += ((str[1] & 0x3f) << 18);
|
|
|
|
u += ((str[2] & 0x3f) << 12);
|
|
|
|
u += ((str[3] & 0x3f) << 6);
|
|
|
|
u += (str[4] & 0x3f);
|
|