/* XUnicode ver.0.3.3
 * 
 * This software is in the public domain.
 * There are no restrictions on any sort of usage of this software.
 * 
 * $xunicode: xunicode.c,v 1.127.3 2001/07/18 21:55:00 Toshihiro Inoue Exp $
 */

#include "xunicode.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#ifdef WIN32
BOOL xunicode_enable = FALSE;
#else
#include <X11/Xlocale.h>
#include <locale.h>

Bool xunicode_enable = False;
static const char* xu_lang = NULL;
static char xu_language[3] = {0, 0, 0};
static int xu_locale_encoding = XU_CONV_NONE;

/* link list */
static XUInfoDisplay* xu_info_display = NULL;
static XUInfoFontSetting* xu_info_font_setting = NULL;

/* font set */
static int xu_fontset_count = 0;
static int xu_fontset_encoding[] = {0, 0, 0, 0, 0};
#endif

/* charsets */
static char* xu_charset[XU_FONT_COUNT] =
{
  NULL,
  "iso8859-1",
  "iso8859-2",
  "iso8859-3",
  "iso8859-4",
  "iso8859-5",
  "iso8859-6",
  "iso8859-7",
  "iso8859-8",
  "iso8859-9",
  "iso8859-10",
  NULL,
  NULL,
  "iso8859-13",
  "iso8859-14",
  "iso8859-15",
  "iso10646-1",
  "koi8-r",
  "jisx0201.1976-0",
  "jisx0208.1983-0",
  "ksc5601.1987-0",
  "gb2312.1980-0",
  "big5-0"
};

/* encodings */
#include "iso8859.h"
#include "koi8r.h"
#include "jis0208.h"
#include "ksc5601.h"
#include "gb2312.h"
#include "big5.h"
static XUChar table_rev_latin2 [ 1024];
static XUChar table_rev_koi8r_1[ 1024];
static XUChar table_rev_koi8r_2[ 1024];
static XUChar table_rev_iso8859[65536];
static XUChar table_rev_jis0208[65536];
static XUChar table_rev_ksc5601[65536];
static XUChar table_rev_gb2312 [65536];
static XUChar table_rev_big5   [65536];


static void XUInitTable();
#ifndef WIN32
static void XUInitLocale();
static void XUInitEncoding();
static void XUInitFontSet();
static Bool XUIsEncoding(const char* enc);
static int XUGetISO8859();
static void XUInitSetting();
static Bool XUReadSetting(const char* fn);
static void XUSetSetting(char* data);
static void XUStripSpace(char* text);
#endif


void XUInit()
{
  if(xunicode_enable) return;
  
#ifdef WIN32
  xunicode_enable = TRUE;
#else
  xunicode_enable = True;
  XUInitLocale();
  XUInitSetting();
#endif
  XUInitTable();
}


static void XUInitTable()
{
  int i, j;
  XUChar c;
  XUChar c1;
  XUChar c2;
  
  for(i = 0; i < 1024; i++) {
    table_rev_latin2 [i] = 0;
    table_rev_koi8r_1[i] = 0;
    table_rev_koi8r_2[i] = 0;
  }
  
  for(i = 0; i < 128; i++) {
    c = table_iso8859[1].data[i];
    if(c) table_rev_latin2[c] = i + 0x0080;
  }
  
  for(i = 0; i < 128; i++) {
    c = table_koi8r[i];
    if(0x0080 <= c && c < 0x0480) {
      table_rev_koi8r_1[c - 0x0080] = i + 0x0080;
    } else if(0x2200 <= c && c < 0x2600) {
      table_rev_koi8r_2[c - 0x2200] = i + 0x0080;
    }
  }
  
  for(i = 0; i < 65536; i++) table_rev_iso8859[i] = 0;
  for(i = 0; i < 65536; i++) table_rev_jis0208[i] = 0;
  for(i = 0; i < 65536; i++) table_rev_ksc5601[i] = 0;
  for(i = 0; i < 65536; i++) table_rev_gb2312 [i] = 0;
  for(i = 0; i < 65536; i++) table_rev_big5   [i] = 0;

  for(i = 9; i >= 1; i--) {
    c = (i << 8) + 128;
    for(j = 0; j < 128; j++, c++) {
      table_rev_iso8859[table_iso8859[i - 1].data[j]] = c;
    }
  }
  
  c1 = 0x21;
  c2 = 0x21;
  for(i = 0; i < 7896; i++) {
    c = table_jis0208[i];
    if(c) table_rev_jis0208[c] = (c1 << 8) + c2;
    c2++;
    if(c2 == 0x7f) {
      c2 = 0x21;
      c1++;
    }
  }
  
  c1 = 0x21;
  c2 = 0x21;
  for(i = 0; i < 8178; i++) {
    c = table_gb2312[i];
    if(c) table_rev_gb2312[c] = (c1 << 8) + c2;
    c2++;
    if(c2 == 0x7f) {
      c2 = 0x21;
      c1++;
    }
  }
  
  c1 = 0xa1;
  c2 = 0x40;
  for(i = 0; i < 13973; i++) {
    c = table_big5[i];
    if(c) table_rev_big5[c] = (c1 << 8) + c2;
    c2++;
    if(c2 == 0x7f) {
      c2 = 0xa1;
    } else if(c2 == 0xff) {
      c2 = 0x40;
      c1++;
    }
  }
  
  c1 = 0x81;
  c2 = 0x41;
  for(i = 0; i < 12816; i++) {
    c = table_ksc5601[i];
    if(c) table_rev_ksc5601[c] = (c1 << 8) + c2;
    c2++;
    if(c2 == 0x5b) {
      c2 = 0x61;
    } else if(c2 == 0x7b) {
      c2 = 0x81;
    } else if(c2 == 0xff) {
      c2 = 0x41;
      c1++;
    }
  }
  
  c1 = 0xca;
  c2 = 0xa1;
  for(i = 0; i < 4888; i++) {
    c = table_ksc5601_hanja[i];
    if(c) table_rev_ksc5601[c] = (c1 << 8) + c2;
    c2++;
    if(c2 == 0xff) {
      c2 = 0xa1;
      c1++;
    }
  }
}


#ifndef WIN32
static void XUInitLocale()
{
  xu_lang = getenv("LANG");
  if(!xu_lang) xu_lang = "C";
  strncpy(xu_language, xu_lang, 2);
  
  XUInitEncoding();
  
  if(setlocale(LC_ALL, "")) XUInitFontSet();
  XSetLocaleModifiers("");
}


static void XUInitEncoding()
{
  if(XUIsEncoding("utf8")) {
    xu_locale_encoding = XU_CONV_UTF8;
  } else if(XUIsEncoding("sjis") || XUIsEncoding("shiftjis")) {
    xu_locale_encoding = XU_CONV_SJIS;
  } else if(XUIsEncoding("eucjp")) {
    xu_locale_encoding = XU_CONV_EUCJP;
  } else if(XUIsEncoding("euckr")) {
    xu_locale_encoding = XU_CONV_EUCKR;
  } else if(XUIsEncoding("euccn")) {
    xu_locale_encoding = XU_CONV_EUCCN;
  } else if(XUIsEncoding("big5")) {
    xu_locale_encoding = XU_CONV_BIG5;
  } else if(XUIsEncoding("koi8r")) {
    xu_locale_encoding = XU_CONV_KOI8R;
  } else if(XUIsEncoding("iso8859") || XUIsEncoding("dis8859")) {
    xu_locale_encoding = XUGetISO8859();
  }
  if(xu_locale_encoding != XU_CONV_NONE) return;
  
  if(!strcmp(xu_language, "cs")) xu_locale_encoding = XU_CONV_ISO8859(2);
  if(!strcmp(xu_language, "he")) xu_locale_encoding = XU_CONV_ISO8859(8);
  if(!strcmp(xu_language, "hr")) xu_locale_encoding = XU_CONV_ISO8859(2);
  if(!strcmp(xu_language, "hu")) xu_locale_encoding = XU_CONV_ISO8859(2);
  if(!strcmp(xu_language, "pl")) xu_locale_encoding = XU_CONV_ISO8859(2);
  if(!strcmp(xu_language, "ro")) xu_locale_encoding = XU_CONV_ISO8859(2);
  if(!strcmp(xu_language, "ru")) xu_locale_encoding = XU_CONV_KOI8R;
  if(!strcmp(xu_language, "ja")) xu_locale_encoding = XU_CONV_EUCJP;
  if(!strcmp(xu_language, "ko")) xu_locale_encoding = XU_CONV_EUCKR;
  if(!strcmp(xu_language, "zh")) xu_locale_encoding = XU_CONV_EUCCN;
  if(xu_locale_encoding != XU_CONV_NONE) return;
  
  xu_locale_encoding = XU_CONV_ISO8859(1);
}


static void XUInitFontSet()
{
  if(!strcmp(xu_language, "ja")) {
    xu_fontset_count = 3;
    xu_fontset_encoding[0] = XU_FONT_ISO8859(1);
    xu_fontset_encoding[1] = XU_FONT_JIS0201;
    xu_fontset_encoding[2] = XU_FONT_JIS0208;
  } else if(!strcmp(xu_language, "ko")) {
    xu_fontset_count = 2;
    xu_fontset_encoding[0] = XU_FONT_ISO8859(1);
    xu_fontset_encoding[1] = XU_FONT_KSC5601;
  } else if(!strncmp(xu_lang, "zh_TW", 5)) {
    xu_fontset_count = 2;
    xu_fontset_encoding[0] = XU_FONT_ISO8859(1);
    xu_fontset_encoding[1] = XU_FONT_BIG5;
  } else if(!strcmp(xu_language, "zh")) {
    xu_fontset_count = 2;
    xu_fontset_encoding[0] = XU_FONT_ISO8859(1);
    xu_fontset_encoding[1] = XU_FONT_GB2312;
  }
}


static Bool XUIsEncoding(const char* enc)
{
  const char* p1 = 0;
  const char* p2 = 0;
  char c = 0;
  
  for(p1 = xu_lang; *p1 != '.'; p1++) {
    if(!*p1) return False;
  }
  p1++;
  for(p2 = enc; (c = *p1) && *p2; p1++) {
    if(c == '_' || c == '-') continue;
    if('A' <= c && c <= 'Z') c += 32;
    if(c != *p2) return False;
    p2++;
  }
  if(!*p2) return True;
  return False;
}


static int XUGetISO8859()
{
  const char* p = 0;
  int iso = 0;
  
  for(p = xu_lang + strlen(xu_lang) - 1; *p != '-'; p--) {
    if(p <= xu_lang) return XU_CONV_NONE;
  }
  iso = atoi(p + 1);
  if(iso < 1 || iso > 15) return XU_CONV_NONE;
  return XU_CONV_ISO8859(iso);
}


static void XUInitSetting()
{
  const char* home = 0;
  char fn[64];
  
  home = getenv("HOME");
  
  snprintf(fn, 64, "%s/.xunicoderc.%s", home, xu_language);
  if(XUReadSetting(fn)) return;
  
  snprintf(fn, 64, "%s/.xunicoderc", home);
  if(XUReadSetting(fn)) return;
  
  snprintf(fn, 64, "/usr/local/etc/xunicoderc.%s", xu_language);
  if(XUReadSetting(fn)) return;
  
  if(XUReadSetting("/usr/local/etc/xunicoderc")) return;
  
  snprintf(fn, 64, "/etc/xunicoderc.%s", xu_language);
  if(XUReadSetting(fn)) return;
  
  if(XUReadSetting("/etc/xunicoderc")) return;
  
  snprintf(fn, 64, "%s/.qti18nrc", home);
  XUReadSetting(fn);
}


