/*
 * Copyright 1992, 1993 by TOSHIBA Corp.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of TOSHIBA not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission. TOSHIBA make no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * TOSHIBA DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * TOSHIBA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Author: Katsuhisa Yano	TOSHIBA Corp.
 *			   	mopi@osa.ilab.toshiba.co.jp
 */
/*
 * Copyright 1995 by FUJITSU LIMITED
 * This is source code modified by FUJITSU LIMITED under the Joint
 * Development Agreement for the CDE/Motif PST.
 *
 * Modifier: Takanori Tateno   FUJITSU LIMITED
 *
 */
/*
 *  2000
 *  Modifier: Ivan Pascal     The XFree86 Project
 *  Modifier: Bruno Haible    The XFree86 Project
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "Xlibint.h"
#include "XlcPubI.h"
#include <nx-X11/Xos.h>
#include <stdio.h>


/* ====================== Built-in Character Sets ====================== */

/*
 * Static representation of a character set that can be used in Compound Text.
 */
typedef struct _CTDataRec {
    const char name[19];
    const char ct_sequence[5];	/* Compound Text encoding, ESC sequence */
} CTDataRec, *CTData;

static const CTDataRec default_ct_data[] =
{
    /*                                                                    */
    /* X11 registry name       MIME name         ISO-IR      ESC sequence */
    /*                                                                    */

    /* Registered character sets with one byte per character */
    { "ISO8859-1:GL",       /* US-ASCII              6   */  "\033(B" },
    { "ISO8859-1:GR",       /* ISO-8859-1          100   */  "\033-A" },
    { "ISO8859-2:GR",       /* ISO-8859-2          101   */  "\033-B" },
    { "ISO8859-3:GR",       /* ISO-8859-3          109   */  "\033-C" },
    { "ISO8859-4:GR",       /* ISO-8859-4          110   */  "\033-D" },
    { "ISO8859-5:GR",       /* ISO-8859-5          144   */  "\033-L" },
    { "ISO8859-6:GR",       /* ISO-8859-6          127   */  "\033-G" },
    { "ISO8859-7:GR",       /* ISO-8859-7          126   */  "\033-F" },
    { "ISO8859-8:GR",       /* ISO-8859-8          138   */  "\033-H" },
    { "ISO8859-9:GR",       /* ISO-8859-9          148   */  "\033-M" },
    { "ISO8859-10:GR",      /* ISO-8859-10         157   */  "\033-V" },
    { "ISO8859-11:GR",      /* ISO-8859-11         166   */  "\033-T" },
    { "ISO8859-13:GR",      /* ISO-8859-13         179   */  "\033-Y" },
    { "ISO8859-14:GR",      /* ISO-8859-14         199   */  "\033-_" },
    { "ISO8859-15:GR",      /* ISO-8859-15         203   */  "\033-b" },
    { "ISO8859-16:GR",      /* ISO-8859-16         226   */  "\033-f" },
    { "JISX0201.1976-0:GL", /* ISO-646-JP           14   */  "\033(J" },
    { "JISX0201.1976-0:GR",                                  "\033)I" },
#if 0
    { "TIS620-0:GR",        /* TIS-620             166   */  "\033-T" },
#endif

    /* Registered character sets with two byte per character */
    { "GB2312.1980-0:GL",   /* GB_2312-80           58   */ "\033$(A" },
    { "GB2312.1980-0:GR",   /* GB_2312-80           58   */ "\033$)A" },
    { "JISX0208.1983-0:GL", /* JIS_X0208-1983       87   */ "\033$(B" },
    { "JISX0208.1983-0:GR", /* JIS_X0208-1983       87   */ "\033$)B" },
    { "JISX0208.1990-0:GL", /* JIS_X0208-1990      168   */ "\033$(B" },
    { "JISX0208.1990-0:GR", /* JIS_X0208-1990      168   */ "\033$)B" },
    { "JISX0212.1990-0:GL", /* JIS_X0212-1990      159   */ "\033$(D" },
    { "JISX0212.1990-0:GR", /* JIS_X0212-1990      159   */ "\033$)D" },
    { "KSC5601.1987-0:GL",  /* KS_C_5601-1987      149   */ "\033$(C" },
    { "KSC5601.1987-0:GR",  /* KS_C_5601-1987      149   */ "\033$)C" },
    { "CNS11643.1986-1:GL", /* CNS 11643-1992 pl.1 171   */ "\033$(G" },
    { "CNS11643.1986-1:GR", /* CNS 11643-1992 pl.1 171   */ "\033$)G" },
    { "CNS11643.1986-2:GL", /* CNS 11643-1992 pl.2 172   */ "\033$(H" },
    { "CNS11643.1986-2:GR", /* CNS 11643-1992 pl.2 172   */ "\033$)H" },
    { "CNS11643.1992-3:GL", /* CNS 11643-1992 pl.3 183   */ "\033$(I" },
    { "CNS11643.1992-3:GR", /* CNS 11643-1992 pl.3 183   */ "\033$)I" },
    { "CNS11643.1992-4:GL", /* CNS 11643-1992 pl.4 184   */ "\033$(J" },
    { "CNS11643.1992-4:GR", /* CNS 11643-1992 pl.4 184   */ "\033$)J" },
    { "CNS11643.1992-5:GL", /* CNS 11643-1992 pl.5 185   */ "\033$(K" },
    { "CNS11643.1992-5:GR", /* CNS 11643-1992 pl.5 185   */ "\033$)K" },
    { "CNS11643.1992-6:GL", /* CNS 11643-1992 pl.6 186   */ "\033$(L" },
    { "CNS11643.1992-6:GR", /* CNS 11643-1992 pl.6 186   */ "\033$)L" },
    { "CNS11643.1992-7:GL", /* CNS 11643-1992 pl.7 187   */ "\033$(M" },
    { "CNS11643.1992-7:GR", /* CNS 11643-1992 pl.7 187   */ "\033$)M" },

    /* Registered encodings with a varying number of bytes per character */
    { "ISO10646-1",         /* UTF-8               196   */ "\033%G"  },

    /* Encodings without ISO-IR assigned escape sequence must be
       defined in XLC_LOCALE files, using "\033%/1" or "\033%/2". */

    /* Backward compatibility with XFree86 3.x */
#if 1
    { "ISO8859-14:GR",                                      "\033%/1" },
    { "ISO8859-15:GR",                                      "\033%/1" },
#endif
    /* For use by utf8 -> ctext */
    { "BIG5-0:GLGR", "\033%/2"},
    { "BIG5HKSCS-0:GLGR", "\033%/2"},
    { "GBK-0:GLGR", "\033%/2"},
    /* used by Emacs, but not backed by ISO-IR */
    { "BIG5-E0:GL", "\033$(0" },
    { "BIG5-E0:GR", "\033$)0" },
    { "BIG5-E1:GL", "\033$(1" },
    { "BIG5-E1:GR", "\033$)1" },

};

