453 lines
11 KiB
C
453 lines
11 KiB
C
/*
|
|
* Basic Charset functions for QEmacs
|
|
*
|
|
* Copyright (c) 2000, 2001, 2002 Fabrice Bellard.
|
|
* Copyright (c) 2002-2008 Charlie Gordon.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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 for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "qe.h"
|
|
|
|
QECharset *first_charset;
|
|
|
|
/* specific tables */
|
|
static unsigned short table_idem[256];
|
|
static unsigned short table_utf8[256];
|
|
|
|
unsigned char utf8_length[256];
|
|
|
|
static const unsigned int utf8_min_code[7] = {
|
|
0, 0, 0x80, 0x800, 0x10000, 0x00200000, 0x04000000,
|
|
};
|
|
|
|
static const unsigned char utf8_first_code_mask[7] = {
|
|
0, 0, 0x1f, 0xf, 0x7, 0x3, 0x1,
|
|
};
|
|
|
|
/********************************************************/
|
|
/* 8859-1 */
|
|
|
|
static void decode_8859_1_init(CharsetDecodeState *s)
|
|
{
|
|
s->table = table_idem;
|
|
}
|
|
|
|
static unsigned char *encode_8859_1(__unused__ QECharset *charset,
|
|
unsigned char *p, int c)
|
|
{
|
|
if (c <= 0xff) {
|
|
*p++ = c;
|
|
return p;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
QECharset charset_8859_1 = {
|
|
"8859-1",
|
|
"ISO-8859-1|iso-ir-100|latin1|l1|819",
|
|
decode_8859_1_init,
|
|
NULL,
|
|
encode_8859_1,
|
|
0, 10, 0, 0, NULL, NULL,
|
|
};
|
|
|
|
/********************************************************/
|
|
/* vt100 */
|
|
|
|
static void decode_vt100_init(CharsetDecodeState *s)
|
|
{
|
|
s->table = table_idem;
|
|
}
|
|
|
|
static unsigned char *encode_vt100(__unused__ QECharset *charset,
|
|
unsigned char *p, int c)
|
|
{
|
|
if (c <= 0xff) {
|
|
*p++ = c;
|
|
return p;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
QECharset charset_vt100 = {
|
|
"vt100",
|
|
NULL,
|
|
decode_vt100_init,
|
|
NULL,
|
|
encode_vt100,
|
|
0, 10, 0, 0, NULL, NULL,
|
|
};
|
|
|
|
/********************************************************/
|
|
/* 7 bit */
|
|
|
|
static unsigned char *encode_7bit(__unused__ QECharset *charset,
|
|
unsigned char *p, int c)
|
|
{
|
|
if (c <= 0x7f) {
|
|
*p++ = c;
|
|
return p;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static QECharset charset_7bit = {
|
|
"7bit",
|
|
"us-ascii|ascii|7-bit|iso-ir-6|ANSI_X3.4|646",
|
|
decode_8859_1_init,
|
|
NULL,
|
|
encode_7bit,
|
|
0, 10, 0, 0, NULL, NULL,
|
|
};
|
|
|
|
/********************************************************/
|
|
/* UTF8 */
|
|
|
|
/* return the utf8 char and increment 'p' of at least one char. strict
|
|
decoding is done (refuse non canonical UTF8) */
|
|
int utf8_decode(const char **pp)
|
|
{
|
|
unsigned int c, c1;
|
|
const unsigned char *p;
|
|
int i, l;
|
|
|
|
p = *(const unsigned char**)pp;
|
|
c = *p++;
|
|
if (c < 128) {
|
|
/* fast case for ASCII */
|
|
} else {
|
|
l = utf8_length[c];
|
|
if (l == 1)
|
|
goto fail; /* can only be multi byte code here */
|
|
c = c & utf8_first_code_mask[l];
|
|
for (i = 1; i < l; i++) {
|
|
c1 = *p;
|
|
if (c1 < 0x80 || c1 >= 0xc0)
|
|
goto fail;
|
|
p++;
|
|
c = (c << 6) | (c1 & 0x3f);
|
|
}
|
|
if (c < utf8_min_code[l])
|
|
goto fail;
|
|
/* exclude surrogate pairs and special codes */
|
|
if ((c >= 0xd800 && c <= 0xdfff) ||
|
|
c == 0xfffe || c == 0xffff)
|
|
goto fail;
|
|
}
|
|
*(const unsigned char**)pp = p;
|
|
return c;
|
|
fail:
|
|
*(const unsigned char**)pp = p;
|
|
return INVALID_CHAR;
|
|
}
|
|
|
|
/* NOTE: the buffer must be at least 6 bytes long. Return number of
|
|
* bytes copied. */
|
|
int utf8_encode(char *q0, int c)
|
|
{
|
|
char *q = q0;
|
|
|
|
if (c < 0x80) {
|
|
*q++ = c;
|
|
} else {
|
|
if (c < 0x800) {
|
|
*q++ = (c >> 6) | 0xc0;
|
|
} else {
|
|
if (c < 0x10000) {
|
|
*q++ = (c >> 12) | 0xe0;
|
|
} else {
|
|
if (c < 0x00200000) {
|
|
*q++ = (c >> 18) | 0xf0;
|
|
} else {
|
|
if (c < 0x04000000) {
|
|
*q++ = (c >> 24) | 0xf8;
|
|
} else {
|
|
*q++ = (c >> 30) | 0xfc;
|
|
*q++ = ((c >> 24) & 0x3f) | 0x80;
|
|
}
|
|
*q++ = ((c >> 18) & 0x3f) | 0x80;
|
|
}
|
|
*q++ = ((c >> 12) & 0x3f) | 0x80;
|
|
}
|
|
*q++ = ((c >> 6) & 0x3f) | 0x80;
|
|
}
|
|
*q++ = (c & 0x3f) | 0x80;
|
|
}
|
|
return q - q0;
|
|
}
|
|
|
|
int utf8_to_unicode(unsigned int *dest, int dest_length,
|
|
const char *str)
|
|
{
|
|
const char *p;
|
|
unsigned int *uq, *uq_end, c;
|
|
|
|
if (dest_length <= 0)
|
|
return 0;
|
|
|
|
p = str;
|
|
uq = dest;
|
|
uq_end = dest + dest_length - 1;
|
|
for (;;) {
|
|
if (uq >= uq_end)
|
|
break;
|
|
c = utf8_decode(&p);
|
|
if (c == '\0')
|
|
break;
|
|
*uq++ = c;
|
|
}
|
|
*uq = 0;
|
|
return uq - dest;
|
|
}
|
|
|
|
static void decode_utf8_init(CharsetDecodeState *s)
|
|
{
|
|
s->table = table_utf8;
|
|
}
|
|
|
|
static int decode_utf8_func(__unused__ CharsetDecodeState *s,
|
|
const unsigned char **pp)
|
|
{
|
|
return utf8_decode((const char **)(void *)pp);
|
|
}
|
|
|
|
static unsigned char *encode_utf8(__unused__ QECharset *charset,
|
|
unsigned char *q, int c)
|
|
{
|
|
return q + utf8_encode((char*)q, c);
|
|
}
|
|
|
|
QECharset charset_utf8 = {
|
|
"utf-8",
|
|
"utf8",
|
|
decode_utf8_init,
|
|
decode_utf8_func,
|
|
encode_utf8,
|
|
0, 10, 0, 0, NULL, NULL,
|
|
};
|
|
|
|
/********************************************************/
|
|
/* generic charset functions */
|
|
|
|
void qe_register_charset(QECharset *charset)
|
|
{
|
|
QECharset **pp;
|
|
|
|
pp = &first_charset;
|
|
while (*pp != NULL) {
|
|
if (*pp == charset)
|
|
return;
|
|
pp = &(*pp)->next;
|
|
}
|
|
*pp = charset;
|
|
}
|
|
|
|
void charset_completion(CompleteState *cp)
|
|
{
|
|
QECharset *charset;
|
|
char name[32];
|
|
const char *p, *q;
|
|
|
|
for (charset = first_charset; charset != NULL; charset = charset->next) {
|
|
if (strxstart(charset->name, cp->current, NULL))
|
|
add_string(&cp->cs, charset->name);
|
|
if (charset->aliases) {
|
|
for (q = p = charset->aliases;; q++) {
|
|
if (*q == '\0' || *q == '|') {
|
|
if (q > p) {
|
|
pstrncpy(name, sizeof(name), p, q - p);
|
|
if (strxstart(name, cp->current, NULL))
|
|
add_string(&cp->cs, name);
|
|
}
|
|
if (*q == '\0')
|
|
break;
|
|
p = q + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QECharset *find_charset(const char *name)
|
|
{
|
|
QECharset *charset;
|
|
|
|
for (charset = first_charset; charset != NULL; charset = charset->next) {
|
|
if (!strxcmp(charset->name, name)
|
|
|| strcasefind(charset->aliases, name)) {
|
|
return charset;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void charset_decode_init(CharsetDecodeState *s, QECharset *charset)
|
|
{
|
|
unsigned short *table;
|
|
|
|
s->table = NULL; /* fail safe */
|
|
if (charset->table_alloc) {
|
|
table = qe_malloc_array(unsigned short, 256);
|
|
if (!table) {
|
|
charset = &charset_8859_1;
|
|
} else {
|
|
s->table = table;
|
|
}
|
|
}
|
|
s->charset = charset;
|
|
s->decode_func = charset->decode_func;
|
|
if (charset->decode_init)
|
|
charset->decode_init(s);
|
|
}
|
|
|
|
void charset_decode_close(CharsetDecodeState *s)
|
|
{
|
|
if (s->charset->table_alloc &&
|
|
s->charset != &charset_8859_1)
|
|
qe_free(&s->table);
|
|
/* safety */
|
|
memset(s, 0, sizeof(CharsetDecodeState));
|
|
}
|
|
|
|
/* detect the charset. Actually only UTF8 is detected */
|
|
QECharset *detect_charset(const unsigned char *buf, int size)
|
|
{
|
|
int i, l, c, has_utf8;
|
|
|
|
has_utf8 = 0;
|
|
for (i = 0; i < size;) {
|
|
c = buf[i++];
|
|
if ((c >= 0x80 && c < 0xc0) || c >= 0xfe)
|
|
goto no_utf8;
|
|
l = utf8_length[c];
|
|
while (l > 1) {
|
|
has_utf8 = 1;
|
|
if (i >= size)
|
|
break;
|
|
c = buf[i++];
|
|
if (!(c >= 0x80 && c < 0xc0)) {
|
|
no_utf8:
|
|
has_utf8 = 0;
|
|
break;
|
|
}
|
|
l--;
|
|
}
|
|
}
|
|
if (has_utf8)
|
|
return &charset_utf8;
|
|
else
|
|
return &charset_8859_1;
|
|
}
|
|
|
|
/* the function uses '?' to indicate that no match could be found in
|
|
current charset */
|
|
int unicode_to_charset(char *buf, unsigned int c, QECharset *charset)
|
|
{
|
|
char *q;
|
|
|
|
q = (char *)charset->encode_func(charset, (unsigned char*)buf, c);
|
|
if (!q) {
|
|
q = buf;
|
|
*q++ = '?';
|
|
}
|
|
*q = '\0';
|
|
return q - buf;
|
|
}
|
|
|
|
/********************************************************/
|
|
/* 8 bit charsets */
|
|
|
|
void decode_8bit_init(CharsetDecodeState *s)
|
|
{
|
|
QECharset *charset = s->charset;
|
|
unsigned short *table;
|
|
int i, n;
|
|
|
|
table = s->table;
|
|
for (i = 0; i < charset->min_char; i++)
|
|
*table++ = i;
|
|
n = charset->max_char - charset->min_char + 1;
|
|
for (i = 0; i < n; i++)
|
|
*table++ = charset->private_table[i];
|
|
for (i = charset->max_char + 1; i < 256; i++)
|
|
*table++ = i;
|
|
}
|
|
|
|
/* not very fast, but not critical yet */
|
|
unsigned char *encode_8bit(QECharset *charset, unsigned char *q, int c)
|
|
{
|
|
int i, n;
|
|
const unsigned short *table;
|
|
|
|
if (c < charset->min_char) {
|
|
/* nothing to do */
|
|
} else
|
|
if (c > charset->max_char && c <= 0xff) {
|
|
/* nothing to do */
|
|
} else {
|
|
n = charset->max_char - charset->min_char + 1;
|
|
table = charset->private_table;
|
|
for (i = 0; i < n; i++) {
|
|
if (table[i] == c)
|
|
goto found;
|
|
}
|
|
return NULL;
|
|
found:
|
|
c = charset->min_char + i;
|
|
}
|
|
*q++ = c;
|
|
return q;
|
|
}
|
|
|
|
/********************************************************/
|
|
|
|
void charset_init(void)
|
|
{
|
|
int l, i, n;
|
|
|
|
// could set utf8_length[128...0xc0] to 0 as invalid bytes
|
|
memset(utf8_length, 1, 256);
|
|
|
|
i = 0xc0;
|
|
l = 2;
|
|
while (l <= 6) {
|
|
n = utf8_first_code_mask[l] + 1;
|
|
while (n > 0) {
|
|
utf8_length[i++] = l;
|
|
n--;
|
|
}
|
|
l++;
|
|
}
|
|
|
|
for (i = 0; i < 256; i++)
|
|
table_idem[i] = i;
|
|
|
|
/* utf8 table */
|
|
for (i = 0; i < 256; i++)
|
|
table_utf8[i] = INVALID_CHAR;
|
|
for (i = 0; i < 0x80; i++)
|
|
table_utf8[i] = i;
|
|
for (i = 0xc0; i < 0xfe; i++)
|
|
table_utf8[i] = ESCAPE_CHAR;
|
|
|
|
qe_register_charset(&charset_8859_1);
|
|
qe_register_charset(&charset_vt100);
|
|
qe_register_charset(&charset_utf8);
|
|
qe_register_charset(&charset_7bit);
|
|
}
|