static Bool XUReadSetting(const char* fn)
{
  FILE* f = 0;
  char line[256];
  int len = 0;
  char data[1024];
  char* ok = 0;
  
  f = fopen(fn, "r");
  if(!f) return False;
  
  *data = 0;
  for(;;) {
    ok = fgets(line, 256, f);
    if(ok) {
      strncat(data, line, 1024);
      len = strlen(data);
      if(len > 0 && data[len - 1] == '\n') len--;
      if(len > 0 && data[len - 1] == '\\') {
        data[len - 1] = 0;
        continue;
      }
    }
    XUStripSpace(data);
    if(*data) {
      XUSetSetting(data);
      *data = 0;
    }
    if(!ok) break;
  }
  
  fclose(f);
  return True;
}


static void XUSetSetting(char* data)
{
  XUInfoFontSetting* fs = 0;
  char* p1 = 0;
  char* p2 = 0;
  char* p3 = 0;
  int i = 0;
  Bool flgEnd;
  int nMinus = 0;
  int charset = 0;
  
  for(p1 = data; *p1 && *p1 != ' '; p1++);
  if(p1 == data || !*p1) return;
  
  *p1 = 0;
  p1++;
  
  fs = (XUInfoFontSetting*)malloc(sizeof(XUInfoFontSetting));
  fs->previous = NULL;
  fs->next = xu_info_font_setting;
  fs->name = (char*)malloc(sizeof(char) * strlen(data) + 1);
  strcpy(fs->name, data);
  fs->count = 0;
  for(i = 0; i < XU_FONT_COUNT; i++) {
    fs->order[i] = XU_FONT_NONE;
    fs->font [i] = NULL;
  }
  if(fs->next) fs->next->previous = fs;
  xu_info_font_setting = fs;
  
  for(flgEnd = False, p2 = p1; !flgEnd; p1++) {
    if(!*p1) flgEnd = True;
    if(*p1 == ',') *p1 = 0;
    if(*p1) continue;
    XUStripSpace(p2);
    for(p3 = p2 + strlen(p2) - 1, nMinus = 0; *p3; p3--) {
      if(*p3 == '-') nMinus++;
      if(nMinus == 2) break;
    }
    if(nMinus == 2) {
      p3++;
      charset = XU_FONT_NONE;
      for(i = 0; i < XU_FONT_COUNT; i++) {
        if(xu_charset[i] && !strcmp(p3, xu_charset[i])) {
          charset = i;
          break;
        }
      }
      if(charset != XU_FONT_NONE) {
        fs->order[fs->count++] = charset;
        fs->font[charset] = (char*)malloc(sizeof(char) * (strlen(p2) + 1));
        strcpy(fs->font[charset], p2);
      }
    }
    p2 = p1 + 1;
  }
}


static void XUStripSpace(char* text)
{
  const char* p1 = 0;
  char* p2 = 0;
  Bool flgSpc;
  
  for(p1 = p2 = text, flgSpc = False; *p1; p1++) {
    if(*p1 == ' ' || *p1 == '\t' || *p1 == '\n' || *p1 == '\r') {
      flgSpc = True;
      continue;
    }
    if(flgSpc && p2 > text) {
      *p2 = ' ';
      p2++;
    }
    if('A' <= *p1 && *p1 <= 'Z') {
      *p2 = *p1 + 32;
    } else {
      *p2 = *p1;
    }
    p2++;
    flgSpc = False;
  }
  *p2 = 0;
}


/**
  UTF-8 -> locale
*/

void XUDebug(const char* fmt,  ...)
{
  va_list ap;
  char buf[512];
  char conv[512];
  int len = 0;
  
  va_start(ap, fmt);
  vsnprintf(buf, 512, fmt, ap);
  va_end(ap);
  
  len = XUCodeConv(conv, 512, XU_CONV_LOCALE, buf, -1, XU_CONV_UTF8);
  printf(conv);
  if(len > 0 && conv[len - 1] != '\n') printf("\n");
}


int XUCloseDisplay(Display* display)
{
  XUFreeInfoDisplay(display);
  return XCloseDisplay(display);
}


int XUDestroyWindow(Display* display, Window window)
{
  XUFreeInfoWindow(display, window);
  return XDestroyWindow(display, window);
}


/**
  XSetErrorHandler(XUErrorHandler);
*/

int XUErrorHandler(Display* display, XErrorEvent* error)
{
  XUInfoDisplay* d = 0;
  
  if(error->error_code == BadWindow){
    d = XUGetInfoDisplay(display);
    if(d->uinput == error->resourceid) {
      d->uinput = 0;
      return 0;
    }
  }
  return 1;
}


void XUGetWindowPos(XPoint* point, Display* display, Window window)
{
  Window r;
  int x = 0;
  int y = 0;
  unsigned int w;
  unsigned int h;
  unsigned int bw;
  unsigned int d;
  
  XGetGeometry(
    display, window,
    &r, &x, &y, &w, &h, &bw, &d
  );
  point->x = x;
  point->y = y;
}


void XUDrawString(Display* display, Drawable d, XFontStruct* font, GC gc, int x, int y, const XUChar* string, int length, Bool erase)
{
  char* fn = 0;
  XUInfoFont* fi = 0;
  XFontStruct* f = 0;
  int i = 0;
  XChar2b* mb = 0;
  XChar2b* p = 0;
  int len = 0;
  XUChar c;
  int code = 0;
  int codeOld = 0;
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  fn = XUGetFontName(display, font);
  fi = XUGetInfoFont(display, fn);
  XFree(fn);
  
  if(length < 0) length = XUStrLen(string);
  p = mb = (XChar2b*)malloc(sizeof(XChar2b) * length);
  for(i = 0; i <= length; i++) {
    codeOld = code;
    if(i == length) {
      code = XU_FONT_NONE;
    } else {
      c = *string;
      string++;
      code = XUQueryChar(&c, display, fi);
    }
    if(len && code != codeOld) {
      f = fi->font[codeOld];
      if(!f) f = fi->font[XU_FONT_ISO8859(1)];
      XSetFont(display, gc, f->fid);
      if(!erase) {
        XDrawString16(display, d, gc, x, y, mb, len);
      } else {
        XDrawImageString16(display, d, gc, x, y, mb, len);
      }
      XTextExtents16(f, mb, len, &dir, &ascent, &descent, &overAll);
      x += overAll.width;
      p = mb;
      len = 0;
    }
    if(i < length) {
      if(fi->font[code]) {
        p->byte1 = c >> 8;
        p->byte2 = c & 0xff;
      } else {
        p->byte1 = 0;
        p->byte2 = '?';
      }
      p++;
      len++;
    }
  }
  free(mb);
}


void XUutf8DrawString(Display* display, Drawable d, XFontStruct* font, GC gc, int x, int y, const char* string, int length, Bool erase)
{
  XUChar* ustr = 0;
  int len = 0;
  
  if(length < 0) length = strlen(string);
  ustr = (XUChar*)malloc(sizeof(XUChar) * length);
  len = XUutf8Encode(ustr, length, string, length);
  XUDrawString(display, d, font, gc, x, y, ustr, len, erase);
  free(ustr);
}


int XUQueryChar(XUChar* c, Display* display, XUInfoFont* fi)
{
  int charset = 0;
  int i = 0;
  XUChar check;
  
  check = 0x2000;
  
  if(0xac00 <= *c && *c <= 0xd7a3 && table_rev_ksc5601[*c]) {
    charset = XU_FONT_KSC5601;
    *c = table_rev_ksc5601[*c];
  } else if(0xff61 <= *c && *c <= 0xff9f) {
    charset = XU_FONT_JIS0201;
    *c -= 0xfec0;
  } else {
    charset = XU_FONT_UNICODE;
  }
  
  for(i = 0; i < fi->count; i++) {
    if(charset != XU_FONT_UNICODE) break;
    switch(fi->order[i]) {
    case XU_FONT_ISO8859(1):
      if(*c < 0x0100) charset = XU_FONT_ISO8859(1);
      break;
    case XU_FONT_ISO8859(2):
      if(*c < 0x0400 && table_rev_latin2[*c]) {
        charset = XU_FONT_ISO8859(2);
        *c = table_rev_latin2[*c];
      }
      break;
    case XU_FONT_KOI8R:
      if(0x0080 <= *c && *c < 0x0480 && table_rev_koi8r_1[*c - 0x0080]) {
        charset = XU_FONT_KOI8R;
        *c = table_rev_koi8r_1[*c - 0x0080];
      } else if(0x2200 <= *c && *c < 0x2600 && table_rev_koi8r_2[*c - 0x2200]) {
        charset = XU_FONT_KOI8R;
        *c = table_rev_koi8r_2[*c - 0x2200];
      }
      if(fi->none[XU_FONT_UNICODE]) check = 0;
      break;
    case XU_FONT_JIS0208:
      if(*c >= check && table_rev_jis0208[*c]) {
        charset = XU_FONT_JIS0208;
        *c = table_rev_jis0208[*c];
      }
      break;
    case XU_FONT_KSC5601:
      if(*c >= check && table_rev_ksc5601[*c]) {
        charset = XU_FONT_KSC5601;
        *c = table_rev_ksc5601[*c];
      }
      break;
    case XU_FONT_GB2312:
      if(*c >= check && table_rev_gb2312[*c]) {
        charset = XU_FONT_GB2312;
        *c = table_rev_gb2312[*c];
      }
      break;
    case XU_FONT_BIG5:
      if(*c >= check && table_rev_big5[*c]) {
        charset = XU_FONT_BIG5;
        *c = table_rev_big5[*c];
      }
      break;
    }
  }
  
  if(charset == XU_FONT_KSC5601) {
    if((*c >> 8) >= 0x80 || (*c & 0xff) >= 0x80) {
      *c -= 0x8080;
    } else {
      charset = XU_FONT_ISO8859(1);
      *c = '?';
    }
  }
  
  if(!fi->none[charset] && !fi->font[charset]) {
    XULoadSearchFont(display, fi->name, charset);
  }
  if(fi->none[charset]) {
    charset = XU_FONT_ISO8859(1);
    *c = '?';
  }
  
  return charset;
}