/* We represent UTF-8 as an XlcGLGR charset, not in extended segments. */
#define UTF8_IN_EXTSEQ 0

/* ======================= Parsing ESC Sequences ======================= */

#define XctC0		0x0000
#define XctHT		0x0009
#define XctNL		0x000a
#define XctESC		0x001b
#define XctGL		0x0020
#define XctC1		0x0080
#define XctCSI		0x009b
#define XctGR		0x00a0
#define XctSTX		0x0002

#define XctCntrlFunc	0x0023
#define XctMB		0x0024
#define XctOtherCoding	0x0025
#define XctGL94		0x0028
#define XctGR94		0x0029
#define XctGR96		0x002d
#define XctNonStandard	0x002f
#define XctIgnoreExt	0x0030
#define XctNotIgnoreExt	0x0031
#define XctLeftToRight	0x0031
#define XctRightToLeft	0x0032
#define XctDirection	0x005d
#define XctDirectionEnd	0x005d

#define XctGL94MB	0x2428
#define XctGR94MB	0x2429
#define XctExtSeg	0x252f
#define XctReturn	0x2540

/*
 * Parses the header of a Compound Text segment, i.e. the charset designator.
 * The string starts at *text and has *length bytes.
 * Return value is one of:
 *   0 (no valid charset designator),
 *   XctGL94, XctGR94, XctGR96, XctGL94MB, XctGR94MB,
 *   XctLeftToRight, XctRightToLeft, XctDirectionEnd,
 *   XctExtSeg, XctOtherCoding, XctReturn, XctIgnoreExt, XctNotIgnoreExt.
 * If the return value is not 0, *text is incremented and *length decremented,
 * to point past the charset designator. If the return value is one of
 *   XctGL94, XctGR94, XctGR96, XctGL94MB, XctGR94MB,
 *   XctExtSeg, XctOtherCoding, XctIgnoreExt, XctNotIgnoreExt,
 * *final_byte is set to the "final byte" of the charset designator.
 */