void XUTextExtents(Display* display, XFontStruct* font_struct, const XUChar* string, int nchars, int* direction_return, int* font_ascent_return, int* font_descent_return, XCharStruct* overall_return)
{
  char* fn = 0;
  XUInfoFont* fi = 0;
  XFontStruct* f = 0;
  int i = 0;
  XChar2b* mb = 0;
  XChar2b* p = 0;
  int len = 0;
  XUChar c;
  int code = 0;
  int codeOld = 0;
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  *direction_return = FontLeftToRight;
  
  *font_ascent_return  = 0;
  *font_descent_return = 0;
  
  overall_return->lbearing = 0;
  overall_return->rbearing = 0;
  overall_return->ascent   = 0;
  overall_return->descent  = 0;
  overall_return->width    = 0;
  
  fn = XUGetFontName(display, font_struct);
  fi = XUGetInfoFont(display, fn);
  XFree(fn);
  
  if(nchars < 0) nchars = XUStrLen(string);
  p = mb = (XChar2b*)malloc(sizeof(XChar2b) * nchars);
  for(i = 0; i <= nchars; i++) {
    codeOld = code;
    if(i == nchars) {
      code = XU_FONT_NONE;
    } else {
      c = *string;
      string++;
      code = XUQueryChar(&c, display, fi);
    }
    if(len && code != codeOld) {
      f = fi->font[codeOld];
      if(!f) f = fi->font[XU_FONT_ISO8859(1)];
      if(*font_ascent_return < f->ascent) {
        *font_ascent_return = f->ascent;
      }
      if(*font_descent_return < f->descent) {
        *font_descent_return = f->descent;
      }
      XTextExtents16(f, mb, len, &dir, &ascent, &descent, &overAll);
      if(!overall_return->width) {
        overall_return->lbearing = overAll.lbearing;
      }
      overall_return->rbearing = overall_return->width + overAll.rbearing;
      if(overall_return->ascent < overAll.ascent ) {
        overall_return->ascent = overAll.ascent;
      }
      if(overall_return->descent < overAll.descent) {
        overall_return->descent = overAll.descent;
      }
      overall_return->width += overAll.width;
      p = mb;
      len = 0;
    }
    if(i < nchars) {
      if(fi->font[code]) {
        p->byte1 = c >> 8;
        p->byte2 = c & 0xff;
      } else {
        p->byte1 = 0;
        p->byte2 = '?';
      }
      p++;
      len++;
    }
  }
  free(mb);
}


int XUCharWidth(Display* display, XFontStruct* font, XUChar ch)
{
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  XUTextExtents(
    display, font, &ch, 1, &dir, &ascent, &descent, &overAll
  );
  return overAll.width;
}


int XUTextWidth(Display* display, XFontStruct* font, const XUChar* text, int length)
{
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  XUTextExtents(
    display, font, text, length, &dir, &ascent, &descent, &overAll
  );
  return overAll.width;
}


void XUutf8TextExtents(Display* display, XFontStruct* font_struct, const char* string, int nchars, int* direction_return, int* font_ascent_return, int* font_descent_return, XCharStruct* overall_return)
{
  XUChar* ustr = 0;
  int len = 0;
  
  if(nchars < 0) nchars = strlen(string);
  ustr = (XUChar*)malloc(sizeof(XUChar) * nchars);
  len = XUutf8Encode(ustr, nchars, string, nchars);
  XUTextExtents(
    display, font_struct, ustr, len,
    direction_return,
    font_ascent_return,
    font_descent_return,
    overall_return
  );
  free(ustr);
}


int XUutf8CharWidth(Display* display, XFontStruct* font, const char* text, int length)
{
  XUChar ch;
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  ch = XUutf8CharEncode(text, length);
  XUTextExtents(
    display, font, &ch, 1, &dir, &ascent, &descent, &overAll
  );
  return overAll.width;
}


int XUutf8TextWidth(Display* display, XFontStruct* font, const char* text, int length)
{
  XCharStruct overAll;
  int dir = 0;
  int ascent = 0;
  int descent = 0;
  
  XUutf8TextExtents(
    display, font, text, length, &dir, &ascent, &descent, &overAll
  );
  return overAll.width;
}


char* XUQueryFont(char* dest, int max, Display* display, const char* family, int pixel, Bool bold, Bool italic)
{
  const char* p = 0;
  Bool flgFoundry;
  char ideal[256];
  char query[256];
  const char* qFont = 0;
  const char* qBold = 0;
  const char* qItalic = 0;
  char qPixel[16];
  int count = 0;
  char** fonts = 0;
  
  if(bold) {
    qBold = "bold";
  } else {
    qBold = "medium";
  }
  if(italic) {
    qItalic = "o";
  } else {
    qItalic = "r";
  }
  snprintf(qPixel, 16, "%d", pixel);
  
  flgFoundry = False;
  for(p = family; *p; p++) {
    if(*p == '-') flgFoundry = True;
  }
  if(flgFoundry) {
    snprintf(
      ideal, 256,
      "-%s-%s-%s-*-*-%s-*-*-*-*-*-%s",
      family, qBold, qItalic, qPixel, "iso8859-1"
    );
    qFont = "-%s-%s-%s-*-%s-*-%s";
  } else {
    snprintf(
      ideal, 256,
      "-*-%s-%s-%s-*-*-%s-*-*-*-*-*-%s",
      family, qBold, qItalic, qPixel, "iso8859-1"
    );
    qFont = "-*-%s-%s-%s-*-%s-*-%s";
  }
  
  snprintf(query, 256, qFont, family, qBold, qItalic, qPixel, "iso8859-1");
  fonts = XListFonts(display, query, 8, &count);
  if(!fonts) {
    snprintf(query, 256, qFont, family, qBold, "*", qPixel, "iso8859-1");
    fonts = XListFonts(display, query, 32, &count);
  }
  if(!fonts) {
    snprintf(query, 256, qFont, family, "*", "*", qPixel, "iso8859-1");
    fonts = XListFonts(display, query, 64, &count);
  }
  if(!fonts) {
    snprintf(query, 256, qFont, family, qBold, qItalic, "*", "iso8859-1");
    fonts = XListFonts(display, query, 128, &count);
  }
  if(!fonts) {
    snprintf(query, 256, qFont, family, qBold, "*", "*", "iso8859-1");
    fonts = XListFonts(display, query, 128, &count);
  }
  if(!fonts) {
    snprintf(query, 256, qFont, family, "*", "*", "*", "iso8859-1");
    fonts = XListFonts(display, query, 128, &count);
  }
  if(!fonts) {
    snprintf(query, 256, qFont, family, "*", "*", "*", "*");
    fonts = XListFonts(display, query, 128, &count);
  }
  if(!fonts) return NULL;
  
  if(count == 1) {
    strncpy(dest, fonts[0], max);
  } else {
    XUCompareFonts(dest, max, ideal, fonts, count);
  }
  XFreeFontNames(fonts);
  dest[max - 1] = 0;
  /* XUDebug("%s\n%s", ideal, dest); */
  return dest;
}


XFontStruct* XULoadQueryFont(Display* display, const char* family, int pixel, Bool bold, Bool italic)
{
  char font[256];
  
  if(!XUQueryFont(font, 256, display, family, pixel, bold, italic)) {
    return NULL;
  }
  return XLoadQueryFont(display, font);
}


char* XUSearchFont(char* dest, int max, Display* display, const char* font, int charset)
{
  XUInfoFont* f = 0;
  char buf[256];
  char* sep[14];
  char query[256];
  int count = 0;
  char** fonts = 0;
  
  if(charset < 1 || charset > XU_FONT_COUNT || !xu_charset[charset]) {
    return NULL;
  }
  
  f = XUGetInfoFont(display, font);
  if(f->none[charset]) return NULL;
  if(f->fontName[charset]) {
    strncpy(dest, f->fontName[charset], max);
    return dest;
  }
  
  strncpy(buf, font, 256);
  if(!XUSplitFontName(buf, sep)) {
    f->none[charset] = True;
    return NULL;
  }
  
  snprintf(
    query, max,
    "-*-%s-*-%s-%s-%s-%s-%s-*-%s",
    sep[1], sep[6], sep[7], sep[8], sep[9], sep[10],
    xu_charset[charset]
  );
  fonts = XListFonts(display, query, 32, &count);
  if(!fonts) {
    snprintf(
      query, max,
      "-*-%s-*-%s",
      sep[1], xu_charset[charset]
    );
    fonts = XListFonts(display, query, 64, &count);
  }
  if(!fonts) {
    snprintf(
      query, max,
      "-*-%s-%s-%s-%s-%s-%s-%s-%s-*-%s",
      sep[2], sep[3], sep[4], sep[5],
      sep[6], sep[7], sep[8], sep[9],
      xu_charset[charset]
    );
    fonts = XListFonts(display, query, 32, &count);
  }
  if(!fonts) {
    snprintf(
      query, max,
      "-*-%s-%s-%s-%s-*-%s",
      sep[6], sep[7], sep[8], sep[9],
      xu_charset[charset]
    );
    fonts = XListFonts(display, query, 64, &count);
  }
  if(!fonts) {
    snprintf(
      query, max,
      "-*-%s",
      xu_charset[charset]
    );
    fonts = XListFonts(display, query, 128, &count);
  }
  if(!fonts) {
    f->none[charset] = True;
    return NULL;
  }
  
  if(count == 1) {
    strncpy(dest, fonts[0], max);
  } else {
    XUCompareFonts(dest, max, font, fonts, count);
  }
  XFreeFontNames(fonts);
  dest[max - 1] = 0;
  
  f->fontName[charset] = (char*)malloc(sizeof(char) * (strlen(dest) + 1));
  strcpy(f->fontName[charset], dest);
  /* XUDebug("%s\n%s", font, dest); */
  return dest;
}


XFontStruct* XULoadSearchFont(Display* display, const char* font, int charset)
{
  XUInfoFont* f = 0;
  char buf[256];
  
  if(charset < 1 || charset > XU_FONT_COUNT || !xu_charset[charset]) {
    return NULL;
  }
  
  f = XUGetInfoFont(display, font);
  if(f->none[charset]) return NULL;
  if(f->font[charset]) return f->font[charset];
  
  if(!XUSearchFont(buf, 256, display, font, charset)) {
    return NULL;
  }
  
  f->font[charset] = XLoadQueryFont(display, buf);
  return f->font[charset];
}


/**
  0:fndry(adobe), 1:fmly(helvetica),
  2:wght(medium), 3:slant(r), 4:sWdth(normal), 5:adstyl(),
  6:pxlsz(14), 7:ptSz(140), 8:resx(75), 9:resy(75), 10:spc(p),
  11:avgWdth(77),
  12:rgstry(iso8859), 13:encdng(1)
*/

char* XUCompareFonts(char* dest, int max, const char* font, char* fonts[], int count)
{
  char buf1[256];
  char* sep1[14];
  char buf2[256];
  char* sep2[14];
  int v = 0;
  int s1a = 0;
  int s1b = 0;
  int s2 = 0;
  int sz = 0;
  int best = -1;
  int no = -1;
  int i = 0;
  
  strncpy(buf1, font, 256);
  if(!XUSplitFontName(buf1, sep1)) return NULL;
  
  s1a = atoi(sep1[6]);
  s1b = atoi(sep1[7]);
  for(i = 0; i < count; i++) {
    strncpy(buf2, fonts[i], 256);
    if(!XUSplitFontName(buf2, sep2)) continue;
    v = 0;
    if(!strcmp(sep1[3], sep2[3])) v += 400;
    if(!strcmp(sep1[2], sep2[2])) v += 200;
    if(!strcmp(sep1[1], sep2[1])) v += 100;
    if(s1a) {
      s2 = atoi(sep2[6]);
      sz = s1a - s2;
    } else {
      s2 = atoi(sep2[7]);
      sz = (int)((s1b - s2) / 10);
    }
    if(sz <  0) sz = 8 - sz;
    if(sz > 98) sz = 98;
    sz = 98 - sz;
    if(sz == 98) sz = 99;
    if(s2 == 0 && atoi(sep2[8]) == 0) sz = 98;
    v += sz;
    if(best < v) {
      best = v;
      no   = i;
    }
  }
  if(no < 0) return NULL;
  
  strncpy(dest, fonts[no], max);
  strncpy(buf2, fonts[no], 256);
  if(!XUSplitFontName(buf2, sep2)) return dest;
  if(atoi(sep2[6]) != 0) return dest;
  
  if(s1a) {
    snprintf(
      dest, max,
      "-%s-%s-%s-%s-%s-%s-%d-0-0-0-%s-0-%s-%s",
      sep2[0],
      sep2[1],
      sep2[2],
      sep2[3],
      sep2[4],
      sep2[5],
      s1a,
      sep2[10],
      sep2[12],
      sep2[13]
    );
  } else {
    snprintf(
      dest, max,
      "-%s-%s-%s-%s-%s-%s-0-%d-0-0-%s-0-%s-%s",
      sep2[0],
      sep2[1],
      sep2[2],
      sep2[3],
      sep2[4],
      sep2[5],
      s1b,
      sep2[10],
      sep2[12],
      sep2[13]
    );
  }
  return dest;
}