static unsigned int
_XlcParseCT(
    const char **text,
    int *length,
    unsigned char *final_byte)
{
    unsigned int ret = 0;
    unsigned char ch;
    const unsigned char *str = (const unsigned char *) *text;

    *final_byte = 0;

    if (*length < 1)
        return 0;
    switch (ch = *str++) {
        case XctESC:
            if (*length < 2)
                return 0;
            switch (ch = *str++) {
                case XctOtherCoding:             /* % */
                    if (*length < 3)
                        return 0;
                    ch = *str++;
                    if (ch == XctNonStandard) {  /* / */
                        if (*length < 4)
                            return 0;
                        ret = XctExtSeg;
                        ch = *str++;
                    } else if (ch == '@') {
                        ret = XctReturn;
                    } else {
                        ret = XctOtherCoding;
                    }
                    *final_byte = ch;
                    break;

                case XctCntrlFunc:               /* # */
                    if (*length < 4)
                        return 0;
                    *final_byte = *str++;
                    switch (*str++) {
                        case XctIgnoreExt:       /* 0 */
                            ret = XctIgnoreExt;
                            break;
                        case XctNotIgnoreExt:    /* 1 */
                            ret = XctNotIgnoreExt;
                            break;
                        default:
                            ret = 0;
                            break;
                    }
                    break;

                case XctMB:                      /* $ */
                    if (*length < 4)
                        return 0;
                    ch = *str++;
                    switch (ch) {
                        case XctGL94:            /* ( */
                            ret = XctGL94MB;
                            break;
                        case XctGR94:            /* ) */
                            ret = XctGR94MB;
                            break;
                        default:
                            ret = 0;
                            break;
                    }
                    *final_byte = *str++;
                    break;

                case XctGL94:                    /* ( */
                    if (*length < 3)
                        return 0;
                    ret = XctGL94;
                    *final_byte = *str++;
                    break;
                case XctGR94:                    /* ) */
                    if (*length < 3)
                        return 0;
                    ret = XctGR94;
                    *final_byte = *str++;
                    break;
                case XctGR96:                    /* - */
                    if (*length < 3)
                        return 0;
                    ret = XctGR96;
                    *final_byte = *str++;
                    break;
            }
            break;
        case XctCSI:
	    /* direction */
            if (*length < 2)
                return 0;
            switch (*str++) {
                case XctLeftToRight:
                    if (*length < 3)
                        return 0;
                    if (*str++ == XctDirection)
                        ret = XctLeftToRight;
                    break;
                case XctRightToLeft:
                    if (*length < 3)
                        return 0;
                    if (*str++ == XctDirection)
                        ret = XctRightToLeft;
                    break;
                case XctDirectionEnd:
                    ret = XctDirectionEnd;
                    break;
            }
            break;
    }

    if (ret) {
        *length -= (const char *) str - *text;
        *text = (const char *) str;
    }
    return ret;
}

/*
 * Fills into a freshly created XlcCharSet the fields that can be inferred
 * from the ESC sequence. These are side, char_size, set_size.
 * Returns True if the charset can be used with Compound Text.
 *
 * Used by _XlcCreateDefaultCharSet.
 */
Bool
_XlcParseCharSet(
    XlcCharSet charset)
{
    unsigned int type;
    unsigned char final_byte;
    const char *ptr = charset->ct_sequence;
    int length;
    int char_size;

    if (*ptr == '\0')
    	return False;

    length = strlen(ptr);

    type = _XlcParseCT(&ptr, &length, &final_byte);

    /* Check for validity and determine char_size.
       char_size = 0 means varying number of bytes per character. */
    switch (type) {
        case XctGL94:
        case XctGR94:
        case XctGR96:
            char_size = 1;
            break;
        case XctGL94MB:
        case XctGR94MB:
            char_size = (final_byte < 0x60 ? 2 : final_byte < 0x70 ? 3 : 4);
            break;
        case XctExtSeg:
            char_size = final_byte - '0';
            if (!(char_size >= 0 && char_size <= 4))
                return False;
            break;
        case XctOtherCoding:
            char_size = 0;
            break;
        default:
            return False;
    }

    charset->char_size = char_size;

    /* Fill in other values. */
    switch (type) {
        case XctGL94:
        case XctGL94MB:
            charset->side = XlcGL;
            charset->set_size = 94;
            break;
        case XctGR94:
        case XctGR94MB:
            charset->side = XlcGR;
            charset->set_size = 94;
            break;
        case XctGR96:
            charset->side = XlcGR;
            charset->set_size = 96;
            break;
        case XctExtSeg:
        case XctOtherCoding:
            charset->side = XlcGLGR;
            charset->set_size = 0;
            break;
    }
    return True;
}


/* =============== Management of the List of Character Sets =============== */

/*
 * Representation of a character set that can be used for Compound Text,
 * at run time.
 * Note: This information is not contained in the XlcCharSet, because
 * multiple ESC sequences may be used for the same XlcCharSet.
 */
typedef struct _CTInfoRec {
    XlcCharSet charset;
    const char *ct_sequence;	/* Compound Text ESC sequence */
    unsigned int type;
    unsigned char final_byte;
				/* If type == XctExtSeg: */
    const char *ext_segment;	/* extended segment name, then '\002' */
    int ext_segment_len;	/* length of above, including final '\002' */

    struct _CTInfoRec *next;
} CTInfoRec, *CTInfo;

/*
 * List of character sets that can be used for Compound Text,
 * Includes all that are listed in default_ct_data, but more can be added
 * at runtime through _XlcAddCT.
 */
static CTInfo ct_list = NULL;
static CTInfo ct_list_end = NULL;

/*
 * Returns a Compound Text info record for an ESC sequence.
 * The first part of the ESC sequence has already been parsed into 'type'
 * and 'final_byte'. The remainder starts at 'text', at least 'text_len'
 * bytes (only used if type == XctExtSeg).
 */
static CTInfo
_XlcGetCTInfo(
    unsigned int type,
    unsigned char final_byte,
    const char *text,
    int text_len)
{
    CTInfo ct_info;

    for (ct_info = ct_list; ct_info; ct_info = ct_info->next)
        if (ct_info->type == type
            && ct_info->final_byte == final_byte
            && (type != XctExtSeg
                || (text_len >= ct_info->ext_segment_len
                    && memcmp(text, ct_info->ext_segment,
                              ct_info->ext_segment_len) == 0)))
            return ct_info;

    return (CTInfo) NULL;
}

/* Returns the Compound Text info for a given XlcCharSet.
   Returns NULL if none is found. */