Bool XUSplitFontName(char* font, char* sep[])
{
  char* p = 0;
  int i = 0;
  
  for(p = font, i = 0; *p; p++) {
    if('A' <= *p && *p <= 'Z') *p += 32;
    if(*p != '-') continue;
    *p = 0;
    sep[i] = p + 1;
    i++;
  }
  if(i == 14) return True;
  return False;
}


char* XUGetFontName(Display* display, XFontStruct* font)
{
  XUInfoDisplay* d = 0;
  unsigned long fn;
  
  d = XUGetInfoDisplay(display);
  if(!XGetFontProperty(font, d->atmFont, &fn)) return NULL;
  return XGetAtomName(display, fn);
}


char* XUGetFontSet(char* dest, int max, const char* separator, Display* display, const char* font)
{
  XUInfoFont* f = 0;
  char* p = 0;
  int sl = 0;
  int i = 0;
  
  if(!xu_fontset_count) return NULL;
  
  f = XUGetInfoFont(display, font);
  if(f->fontSetName) {
    strncpy(dest, f->fontSetName, max);
    return dest;
  }
  
  p = dest;
  sl = strlen(separator);
  for(i = 0; i < xu_fontset_count; i++) {
    if(i > 0) {
      if(max < sl) return NULL;
      strcpy(p, separator);
      p += sl;
      max -= sl;
    }
    if(!XUSearchFont(p, max, display, font, xu_fontset_encoding[i])) {
      return NULL;
    }
    while(*p) {
      p++;
      max--;
      if(max < 1) return NULL;
    }
  }
  
  f->fontSetName = (char*)malloc(sizeof(char) * (strlen(dest) + 1));
  strcpy(f->fontSetName, dest);
  return dest;
}


XFontSet XUCreateFontSet(Display* display, XFontStruct* font)
{
  XFontSet ret;
  char* fn = 0;
  XUInfoFont* f = 0;
  char fs[512];
  char* fsret = 0;
  char** missList = 0;
  int missCount = 0;
  char* defStr = 0;
  
  if(!xu_fontset_count) return NULL;
  
  fn = XUGetFontName(display, font);
  if(!fn) return NULL;
  
  f = XUGetInfoFont(display, fn);
  if(f->fontSet) return f->fontSet;
  
  fsret = XUGetFontSet(fs, 512, ",", display, fn);
  XFree(fn);
  if(!fsret) return NULL;
  
  ret
  = XCreateFontSet(
      display, fs, &missList, &missCount, &defStr
    );
  if(missCount > 0) XFreeStringList(missList);
  
  f->fontSet = ret;
  return ret;
}


Bool XUIMOpen(Display* display)
{
  XUInfoDisplay* d = 0;
  XIM xim;
  XIMStyles* st = 0;
  XIMStyle* xs = 0;
  Bool ok;
  int i = 0;
  
  if(!xu_fontset_count) return False;
  
  d = XUGetInfoDisplay(display);
  
  xim = XOpenIM(display, NULL, NULL, NULL);
  if(!xim) {
    xu_fontset_count = 0;
    return False;
  }
  
  XGetIMValues(xim, XNQueryInputStyle, &st, NULL);
  xs = st->supported_styles;
  ok = False;
  for(i = 0; i < st->count_styles; i++, xs++){
    if(*xs == XIMPreeditPosition | XIMStatusNothing) ok = True;
  }
  XFree(st);
  
  if(!ok){
    XCloseIM(xim);
    xim = NULL;
    return False;
  }
  
  d->xim = xim;
  return True;
}


Bool XUIMSet(Display* display, Window window, XFontStruct* font, int left, int top, int width, int height, int x, int y)
{
  XUInfoWindow* w = 0;
  XFontSet fs;
  XVaNestedList patr;
  XPoint p;
  XRectangle pre;
  
  if(!xu_fontset_count) return False;
  
  fs = NULL;
  w = XUGetInfoWindow(display, window);
  if(font && font != w->font) {
    fs = XUCreateFontSet(display, font);
    if(fs == w->fontSet) {
      fs = NULL;
    } else {
      w->fontSet = fs;
    }
    w->font = font;
  }
  if(!w->font) return False;
  
  if(!w->xic) {
    w->left   = left;
    w->top    = top;
    w->width  = width;
    w->height = height;
    w->xicX   = x;
    w->xicY   = y;
    return XUIMCreateIC(display, window, w);
  }
  
  if(
       w->left  == left  && w->top    == top
    && w->width == width && w->height == height
    && w->xicX  == x     && w->xicY   == y
    && w->xicFocus && !fs
  ) {
    return True;
  }
  
  w->xicX = p.x = x;
  w->xicY = p.y = y;
  w->left   = pre.x      = left;
  w->top    = pre.y      = top;
  w->width  = pre.width  = width;
  w->height = pre.height = height;
  
  patr
  = XVaCreateNestedList(
      0,
      XNFontSet     , w->fontSet,
      XNSpotLocation, &p,
      XNArea        , &pre,
      NULL
    );
  XSetICValues(
    w->xic,
    XNPreeditAttributes, patr,
    NULL
  );
  XFree(patr);
  
  if(!w->xicFocus) {
    XSetICFocus(w->xic);
    w->xicFocus = True;
  }
  
  return True;
}


Bool XUIMCreateIC(Display* display, Window window, XUInfoWindow* w)
{
  XUInfoDisplay* d = 0;
  XVaNestedList patr;
  XPoint p;
  XRectangle pre;
  XVaNestedList satr;
  XIMStyle st;
  
  if(!w->fontSet) return False;
  
  d = XUGetInfoDisplay(display);
  
  p.x = w->xicX;
  p.y = w->xicY;
  
  pre.x      = w->left;
  pre.y      = w->top;
  pre.width  = w->width;
  pre.height = w->height;
  
  patr
  = XVaCreateNestedList(
      0,
      XNFontSet     , w->fontSet,
      XNSpotLocation, &p        ,
      XNArea        , &pre      ,
      NULL
    );
  satr
  = XVaCreateNestedList(
      0,
      XNFontSet, w->fontSet,
      NULL
    );
  st = XIMPreeditPosition | XIMStatusNothing;
  w->xic = XCreateIC(
    d->xim,
    XNInputStyle,        st,
    XNClientWindow,      window,
    XNFocusWindow,       window,
    XNPreeditAttributes, patr,
    XNStatusAttributes,  satr,
    NULL
  );
  XFree(patr);
  XFree(satr);
  if(!w->xic) return False;
  
  XSetICFocus(w->xic);
  w->xicFocus = True;
  return True;
}


void XUIMUnset(Display* display, Window window)
{
  XUInfoWindow* w = 0;
  
  if(!xu_fontset_count) return;
  
  w = XUGetInfoWindow(display, window);
  if(w->xic) {
    XUnsetICFocus(w->xic);
    w->xicFocus = False;
  }
}


int XULookupString(Display* display, Window window, XEvent* event_struct, XUChar* buffer_return, int bytes_buffer, KeySym* keysym_return, XComposeStatus* status_in_out)
{
  XUInfoWindow* w = 0;
  Status st;
  char buf[256];
  int len = 0;
  int len2 = 0;
  
  w = XUGetInfoWindow(display, window);
  
  if(event_struct->type == KeyPress && w->xic) {
    len
    = XmbLookupString(
        w->xic, &event_struct->xkey, buf, 240,
        keysym_return, &st
      );
    if(st != XLookupKeySym && st != XLookupBoth) *keysym_return = 0;
  } else {
    len
    = XLookupString(
        &event_struct->xkey, buf, 240,
        keysym_return, status_in_out
      );
  }
  
  len2 = XUEncode(buffer_return, bytes_buffer, buf, len, XU_CONV_LOCALE);
  if(len2 < bytes_buffer) return len2;
  return bytes_buffer;
}


int XUutf8LookupString(Display* display, Window window, XEvent* event_struct, char* buffer_return, int bytes_buffer, KeySym* keysym_return, XComposeStatus* status_in_out)
{
  XUChar buf[256];
  int len = 0;
  
  len
  = XULookupString(
      display, window, event_struct,
      buf, 256, keysym_return, status_in_out
    );
  
  return XUutf8Decode(buffer_return, bytes_buffer, buf, len);
}


void XUUISearch(Display* display, Window window)
{
  XUInfoDisplay* d = 0;
  Window r;
  Window p;
  Window* c = 0;
  unsigned int n;
  int i = 0;
  XEvent e;
  
  if(!XQueryTree(display, DefaultRootWindow(display), &r, &p, &c, &n)) {
    return;
  }
  
  d = XUGetInfoDisplay(display);
  for(i = 0; i < (int)n; i++){
    e.xclient.type         = ClientMessage;
    e.xclient.display      = display;
    e.xclient.window       = window;
    e.xclient.message_type = d->isSrv;
    e.xclient.format       = 32;
    e.xclient.data.l[0]    = 0;
    XSendEvent(display, c[i], False, 0, &e);
    XFlush(display);
  }
  
  if(c) XFree(c);
}


void XUUISet(Display* display, const XEvent* event)
{
  XUInfoDisplay* d = 0;
  
  d = XUGetInfoDisplay(display);
  d->uinput = event->xclient.window;
}


Bool XUUISend(Display* display, Window window, const XEvent* event)
{
  XUInfoDisplay* d = 0;
  XEvent e;
  
  d = XUGetInfoDisplay(display);
  if(d->uinput == None) return False;
  
  e = *event;
  e.xclient.window = window;
  XSendEvent(display, d->uinput, False, 0, &e);
  XFlush(display);
  return True;
}


Bool XUUICancel(Display* display)
{
  XUInfoDisplay* d = 0;
  XEvent e;
  
  d = XUGetInfoDisplay(display);
  if(d->uinput == None) return False;
  
  e.xclient.type         = ClientMessage;
  e.xclient.display      = display;
  e.xclient.window       = 0;
  e.xclient.message_type = d->cancel;
  e.xclient.format       = 32;
  e.xclient.data.l[0]    = 0;
  XSendEvent(display, d->uinput, False, 0, &e);
  XFlush(display);
  return True;
}