static CTInfo
_XlcGetCTInfoFromCharSet(
    XlcCharSet charset)
{
    CTInfo ct_info;

    for (ct_info = ct_list; ct_info; ct_info = ct_info->next)
	if (ct_info->charset == charset)
	    return ct_info;

    return (CTInfo) NULL;
}

/* Creates a new XlcCharSet, given its name (including side suffix) and
   Compound Text ESC sequence (normally at most 4 bytes), and makes it
   eligible for Compound Text processing. */
XlcCharSet
_XlcAddCT(
    const char *name,
    const char *ct_sequence)
{
    CTInfo ct_info, existing_info;
    XlcCharSet charset;
    const char *ct_ptr;
    int length;
    unsigned int type;
    unsigned char final_byte;

    charset = _XlcGetCharSet(name);
    if (charset != NULL) {
        /* Even if the charset already exists, it is OK to register a second
           Compound Text sequence for it. */
    } else {
        /* Attempt to create the charset. */
        charset = _XlcCreateDefaultCharSet(name, ct_sequence);
        if (charset == NULL)
	    return (XlcCharSet) NULL;
        _XlcAddCharSet(charset);
    }

    /* Allocate a CTinfo record. */
    length = strlen(ct_sequence);
    ct_info = Xmalloc(sizeof(CTInfoRec) + length+1);
    if (ct_info == NULL)
	return charset;

    ct_info->charset = charset;
    ct_info->ct_sequence = strcpy((char *) (ct_info + 1), ct_sequence);

    /* Parse the Compound Text sequence. */
    ct_ptr = ct_sequence;
    type = _XlcParseCT(&ct_ptr, &length, &final_byte);

    ct_info->type = type;
    ct_info->final_byte = final_byte;

    switch (type) {
	case XctGL94:
	case XctGR94:
	case XctGR96:
	case XctGL94MB:
	case XctGR94MB:
	case XctOtherCoding:
            ct_info->ext_segment = NULL;
            ct_info->ext_segment_len = 0;
            break;
	case XctExtSeg: {
            /* By convention, the extended segment name is the encoding_name
               in lowercase. */
            const char *q = charset->encoding_name;
            int n = strlen(q);
            char *p;

            /* Ensure ct_info->ext_segment_len <= 0x3fff - 6. */
            if (n > 0x3fff - 6 - 1) {
                Xfree(ct_info);
                return charset;
            }
            p = Xmalloc(n+1);
            if (p == NULL) {
                Xfree(ct_info);
                return charset;
            }
            ct_info->ext_segment = p;
            ct_info->ext_segment_len = n+1;
            for ( ; n > 0; p++, q++, n--)
                *p = (*q >= 'A' && *q <= 'Z' ? *q - 'A' + 'a' : *q);
            *p = XctSTX;
            break;
        }
	default:
            Xfree(ct_info);
            return (XlcCharSet) NULL;
    }

    /* Insert it into the list, if not already present. */
    existing_info =
        _XlcGetCTInfo(type, ct_info->final_byte,
                      ct_info->ext_segment, ct_info->ext_segment_len);
    if (existing_info == NULL) {
        /* Insert it at the end. If there are duplicates CTinfo entries
           for the same XlcCharSet, we want the first (standard) one to
           override the second (user defined) one. */
	ct_info->next = NULL;
	if (ct_list_end)
	    ct_list_end->next = ct_info;
	else
	    ct_list = ct_info;
	ct_list_end = ct_info;
    } else {
        if (existing_info->charset != charset
            /* We have a conflict, with one exception: JISX0208.1983-0 and
               JISX0208.1990-0 are the same for all practical purposes. */
            && !(strncmp(existing_info->charset->name, "JISX0208", 8) == 0
                 && strncmp(charset->name, "JISX0208", 8) == 0)) {
            fprintf(stderr,
                    "Xlib: charsets %s and %s have the same CT sequence\n",
                    charset->name, existing_info->charset->name);
            if (strcmp(charset->ct_sequence, ct_sequence) == 0)
                charset->ct_sequence = "";
        }
        Xfree(ct_info);
    }

    return charset;
}


/* ========== Converters String <--> CharSet <--> Compound Text ========== */

/*
 * Structure representing the parse state of a Compound Text string.
 */
typedef struct _StateRec {
    XlcCharSet charset;		/* The charset of the current segment */
    XlcCharSet GL_charset;	/* The charset responsible for 0x00..0x7F */
    XlcCharSet GR_charset;	/* The charset responsible for 0x80..0xFF */
    XlcCharSet Other_charset;	/* != NULL if currently in an other segment */
    int ext_seg_left;		/* > 0 if currently in an extended segment */
} StateRec, *State;


/* Subroutine for parsing an ESC sequence. */

typedef enum {
    resOK,		/* Charset saved in 'state', sequence skipped */
    resNotInList,	/* Charset not found, sequence skipped */
    resNotCTSeq		/* EscSeq not recognized, pointers not changed */
} CheckResult;

static CheckResult
_XlcCheckCTSequence(
    State state,
    const char **ctext,
    int *ctext_len)
{
    XlcCharSet charset;
    CTInfo ct_info;
    const char *tmp_ctext = *ctext;
    int tmp_ctext_len = *ctext_len;
    unsigned int type;
    unsigned char final_byte;
    int ext_seg_left = 0;

    /* Check for validity. */
    type = _XlcParseCT(&tmp_ctext, &tmp_ctext_len, &final_byte);

    switch (type) {
	case XctGL94:
	case XctGR94:
	case XctGR96:
	case XctGL94MB:
	case XctGR94MB:
	case XctOtherCoding:
            *ctext = tmp_ctext;
            *ctext_len = tmp_ctext_len;
            break;
        case XctReturn:
            *ctext = tmp_ctext;
            *ctext_len = tmp_ctext_len;
            state->Other_charset = NULL;
            return resOK;
        case XctExtSeg:
            if (tmp_ctext_len > 2
                && (tmp_ctext[0] & 0x80) && (tmp_ctext[0] & 0x80)) {
                unsigned int msb = tmp_ctext[0] & 0x7f;
                unsigned int lsb = tmp_ctext[1] & 0x7f;
                ext_seg_left = (msb << 7) + lsb;
                if (ext_seg_left <= tmp_ctext_len - 2) {
                    *ctext = tmp_ctext + 2;
                    *ctext_len = tmp_ctext_len - 2;
                    break;
                }
            }
            return resNotCTSeq;
        default:
            return resNotCTSeq;
    }

    ct_info = _XlcGetCTInfo(type, final_byte, *ctext, ext_seg_left);

    if (ct_info) {
        charset = ct_info->charset;
        state->ext_seg_left = ext_seg_left;
        if (type == XctExtSeg) {
            state->charset = charset;
            /* Skip past the extended segment name and the separator. */
            *ctext += ct_info->ext_segment_len;
            *ctext_len -= ct_info->ext_segment_len;
            state->ext_seg_left -= ct_info->ext_segment_len;
        } else if (type == XctOtherCoding) {
            state->Other_charset = charset;
        } else {
            if (charset->side == XlcGL) {
                state->GL_charset = charset;
            } else if (charset->side == XlcGR) {
                state->GR_charset = charset;
            } else {
                state->GL_charset = charset;
                state->GR_charset = charset;
            }
        }
        return resOK;
    } else {
        state->ext_seg_left = 0;
        if (type == XctExtSeg) {
            /* Skip the entire extended segment. */
            *ctext += ext_seg_left;
            *ctext_len -= ext_seg_left;
        }
        return resNotInList;
    }
}

static void
init_state(
    XlcConv conv)
{
    State state = (State) conv->state;
    static XlcCharSet default_GL_charset = NULL;
    static XlcCharSet default_GR_charset = NULL;

    if (default_GL_charset == NULL) {
	default_GL_charset = _XlcGetCharSet("ISO8859-1:GL");
	default_GR_charset = _XlcGetCharSet("ISO8859-1:GR");
    }

    /* The initial state is ISO-8859-1 on both sides. */
    state->GL_charset = state->charset = default_GL_charset;
    state->GR_charset = default_GR_charset;

    state->Other_charset = NULL;

    state->ext_seg_left = 0;
}

/* from XlcNCompoundText to XlcNCharSet */