XUInfoDisplay* XUGetInfoDisplay(Display* display)
{
  XUInfoDisplay* d = 0;
  int i = 0;
  
  /* search */
  for(d = xu_info_display; d; d = d->next) {
    if(d->display == display) {
      /* found */
      if(d != xu_info_display) {
        /* move first */
        if(d->previous) d->previous->next = d->next;
        if(d->next    ) d->next->previous = d->previous;
        d->previous = NULL;
        d->next = xu_info_display;
        d->next->previous = d;
        xu_info_display = d;
      }
      return d;
    }
  }
  
  /* create */
  d = (XUInfoDisplay*)malloc(sizeof(XUInfoDisplay));
  d->previous = NULL;
  d->next     = xu_info_display;
  d->display  = display;
  d->xim      = NULL;
  d->uinput   = None;
  d->window   = NULL;
  d->isSrv    = XInternAtom(display, "IsUnicodeInputServer" , False);
  d->setSrv   = XInternAtom(display, "SetUnicodeInputServer", False);
  d->input    = XInternAtom(display, "UnicodeInput"         , False);
  d->cancel   = XInternAtom(display, "UnicodeInputCancel"   , False);
  d->atmFont  = XInternAtom(display, "FONT"                 , False);
  for(i = 0; i < 256; i++) d->font[i] = NULL;
  if(d->next) d->next->previous = d;
  xu_info_display = d;
  return d;
}


XUInfoWindow* XUGetInfoWindow(Display* display, Window window)
{
  XUInfoDisplay* d = 0;
  XUInfoWindow* w = 0;
  
  /* search */
  d = XUGetInfoDisplay(display);
  for(w = d->window; w; w = w->next) {
    if(w->window == window) {
      /* found */
      if(w != d->window) {
        /* move first */
        w->previous->next = w->next;
        if(w->next) w->next->previous = w->previous;
        w->previous = NULL;
        w->next = d->window;
        w->next->previous = w;
        d->window = w;
      }
      return w;
    }
  }
  
  /* create */
  w = (XUInfoWindow*)malloc(sizeof(XUInfoWindow));
  w->previous = NULL;
  w->next     = d->window;
  w->window   = window;
  w->left     = 0;
  w->top      = 0;
  w->width    = 0;
  w->height   = 0;
  w->font     = NULL;
  w->fontSet  = NULL;
  w->xic      = NULL;
  w->xicX     = 0;
  w->xicY     = 0;
  if(w->next) w->next->previous = w;
  d->window = w;
  return w;
}


XUInfoFontSetting* XUGetInfoFontSetting(const char* family)
{
  XUInfoFontSetting* f = 0;
  
  /* search */
  for(f = xu_info_font_setting; f; f = f->next) {
    if(!strcmp(f->name, family)) {
      /* found */
      if(f != xu_info_font_setting) {
        /* move first */
        f->previous->next = f->next;
        if(f->next) f->next->previous = f->previous;
        f->previous = NULL;
        f->next = xu_info_font_setting;
        f->next->previous = f;
        xu_info_font_setting = f;
      }
      return f;
    }
  }
  return NULL;
}


XUInfoFont* XUGetInfoFont(Display* display, const char* font)
{
  XUInfoFont* f = 0;
  XUInfoDisplay* d = 0;
  const char* p1 = 0;
  char* p2 = 0;
  char fn[256];
  char c = 0;
  int hash = 0;
  int i = 0;
  
  /* hash */
  for(p1 = font, p2 = fn; *p1; p1++, p2++) {
    c = *p1;
    if('A' <= c && c <= 'Z') c += 32;
    *p2 = c;
    hash += (int)(unsigned char)c;
  }
  *p2 = 0;
  hash = hash & 0xff;
  
  /* search */
  d = XUGetInfoDisplay(display);
  for(f = d->font[hash]; f; f = f->next) {
    if(!strcmp(f->name, fn)) {
      /* found */
      if(f != d->font[hash]) {
        /* move first */
        f->previous->next = f->next;
        if(f->next) f->next->previous = f->previous;
        f->previous = NULL;
        f->next = d->font[hash];
        f->next->previous = f;
        d->font[hash] = f;
      }
      return f;
    }
  }
  
  /* create */
  f = (XUInfoFont*)malloc(sizeof(XUInfoFont));
  f->previous = NULL;
  f->next     = d->font[hash];
  f->name = (char*)malloc(sizeof(char) * (strlen(fn) + 1));
  strcpy(f->name, fn);
  f->count = 0;
  for(i = 0; i < XU_FONT_COUNT; i++) {
    f->order[i] = XU_FONT_NONE;
    f->none [i] = False;
    f->font [i] = NULL;
    if(i == XU_FONT_ISO8859(1)) {
      f->fontName[i] = (char*)malloc(sizeof(char) * (strlen(font) + 1));
      strcpy(f->fontName[i], font);
    } else {
      f->fontName[i] = NULL;
    }
  }
  f->fontSetName = NULL;
  f->fontSet = NULL;
  if(f->next) f->next->previous = f;
  d->font[hash] = f;
  XUSearchOrder(display, f);
  XULoadSearchFont(display, fn, XU_FONT_ISO8859(1));
  if(!f->none[XU_FONT_UNICODE] && !f->fontName[XU_FONT_UNICODE]) {
    XULoadSearchFont(display, fn, XU_FONT_UNICODE);
  }
  return f;
}


void XUSearchOrder(Display* display, XUInfoFont* f)
{
  char buf[256];
  char* sep[14];
  char query[256];
  Bool prfJa;
  Bool prfKo;
  Bool prfZh;
  Bool prfBig5;
  
  strncpy(buf, f->name, 256);
  if(!XUSplitFontName(buf, sep)) return;
  
  snprintf(
    query, 256,
    "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-*",
    sep[0], sep[1], sep[2], sep[3], sep[4], sep[5],
    sep[6], sep[7], sep[8], sep[9], sep[10]
  );
  XUAddFonts(display, f, query);
  
  snprintf(query, 256, "%s-%s", sep[0], sep[1]);
  if(XUApplySetting(display, f, query )) return;
  if(XUApplySetting(display, f, sep[1])) return;
  if(XUApplySetting(display, f, "*"   )) return;
  
  if(!f->fontName[XU_FONT_UNICODE]) {
    XUAddCharset(f, XU_FONT_ISO8859(1));
    XUAddCharset(f, XU_FONT_ISO8859(2));
    XUAddCharset(f, XU_FONT_KOI8R     );
  }
  
  snprintf(query, 256, "-%s-%s-*", sep[0], sep[1]);
  prfJa   = XUFontExists(display, query, XU_FONT_JIS0208);
  prfKo   = XUFontExists(display, query, XU_FONT_KSC5601);
  prfZh   = XUFontExists(display, query, XU_FONT_GB2312 );
  prfBig5 = XUFontExists(display, query, XU_FONT_BIG5   );
  if(!prfJa && !prfKo && !prfZh && !prfBig5) {
    snprintf(query, 256, "-*-%s-*", sep[1]);
    prfJa   = XUFontExists(display, query, XU_FONT_JIS0208);
    prfKo   = XUFontExists(display, query, XU_FONT_KSC5601);
    prfZh   = XUFontExists(display, query, XU_FONT_GB2312 );
    prfBig5 = XUFontExists(display, query, XU_FONT_BIG5   );
  }
  if(!prfJa && !prfKo && !prfZh && !prfBig5) {
    if(!strcmp (xu_language, "ko"  )) prfKo   = True;
    if(!strncmp(xu_lang, "zh_TW", 5)) prfBig5 = True;
    if(!strcmp (xu_language, "zh"  )) prfZh   = True;
  }
  if(prfJa && !prfKo && !prfZh && !prfBig5) {
    XUAddCharset(f, XU_FONT_JIS0208);
  } else if(!prfJa && prfKo && !prfZh && !prfBig5) {
    XUAddCharset(f, XU_FONT_KSC5601);
  } else if(!prfJa && !prfKo && prfZh && !prfBig5) {
    XUAddCharset(f, XU_FONT_GB2312);
  } else if(!prfJa && !prfKo && !prfZh && prfBig5) {
    XUAddCharset(f, XU_FONT_BIG5);
  }
  XUAddCharset(f, XU_FONT_JIS0208);
  XUAddCharset(f, XU_FONT_GB2312 );
  XUAddCharset(f, XU_FONT_BIG5   );
  XUAddCharset(f, XU_FONT_KSC5601);
}


Bool XUApplySetting(Display* display, XUInfoFont* f, const char* family)
{
  XUInfoFontSetting* fs = 0;
  int i = 0;
  int charset = 0;
  char** fonts = 0;
  int count = 0;
  char dest[256];
  Bool useUnicode;
  
  fs = XUGetInfoFontSetting(family);
  if(!fs) return False;
  
  if(f->fontName[XU_FONT_UNICODE]) {
    useUnicode = True;
  } else {
    useUnicode = False;
  }
  
  for(i = 0; i < fs->count; i++) {
    charset = fs->order[i];
    fonts = XListFonts(display, fs->font[charset], 64, &count);
    if(fonts) {
      XUCompareFonts(dest, 256, f->name, fonts, count);
      XFreeFontNames(fonts);
    } else {
      if(!XUSearchFont(dest, 256, display, f->name, charset)) continue;
    }
    dest[255] = 0;
    if(f->fontName[charset]) free(f->fontName[charset]);
    f->fontName[charset] = (char*)malloc(sizeof(char) * (strlen(dest) + 1));
    strcpy(f->fontName[charset], dest);
    XUAddCharset(f, charset);
  }
  
  if(!useUnicode) {
    XUInsertCharset(f, XU_FONT_KOI8R     );
    XUInsertCharset(f, XU_FONT_ISO8859(2));
    XUInsertCharset(f, XU_FONT_ISO8859(1));
  }
  
  XUAddCharset(f, XU_FONT_JIS0208);
  XUAddCharset(f, XU_FONT_GB2312 );
  XUAddCharset(f, XU_FONT_BIG5   );
  XUAddCharset(f, XU_FONT_KSC5601);
  
  return True;
}


void XUAddFonts(Display* display, XUInfoFont* f, const char* query)
{
  char** fonts = 0;
  int count = 0;
  int i = 0;
  int j = 0;
  char buf[256];
  char* sep[14];
  char encoding[32];
  
  fonts = XListFonts(display, query, 32, &count);
  if(!fonts) return;
  
  for(i = 0; i < count; i++) {
    strncpy(buf, fonts[i], 256);
    if(!XUSplitFontName(buf, sep)) continue;
    snprintf(encoding, 32, "%s-%s", sep[12], sep[13]);
    for(j = 0; j < XU_FONT_COUNT; j++) {
      if(!xu_charset[j] || strcmp(encoding, xu_charset[j])) continue;
      if(f->fontName[j]) free(f->fontName[j]);
      f->fontName[j] = (char*)malloc(sizeof(char) * (strlen(fonts[i]) + 1));
      strcpy(f->fontName[j], fonts[i]);
      break;
    }
  }
  
  XFreeFontNames(fonts);
}


Bool XUFontExists(Display* display, const char* font, int charset)
{
  char query[256];
  char** fonts = 0;
  int count = 0;
  
  if(charset < 1 || charset > XU_FONT_COUNT || !xu_charset[charset]) {
    return False;
  }
  
  snprintf(query, 256, "%s-%s", font, xu_charset[charset]);
  fonts = XListFonts(display, query, 1, &count);
  if(!fonts) return False;
  
  XFreeFontNames(fonts);
  return True;
}


void XUAddCharset(XUInfoFont* f, int charset)
{
  int i = 0;
  
  for(i = 0; i < f->count; i++) {
    if(f->order[i] == charset) return;
  }
  f->order[f->count] = charset;
  f->count++;
}