static int
cttocs(
    XlcConv conv,
    XPointer *from,
    int *from_left,
    XPointer *to,
    int *to_left,
    XPointer *args,
    int num_args)
{
    State state = (State) conv->state;
    XlcCharSet charset = NULL;
    const char *ctptr;
    char *bufptr;
    int ctext_len, buf_len;
    int unconv_num = 0;

    ctptr = (const char *) *from;
    bufptr = (char *) *to;
    ctext_len = *from_left;
    buf_len = *to_left;

    while (ctext_len > 0 && buf_len > 0) {
        if (state->ext_seg_left == 0) {
            /* Not in the middle of an extended segment; look at next byte. */
            unsigned char ch = *ctptr;
            XlcCharSet ch_charset;

            if (ch == XctESC) {
                CheckResult ret =
                    _XlcCheckCTSequence(state, &ctptr, &ctext_len);
                if (ret == resOK)
                    /* state has been modified. */
                    continue;
                if (ret == resNotInList) {
                    /* XXX Just continue with previous charset. */
                    unconv_num++;
                    continue;
                }
            } else if (ch == XctCSI) {
                /* XXX Simply ignore the XctLeftToRight, XctRightToLeft,
                   XctDirectionEnd sequences for the moment. */
                unsigned char dummy;
                if (_XlcParseCT(&ctptr, &ctext_len, &dummy)) {
                    unconv_num++;
                    continue;
                }
            }

            /* Find the charset which is responsible for this byte. */
            ch_charset = (state->Other_charset != NULL ? state->Other_charset :
                          (ch & 0x80 ? state->GR_charset : state->GL_charset));

            /* Set the charset of this run, or continue the current run,
               or stop the current run. */
            if (charset) {
                if (charset != ch_charset)
                    break;
            } else {
                state->charset = charset = ch_charset;
            }

            /* We don't want to split a character into multiple pieces. */
            if (buf_len < 6) {
                if (charset->char_size > 0) {
                    if (buf_len < charset->char_size)
                        break;
                } else {
                    /* char_size == 0 is tricky. The code here is good only
                       for valid UTF-8 input. */
                    if (charset->ct_sequence[0] == XctESC
                        && charset->ct_sequence[1] == XctOtherCoding
                        && charset->ct_sequence[2] == 'G') {
                        int char_size = (ch < 0xc0 ? 1 :
                                         ch < 0xe0 ? 2 :
                                         ch < 0xf0 ? 3 :
                                         ch < 0xf8 ? 4 :
                                         ch < 0xfc ? 5 :
                                                     6);
                        if (buf_len < char_size)
                            break;
                    }
                }
            }

            *bufptr++ = *ctptr++;
            ctext_len--;
            buf_len--;
        } else {
            /* Copy as much as possible from the current extended segment
               to the buffer. */
            int char_size;

            /* Set the charset of this run, or continue the current run,
               or stop the current run. */
            if (charset) {
                if (charset != state->charset)
                    break;
            } else {
                charset = state->charset;
            }

            char_size = charset->char_size;

            if (state->ext_seg_left <= buf_len || char_size > 0) {
                int n = (state->ext_seg_left <= buf_len
                         ? state->ext_seg_left
                         : (buf_len / char_size) * char_size);
                memcpy(bufptr, ctptr, n);
                ctptr += n; ctext_len -= n;
                bufptr += n; buf_len -= n;
                state->ext_seg_left -= n;
            } else {
#if UTF8_IN_EXTSEQ
                /* char_size == 0 is tricky. The code here is good only
                   for valid UTF-8 input. */
                if (strcmp(charset->name, "ISO10646-1") == 0) {
                    unsigned char ch = *ctptr;
                    int char_size = (ch < 0xc0 ? 1 :
                                     ch < 0xe0 ? 2 :
                                     ch < 0xf0 ? 3 :
                                     ch < 0xf8 ? 4 :
                                     ch < 0xfc ? 5 :
                                                 6);
                    int i;
                    if (buf_len < char_size)
                        break;
                    /* A small loop is faster than calling memcpy. */
                    for (i = char_size; i > 0; i--)
                        *bufptr++ = *ctptr++;
                    ctext_len -= char_size;
                    buf_len -= char_size;
                    state->ext_seg_left -= char_size;
                } else
#endif
                {
                    /* Here ctext_len >= state->ext_seg_left > buf_len.
                       We may be splitting a character into multiple pieces.
                       Oh well. */
                    int n = buf_len;
                    memcpy(bufptr, ctptr, n);
                    ctptr += n; ctext_len -= n;
                    bufptr += n; buf_len -= n;
                    state->ext_seg_left -= n;
                }
            }
        }
    }

    /* 'charset' is the charset for the current run. In some cases,
       'state->charset' contains the charset for the next run. Therefore,
       return 'charset'.
       'charset' may still be NULL only if no output was produced. */
    if (num_args > 0)
	*((XlcCharSet *) args[0]) = charset;

    *from_left -= ctptr - *((const char **) from);
    *from = (XPointer) ctptr;

    *to_left -= bufptr - *((char **) to);
    *to = (XPointer) bufptr;

    return unconv_num;
}

/* from XlcNCharSet to XlcNCompoundText */