void XUInsertCharset(XUInfoFont* f, int charset)
{
  int i = 0;
  
  for(i = 0; i < f->count; i++) {
    if(f->order[i] == charset) return;
  }
  for(i = f->count; i > 0; i--) {
    f->order[i] = f->order[i - 1];
  }
  f->order[0] = charset;
  f->count++;
}


void XUFreeInfoDisplay(Display* display)
{
  XUInfoDisplay* d = 0;
  XUInfoWindow* w = 0;
  XUInfoWindow* wn = 0;
  int i = 0;
  
  XUUICancel(display);
  
  /* search info */
  for(d = xu_info_display; d; d = d->next) {
    if(d->display == display) break;
  }
  if(!d) return;
  
  /* delete info */
  if(d->previous) d->previous->next = d->next;
  if(d->next    ) d->next->previous = d->previous;
  if(xu_info_display == d) xu_info_display = d->next;
  for(w = d->window; w; w = wn) {
    if(w->xic) XDestroyIC(w->xic);
    wn = w->next;
    free(w);
  }
  for(i = 0; i < 256; i++) {
    if(d->font[i]) XUFreeInfoFont(display, d->font[i]);
  }
  if(d->xim) XCloseIM(d->xim);
  free(d);
}


void XUFreeInfoWindow(Display* display, Window window)
{
  XUInfoDisplay* d = 0;
  XUInfoWindow* w = 0;
  
  XUUICancel(display);
  
  /* search info */
  d = XUGetInfoDisplay(display);
  for(w = d->window; w; w = w->next) {
    if(w->window == window) break;
  }
  if(!w) return;
  
  /* delete info */
  if(w->previous) w->previous->next = w->next;
  if(w->next    ) w->next->previous = w->previous;
  if(d->window == w) d->window = w->next;
  if(w->xic) XDestroyIC(w->xic);
  free(w);
}


void XUFreeInfoFont(Display* display, XUInfoFont* font)
{
  XUInfoFont* f = 0;
  XUInfoFont* fn = 0;
  int i = 0;
  
  if(!font) return;
  
  for(f = font; f; f = fn) {
    fn = f->next;
    free(f->name);
    for(i = 0; i < XU_FONT_COUNT; i++) {
      if(f->fontName[i]) free(f->fontName[i]);
      if(f->font[i]) XFreeFont(display, f->font[i]);
    }
    if(f->fontSetName) free(f->fontSetName);
    if(f->fontSet) XFreeFontSet(display, f->fontSet);
    free(f);
  }
}


/**
  Compound Text -> UCS-2
*/

int XUctEncode(XUChar* dest, int max, const char* text, int length)
{
  int ret = 0;
  int g0 = XU_FONT_ISO8859(1);
  int g1 = XU_FONT_ISO8859(1);
  int code;
  int iso[] = { 1, 2, 3, 4, 0, 7, 6, 8, 0, 0, 0, 5, 9 };
  int mb[] = { XU_FONT_GB2312, XU_FONT_JIS0208, XU_FONT_KSC5601 };
  int i;
  XUChar c1, c2, ch;
  
  for(i = 0; i < length; i++) {
    c1 = (XUChar)(unsigned char)text[i];
    if(c1 == 0x1b) {
      if(++i >= length) break;
      c1 = (XUChar)(unsigned char)text[i];
      if(c1 == '(') {
        if(++i >= length) break;
        c1 = (XUChar)(unsigned char)text[i];
        if(c1 == 'B') {
          g0 = XU_FONT_ISO8859(1);
          continue;
        } else if(c1 == 'J') {
          g0 = XU_FONT_JIS0201;
          continue;
        }
        g0 = XU_FONT_NONE;
      } else if(c1 == ')') {
        if(++i >= length) break;
        c1 = (XUChar)(unsigned char)text[i];
        if(c1 == 'I') {
          g1 = XU_FONT_JIS0201;
          continue;
        }
        g1 = XU_FONT_NONE;
      } else if(c1 == '-') {
        if(++i >= length) break;
        c1 = (XUChar)(unsigned char)text[i];
        if('A' <= c1 && c1 <= 'M' && iso[c1 - 'A']) {
          g1 = XU_FONT_ISO8859(iso[c1 - 'A']);
          continue;
        }
        g1 = XU_FONT_NONE;
      } else if(c1 == '$') {
        if(++i >= length) break;
        c1 = (XUChar)(unsigned char)text[i];
        if(c1 == '(') {
          if(++i >= length) break;
          c1 = (XUChar)(unsigned char)text[i];
          if('A' <= c1 && c1 <= 'C') {
            g0 = mb[c1 - 'A'];
            continue;
          } else if(c1 == '0') {
            g0 = XU_FONT_BIG5;
            continue;
          }
          g0 = XU_FONT_NONE;
        } else if(c1 == ')') {
          if(++i >= length) break;
          c1 = (XUChar)(unsigned char)text[i];
          if('A' <= c1 && c1 <= 'C') {
            g1 = mb[c1 - 'A'];
            continue;
          } else if(c1 == '0') {
            g1 = XU_FONT_BIG5;
            continue;
          }
          g1 = XU_FONT_NONE;
        }
      }
      continue;
    }
    ch = 0;
    code = 0;
    if(c1 < 0x80) {
      if(g0 == XU_FONT_NONE) {
        ch = '?';
      } else if(g0 == XU_FONT_ISO8859(1)) {
        ch = c1;
      } else if(g0 == XU_FONT_JIS0201) {
        if(c1 == '\\') {
          ch = 0x00a5;
        } else if(c1 == '~') {
          ch = 0x203e;
        } else {
          ch = c1;
        }
      } else {
        if(++i >= length) break;
        c2 = (XUChar)(unsigned char)text[i];
        if(c2 < 0x80) c2 += 0x80;
        code = g0;
        c1 += 0x80;
      }
    } else {
      if(g1 == XU_FONT_NONE) {
        ch = '?';
      } else if(g1 <= XU_FONT_ISO8859(15)) {
        ch = table_iso8859[g1 - XU_FONT_ISO8859(1)].data[c1 - 0x80];
      } else if(g1 == XU_FONT_JIS0201) {
        if(0xa1 <= c1 && c1 <= 0xdf) ch = c1 + 0xfec0;
      } else {
        if(++i >= length) break;
        c2 = (XUChar)(unsigned char)text[i];
        if(c2 < 0x80) c2 += 0x80;
        code = g1;
      }
    }
    if(code == XU_FONT_GB2312) {
      if(c1 < 0xa1 || c1 > 0xf7) ch = '?';
      if(c2 < 0xa1 || c2 > 0xfe) ch = '?';
      if(!ch) ch = table_gb2312[(c1 - 0xa1) * 94 + (c2 - 0xa1)];
    } else if(code == XU_FONT_JIS0208) {
      if(c1 < 0xa1 || c1 > 0xf4) ch = '?';
      if(c2 < 0xa1 || c2 > 0xfe) ch = '?';
      if(!ch) ch = table_jis0208[(c1 - 0xa1) * 94 + (c2 - 0xa1)];
    } else if(code == XU_FONT_KSC5601) {
      if(0x81 <= c1 && c1 <= 0xc8) {
        if(c2 < 0x41 || c2 > 0xfe) ch = '?';
        if(0x5a < c2 && c2 < 0x61) ch = '?';
        if(0x7a < c2 && c2 < 0x81) ch = '?';
        if(c2 > 0x7a) c2 -= 6;
        if(c2 > 0x5a) c2 -= 6;
        if(!ch) ch = table_ksc5601[(c1 - 0x81) * 178 + (c2 - 0x41)];
      } else if(0xca <= c1 && c1 <= 0xfd) {
        if(c2 < 0xa1 || c2 > 0xfe) ch = '?';
        if(!ch) ch = table_ksc5601_hanja[(c1 - 0xca) * 94 + (c2 - 0xa1)];
      } else {
        ch = '?';
      }
    } else if(code == XU_FONT_BIG5) {
      if(c1 < 0xa1 || c1 > 0xfe) ch = '?';
      if(c2 < 0xa1 || c2 > 0xfe) ch = '?';
      if(!ch) ch = table_big5[(c1 - 0xa1) * 94 + (c2 - 0xa1)];
    }
    if(ch && ret < max) {
      *dest = ch;
      dest++;
    }
    ret++;
  }
  
  if(ret < max) *dest = 0;
  return ret;
}


/**
  UCS-2 -> Compound Text
*/

int XUctDecode(char* dest, int max, const XUChar* text, int length)
{
  int ret = 0;
  int g0 = XU_FONT_ISO8859(1);
  int g1 = XU_FONT_ISO8859(1);
  int i, code;
  XUChar ch;
  const char* iso = "ABCDLGFHM";
  
  for(i = 0; i < length; i++) {
    ch = text[i];
    if(ch < 0x0080) {
      if(g0 != XU_FONT_ISO8859(1)) {
        if(ret++ < max) *(dest++) = '\x1b';
        if(ret++ < max) *(dest++) = '(';
        if(ret++ < max) *(dest++) = 'B';
        g0 = XU_FONT_ISO8859(1);
      }
      if(ret++ < max) *(dest++) = (char)ch;
      continue;
    }
    code = table_rev_iso8859[ch];
    if(code) {
      ch = code & 0x00ff;
      code = code >> 8;
      if(g1 != XU_FONT_ISO8859(code)) {
        if(ret++ < max) *(dest++) = '\x1b';
        if(ret++ < max) *(dest++) = '-';
        if(ret++ < max) *(dest++) = iso[code - 1];
        g1 = XU_FONT_ISO8859(code);
      }
      if(ret++ < max) *(dest++) = (char)ch;
      continue;
    }
    if(ch == 0x203e) {
      if(g0 != XU_FONT_JIS0201) {
        if(ret++ < max) *(dest++) = '\x1b';
        if(ret++ < max) *(dest++) = '(';
        if(ret++ < max) *(dest++) = 'J';
        g0 = XU_FONT_JIS0201;
      }
      if(ret++ < max) *(dest++) = '~';
      continue;
    }
    if(0xff61 <= ch && ch <= 0xff9f) {
      if(g1 != XU_FONT_JIS0201) {
        if(ret++ < max) *(dest++) = '\x1b';
        if(ret++ < max) *(dest++) = ')';
        if(ret++ < max) *(dest++) = 'I';
        g1 = XU_FONT_JIS0201;
      }
      if(ret++ < max) *(dest++) = (char)(ch - 0xfec0);
      continue;
    }
    if(xu_locale_encoding == XU_CONV_EUCCN && table_rev_gb2312[ch]) {
      code = XU_FONT_GB2312;
    } else if(xu_locale_encoding == XU_CONV_EUCKR && table_rev_ksc5601[ch]) {
      code = XU_FONT_KSC5601;
    } else if(table_rev_jis0208[ch]) {
      code = XU_FONT_JIS0208;
    } else if(xu_locale_encoding != XU_CONV_EUCCN && table_rev_gb2312[ch]) {
      code = XU_FONT_GB2312;
    } else if(xu_locale_encoding != XU_CONV_EUCKR && table_rev_ksc5601[ch]) {
      code = XU_FONT_KSC5601;
    }
    if(code == XU_FONT_GB2312 ) ch = table_rev_gb2312 [ch];
    if(code == XU_FONT_JIS0208) ch = table_rev_jis0208[ch];
    if(code == XU_FONT_KSC5601) {
      if((table_rev_ksc5601[ch] & 0x00ff) > 0x0080) {
        ch = table_rev_ksc5601[ch] - 0x8080;
      } else {
        ch = 0;
      }
    }
    if(code && ch) {
      if(g0 != code) {
        if(ret++ < max) *(dest++) = '\x1b';
        if(ret++ < max) *(dest++) = '$';
        if(ret++ < max) *(dest++) = '(';
        if(code == XU_FONT_GB2312  && ret++ < max) *(dest++) = 'A';
        if(code == XU_FONT_JIS0208 && ret++ < max) *(dest++) = 'B';
        if(code == XU_FONT_KSC5601 && ret++ < max) *(dest++) = 'C';
        g0 = code;
      }
      if(ret++ < max) *(dest++) = (char)(ch >> 8);
      if(ret++ < max) *(dest++) = (char)(ch & 0x00ff);
      continue;
    }
    if(g0 != XU_FONT_ISO8859(1)) {
      if(ret++ < max) *(dest++) = '\x1b';
      if(ret++ < max) *(dest++) = '(';
      if(ret++ < max) *(dest++) = 'B';
      g0 = XU_FONT_ISO8859(1);
    }
    if(ret++ < max) *(dest++) = '?';
  }
  
  if(ret < max) *dest = 0;
  return ret;
}
#endif


/**
  Other -> Unicode
*/

XUChar XUCharEncode(const char* text, int max, int code)
{
  XUChar ret = 0;
  XUChar c1;
  XUChar c2;
  XUChar num;
  
  if(max == 0 || !*text) return 0;
  
  if(code == XU_CONV_LOCALE) {
#ifdef WIN32
    MultiByteToWideChar(CP_ACP, 0, text, max, &ret, 1);
    return ret;
#else
    code = xu_locale_encoding;
#endif
  }
  if(code <= XU_CONV_NONE) return '?';
  if(code == XU_CONV_UTF8) return XUutf8CharEncode(text, max);
  
  c1 = (XUChar)(unsigned char)*text;
  if(c1 < 0x80) return c1;
  if(code <= XU_CONV_ISO8859(15)) {
    ret = table_iso8859[code - XU_CONV_ISO8859(1)].data[c1 - 0x80];
    if(ret) return ret;
    return '?';
  } else if(code == XU_CONV_KOI8R) {
    ret = table_koi8r[c1 - 0x80];
    if(ret) return ret;
    return '?';
  }
  if(code == XU_CONV_SJIS) {
    if(0xa1 <= c1 && c1 <= 0xdf) return c1 + 0xfec0;
  }
  if(max == 1) return '?';
  text++;
  
  c2 = (XUChar)(unsigned char)*text;
  if(!c2) return '?';
  if(code == XU_CONV_EUCJP) {
    if(c1 == 0x8e) return c2 + 0xfec0;
    if(c1 < 0xa1 || c1 > 0xf4) return '?';
    if(c2 < 0xa1 || c2 > 0xfe) return '?';
    ret = table_jis0208[(c1 - 0xa1) * 94 + (c2 - 0xa1)];
  } else if(code == XU_CONV_SJIS) {
    if(c1 == 0x80 || c1 == 0xa0 || c1 > 0xfc) return '?';
    if(c2  < 0x40 || c2 == 0x7f || c2 > 0xfc) return '?';
    if(c1 >= 0xe0) c1 -= 0x40;
    if(c2 > 0x7e) c2--;
    num = (c1 - 0x81) * 188 + (c2 - 0x40);
    if(num < 7896) ret = table_jis0208[num];
  } else if(code == XU_CONV_EUCKR) {
    if(0x81 <= c1 && c1 <= 0xc8) {
      if(c2 < 0x41 || c2 > 0xfe) return '?';
      if(0x5a < c2 && c2 < 0x61) return '?';
      if(0x7a < c2 && c2 < 0x81) return '?';
      if(c2 > 0x7a) c2 -= 6;
      if(c2 > 0x5a) c2 -= 6;
      ret = table_ksc5601[(c1 - 0x81) * 178 + (c2 - 0x41)];
    } else if(0xca <= c1 && c1 <= 0xfd) {
      if(c2 < 0xa1 || c2 > 0xfe) return '?';
      ret = table_ksc5601_hanja[(c1 - 0xca) * 94 + (c2 - 0xa1)];
    }
  } else if(code == XU_CONV_EUCCN) {
    if(c1 < 0xa1 || c1 > 0xf7) return '?';
    if(c2 < 0xa1 || c2 > 0xfe) return '?';
    ret = table_gb2312[(c1 - 0xa1) * 94 + (c2 - 0xa1)];
  } else if(code == XU_CONV_BIG5) {
    if(c1 < 0xa1 || c1 > 0xf9) return '?';
    if(c2 < 0x40 || c2 > 0xfe) return '?';
    if(0x7e < c2 && c2 < 0xa1) return '?';
    if(c2 > 0x7e) c2 -= 0x22;
    ret = table_big5[(c1 - 0xa1) * 157 + (c2 - 0x40)];
  }
  if(ret) return ret;
  
  return '?';
}


/**
  Unicode -> Other
*/

int XUCharDecode(char* dest, int max, XUChar ch, int code)
{
  XUChar ret = 0;
  XUChar r1;
  XUChar r2;
  XUChar i;
  XUChar* table;
  
  if(code == XU_CONV_LOCALE) {
#ifdef WIN32
    WideCharToMultiByte(CP_ACP, 0, &ch, 1, dest, max, NULL, NULL);
    return WideCharToMultiByte(CP_ACP, 0, &ch, 1, NULL, 0, NULL, NULL);
#else
    code = xu_locale_encoding;
#endif
  } else if(code == XU_CONV_NONE) {
    if(max >= 1) dest[0] = '?';
    return 1;
  } else if(code == XU_CONV_UTF8) {
    return XUutf8CharDecode(dest, max, ch);
  }
  
  if(ch < 0x0080) {
    if(max >= 1) dest[0] = (char)ch;
    return 1;
  }
  if(code <= XU_CONV_ISO8859(15)) {
    if(ch < 0x00a1) {
      ret = ch;
    } else {
      table = table_iso8859[code - XU_CONV_ISO8859(1)].data;
      for(i = 0x21; i < 0x80; i++) {
        if(ch == table[i]) {
          ret = i + 0x80;
          break;
        }
      }
    }
    if(!ret) ret = '?';
    if(max >= 1) dest[0] = (char)ret;
    return 1;
  } else if(code == XU_CONV_KOI8R) {
    if(0x0080 <= ch && ch < 0x0480) {
      ret = table_rev_koi8r_1[ch - 0x0080];
    } else if(0x2200 <= ch && ch < 0x2600) {
      ret = table_rev_koi8r_2[ch - 0x2200];
    }
    if(!ret) ret = '?';
    if(max >= 1) dest[0] = (char)ret;
    return 1;
  }
  
  if(code == XU_CONV_EUCJP) {
    if(0xff61 <= ch && ch <= 0xff9f) {
      ret = (ch - 0xfec0) + 0x8e00;
    } else {
      ret = table_rev_jis0208[ch];
      if(ret) ret += 0x8080;
    }
  } else if(code == XU_CONV_SJIS) {
    if(0xff61 <= ch && ch <= 0xff9f) {
      if(max >= 1) dest[0] = (char)(ch - 0xfec0);
      return 1;
    } else {
      ret = table_rev_jis0208[ch];
      r1 = (ret >> 8  ) - 0x21;
      r2 = (ret & 0xff) - 0x21;
      if(r1 & 1) r2 += 94;
      r1 = (r1 >> 1) + 0x81;
      if(r1 > 0x9f) r1 += 0x40;
      r2 += 0x40;
      if(r2 > 0x7e) r2++;
      ret = (r1 << 8) + r2;
    }
  } else if(code == XU_CONV_EUCKR) {
    ret = table_rev_ksc5601[ch];
  } else if(code == XU_CONV_EUCCN) {
    ret = table_rev_gb2312[ch];
    if(ret) ret += 0x8080;
  } else if(code == XU_CONV_BIG5) {
    ret = table_rev_big5[ch];
  }
  if(!ret) {
    if(max >= 1) dest[0] = '?';
    return 1;
  }
  
  if(max >= 2) {
    dest[0] = (char)(ret >> 8);
    dest[1] = (char)(ret & 0xff);
  }
  return 2;
}


int XUCharLen(const char* text, int max, int code)
{
  XUChar c1;
  XUChar c2;
  
  if(max == 0 || !*text) return 1;
  
  if(code == XU_CONV_LOCALE) {
#ifdef WIN32
    return MultiByteToWideChar(CP_ACP, 0, text, max, NULL, 0);
#else
    code = xu_locale_encoding;
#endif
  }
  if(code == XU_CONV_UTF8  ) return XUutf8CharLen(text, max);
  if(code <= XU_CONV_KOI8R ) return 1;
  
  c1 = (XUChar)(unsigned char)*text;
  if(code == XU_CONV_SJIS) {
    if(c1 < 0x80 || (0xa1 <= c1 && c1 <= 0xdf)) return 1;
  }
  if(max == 1) return 1;
  text++;
  
  c2 = (XUChar)(unsigned char)*text;
  if(!c2) return 1;
  if(code == XU_CONV_EUCJP) {
    if(c1 == 0x8e) return 2;
    if(c1 < 0xa1 || c1 > 0xf4) return 1;
    if(c2 < 0xa1 || c2 > 0xfe) return 1;
    return 2;
  } else if(code == XU_CONV_SJIS) {
    if(c1 == 0x80 || c1 == 0xa0 || c1 > 0xfc) return 1;
    if(c2  < 0x40 || c2 == 0x7f || c2 > 0xfc) return 1;
    return 2;
  } else if(code == XU_CONV_EUCKR) {
    if(0x81 <= c1 && c1 <= 0xc8) {
      if(c2 < 0x41 || c2 > 0xfe) return 1;
      if(0x5a < c2 && c2 < 0x61) return 1;
      if(0x7a < c2 && c2 < 0x81) return 1;
      return 2;
    } else if(0xca <= c1 && c1 <= 0xfd) {
      if(c2 < 0xa1 || c2 > 0xfe) return 1;
      return 2;
    }
  } else if(code == XU_CONV_EUCCN) {
    if(c1 < 0xa1 || c1 > 0xf7) return 1;
    if(c2 < 0xa1 || c2 > 0xfe) return 1;
    return 2;
  } else if(code == XU_CONV_BIG5) {
    if(c1 < 0xa1 || c1 > 0xf9) return 1;
    if(c2 < 0x40 || c2 > 0xfe) return 1;
    if(0x7e < c2 && c2 < 0xa1) return 1;
    return 2;
  }
  
  return 1;
}


/**
  UTF-8 -> Unicode
*/