static int
cstoct(
    XlcConv conv,
    XPointer *from,
    int *from_left,
    XPointer *to,
    int *to_left,
    XPointer *args,
    int num_args)
{
    State state = (State) conv->state;
    XlcSide side;
    unsigned char min_ch = 0, max_ch = 0;
    int length, unconv_num;
    CTInfo ct_info;
    XlcCharSet charset;
    const char *csptr;
    char *ctptr;
    int csstr_len, ct_len;
    char *ext_segment_start;
    int char_size;

    /* One argument is required, of type XlcCharSet. */
    if (num_args < 1)
	return -1;

    csptr = *((const char **) from);
    ctptr = *((char **) to);
    csstr_len = *from_left;
    ct_len = *to_left;

    charset = (XlcCharSet) args[0];

    ct_info = _XlcGetCTInfoFromCharSet(charset);
    if (ct_info == NULL)
	return -1;

    side = charset->side;
    length = strlen(ct_info->ct_sequence);

    ext_segment_start = NULL;

    if (ct_info->type == XctOtherCoding) {
        /* Output the Escape sequence for switching to the charset, and
           reserve room now for the XctReturn sequence at the end. */
        if (ct_len < length + 3)
            return -1;

        memcpy(ctptr, ct_info->ct_sequence, length);
        ctptr += length;
        ct_len -= length + 3;
    } else
    /* Test whether the charset is already active. */
    if (((side == XlcGR || side == XlcGLGR)
	 && charset != state->GR_charset)
	|| ((side == XlcGL || side == XlcGLGR)
	    && charset != state->GL_charset)) {

        /* Output the Escape sequence for switching to the charset. */
        if (ct_info->type == XctExtSeg) {
            if (ct_len < length + 2 + ct_info->ext_segment_len)
                return -1;

            memcpy(ctptr, ct_info->ct_sequence, length);
            ctptr += length;
            ct_len -= length;

            ctptr += 2;
            ct_len -= 2;
            ext_segment_start = ctptr;

            /* The size of an extended segment must fit in 14 bits. */
            if (ct_len > 0x3fff)
                ct_len = 0x3fff;

            memcpy(ctptr, ct_info->ext_segment, ct_info->ext_segment_len);
            ctptr += ct_info->ext_segment_len;
            ct_len -= ct_info->ext_segment_len;
        } else {
            if (ct_len < length)
                return -1;

            memcpy(ctptr, ct_info->ct_sequence, length);
            ctptr += length;
            ct_len -= length;
        }
    }

    /* If the charset has side GL or GR, prepare remapping the characters
       to the correct side. */
    if (charset->set_size) {
        min_ch = 0x20;
        max_ch = 0x7f;
        if (charset->set_size == 94) {
            max_ch--;
	    if (charset->char_size > 1 || side == XlcGR)
		min_ch++;
        }
    }

    /* Actually copy the contents. */
    unconv_num = 0;
    char_size = charset->char_size;
    if (char_size == 1) {
	while (csstr_len > 0 && ct_len > 0) {
	    if (charset->set_size) {
		/* The CompoundText specification says that the only
		   control characters allowed are 0x09, 0x0a, 0x1b, 0x9b.
		   Therefore here we eliminate other control characters. */
		unsigned char ch = *((const unsigned char *) csptr) & 0x7f;
		if (!((ch >= min_ch && ch <= max_ch)
		      || (side == XlcGL
			  && (ch == 0x00 || ch == 0x09 || ch == 0x0a))
		      || ((side == XlcGL || side == XlcGR)
			  && (ch == 0x1b)))) {
                    csptr++;
                    csstr_len--;
		    unconv_num++;
                    continue;
 		}
	    }

	    if (side == XlcGL)
		*ctptr++ = *csptr++ & 0x7f;
	    else if (side == XlcGR)
		*ctptr++ = *csptr++ | 0x80;
	    else
		*ctptr++ = *csptr++;
	    csstr_len--;
	    ct_len--;
	}
    } else if (char_size > 1) {
	while (csstr_len >= char_size && ct_len >= char_size) {
	    if (side == XlcGL) {
		int i;
		for (i = char_size; i > 0; i--)
		    *ctptr++ = *csptr++ & 0x7f;
	    } else if (side == XlcGR) {
		int i;
		for (i = char_size; i > 0; i--)
		    *ctptr++ = *csptr++ | 0x80;
	    } else {
		int i;
		for (i = char_size; i > 0; i--)
		    *ctptr++ = *csptr++;
	    }
	    csstr_len -= char_size;
	    ct_len -= char_size;
	}
    } else {
        /* char_size = 0. The code here is good only for valid UTF-8 input. */
        if ((charset->ct_sequence[0] == XctESC
             && charset->ct_sequence[1] == XctOtherCoding
             && charset->ct_sequence[2] == 'G')
#if UTF8_IN_EXTSEQ
            || strcmp(charset->name, "ISO10646-1") == 0
#endif
           ) {
            while (csstr_len > 0 && ct_len > 0) {
                unsigned char ch = * (const unsigned char *) csptr;
                int ch_size = (ch < 0xc0 ? 1 :
                                 ch < 0xe0 ? 2 :
                                 ch < 0xf0 ? 3 :
                                 ch < 0xf8 ? 4 :
                                 ch < 0xfc ? 5 :
                                             6);
                int i;
                if (!(csstr_len >= ch_size && ct_len >= ch_size))
                    break;
                for (i = ch_size; i > 0; i--)
                    *ctptr++ = *csptr++;
                csstr_len -= ch_size;
                ct_len -= ch_size;
            }
        } else {
            while (csstr_len > 0 && ct_len > 0) {
                *ctptr++ = *csptr++;
                csstr_len--;
                ct_len--;
            }
        }
    }

    if (ct_info->type == XctOtherCoding) {
        /* Terminate with an XctReturn sequence. */
        ctptr[0] = XctESC;
        ctptr[1] = XctOtherCoding;
        ctptr[2] = '@';
        ctptr += 3;
    } else if (ext_segment_start != NULL) {
        /* Backpatch the extended segment's length. */
        int ext_segment_length = ctptr - ext_segment_start;
        *(ext_segment_start - 2) = (ext_segment_length >> 7) | 0x80;
        *(ext_segment_start - 1) = (ext_segment_length & 0x7f) | 0x80;
    } else {
        if (side == XlcGR || side == XlcGLGR)
            state->GR_charset = charset;
        if (side == XlcGL || side == XlcGLGR)
            state->GL_charset = charset;
    }

    *from_left -= csptr - *((const char **) from);
    *from = (XPointer) csptr;

    *to_left -= ctptr - *((char **) to);
    *to = (XPointer) ctptr;

    return 0;
}

/* from XlcNString to XlcNCharSet */