XUChar XUutf8CharEncode(const char* text, int max)
{
  XUChar c[6];
  
  if(max == 0 || !*text) return 0;
  
  c[0] = (XUChar)(unsigned char)*text;
  if(max == 1 || c[0] < 0xc0 || c[0] > 0xfd) return c[0];
  text++;
  
  c[1] = (XUChar)(unsigned char)*text;
  if((c[1] & 0xc0) != 0x80) return c[0];
  c[1] = c[1] & 0x3f;
  if((c[0] & 0xe0) == 0xc0) {
    if(c[0] < 0xc2) return c[0];
    return ((c[0] & 0x1f) << 6) + c[1];
  }
  if(max == 2) return c[0];
  text++;
  
  c[2] = (XUChar)(unsigned char)*text;
  if((c[2] & 0xc0) != 0x80) return c[0];
  c[2] = c[2] & 0x3f;
  if((c[0] & 0xf0) == 0xe0) {
    if(c[1] < 0x20 && c[0] < 0xe1) return c[0];
    return ((c[0] & 0x0f) << 12) + (c[1] << 6) + c[2];
  }
  if(max == 3) return c[0];
  text++;
  
  c[3] = (XUChar)(unsigned char)*text;
  if((c[3] & 0xc0) != 0x80) return c[0];
  c[3] = c[3] & 0x3f;
  if((c[0] & 0xf8) == 0xf0) {
    if(c[1] < 0x10 && c[0] < 0xf1) return c[0];
    return ((c[1] & 0x0f) << 12) + (c[2] << 6) + c[3];
  }
  if(max == 4) return c[0];
  text++;
  
  c[4] = (XUChar)(unsigned char)*text;
  if((c[4] & 0xc0) != 0x80) return c[0];
  c[4] = c[4] & 0x3f;
  if((c[0] & 0xfc) == 0xf8) {
    if(c[1] < 0x08 && c[0] < 0xf9) return c[0];
    return ((c[2] & 0x0f) << 12) + (c[3] << 6) + c[4];
  }
  if(max == 5) return c[0];
  text++;
  
  c[5] = (XUChar)(unsigned char)*text;
  if((c[5] & 0xc0) != 0x80) return c[0];
  c[5] = c[5] & 0x3f;
  if(c[1] < 0x04 && c[0] < 0xfd) return c[0];
  return ((c[3] & 0x0f) << 12) + (c[4] << 6) + c[5];
}


/**
  Unicode -> UTF-8
*/

int XUutf8CharDecode(char* dest, int max, XUChar ch)
{
  if(ch < 0x0080) {
    if(max >= 1) dest[0] = (char)ch;
    if(max >= 2) dest[1] = '\0';
    return 1;
  }
  
  if(ch < 0x0800) {
    if(max >= 1) dest[0] = (char)(0xc0 + ((ch >> 6) & 0x001f));
    if(max >= 2) dest[1] = (char)(0x80 + (ch & 0x003f));
    if(max >= 3) dest[2] = '\0';
    return 2;
  }
  
  if(max >= 1) dest[0] = (char)(0xe0 + ((ch >> 12) & 0x000f));
  if(max >= 2) dest[1] = (char)(0x80 + ((ch >>  6) & 0x003f));
  if(max >= 3) dest[2] = (char)(0x80 + (ch & 0x003f));
  if(max >= 4) dest[3] = '\0';
  return 3;
}


int XUutf8CharLen(const char* text, int max)
{
  XUChar c1;
  XUChar c2;
  XUChar ch;
  
  if(max == 0 || !*text) return 1;
  
  c1 = (XUChar)(unsigned char)*text;
  if(max == 1 || c1 < 0xc0 || c1 > 0xfd) return 1;
  text++;
  
  c2 = (XUChar)(unsigned char)*text;
  if((c2 & 0xc0) != 0x80) return 1;
  c2 = c2 & 0x3f;
  if((c1 & 0xe0) == 0xc0) {
    if(c1 < 0xc2) return 1;
    return 2;
  }
  if(max == 2) return 1;
  text++;
  
  ch = (XUChar)(unsigned char)*text;
  if((ch & 0xc0) != 0x80) return 1;
  if((c1 & 0xf0) == 0xe0) {
    if(c2 < 0x20 && c1 < 0xe1) return 1;
    return 3;
  }
  if(max == 3) return 1;
  text++;
  
  ch = (XUChar)(unsigned char)*text;
  if((ch & 0xc0) != 0x80) return 1;
  if((c1 & 0xf8) == 0xf0) {
    if(c2 < 0x10 && c1 < 0xf1) return 1;
    return 4;
  }
  if(max == 4) return 1;
  text++;
  
  ch = (XUChar)(unsigned char)*text;
  if((ch & 0xc0) != 0x80) return 1;
  if((c1 & 0xfc) == 0xf8) {
    if(c2 < 0x08 && c1 < 0xf9) return 1;
    return 5;
  }
  if(max == 5) return 1;
  text++;
  
  ch = (XUChar)(unsigned char)*text;
  if((ch & 0xc0) != 0x80) return 1;
  if(c2 < 0x04 && c1 < 0xfd) return 1;
  return 6;
}


int XUutf8CharRLen(const char* text, int max)
{
  XUChar c1;
  XUChar c2;
  
  if(max == 0) return 1;
  text--;
  
  c1 = (XUChar)(unsigned char)*text;
  if(max == 1 || (c1 & 0xc0) != 0x80) return 1;
  text--;
  
  c1 = (XUChar)(unsigned char)*text;
  if((c1 & 0xe0) == 0xc0) {
    if(c1 < 0xc2) return 1;
    return 2;
  }
  if(max == 2 || (c1 & 0xc0) != 0x80) return 1;
  text--;
  
  c2 = c1 & 0x3f;
  c1 = (XUChar)(unsigned char)*text;
  if((c1 & 0xf0) == 0xe0) {
    if(c2 < 0x20 && c1 < 0xe1) return 1;
    return 3;
  }
  if(max == 3 || (c1 & 0xc0) != 0x80) return 1;
  text--;
  
  c2 = c1 & 0x3f;
  c1 = (XUChar)(unsigned char)*text;
  if((c1 & 0xf8) == 0xf0) {
    if(c2 < 0x10 && c1 < 0xf1) return 1;
    return 4;
  }
  if(max == 4 || (c1 & 0xc0) != 0x80) return 1;
  text--;
  
  c2 = c1 & 0x3f;
  c1 = (XUChar)(unsigned char)*text;
  if((c1 & 0xfc) == 0xf8) {
    if(c2 < 0x08 && c1 < 0xf9) return 1;
    return 5;
  }
  if(max == 5 || (c1 & 0xc0) != 0x80) return 1;
  text--;
  
  c2 = c1 & 0x3f;
  c1 = (XUChar)(unsigned char)*text;
  if((c1 & 0xfe) == 0xfc) {
    if(c2 < 0x04 && c1 < 0xfd) return 1;
    return 6;
  }
  return 1;
}


int XUStrLen(const XUChar* text)
{
  int ret = 0;
  
  while(*text) {
    text++;
    ret++;
  }
  return ret;
}


/**
  Other -> Unicode(UCS-2)
*/

int XUEncode(XUChar* dest, int max, const char* text, int length, int code)
{
  int ret = 0;
  int chlen;
  
  if(length == 0) {
    if(max > 0) *dest = 0;
    return 0;
  }
  
#ifdef WIN32
  if(code == XU_CONV_LOCALE) {
    ret = MultiByteToWideChar(CP_ACP, 0, text, length, NULL, 0);
    MultiByteToWideChar(CP_ACP, 0, text, length, dest, max);
    if(ret < max) dest[ret] = L'\0';
    return ret;
  }
#endif

  for(;;) {
    if(length < 0 && !*text) break;
    if(ret < max) {
      *dest = XUCharEncode(text, length, code);
      dest++;
    }
    ret++;
    chlen = XUCharLen(text, length, code);
    text += chlen;
    if(length > 0) {
      length -= chlen;
      if(length < 1) break;
    }
  }
  
  if(ret < max) *dest = 0;
  return ret;
}


/**
  Unicode(UCS-2) -> Other
*/

int XUDecode(char* dest, int max, const XUChar* text, int length, int code)
{
  int ret = 0;
  int chlen;
  
  if(length == 0) {
    if(max > 0) *dest = 0;
    return 0;
  }
  
#ifdef WIN32
  if(code == XU_CONV_LOCALE) {
    ret = WideCharToMultiByte(CP_ACP, 0, text, length, NULL, 0, NULL, NULL);
    WideCharToMultiByte(CP_ACP, 0, text, length, dest, max, NULL, NULL);
    if(ret < max) dest[ret] = '\0';
    return ret;
  }
#endif
  
  for(;;) {
    if(length < 0 && !*text) break;
    chlen = XUCharDecode(dest, max, *text, code);
    ret += chlen;
    if(max > 0) {
      dest += chlen;
      max -= chlen;
    }
    text++;
    if(length > 0) {
      length--;
      if(length < 1) break;
    }
  }
  
  if(max > 0) *dest = 0;
  return ret;
}


int XULen(const char* text, int max, int code)
{
  int ret = 0;
  int chlen;
  
  if(max == 0) return 0;
  
#ifdef WIN32
  if(code == XU_CONV_LOCALE) {
    return MultiByteToWideChar(CP_ACP, 0, text, max, NULL, 0);
  }
#endif
  
  for(;;) {
    if(max < 0 && !*text) break;
    ret++;
    chlen = XUCharLen(text, max, code);
    text += chlen;
    if(max > 0) {
      max -= chlen;
      if(max < 1) break;
    }
  }
  return ret;
}


/**
  UTF-8 -> UCS-2
*/

int XUutf8Encode(XUChar* dest, int max, const char* text, int length)
{
  int ret = 0;
  int chlen;
  
  if(length == 0) {
    if(max > 0) *dest = 0;
    return 0;
  }
  
  for(;;) {
    if(length < 0 && !*text) break;
    if(ret < max) {
      *dest = XUutf8CharEncode(text, length);
      dest++;
    }
    ret++;
    chlen = XUutf8CharLen(text, length);
    text += chlen;
    if(length > 0) {
      length -= chlen;
      if(length < 1) break;
    }
  }
  
  if(ret < max) *dest = 0;
  return ret;
}


/**
  UCS-2 -> UTF-8
*/

int XUutf8Decode(char* dest, int max, const XUChar* text, int length)
{
  int ret = 0;
  int chlen;
  
  if(length == 0) {
    if(max > 0) *dest = 0;
    return 0;
  }
  
  for(;;) {
    if(length < 0 && !*text) break;
    chlen = XUutf8CharDecode(dest, max, *text);
    ret += chlen;
    if(max > 0) {
      dest += chlen;
      max -= chlen;
    }
    text++;
    if(length > 0) {
      length--;
      if(length < 1) break;
    }
  }
  
  if(max > 0) *dest = 0;
  return ret;
}


int XUutf8Len(const char* text, int max)
{
  int ret = 0;
  int chlen;
  
  if(max == 0) return 0;
  
  for(;;) {
    if(max < 0 && !*text) break;
    ret++;
    chlen = XUutf8CharLen(text, max);
    text += chlen;
    if(max > 0) {
      max -= chlen;
      if(max < 1) break;
    }
  }
  return ret;
}


int XUCodeConv(char* dest, int max, int codeTo, const char* text, int length, int codeFrom)
{
  XUChar* buf;
  int len1;
  int len2;
  
  if(length < 0) length = strlen(text);
  buf = (XUChar*)malloc(sizeof(XUChar) * length);
  len1 = XUEncode(buf, length, text, length, codeFrom);
  len2 = XUDecode(dest, max, buf, len1, codeTo);
  free(buf);
  return len2;
}