static int
strtocs(
    XlcConv conv,
    XPointer *from,
    int *from_left,
    XPointer *to,
    int *to_left,
    XPointer *args,
    int num_args)
{
    State state = (State) conv->state;
    const char *src;
    char *dst;
    unsigned char side;
    int length;

    src = (const char *) *from;
    dst = (char *) *to;

    length = min(*from_left, *to_left);
    side = *((const unsigned char *) src) & 0x80;

    while (side == (*((const unsigned char *) src) & 0x80) && length-- > 0)
	*dst++ = *src++;

    *from_left -= src - (const char *) *from;
    *from = (XPointer) src;
    *to_left -= dst - (char *) *to;
    *to = (XPointer) dst;

    if (num_args > 0)
	*((XlcCharSet *)args[0]) = (side ? state->GR_charset : state->GL_charset);

    return 0;
}

/* from XlcNCharSet to XlcNString */

static int
cstostr(
    XlcConv conv,
    XPointer *from,
    int *from_left,
    XPointer *to,
    int *to_left,
    XPointer *args,
    int num_args)
{
    State state = (State) conv->state;
    const char *csptr;
    char *string_ptr;
    int csstr_len, str_len;
    unsigned char ch;
    int unconv_num = 0;

    /* This converter can only convert from ISO8859-1:GL and ISO8859-1:GR. */
    if (num_args < 1
	|| !((XlcCharSet) args[0] == state->GL_charset
	     || (XlcCharSet) args[0] == state->GR_charset))
	return -1;

    csptr = *((const char **) from);
    string_ptr = *((char **) to);
    csstr_len = *from_left;
    str_len = *to_left;

    while (csstr_len > 0 && str_len > 0) {
	ch = *((const unsigned char *) csptr++);
	csstr_len--;
	/* Citing ICCCM: "STRING as a type specifies the ISO Latin-1 character
	   set plus the control characters TAB and NEWLINE." */
	if ((ch < 0x20 && ch != 0x00 && ch != 0x09 && ch != 0x0a)
	    || (ch >= 0x7f && ch < 0xa0)) {
	    unconv_num++;
	    continue;
	}
	*((unsigned char *) string_ptr++) = ch;
	str_len--;
    }

    *from_left -= csptr - *((const char **) from);
    *from = (XPointer) csptr;

    *to_left -= string_ptr - *((char **) to);
    *to = (XPointer) string_ptr;

    return unconv_num;
}


static XlcConv
create_conv(
    XlcConvMethods methods)
{
    XlcConv conv;

    conv = Xmalloc(sizeof(XlcConvRec) + sizeof(StateRec));
    if (conv == NULL)
	return (XlcConv) NULL;

    conv->state = (XPointer) &conv[1];

    conv->methods = methods;

    init_state(conv);

    return conv;
}

static void
close_converter(
    XlcConv conv)
{
    /* conv->state is allocated together with conv, free both at once.  */
    Xfree(conv);
}


static XlcConvMethodsRec cttocs_methods = {
    close_converter,
    cttocs,
    init_state
};

static XlcConv
open_cttocs(
    XLCd from_lcd,
    const char *from_type,
    XLCd to_lcd,
    const char *to_type)
{
    return create_conv(&cttocs_methods);
}


static XlcConvMethodsRec cstoct_methods = {
    close_converter,
    cstoct,
    init_state
};

static XlcConv
open_cstoct(
    XLCd from_lcd,
    const char *from_type,
    XLCd to_lcd,
    const char *to_type)
{
    return create_conv(&cstoct_methods);
}


static XlcConvMethodsRec strtocs_methods = {
    close_converter,
    strtocs,
    init_state
};

static XlcConv
open_strtocs(
    XLCd from_lcd,
    const char *from_type,
    XLCd to_lcd,
    const char *to_type)
{
    return create_conv(&strtocs_methods);
}


static XlcConvMethodsRec cstostr_methods = {
    close_converter,
    cstostr,
    init_state
};

static XlcConv
open_cstostr(
    XLCd from_lcd,
    const char *from_type,
    XLCd to_lcd,
    const char *to_type)
{
    return create_conv(&cstostr_methods);
}


/* =========================== Initialization =========================== */

Bool
_XlcInitCTInfo(void)
{
    if (ct_list == NULL) {
        const CTDataRec *ct_data;
        int num;
        XlcCharSet charset;

        /* Initialize ct_list.  */

	num = sizeof(default_ct_data) / sizeof(CTDataRec);
	for (ct_data = default_ct_data; num > 0; ct_data++, num--) {
	    charset = _XlcAddCT(ct_data->name, ct_data->ct_sequence);
            if (charset == NULL)
                continue;
			if (strncmp(charset->ct_sequence, "\x1b\x25\x2f", 3) != 0)
				charset->source = CSsrcStd;
			else
				charset->source = CSsrcXLC;
	}

        /* Register CompoundText and CharSet converters.  */

        _XlcSetConverter((XLCd) NULL, XlcNCompoundText,
                         (XLCd) NULL, XlcNCharSet,
                         open_cttocs);
        _XlcSetConverter((XLCd) NULL, XlcNString,
                         (XLCd) NULL, XlcNCharSet,
                         open_strtocs);

        _XlcSetConverter((XLCd) NULL, XlcNCharSet,
                         (XLCd) NULL, XlcNCompoundText,
                         open_cstoct);
        _XlcSetConverter((XLCd) NULL, XlcNCharSet,
                         (XLCd) NULL, XlcNString,
                         open_cstostr);
    }

    return True;
}
