]> granicus.if.org Git - uw-imap/commitdiff
add files for 2008-01-17T23:34:20Z
authorUnknown <>
Thu, 17 Jan 2008 23:34:20 +0000 (23:34 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Fri, 7 Sep 2018 00:02:38 +0000 (00:02 +0000)
src/c-client/utf8.c [new file with mode: 0644]

diff --git a/src/c-client/utf8.c b/src/c-client/utf8.c
new file mode 100644 (file)
index 0000000..8fda7ff
--- /dev/null
@@ -0,0 +1,2554 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 
+ * ========================================================================
+ */
+
+/*
+ * Program:    UTF-8 routines
+ *
+ * Author:     Mark Crispin
+ *             Networks and Distributed Computing
+ *             Computing & Communications
+ *             University of Washington
+ *             Administration Building, AG-44
+ *             Seattle, WA  98195
+ *             Internet: MRC@CAC.Washington.EDU
+ *
+ * Date:       11 June 1997
+ * Last Edited:        17 January 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include "c-client.h"
+\f
+/*     *** IMPORTANT ***
+ *
+ *  There is a very important difference between "character set" and "charset",
+ * and the comments in this file reflect these differences.  A "character set"
+ * (also known as "coded character set") is a mapping between codepoints and
+ * characters.  A "charset" is as defined in MIME, and incorporates one or more
+ * coded character sets in a character encoding scheme.  See RFC 2130 for more
+ * details.
+ */
+
+
+/* Character set conversion tables */
+
+#include "iso_8859.c"          /* 8-bit single-byte coded graphic */
+#include "koi8_r.c"            /* Cyrillic - Russia */
+#include "koi8_u.c"            /* Cyrillic - Ukraine */
+#include "tis_620.c"           /* Thai */
+#include "viscii.c"            /* Vietnamese */
+#include "windows.c"           /* Windows */
+#include "ibm.c"               /* IBM */
+#include "gb_2312.c"           /* Chinese (PRC) - simplified */
+#include "gb_12345.c"          /* Chinese (PRC) - traditional */
+#include "jis_0208.c"          /* Japanese - basic */
+#include "jis_0212.c"          /* Japanese - supplementary */
+#include "ksc_5601.c"          /* Korean */
+#include "big5.c"              /* Taiwanese (ROC) - industrial standard */
+#include "cns11643.c"          /* Taiwanese (ROC) - national standard */
+
+
+#include "widths.c"            /* Unicode character widths */
+#include "tmap.c"              /* Unicode titlecase mapping */
+#include "decomtab.c"          /* Unicode decomposions */
+\f
+/* EUC parameters */
+
+#ifdef GBTOUNICODE             /* PRC simplified Chinese */
+static const struct utf8_eucparam gb_param = {
+  BASE_GB2312_KU,BASE_GB2312_TEN,MAX_GB2312_KU,MAX_GB2312_TEN,
+  (void *) gb2312tab};
+#endif
+
+
+#ifdef GB12345TOUNICODE                /* PRC traditional Chinese */
+static const struct utf8_eucparam gbt_param = {
+  BASE_GB12345_KU,BASE_GB12345_TEN,MAX_GB12345_KU,MAX_GB12345_TEN,
+  (void *) gb12345tab};
+#endif
+
+
+#ifdef BIG5TOUNICODE           /* ROC traditional Chinese */
+static const struct utf8_eucparam big5_param[] = {
+  {BASE_BIG5_KU,BASE_BIG5_TEN_0,MAX_BIG5_KU,MAX_BIG5_TEN_0,(void *) big5tab},
+  {BASE_BIG5_KU,BASE_BIG5_TEN_1,MAX_BIG5_KU,MAX_BIG5_TEN_1,NIL}
+};
+#endif
+
+
+#ifdef JISTOUNICODE            /* Japanese */
+static const struct utf8_eucparam jis_param[] = {
+  {BASE_JIS0208_KU,BASE_JIS0208_TEN,MAX_JIS0208_KU,MAX_JIS0208_TEN,
+     (void *) jis0208tab},
+  {MIN_KANA_8,0,MAX_KANA_8,0,(void *) KANA_8},
+#ifdef JIS0212TOUNICODE                /* Japanese extended */
+  {BASE_JIS0212_KU,BASE_JIS0212_TEN,MAX_JIS0212_KU,MAX_JIS0212_TEN,
+     (void *) jis0212tab}
+#else
+  {0,0,0,0,NIL}
+#endif
+};
+#endif
+
+
+#ifdef KSCTOUNICODE            /* Korean */
+static const struct utf8_eucparam ksc_param = {
+  BASE_KSC5601_KU,BASE_KSC5601_TEN,MAX_KSC5601_KU,MAX_KSC5601_TEN,
+  (void *) ksc5601tab};
+#endif
+\f
+/* List of supported charsets */
+
+static const CHARSET utf8_csvalid[] = {
+  {"US-ASCII",CT_ASCII,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   NIL,NIL,NIL},
+  {"UTF-8",CT_UTF8,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   NIL,SC_UNICODE,NIL},
+  {"UTF-7",CT_UTF7,CF_PRIMARY | CF_POSTING | CF_UNSUPRT,
+   NIL,SC_UNICODE,"UTF-8"},
+  {"ISO-8859-1",CT_1BYTE0,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   NIL,SC_LATIN_1,NIL},
+  {"ISO-8859-2",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_2tab,SC_LATIN_2,NIL},
+  {"ISO-8859-3",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_3tab,SC_LATIN_3,NIL},
+  {"ISO-8859-4",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_4tab,SC_LATIN_4,NIL},
+  {"ISO-8859-5",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_5tab,SC_CYRILLIC,"KOI8-R"},
+  {"ISO-8859-6",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_6tab,SC_ARABIC,NIL},
+  {"ISO-8859-7",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_7tab,SC_GREEK,NIL},
+  {"ISO-8859-8",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_8tab,SC_HEBREW,NIL},
+  {"ISO-8859-9",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_9tab,SC_LATIN_5,NIL},
+  {"ISO-8859-10",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_10tab,SC_LATIN_6,NIL},
+  {"ISO-8859-11",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_11tab,SC_THAI,NIL},
+#if 0                          /* ISO 8859-12 reserved for ISCII(?) */
+  {"ISO-8859-12",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_12tab,NIL,NIL},
+#endif
+  {"ISO-8859-13",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_13tab,SC_LATIN_7,NIL},
+  {"ISO-8859-14",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_14tab,SC_LATIN_8,NIL},
+  {"ISO-8859-15",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_15tab,SC_LATIN_9,NIL},
+  {"ISO-8859-16",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) iso8859_16tab,SC_LATIN_10,NIL},
+  {"KOI8-R",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) koi8rtab,SC_CYRILLIC,NIL},
+  {"KOI8-U",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) koi8utab,SC_CYRILLIC | SC_UKRANIAN,NIL},
+  {"KOI8-RU",CT_1BYTE,CF_DISPLAY,
+   (void *) koi8utab,SC_CYRILLIC | SC_UKRANIAN,"KOI8-U"},
+  {"TIS-620",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) tis620tab,SC_THAI,"ISO-8859-11"},
+  {"VISCII",CT_1BYTE8,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) visciitab,SC_VIETNAMESE,NIL},
+\f
+#ifdef GBTOUNICODE
+  {"GBK",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+     (void *) &gb_param,SC_CHINESE_SIMPLIFIED,NIL},
+  {"GB2312",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+   (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"},
+  {"CN-GB",CT_DBYTE,CF_DISPLAY,
+     (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"},
+#ifdef CNS1TOUNICODE
+  {"ISO-2022-CN",CT_2022,CF_PRIMARY | CF_UNSUPRT,
+     NIL,SC_CHINESE_SIMPLIFIED | SC_CHINESE_TRADITIONAL,
+   NIL},
+#endif
+#endif
+#ifdef GB12345TOUNICODE
+  {"CN-GB-12345",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &gbt_param,SC_CHINESE_TRADITIONAL,"BIG5"},
+#endif
+#ifdef BIG5TOUNICODE
+  {"BIG5",CT_DBYTE2,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+     (void *) big5_param,SC_CHINESE_TRADITIONAL,NIL},
+  {"CN-BIG5",CT_DBYTE2,CF_DISPLAY,
+     (void *) big5_param,SC_CHINESE_TRADITIONAL,"BIG5"},
+  {"BIG-5",CT_DBYTE2,CF_DISPLAY,
+     (void *) big5_param,SC_CHINESE_TRADITIONAL,"BIG5"},
+#endif
+#ifdef JISTOUNICODE
+  {"ISO-2022-JP",CT_2022,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+     NIL,SC_JAPANESE,NIL},
+  {"EUC-JP",CT_EUC,CF_PRIMARY | CF_DISPLAY,
+     (void *) jis_param,SC_JAPANESE,"ISO-2022-JP"},
+  {"SHIFT_JIS",CT_SJIS,CF_PRIMARY | CF_DISPLAY,
+     NIL,SC_JAPANESE,"ISO-2022-JP"},
+  {"SHIFT-JIS",CT_SJIS,CF_PRIMARY | CF_DISPLAY,
+     NIL,SC_JAPANESE,"ISO-2022-JP"},
+#ifdef JIS0212TOUNICODE
+  {"ISO-2022-JP-1",CT_2022,CF_UNSUPRT,
+     NIL,SC_JAPANESE,"ISO-2022-JP"},
+#ifdef GBTOUNICODE
+#ifdef KSCTOUNICODE
+  {"ISO-2022-JP-2",CT_2022,CF_UNSUPRT,
+     NIL,
+     SC_LATIN_1 | SC_LATIN_2 | SC_LATIN_3 | SC_LATIN_4 | SC_LATIN_5 |
+       SC_LATIN_6 | SC_LATIN_7 | SC_LATIN_8 | SC_LATIN_9 | SC_LATIN_10 |
+        SC_ARABIC | SC_CYRILLIC | SC_GREEK | SC_HEBREW | SC_THAI |
+          SC_VIETNAMESE | SC_CHINESE_SIMPLIFIED | SC_JAPANESE | SC_KOREAN
+#ifdef CNS1TOUNICODE
+            | SC_CHINESE_TRADITIONAL
+#endif
+              ,"UTF-8"},
+#endif
+#endif
+#endif
+#endif
+\f
+#ifdef KSCTOUNICODE
+  {"ISO-2022-KR",CT_2022,CF_PRIMARY | CF_DISPLAY | CF_UNSUPRT,
+     NIL,SC_KOREAN,"EUC-KR"},
+  {"EUC-KR",CT_DBYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+     (void *) &ksc_param,SC_KOREAN,NIL},
+  {"KSC5601",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"KSC_5601",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"KS_C_5601-1987",CT_DBYTE,CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"KS_C_5601-1989",CT_DBYTE,CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"KS_C_5601-1992",CT_DBYTE,CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"KS_C_5601-1997",CT_DBYTE,CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+#endif
+\f
+                               /* deep sigh */
+  {"WINDOWS-874",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_874tab,SC_THAI,"ISO-8859-11"},
+  {"CP874",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_874tab,SC_THAI,"ISO-8859-11"},
+#ifdef GBTOUNICODE
+  {"WINDOWS-936",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"},
+  {"CP936",CT_DBYTE,CF_DISPLAY,
+     (void *) &gb_param,SC_CHINESE_SIMPLIFIED,"GBK"},
+#endif
+#ifdef KSCTOUNICODE
+  {"WINDOWS-949",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"CP949",CT_DBYTE,CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+  {"X-WINDOWS-949",CT_DBYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) &ksc_param,SC_KOREAN,"EUC-KR"},
+#endif
+  {"WINDOWS-1250",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1250tab,SC_LATIN_2,"ISO-8859-2"},
+  {"CP1250",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1250tab,SC_LATIN_2,"ISO-8859-2"},
+  {"WINDOWS-1251",CT_1BYTE,CF_PRIMARY | CF_DISPLAY | CF_POSTING,
+     (void *) windows_1251tab,SC_CYRILLIC,"KOI8-R"},
+  {"CP1251",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1251tab,SC_CYRILLIC,"KOI8-R"},
+  {"WINDOWS-1252",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1252tab,SC_LATIN_1,"ISO-8859-1"},
+  {"CP1252",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1252tab,SC_LATIN_1,"ISO-8859-1"},
+  {"WINDOWS-1253",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1253tab,SC_GREEK,"ISO-8859-7"},
+  {"CP1253",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1253tab,SC_GREEK,"ISO-8859-7"},
+  {"WINDOWS-1254",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1254tab,SC_LATIN_5,"ISO-8859-9"},
+  {"CP1254",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1254tab,SC_LATIN_5,"ISO-8859-9"},
+  {"WINDOWS-1255",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1255tab,SC_HEBREW,"ISO-8859-8"},
+  {"CP1255",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1255tab,SC_HEBREW,"ISO-8859-8"},
+  {"WINDOWS-1256",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1256tab,SC_ARABIC,"ISO-8859-6"},
+  {"CP1256",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1256tab,SC_ARABIC,"ISO-8859-6"},
+  {"WINDOWS-1257",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1257tab,SC_LATIN_7,"ISO-8859-13"},
+  {"CP1257",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1257tab,SC_LATIN_7,"ISO-8859-13"},
+  {"WINDOWS-1258",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) windows_1258tab,SC_VIETNAMESE,"VISCII"},
+  {"CP1258",CT_1BYTE,CF_DISPLAY,
+     (void *) windows_1258tab,SC_VIETNAMESE,"VISCII"},
+\f
+                               /* deeper sigh */
+  {"IBM367",CT_ASCII,CF_PRIMARY | CF_DISPLAY,
+     NIL,NIL,"US-ASCII"},
+  {"IBM437",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_437tab,SC_LATIN_1,"ISO-8859-1"},
+  {"IBM737",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_737tab,SC_GREEK,"ISO-8859-7"},
+  {"IBM775",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_775tab,SC_LATIN_7,"ISO-8859-13"},
+  {"IBM850",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_850tab,SC_LATIN_1,"ISO-8859-1"},
+  {"IBM852",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_852tab,SC_LATIN_2,"ISO-8859-2"},
+  {"IBM855",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_855tab,SC_CYRILLIC,"ISO-8859-5"},
+  {"IBM857",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_857tab,SC_LATIN_5,"ISO-8859-9"},
+  {"IBM860",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_860tab,SC_LATIN_1,"ISO-8859-1"},
+  {"IBM861",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_861tab,SC_LATIN_6,"ISO-8859-10"},
+  {"IBM862",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_862tab,SC_HEBREW,"ISO-8859-8"},
+  {"IBM863",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_863tab,SC_LATIN_1,"ISO-8859-1"},
+  {"IBM864",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_864tab,SC_ARABIC,"ISO-8859-6"},
+  {"IBM865",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_865tab,SC_LATIN_6,"ISO-8859-10"},
+  {"IBM866",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_866tab,SC_CYRILLIC,"KOI8-R"},
+  {"IBM869",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_869tab,SC_GREEK,"ISO-8859-7"},
+  {"IBM874",CT_1BYTE,CF_PRIMARY | CF_DISPLAY,
+     (void *) ibm_874tab,SC_THAI,"ISO-8859-11"},
+                               /* deepest sigh */
+  {"ANSI_X3.4-1968",CT_ASCII,CF_DISPLAY,
+     NIL,NIL,"US-ASCII"},
+  {"UNICODE-1-1-UTF-7",CT_UTF7,CF_UNSUPRT,
+     NIL,SC_UNICODE,"UTF-8"},
+                               /* these should never appear in email */
+  {"UCS-2",CT_UCS2,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL,
+     NIL,SC_UNICODE,"UTF-8"},
+  {"UCS-4",CT_UCS4,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL,
+     NIL,SC_UNICODE,"UTF-8"},
+  {"UTF-16",CT_UTF16,CF_PRIMARY | CF_DISPLAY | CF_NOEMAIL,
+     NIL,SC_UNICODE,"UTF-8"},
+  NIL
+};
+\f
+/* Non-Unicode Script table */
+
+static const SCRIPT utf8_scvalid[] = {
+  {"Arabic",NIL,SC_ARABIC},
+  {"Chinese Simplified","China, Singapore",SC_CHINESE_SIMPLIFIED},
+  {"Chinese Traditional","Taiwan, Hong Kong, Macao",SC_CHINESE_TRADITIONAL},
+  {"Cyrillic",NIL,SC_CYRILLIC},
+  {"Cyrillic Ukranian",NIL,SC_UKRANIAN},
+  {"Greek",NIL,SC_GREEK},
+  {"Hebrew",NIL,SC_HEBREW},
+  {"Japanese",NIL,SC_JAPANESE},
+  {"Korean",NIL,SC_KOREAN},
+  {"Latin-1","Western Europe",SC_LATIN_1},
+  {"Latin-2","Eastern Europe",SC_LATIN_2},
+  {"Latin-3","Southern Europe",SC_LATIN_3},
+  {"Latin-4","Northern Europe",SC_LATIN_4},
+  {"Latin-5","Turkish",SC_LATIN_5},
+  {"Latin-6","Nordic",SC_LATIN_6},
+  {"Latin-7","Baltic",SC_LATIN_7},
+  {"Latin-8","Celtic",SC_LATIN_8},
+  {"Latin-9","Euro",SC_LATIN_9},
+  {"Latin-10","Balkan",SC_LATIN_10},
+  {"Thai",NIL,SC_THAI},
+  {"Vietnamese",NIL,SC_VIETNAMESE},
+  NIL
+};
+\f
+/* Look up script name or return entire table
+ * Accepts: script name or NIL
+ * Returns: pointer to script table entry or NIL if unknown
+ */
+
+SCRIPT *utf8_script (char *script)
+{
+  unsigned long i;
+  if (!script) return (SCRIPT *) &utf8_scvalid[0];
+  else if (*script && (strlen (script) < 128))
+    for (i = 0; utf8_scvalid[i].name; i++)
+      if (!compare_cstring (script,utf8_scvalid[i].name))
+       return (SCRIPT *) &utf8_scvalid[i];
+  return NIL;                  /* failed */
+}
+
+
+/* Look up charset name or return entire table
+ * Accepts: charset name or NIL
+ * Returns: charset table entry or NIL if unknown
+ */
+
+const CHARSET *utf8_charset (char *charset)
+{
+  unsigned long i;
+  if (!charset) return (CHARSET *) &utf8_csvalid[0];
+  else if (*charset && (strlen (charset) < 128))
+    for (i = 0; utf8_csvalid[i].name; i++)
+      if (!compare_cstring (charset,utf8_csvalid[i].name))
+       return (CHARSET *) &utf8_csvalid[i];
+  return NIL;                  /* failed */
+}
+\f
+/* Validate charset and generate error message if invalid
+ * Accepts: bad character set
+ * Returns: NIL if good charset, else error message string
+ */
+
+#define BADCSS "[BADCHARSET ("
+#define BADCSE ")] Unknown charset: "
+
+char *utf8_badcharset (char *charset)
+{
+  char *msg = NIL;
+  if (!utf8_charset (charset)) {
+    char *s,*t;
+    unsigned long i,j;
+                               /* calculate size of header, trailer, and bad
+                                * charset plus charset names */
+    for (i = 0, j = sizeof (BADCSS) + sizeof (BADCSE) + strlen (charset) - 2;
+        utf8_csvalid[i].name; i++)
+      j += strlen (utf8_csvalid[i].name) + 1;
+                               /* not built right */
+    if (!i) fatal ("No valid charsets!");
+                               /* header */
+    for (s = msg = (char *) fs_get (j), t = BADCSS; *t; *s++ = *t++);
+                               /* each charset */
+    for (i = 0; utf8_csvalid[i].name; *s++ = ' ', i++)
+      for (t = utf8_csvalid[i].name; *t; *s++ = *t++);
+                               /* back over last space, trailer */
+    for (t = BADCSE, --s; *t; *s++ = *t++);
+                               /* finally bogus charset */
+    for (t = charset; *t; *s++ = *t++);
+    *s++ = '\0';               /* finally tie off string */
+    if (s != (msg + j)) fatal ("charset msg botch");
+  }
+  return msg;
+}
+\f
+/* Convert charset labelled sized text to UTF-8
+ * Accepts: source sized text
+ *         charset
+ *         pointer to returned sized text if non-NIL
+ *         flags
+ * Returns: T if successful, NIL if failure
+ */
+
+long utf8_text (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret,long flags)
+{
+  ucs4cn_t cv = (flags & U8T_CASECANON) ? ucs4_titlecase : NIL;
+  ucs4de_t de = (flags & U8T_DECOMPOSE) ? ucs4_decompose_recursive : NIL;
+  const CHARSET *cs = (charset && *charset) ?
+    utf8_charset (charset) : utf8_infercharset (text);
+  if (cs) return (text && ret) ? utf8_text_cs (text,cs,ret,cv,de) : LONGT;
+  if (ret) {                   /* no conversion possible */
+    ret->data = text->data;    /* so return source */
+    ret->size = text->size;
+  }
+  return NIL;                  /* failure */
+}
+
+
+/* Operations used in converting data */
+
+#define UTF8_COUNT_BMP(count,c,cv,de) {                \
+  void *more = NIL;                            \
+  if (cv) c = (*cv) (c);                       \
+  if (de) c = (*de) (c,&more);                 \
+  do count += UTF8_SIZE_BMP(c);                        \
+  while (more && (c = (*de) (U8G_ERROR,&more)));\
+}
+
+#define UTF8_WRITE_BMP(b,c,cv,de) {            \
+  void *more = NIL;                            \
+  if (cv) c = (*cv) (c);                       \
+  if (de) c = (*de) (c,&more);                 \
+  do UTF8_PUT_BMP (b,c)                                \
+  while (more && (c = (*de) (U8G_ERROR,&more)));\
+}
+
+#define UTF8_COUNT(count,c,cv,de) {            \
+  void *more = NIL;                            \
+  if (cv) c = (*cv) (c);                       \
+  if (de) c = (*de) (c,&more);                 \
+  do count += utf8_size (c);                   \
+  while (more && (c = (*de) (U8G_ERROR,&more)));\
+}
+
+#define UTF8_WRITE(b,c,cv,de) {                        \
+  void *more = NIL;                            \
+  if (cv) c = (*cv) (c);                       \
+  if (de) c = (*de) (c,&more);                 \
+  do b = utf8_put (b,c);                       \
+  while (more && (c = (*de) (U8G_ERROR,&more)));\
+}
+\f
+/* Convert sized text to UTF-8 given CHARSET block
+ * Accepts: source sized text
+ *         CHARSET block
+ *         pointer to returned sized text 
+ *         canonicalization function
+ *         decomposition function
+ * Returns: T if successful, NIL if failure
+ */
+
+long utf8_text_cs (SIZEDTEXT *text,const CHARSET *cs,SIZEDTEXT *ret,
+                  ucs4cn_t cv,ucs4de_t de)
+{
+  ret->data = text->data;      /* default to source */
+  ret->size = text->size;
+  switch (cs->type) {          /* convert if type known */
+  case CT_ASCII:               /* 7-bit ASCII no table */
+  case CT_UTF8:                        /* variable UTF-8 encoded Unicode no table */
+    if (cv || de) utf8_text_utf8 (text,ret,cv,de);
+    break;
+  case CT_1BYTE0:              /* 1 byte no table */
+    utf8_text_1byte0 (text,ret,cv,de);
+    break;
+  case CT_1BYTE:               /* 1 byte ASCII + table 0x80-0xff */
+    utf8_text_1byte (text,ret,cs->tab,cv,de);
+    break;
+  case CT_1BYTE8:              /* 1 byte table 0x00 - 0xff */
+    utf8_text_1byte8 (text,ret,cs->tab,cv,de);
+    break;
+  case CT_EUC:                 /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+    utf8_text_euc (text,ret,cs->tab,cv,de);
+    break;
+  case CT_DBYTE:               /* 2 byte ASCII + utf8_eucparam */
+    utf8_text_dbyte (text,ret,cs->tab,cv,de);
+    break;
+  case CT_DBYTE2:              /* 2 byte ASCII + utf8_eucparam plane1/2 */
+    utf8_text_dbyte2 (text,ret,cs->tab,cv,de);
+    break;
+  case CT_UTF7:                        /* variable UTF-7 encoded Unicode no table */
+    utf8_text_utf7 (text,ret,cv,de);
+    break;
+  case CT_UCS2:                        /* 2 byte 16-bit Unicode no table */
+    utf8_text_ucs2 (text,ret,cv,de);
+    break;
+  case CT_UCS4:                        /* 4 byte 32-bit Unicode no table */
+    utf8_text_ucs4 (text,ret,cv,de);
+    break;
+  case CT_UTF16:               /* variable UTF-16 encoded Unicode no table */
+    utf8_text_utf16 (text,ret,cv,de);
+    break;
+  case CT_2022:                        /* variable ISO-2022 encoded no table*/
+    utf8_text_2022 (text,ret,cv,de);
+    break;
+  case CT_SJIS:                        /* 2 byte Shift-JIS encoded JIS no table */
+    utf8_text_sjis (text,ret,cv,de);
+    break;
+  default:                     /* unknown character set type */
+    return NIL;
+  }
+  return LONGT;                        /* return success */
+}
+\f
+/* Reverse mapping routines
+ *
+ * These routines only support character sets, not all possible charsets.  In
+ * particular, they do not support any Unicode encodings or ISO 2022.
+ *
+ * As a special dispensation, utf8_cstext() and utf8_cstocstext() support
+ * support ISO-2022-JP if EUC-JP can be reverse mapped; and utf8_rmaptext()
+ * will generated ISO-2022-JP using an EUC-JP rmap if flagged to do so.
+ *
+ * No attempt is made to map "equivalent" Unicode characters or Unicode
+ * characters that have the same glyph; nor is there any attempt to handle
+ * combining characters or otherwise do any stringprep.  Maybe later.
+ */
+
+
+/* Convert UTF-8 sized text to charset
+ * Accepts: source sized text
+ *         destination charset
+ *         pointer to returned sized text
+ *         substitute character if not in cs, else NIL to return failure
+ * Returns: T if successful, NIL if failure
+ */
+
+
+long utf8_cstext (SIZEDTEXT *text,char *charset,SIZEDTEXT *ret,
+                 unsigned long errch)
+{
+  short iso2022jp = !compare_cstring (charset,"ISO-2022-JP");
+  unsigned short *rmap = utf8_rmap (iso2022jp ? "EUC-JP" : charset);
+  return rmap ? utf8_rmaptext (text,rmap,ret,errch,iso2022jp) : NIL;
+}
+\f
+/* Convert charset labelled sized text to another charset
+ * Accepts: source sized text
+ *         source charset
+ *         pointer to returned sized text
+ *         destination charset
+ *         substitute character if not in dest cs, else NIL to return failure
+ * Returns: T if successful, NIL if failure
+ *
+ * This routine has the same restricts as utf8_cstext().
+ */
+
+long utf8_cstocstext (SIZEDTEXT *src,char *sc,SIZEDTEXT *dst,char *dc,
+                     unsigned long errch)
+{
+  SIZEDTEXT utf8;
+  const CHARSET *scs,*dcs;
+  unsigned short *rmap;
+  long ret = NIL;
+  long iso2022jp;
+                               /* lookup charsets and reverse map */
+  if ((dc && (dcs = utf8_charset (dc))) &&
+      (rmap = (iso2022jp = ((dcs->type == CT_2022) &&
+                           !compare_cstring (dcs->name,"ISO-2022-JP"))) ?
+       utf8_rmap ("EUC-JP") : utf8_rmap_cs (dcs)) &&
+      (scs = (sc && *sc) ? utf8_charset (sc) : utf8_infercharset (src))) {
+                               /* init temporary buffer */
+    memset (&utf8,NIL,sizeof (SIZEDTEXT));
+                               /* source cs equivalent to dest cs? */
+    if ((scs->type == dcs->type) && (scs->tab == dcs->tab)) {
+      dst->data = src->data;   /* yes, just copy pointers */
+      dst->size = src->size;
+      ret = LONGT;
+    }
+                               /* otherwise do the conversion */
+    else ret = (utf8_text_cs (src,scs,&utf8,NIL,NIL) &&
+               utf8_rmaptext (&utf8,rmap,dst,errch,iso2022jp));
+                               /* flush temporary buffer */
+    if (utf8.data && (utf8.data != src->data) && (utf8.data != dst->data))
+      fs_give ((void **) &utf8.data);
+  }
+  return ret;
+}
+\f
+/* Cached rmap */
+
+static const CHARSET *currmapcs = NIL;
+static unsigned short *currmap = NIL;
+
+
+/* Cache and return map for UTF-8 -> character set
+ * Accepts: character set name
+ * Returns: cached map if character set found, else NIL
+ */
+
+unsigned short *utf8_rmap (char *charset)
+{
+  return (currmapcs && !compare_cstring (charset,currmapcs->name)) ? currmap :
+    utf8_rmap_cs (utf8_charset (charset));
+}
+
+
+/* Cache and return map for UTF-8 -> character set given CHARSET block
+ * Accepts: CHARSET block
+ * Returns: cached map if character set found, else NIL
+ */
+
+unsigned short *utf8_rmap_cs (const CHARSET *cs)
+{
+  unsigned short *ret = NIL;
+  if (!cs);                    /* have charset? */
+  else if (cs == currmapcs) ret = currmap;
+  else if (ret = utf8_rmap_gen (cs,currmap)) {
+    currmapcs = cs;
+    currmap = ret;
+  }
+  return ret;
+}
+\f
+/* Return map for UTF-8 -> character set given CHARSET block
+ * Accepts: CHARSET block
+ *         old map to recycle
+ * Returns: map if character set found, else NIL
+ */
+
+unsigned short *utf8_rmap_gen (const CHARSET *cs,unsigned short *oldmap)
+{
+  unsigned short u,*tab,*rmap;
+  unsigned int i,m,ku,ten;
+  struct utf8_eucparam *param,*p2;
+  switch (cs->type) {          /* is a character set? */
+  case CT_ASCII:               /* 7-bit ASCII no table */
+  case CT_1BYTE0:              /* 1 byte no table */
+  case CT_1BYTE:               /* 1 byte ASCII + table 0x80-0xff */
+  case CT_1BYTE8:              /* 1 byte table 0x00 - 0xff */
+  case CT_EUC:                 /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+  case CT_DBYTE:               /* 2 byte ASCII + utf8_eucparam */
+  case CT_DBYTE2:              /* 2 byte ASCII + utf8_eucparam plane1/2 */
+  case CT_SJIS:                        /* 2 byte Shift-JIS */
+    rmap = oldmap ? oldmap :   /* recycle old map if supplied else make new */
+      (unsigned short *) fs_get (65536 * sizeof (unsigned short));
+                               /* initialize table for ASCII */
+    for (i = 0; i < 128; i++) rmap[i] = (unsigned short) i;
+                               /* populate remainder of table with NOCHAR */
+#define NOCHARBYTE (NOCHAR & 0xff)
+#if NOCHAR - ((NOCHARBYTE << 8) | NOCHARBYTE)
+    while (i < 65536) rmap[i++] = NOCHAR;
+#else
+    memset (rmap + 128,NOCHARBYTE,(65536 - 128) * sizeof (unsigned short));
+#endif
+    break;
+  default:                     /* unsupported charset type */
+    rmap = NIL;                        /* no map possible */
+  }
+  if (rmap) {                  /* have a map? */
+    switch (cs->type) {                /* additional reverse map actions */
+    case CT_1BYTE0:            /* 1 byte no table */
+      for (i = 128; i < 256; i++) rmap[i] = (unsigned short) i;
+      break;
+    case CT_1BYTE:             /* 1 byte ASCII + table 0x80-0xff */
+      for (tab = (unsigned short *) cs->tab,i = 128; i < 256; i++)
+       if (tab[i & BITS7] != UBOGON) rmap[tab[i & BITS7]] = (unsigned short)i;
+      break;
+    case CT_1BYTE8:            /* 1 byte table 0x00 - 0xff */
+      for (tab = (unsigned short *) cs->tab,i = 0; i < 256; i++)
+       if (tab[i] != UBOGON) rmap[tab[i]] = (unsigned short) i;
+      break;
+    case CT_EUC:               /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+      for (param = (struct utf8_eucparam *) cs->tab,
+            tab = (unsigned short *) param->tab, ku = 0;
+          ku < param->max_ku; ku++)
+       for (ten = 0; ten < param->max_ten; ten++)
+         if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON)
+           rmap[u] = ((ku + param->base_ku) << 8) +
+             (ten + param->base_ten) + 0x8080;
+      break;
+\f
+    case CT_DBYTE:             /* 2 byte ASCII + utf8_eucparam */
+      for (param = (struct utf8_eucparam *) cs->tab,
+            tab = (unsigned short *) param->tab, ku = 0;
+          ku < param->max_ku; ku++)
+       for (ten = 0; ten < param->max_ten; ten++)
+         if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON)
+           rmap[u] = ((ku + param->base_ku) << 8) + (ten + param->base_ten);
+      break;
+    case CT_DBYTE2:            /* 2 byte ASCII + utf8_eucparam plane1/2 */
+      param = (struct utf8_eucparam *) cs->tab;
+      p2 = param + 1;          /* plane 2 parameters */
+                               /* only ten parameters should differ */
+      if ((param->base_ku != p2->base_ku) || (param->max_ku != p2->max_ku))
+       fatal ("ku definition error for CT_DBYTE2 charset");
+                               /* total codepoints in each ku */
+      m = param->max_ten + p2->max_ten;
+      tab = (unsigned short *) param->tab;
+      for (ku = 0; ku < param->max_ku; ku++) {
+       for (ten = 0; ten < param->max_ten; ten++)
+         if ((u = tab[(ku * m) + ten]) != UBOGON)
+           rmap[u] = ((ku + param->base_ku) << 8) + (ten + param->base_ten);
+       for (ten = 0; ten < p2->max_ten; ten++)
+         if ((u = tab[(ku * m) + param->max_ten + ten]) != UBOGON)
+           rmap[u] = ((ku + param->base_ku) << 8) + (ten + p2->base_ten);
+      }
+      break;
+    case CT_SJIS:              /* 2 byte Shift-JIS */
+      for (ku = 0; ku < MAX_JIS0208_KU; ku++)
+       for (ten = 0; ten < MAX_JIS0208_TEN; ten++)
+         if ((u = jis0208tab[ku][ten]) != UBOGON) {
+           int sku = ku + BASE_JIS0208_KU;
+           int sten = ten + BASE_JIS0208_TEN;
+           rmap[u] = ((((sku + 1) >> 1) + ((sku < 95) ? 112 : 176)) << 8) +
+             sten + ((sku % 2) ? ((sten > 95) ? 32 : 31) : 126);
+         }
+                               /* JIS Roman */
+      rmap[UCS2_YEN] = JISROMAN_YEN;
+      rmap[UCS2_OVERLINE] = JISROMAN_OVERLINE;
+                               /* JIS hankaku katakana */
+      for (u = 0; u < (MAX_KANA_8 - MIN_KANA_8); u++)
+       rmap[UCS2_KATAKANA + u] = MIN_KANA_8 + u;
+      break;
+    }
+                               /* hack: map NBSP to SP if otherwise no map */
+    if (rmap[0x00a0] == NOCHAR) rmap[0x00a0] = rmap[0x0020];
+  }
+  return rmap;                 /* return map */
+}
+\f
+/* Convert UTF-8 sized text to charset using rmap
+ * Accepts: source sized text
+ *         conversion rmap
+ *         pointer to returned sized text
+ *         substitute character if not in rmap, else NIL to return failure
+ *         ISO-2022-JP conversion flag
+ * Returns T if successful, NIL if failure
+ *
+ * This routine doesn't try to convert to all possible charsets; in particular
+ * it doesn't support other Unicode encodings or any ISO 2022 other than
+ * ISO-2022-JP.
+ */
+
+long utf8_rmaptext (SIZEDTEXT *text,unsigned short *rmap,SIZEDTEXT *ret,
+                   unsigned long errch,long iso2022jp)
+{
+  unsigned long i,u,c;
+                               /* get size of buffer */
+  if (i = utf8_rmapsize (text,rmap,errch,iso2022jp)) {
+    unsigned char *s = text->data;
+    unsigned char *t = ret->data = (unsigned char *) fs_get (i);
+    ret->size = i - 1;         /* number of octets in destination buffer */
+                               /* start non-zero ISO-2022-JP state at 1 */
+    if (iso2022jp) iso2022jp = 1;
+                               /* convert string, ignore BOM */
+    for (i = text->size; i;) if ((u = utf8_get (&s,&i)) != UCS2_BOM) {
+                               /* substitute error character for NOCHAR */
+      if ((u & U8GM_NONBMP) || ((c = rmap[u]) == NOCHAR)) c = errch;
+      switch (iso2022jp) {     /* depends upon ISO 2022 mode */
+      case 0:                  /* ISO 2022 not in effect */
+                               /* two-byte character */
+       if (c > 0xff) *t++ = (unsigned char) (c >> 8);
+                               /* single-byte or low-byte of two-byte */
+       *t++ = (unsigned char) (c & 0xff);
+       break;
+      case 1:                  /* ISO 2022 Roman */
+                               /* <ch> */
+       if (c < 0x80) *t++ = (unsigned char) c;
+       else {                  /* JIS character */
+         *t++ = I2C_ESC;       /* ESC $ B <hi> <lo> */
+         *t++ = I2C_MULTI;
+         *t++ = I2CS_94x94_JIS_NEW;
+         *t++ = (unsigned char) (c >> 8) & 0x7f;
+         *t++ = (unsigned char) c & 0x7f;
+         iso2022jp = 2;        /* shift to ISO 2022 JIS */
+       }
+       break;
+      case 2:                  /* ISO 2022 JIS */
+       if (c > 0x7f) {         /* <hi> <lo> */
+         *t++ = (unsigned char) (c >> 8) & 0x7f;
+         *t++ = (unsigned char) c & 0x7f;
+       }
+       else {                  /* ASCII character */
+         *t++ = I2C_ESC;       /* ESC ( J <ch> */
+         *t++ = I2C_G0_94;
+         *t++ = I2CS_94_JIS_ROMAN;
+         *t++ = (unsigned char) c;
+         iso2022jp = 1;        /* shift to ISO 2022 Roman */
+       }
+       break;
+      }
+    }
+    if (iso2022jp == 2) {      /* ISO-2022-JP string must end in Roman */
+      *t++ = I2C_ESC;          /* ESC ( J */
+      *t++ = I2C_G0_94;
+      *t++ = I2CS_94_JIS_ROMAN;
+    }
+    *t++ = NIL;                        /* tie off returned data */
+    return LONGT;              /* return success */
+  }
+  ret->data = NIL;
+  ret->size = 0;
+  return NIL;                  /* failure */
+}
+\f
+/* Calculate size of convertsion of UTF-8 sized text to charset using rmap
+ * Accepts: source sized text
+ *         conversion rmap
+ *         pointer to returned sized text
+ *         substitute character if not in rmap, else NIL to return failure
+ *         ISO-2022-JP conversion flag
+ * Returns size+1 if successful, NIL if failure
+ *
+ * This routine doesn't try to handle to all possible charsets; in particular
+ * it doesn't support other Unicode encodings or any ISO 2022 other than
+ * ISO-2022-JP.
+ */
+
+unsigned long utf8_rmapsize (SIZEDTEXT *text,unsigned short *rmap,
+                            unsigned long errch,long iso2022jp)
+{
+  unsigned long i,u,c;
+  unsigned long ret = 1;       /* terminating NUL */
+  unsigned char *s = text->data;
+  if (iso2022jp) iso2022jp = 1;        /* start non-zero ISO-2022-JP state at 1 */
+  for (i = text->size; i;) if ((u = utf8_get (&s,&i)) != UCS2_BOM) {
+    if ((u & U8GM_NONBMP) || (((c = rmap[u]) == NOCHAR) && !(c = errch)))
+      return NIL;              /* not in BMP, or NOCHAR and no err char */
+    switch (iso2022jp) {       /* depends upon ISO 2022 mode */
+    case 0:                    /* ISO 2022 not in effect */
+      ret += (c > 0xff) ? 2 : 1;
+      break;
+    case 1:                    /* ISO 2022 Roman */
+      if (c < 0x80) ret += 1;  /* <ch> */
+      else {                   /* JIS character */
+       ret += 5;               /* ESC $ B <hi> <lo> */
+       iso2022jp = 2;          /* shift to ISO 2022 JIS */
+      }
+      break;
+    case 2:                    /* ISO 2022 JIS */
+      if (c > 0x7f) ret += 2;  /* <hi> <lo> */
+      else {                   /* ASCII character */
+       ret += 4;               /* ESC ( J <ch> */
+       iso2022jp = 1;          /* shift to ISO 2022 Roman */
+      }
+      break;
+    }
+  }
+  if (iso2022jp == 2) {                /* ISO-2022-JP string must end in Roman */
+    ret += 3;                  /* ESC ( J */
+    iso2022jp = 1;             /* reset state to Roman */
+  }
+  return ret;
+}
+\f
+/* Convert UCS-4 to charset using rmap
+ * Accepts: source UCS-4 character(s)
+ *         numver of UCS-4 characters
+ *         conversion rmap
+ *         pointer to returned sized text
+ *         substitute character if not in rmap, else NIL to return failure
+ * Returns T if successful, NIL if failure
+ *
+ * Currently only supports BMP characters, and does not support ISO-2022
+ */
+
+long ucs4_rmaptext (unsigned long *ucs4,unsigned long len,unsigned short *rmap,
+                   SIZEDTEXT *ret,unsigned long errch)
+{
+  long size = ucs4_rmaplen (ucs4,len,rmap,errch);
+  return (size >= 0) ?         /* build in newly-created buffer */
+    ucs4_rmapbuf (ret->data = (unsigned char *) fs_get ((ret->size = size) +1),
+                 ucs4,len,rmap,errch) : NIL;
+}
+\f
+/* Return size of UCS-4 string converted to other CS via rmap
+ * Accepts: source UCS-4 character(s)
+ *         numver of UCS-4 characters
+ *         conversion rmap
+ *         substitute character if not in rmap, else NIL to return failure
+ * Returns: length if success, negative if failure (no-convert)
+ */
+
+long ucs4_rmaplen (unsigned long *ucs4,unsigned long len,unsigned short *rmap,
+                  unsigned long errch)
+{
+  long ret;
+  unsigned long i,u,c;
+                               /* count non-BOM characters */
+  for (ret = 0,i = 0; i < len; ++i) if ((u = ucs4[i]) != UCS2_BOM) {
+    if ((u & U8GM_NONBMP) || (((c = rmap[u]) == NOCHAR) && !(c = errch)))
+      return -1;               /* not in BMP, or NOCHAR and no err char? */
+    ret += (c > 0xff) ? 2 : 1;
+  }
+  return ret;
+}
+
+
+/* Stuff buffer with UCS-4 string converted to other CS via rmap
+ * Accepts: destination buffer
+ *         source UCS-4 character(s)
+ *         number of UCS-4 characters
+ *         conversion rmap
+ *         substitute character if not in rmap, else NIL to return failure
+ * Returns: T, always
+ */
+
+long ucs4_rmapbuf (unsigned char *t,unsigned long *ucs4,unsigned long len,
+                  unsigned short *rmap,unsigned long errch)
+{
+  unsigned long i,u,c;
+                               /* convert non-BOM characters */
+  for (i = 0; i < len; ++i) if ((u = ucs4[i]) != UCS2_BOM) {
+                               /* substitute error character for NOCHAR */
+    if ((u & U8GM_NONBMP) || ((c = rmap[u]) == NOCHAR)) c = errch;
+                               /* two-byte character? */
+    if (c > 0xff) *t++ = (unsigned char) (c >> 8);
+                               /* single-byte or low-byte of two-byte */
+    *t++ = (unsigned char) (c & 0xff);
+  }
+  *t++ = NIL;                  /* tie off returned data */
+  return LONGT;
+}
+\f
+/* Return UCS-4 Unicode character from UTF-8 string
+ * Accepts: pointer to string
+ *         remaining octets in string
+ * Returns: UCS-4 character with pointer and count updated
+ *         or error code with pointer and count unchanged
+ */
+
+unsigned long utf8_get (unsigned char **s,unsigned long *i)
+{
+  unsigned char *t = *s;
+  unsigned long j = *i;
+                               /* decode raw UTF-8 string */
+  unsigned long ret = utf8_get_raw (&t,&j);
+  if (ret & U8G_ERROR);                /* invalid raw UTF-8 decoding? */
+                               /* no, is it surrogate? */
+  else if ((ret >= UTF16_SURR) && (ret <= UTF16_MAXSURR)) ret = U8G_SURROGA;
+                               /* or in non-Unicode ISO 10646 space? */
+  else if (ret > UCS4_MAXUNICODE) ret = U8G_NOTUNIC;
+  else {
+    *s = t;                    /* all is well, update pointer */
+    *i = j;                    /* and counter */
+  }
+  return ret;                  /* return value */
+}
+\f
+/* Return raw (including non-Unicode) UCS-4 character from UTF-8 string
+ * Accepts: pointer to string
+ *         remaining octets in string
+ * Returns: UCS-4 character with pointer and count updated
+ *         or error code with pointer and count unchanged
+ */
+
+unsigned long utf8_get_raw (unsigned char **s,unsigned long *i)
+{
+  unsigned char c,c1;
+  unsigned char *t = *s;
+  unsigned long j = *i;
+  unsigned long ret = U8G_NOTUTF8;
+  int more = 0;
+  do {                         /* make sure have source octets available */
+    if (!j--) return more ? U8G_ENDSTRI : U8G_ENDSTRG;
+                               /* UTF-8 continuation? */
+    else if (((c = *t++) > 0x7f) && (c < 0xc0)) {
+                               /* continuation when not in progress */
+      if (!more) return U8G_BADCONT;
+      --more;                  /* found a continuation octet */
+      ret <<= 6;               /* shift current value by 6 bits */
+      ret |= c & 0x3f;         /* merge continuation octet */
+    }
+                               /* incomplete UTF-8 character */
+    else if (more) return U8G_INCMPLT;
+    else {                     /* start of sequence */
+      c1 = j ? *t : 0xbf;      /* assume valid continuation if incomplete */
+      if (c < 0x80) ret = c;   /* U+0000 - U+007f */
+      else if (c < 0xc2);      /* c0 and c1 never valid */
+      else if (c < 0xe0) {     /* U+0080 - U+07ff */
+       if (c &= 0x1f) more = 1;
+      }
+      else if (c < 0xf0) {     /* U+0800 - U+ffff */
+       if ((c &= 0x0f) || (c1 >= 0xa0)) more = 2;
+      }
+      else if (c < 0xf8) {     /* U+10000 - U+10ffff (and 110000 - 1fffff) */
+       if ((c &= 0x07) || (c1 >= 0x90)) more = 3;
+      }
+      else if (c < 0xfc) {     /* ISO 10646 200000 - 3ffffff */
+       if ((c &= 0x03) || (c1 >= 0x88)) more = 4;
+      }
+      else if (c < 0xfe) {     /* ISO 10646 4000000 - 7fffffff */
+       if ((c &= 0x01) || (c1 >= 0x84)) more = 5;
+      }
+                               /* fe and ff never valid */
+      if (more) {              /* multi-octet, make sure more to come */
+       if (!j) return U8G_ENDSTRI;
+       ret = c;                /* continuation needed, save start bits */
+      }
+    }
+  } while (more);
+  if (!(ret & U8G_ERROR)) {    /* success return? */
+    *s = t;                    /* yes, update pointer */
+    *i = j;                    /* and counter */
+  }
+  return ret;                  /* return value */
+}
+\f
+/* Return UCS-4 character from named charset string
+ * Accepts: charset
+ *         pointer to string
+ *         remaining octets in string
+ * Returns: UCS-4 character with pointer and count updated, negative if error
+ *
+ * Error codes are the same as utf8_get().
+ */
+
+unsigned long ucs4_cs_get (CHARSET *cs,unsigned char **s,unsigned long *i)
+{
+  unsigned char c,c1,ku,ten;
+  unsigned long ret,d;
+  unsigned char *t = *s;
+  unsigned long j = *i;
+  struct utf8_eucparam *p1,*p2,*p3;
+  if (j--) c = *t++;           /* get first octet */
+  else return U8G_ENDSTRG;     /* empty string */
+  switch (cs->type) {          /* convert if type known */
+  case CT_UTF8:                        /* variable UTF-8 encoded Unicode no table */
+    return utf8_get (s,i);
+  case CT_ASCII:               /* 7-bit ASCII no table */
+    if (c >= 0x80) return U8G_NOTUTF8;
+  case CT_1BYTE0:              /* 1 byte no table */
+    ret = c;                   /* identity */
+    break;
+  case CT_1BYTE:               /* 1 byte ASCII + table 0x80-0xff */
+    ret = (c > 0x80) ? ((unsigned short *) cs->tab)[c & BITS7] : c;
+    break;
+  case CT_1BYTE8:              /* 1 byte table 0x00 - 0xff */
+    ret = ((unsigned short *) cs->tab)[c];
+    break;
+\f
+  case CT_EUC:                 /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+    if (c & BIT8) {
+      p1 = (struct utf8_eucparam *) cs->tab;
+      p2 = p1 + 1;
+      p3 = p1 + 2;
+      if (j--) c1 = *t++;      /* get second octet */
+      else return U8G_ENDSTRI;
+      if (!(c1 & BIT8)) return U8G_NOTUTF8;
+      switch (c) {             /* check 8bit code set */
+      case EUC_CS2:            /* CS2 */
+       if (p2->base_ku) {      /* CS2 set up? */
+         if (p2->base_ten) {   /* yes, multibyte? */
+           if (j--) c = *t++;  /* get second octet */
+           else return U8G_ENDSTRI;
+           if ((c & BIT8) &&
+               ((ku = (c1 & BITS7) - p2->base_ku) < p2->max_ku) &&
+               ((ten = (c & BITS7) - p2->base_ten) < p2->max_ten)) {
+             ret = ((unsigned short *) p2->tab)[(ku*p2->max_ten) + ten];
+             break;
+           }
+         }
+         else if ((c1 >= p2->base_ku) && (c1 < p2->max_ku)) {
+           ret = c1 + ((unsigned long) p2->tab);
+           break;
+         }
+       }
+       return U8G_NOTUTF8;     /* CS2 not set up or bogus */
+      case EUC_CS3:            /* CS3 */
+       if (p3->base_ku) {      /* CS3 set up? */
+         if (p3->base_ten) {   /* yes, multibyte? */
+           if (j--) c = *t++;  /* get second octet */
+           else return U8G_ENDSTRI;
+           if ((c & BIT8) &&
+               ((ku = (c1 & BITS7) - p3->base_ku) < p3->max_ku) &&
+               ((ten = (c & BITS7) - p3->base_ten) < p3->max_ten)) {
+             ret = ((unsigned short *) p3->tab)[(ku*p3->max_ten) + ten];
+             break;
+           }
+         }
+         else if ((c1 >= p3->base_ku) && (c1 < p3->max_ku)) {
+           ret = c1 + ((unsigned long) p3->tab);
+           break;
+         }
+       }
+       return U8G_NOTUTF8;     /* CS3 not set up or bogus */
+      default:
+       if (((ku = (c & BITS7) - p1->base_ku) >= p1->max_ku) ||
+           ((ten = (c1 & BITS7) - p1->base_ten) >= p1->max_ten))
+         return U8G_NOTUTF8;
+       ret = ((unsigned short *) p1->tab)[(ku*p1->max_ten) + ten];
+               /* special hack for JIS X 0212: merge rows less than 10 */
+       if ((ret == UBOGON) && ku && (ku < 10) && p3->tab && p3->base_ten)
+         ret = ((unsigned short *) p3->tab)
+           [((ku - (p3->base_ku - p1->base_ku))*p3->max_ten) + ten];
+       break;
+      }
+    }
+    else ret = c;              /* ASCII character */
+    break;
+\f
+  case CT_DBYTE:               /* 2 byte ASCII + utf8_eucparam */
+    if (c & BIT8) {            /* double-byte character? */
+      p1 = (struct utf8_eucparam *) cs->tab;
+      if (j--) c1 = *t++;      /* get second octet */
+      else return U8G_ENDSTRI;
+      if (((ku = c - p1->base_ku) < p1->max_ku) &&
+         ((ten = c1 - p1->base_ten) < p1->max_ten))
+       ret = ((unsigned short *) p1->tab)[(ku*p1->max_ten) + ten];
+      else return U8G_NOTUTF8;
+    }
+    else ret = c;              /* ASCII character */
+    break;
+  case CT_DBYTE2:              /* 2 byte ASCII + utf8_eucparam plane1/2 */
+    if (c & BIT8) {            /* double-byte character? */
+      p1 = (struct utf8_eucparam *) cs->tab;
+      p2 = p1 + 1;
+      if (j--) c1 = *t++;      /* get second octet */
+      else return U8G_ENDSTRI;
+      if (c1 & BIT8) {         /* high vs. low plane */
+       if ((ku = c - p2->base_ku) < p2->max_ku &&
+           ((ten = c1 - p2->base_ten) < p2->max_ten))
+         ret = ((unsigned short *) p1->tab)
+           [(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten];
+       else return U8G_NOTUTF8;
+      }
+      else if ((ku = c - p1->base_ku) < p1->max_ku &&
+              ((ten = c1 - p1->base_ten) < p1->max_ten))
+         ret = ((unsigned short *) p1->tab)
+           [(ku*(p1->max_ten + p2->max_ten)) + ten];
+      else return U8G_NOTUTF8;
+    }
+    else ret = c;              /* ASCII character */
+    break;
+  case CT_SJIS:                        /* 2 byte Shift-JIS encoded JIS no table */
+                               /* compromise - do yen sign but not overline */
+    if (!(c & BIT8)) ret = (c == JISROMAN_YEN) ? UCS2_YEN : c;
+                               /* half-width katakana? */
+    else if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) ret = c + KANA_8;
+    else {                     /* Shift-JIS */
+      if (j--) c1 = *t++;      /* get second octet */
+      else return U8G_ENDSTRI;
+      SJISTOJIS (c,c1);
+      c = JISTOUNICODE (c,c1,ku,ten);
+    }
+    break;
+\f
+  case CT_UCS2:                        /* 2 byte 16-bit Unicode no table */
+    ret = c << 8;
+    if (j--) c = *t++;         /* get second octet */
+    else return U8G_ENDSTRI;   /* empty string */
+    ret |= c;
+    break;
+  case CT_UCS4:                        /* 4 byte 32-bit Unicode no table */
+    if (c & 0x80) return U8G_NOTUTF8;
+    if (j < 3) return U8G_ENDSTRI;
+    j -= 3;                    /* count three octets */
+    ret = c << 24;
+    ret |= (*t++) << 16;
+    ret |= (*t++) << 8;
+    ret |= (*t++);
+    break;
+  case CT_UTF16:               /* variable UTF-16 encoded Unicode no table */
+    ret = c << 8;
+    if (j--) c = *t++;         /* get second octet */
+    else return U8G_ENDSTRI;   /* empty string */
+    ret |= c;
+                               /* surrogate? */
+    if ((ret >= UTF16_SURR) && (ret <= UTF16_MAXSURR)) {
+                               /* invalid first surrogate */
+      if ((ret > UTF16_SURRHEND) || (j < 2)) return U8G_NOTUTF8;
+      j -= 2;                  /* count two octets */
+      d = (*t++) << 8;         /* first octet of second surrogate */
+      d |= *t++;               /* second octet of second surrogate */
+      if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) return U8G_NOTUTF8;
+      ret = UTF16_BASE + ((ret & UTF16_MASK) << UTF16_SHIFT) +
+       (d & UTF16_MASK);
+    }
+    break;
+  default:                     /* unknown/unsupported character set type */
+    return U8G_NOTUTF8;
+  }
+  *s = t;                      /* update pointer and counter */
+  *i = j;
+  return ret;
+}
+\f
+/* Produce charset validity map for BMP
+ * Accepts: list of charsets to map
+ * Returns: validity map, indexed by BMP codepoint
+ *
+ * Bit 0x1 is the "not-CJK" character bit
+ */
+
+unsigned long *utf8_csvalidmap (char *charsets[])
+{
+  unsigned short u,*tab;
+  unsigned int m,ku,ten;
+  unsigned long i,csi,csb;
+  struct utf8_eucparam *param,*p2;
+  char *s;
+  const CHARSET *cs;
+  unsigned long *ret = (unsigned long *)
+    fs_get (i = 0x10000 * sizeof (unsigned long));
+  memset (ret,0,i);            /* zero the entire vector */
+                               /* mark all the non-CJK codepoints */
+       /* U+0000 - U+2E7F non-CJK */
+  for (i = 0; i < 0x2E7F; ++i) ret[i] = 0x1;
+       /* U+2E80 - U+2EFF CJK Radicals Supplement
+        * U+2F00 - U+2FDF Kangxi Radicals
+        * U+2FE0 - U+2FEF unassigned
+        * U+2FF0 - U+2FFF Ideographic Description Characters
+        * U+3000 - U+303F CJK Symbols and Punctuation
+        * U+3040 - U+309F Hiragana
+        * U+30A0 - U+30FF Katakana
+        * U+3100 - U+312F BoPoMoFo
+        * U+3130 - U+318F Hangul Compatibility Jamo
+        * U+3190 - U+319F Kanbun
+        * U+31A0 - U+31BF BoPoMoFo Extended
+        * U+31C0 - U+31EF CJK Strokes
+        * U+31F0 - U+31FF Katakana Phonetic Extensions
+        * U+3200 - U+32FF Enclosed CJK Letters and Months
+        * U+3300 - U+33FF CJK Compatibility
+        * U+3400 - U+4DBF CJK Unified Ideographs Extension A
+        * U+4DC0 - U+4DFF Yijing Hexagram Symbols
+        * U+4E00 - U+9FFF CJK Unified Ideographs
+        * U+A000 - U+A48F Yi Syllables
+        * U+A490 - U+A4CF Yi Radicals
+        * U+A700 - U+A71F Modifier Tone Letters
+        */
+  for (i = 0xa720; i < 0xabff; ++i) ret[i] = 0x1;
+       /* U+AC00 - U+D7FF Hangul Syllables */
+  for (i = 0xd800; i < 0xf8ff; ++i) ret[i] = 0x1;
+       /* U+F900 - U+FAFF CJK Compatibility Ideographs */
+  for (i = 0xfb00; i < 0xfe2f; ++i) ret[i] = 0x1;
+       /* U+FE30 - U+FE4F CJK Compatibility Forms
+        * U+FE50 - U+FE6F Small Form Variants (for CNS 11643)
+        */
+  for (i = 0xfe70; i < 0xfeff; ++i) ret[i] = 0x1;
+       /* U+FF00 - U+FFEF CJK Compatibility Ideographs */
+  for (i = 0xfff0; i < 0x10000; ++i) ret[i] = 0x1;
+\f
+                               /* for each supplied charset */
+  for (csi = 1; ret && charsets && (s = charsets[csi - 1]); ++csi) {
+                               /* substitute EUC-JP for ISO-2022-JP */
+    if (!compare_cstring (s,"ISO-2022-JP")) s = "EUC-JP";
+                               /* look up charset */
+    if (cs = utf8_charset (s)) {
+      csb = 1 << csi;          /* charset bit */
+      switch (cs->type) {
+      case CT_ASCII:           /* 7-bit ASCII no table */
+      case CT_1BYTE0:          /* 1 byte no table */
+      case CT_1BYTE:           /* 1 byte ASCII + table 0x80-0xff */
+      case CT_1BYTE8:          /* 1 byte table 0x00 - 0xff */
+      case CT_EUC:             /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+      case CT_DBYTE:           /* 2 byte ASCII + utf8_eucparam */
+      case CT_DBYTE2:          /* 2 byte ASCII + utf8_eucparam plane1/2 */
+      case CT_SJIS:            /* 2 byte Shift-JIS */
+                               /* supported charset type, all ASCII is OK */
+       for (i = 0; i < 128; ++i) ret[i] |= csb;
+       break;
+      default:                 /* unsupported charset type */
+       fs_give ((void **) &ret);
+       break;
+      }
+                               /* now do additional operations */
+      if (ret) switch (cs->type) {
+      case CT_1BYTE0:          /* 1 byte no table */
+       for (i = 128; i < 256; i++) ret[i] |= csb;
+       break;
+      case CT_1BYTE:           /* 1 byte ASCII + table 0x80-0xff */
+       for (tab = (unsigned short *) cs->tab,i = 128; i < 256; i++)
+         if (tab[i & BITS7] != UBOGON) ret[tab[i & BITS7]] |= csb;
+       break;
+      case CT_1BYTE8:          /* 1 byte table 0x00 - 0xff */
+       for (tab = (unsigned short *) cs->tab,i = 0; i < 256; i++)
+         if (tab[i] != UBOGON) ret[tab[i]] |= csb;
+      break;
+      case CT_EUC:             /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+       for (param = (struct utf8_eucparam *) cs->tab,
+              tab = (unsigned short *) param->tab, ku = 0;
+            ku < param->max_ku; ku++)
+         for (ten = 0; ten < param->max_ten; ten++)
+           if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON)
+             ret[u] |= csb;
+       break;
+\f
+      case CT_DBYTE:           /* 2 byte ASCII + utf8_eucparam */
+       for (param = (struct utf8_eucparam *) cs->tab,
+              tab = (unsigned short *) param->tab, ku = 0;
+            ku < param->max_ku; ku++)
+         for (ten = 0; ten < param->max_ten; ten++)
+           if ((u = tab[(ku * param->max_ten) + ten]) != UBOGON)
+             ret[u] |= csb;
+      break;
+      case CT_DBYTE2:          /* 2 byte ASCII + utf8_eucparam plane1/2 */
+       param = (struct utf8_eucparam *) cs->tab;
+       p2 = param + 1;         /* plane 2 parameters */
+                               /* only ten parameters should differ */
+       if ((param->base_ku != p2->base_ku) || (param->max_ku != p2->max_ku))
+         fatal ("ku definition error for CT_DBYTE2 charset");
+                               /* total codepoints in each ku */
+       m = param->max_ten + p2->max_ten;
+       tab = (unsigned short *) param->tab;
+       for (ku = 0; ku < param->max_ku; ku++) {
+         for (ten = 0; ten < param->max_ten; ten++)
+           if ((u = tab[(ku * m) + ten]) != UBOGON)
+             ret[u] |= csb;
+         for (ten = 0; ten < p2->max_ten; ten++)
+           if ((u = tab[(ku * m) + param->max_ten + ten]) != UBOGON)
+             ret[u] |= csb;
+       }
+       break;
+      case CT_SJIS:            /* 2 byte Shift-JIS */
+       for (ku = 0; ku < MAX_JIS0208_KU; ku++)
+         for (ten = 0; ten < MAX_JIS0208_TEN; ten++)
+           if ((u = jis0208tab[ku][ten]) != UBOGON) ret[u] |= csb;
+                               /* JIS hankaku katakana */
+       for (u = 0; u < (MAX_KANA_8 - MIN_KANA_8); u++)
+         ret[UCS2_KATAKANA + u] |= csb;
+       break;
+      }
+    }
+                               /* invalid charset, punt */
+    else fs_give ((void **) &ret);
+  }
+  return ret;
+}
+\f
+/* Infer charset from unlabelled sized text
+ * Accepts: sized text
+ * Returns: charset if one inferred, or NIL if unknown
+ */
+
+const CHARSET *utf8_infercharset (SIZEDTEXT *src)
+{
+  long iso2022jp = NIL;
+  long eightbit = NIL;
+  unsigned long i;
+                               /* look for ISO 2022 */
+  if (src) for (i = 0; i < src->size; i++) {
+                               /* ESC sequence? */
+    if ((src->data[i] == I2C_ESC) && (++i < src->size)) switch (src->data[i]) {
+    case I2C_MULTI:            /* yes, multibyte? */
+      if (++i < src->size) switch (src->data[i]) {
+      case I2CS_94x94_JIS_OLD: /* JIS X 0208-1978 */
+      case I2CS_94x94_JIS_NEW: /* JIS X 0208-1983 */
+      case I2CS_94x94_JIS_EXT: /* JIS X 0212-1990 (kludge...) */
+       iso2022jp = T;          /* found an ISO-2022-JP sequence */
+       break;
+      default:                 /* other multibyte */
+       return NIL;             /* definitely invalid */
+      }
+      break;
+    case I2C_G0_94:            /* single byte */
+      if (++i < src->size) switch (src->data[i]) {
+      case I2CS_94_JIS_BUGROM: /* in case old buggy software */
+      case I2CS_94_JIS_ROMAN:  /* JIS X 0201-1976 left half */
+      case I2CS_94_ASCII:      /* ASCII */
+      case I2CS_94_BRITISH:    /* good enough for gov't work */
+       break;
+      default:                 /* other 94 single byte */
+       return NIL;             /* definitely invalid */
+      }
+    }
+                               /* if possible UTF-8 and not ISO-2022-JP */
+    else if (!iso2022jp && (eightbit >= 0) && (src->data[i] & BIT8) &&
+            (eightbit = utf8_validate (src->data + i,src->size - i)) > 0)
+      i += eightbit - 1;       /* skip past all but last of UTF-8 char */
+  }
+                               /* ISO-2022-JP overrides other guesses */
+  if (iso2022jp) return utf8_charset ("ISO-2022-JP");
+  if (eightbit > 0) return utf8_charset ("UTF-8");
+  return eightbit ? NIL : utf8_charset ("US-ASCII");
+}
+
+
+/* Validate that character at this position is UTF-8
+ * Accepts: string pointer
+ *         size of remaining string
+ * Returns: size of UTF-8 character in octets or -1 if not UTF-8
+ */
+
+long utf8_validate (unsigned char *s,unsigned long i)
+{
+  unsigned long j = i;
+  return (utf8_get (&s,&i) & U8G_ERROR) ? -1 : j - i;
+}
+\f
+/* Convert ISO 8859-1 to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         canonicalization function
+ */
+
+void utf8_text_1byte0 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c;
+  for (ret->size = i = 0; i < text->size;) {
+    c = text->data[i++];
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL;
+  for (i = 0; i < text->size;) {
+    c = text->data[i++];
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+
+
+/* Convert single byte ASCII+8bit character set sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         conversion table
+ *         canonicalization function
+ */
+
+void utf8_text_1byte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv,
+                     ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c;
+  unsigned short *tbl = (unsigned short *) tab;
+  for (ret->size = i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) c = tbl[c & BITS7];
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL;
+  for (i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) c = tbl[c & BITS7];
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+\f
+/* Convert single byte 8bit character set sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         conversion table
+ *         canonicalization function
+ */
+
+void utf8_text_1byte8 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv,
+                      ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c;
+  unsigned short *tbl = (unsigned short *) tab;
+  for (ret->size = i = 0; i < text->size;) {
+    c = tbl[text->data[i++]];
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL;
+  for (i = 0; i < text->size;) {
+    c = tbl[text->data[i++]];
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+\f
+/* Convert EUC sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         EUC parameter table
+ *         canonicalization function
+ */
+
+void utf8_text_euc (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv,
+                   ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int pass,c,c1,ku,ten;
+  struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab;
+  struct utf8_eucparam *p2 = p1 + 1;
+  struct utf8_eucparam *p3 = p1 + 2;
+  unsigned short *t1 = (unsigned short *) p1->tab;
+  unsigned short *t2 = (unsigned short *) p2->tab;
+  unsigned short *t3 = (unsigned short *) p3->tab;
+  for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) {
+    for (i = 0; i < text->size;) {
+                               /* not CS0? */
+      if ((c = text->data[i++]) & BIT8) {
+                               /* yes, must have another high byte */
+       if ((i >= text->size) || !((c1 = text->data[i++]) & BIT8))
+         c = UBOGON;           /* out of space or bogon */
+       else switch (c) {       /* check 8bit code set */
+       case EUC_CS2:           /* CS2 */
+         if (p2->base_ku) {    /* CS2 set up? */
+           if (p2->base_ten)   /* yes, multibyte? */
+             c = ((i < text->size) && ((c = text->data[i++]) & BIT8) &&
+                  ((ku = (c1 & BITS7) - p2->base_ku) < p2->max_ku) &&
+                  ((ten = (c & BITS7) - p2->base_ten) < p2->max_ten)) ?
+                    t2[(ku*p2->max_ten) + ten] : UBOGON;
+           else c = ((c1 >= p2->base_ku) && (c1 < p2->max_ku)) ?
+             c1 + ((unsigned long) p2->tab) : UBOGON;
+         }       
+         else {                /* CS2 not set up */
+           c = UBOGON;         /* swallow byte, say bogon */
+           if (i < text->size) i++;
+         }
+         break;
+       case EUC_CS3:           /* CS3 */
+         if (p3->base_ku) {    /* CS3 set up? */
+           if (p3->base_ten)   /* yes, multibyte? */
+             c = ((i < text->size) && ((c = text->data[i++]) & BIT8) &&
+                  ((ku = (c1 & BITS7) - p3->base_ku) < p3->max_ku) &&
+                  ((ten = (c & BITS7) - p3->base_ten) < p3->max_ten)) ?
+                    t3[(ku*p3->max_ten) + ten] : UBOGON;
+           else c = ((c1 >= p3->base_ku) && (c1 < p3->max_ku)) ?
+             c1 + ((unsigned long) p3->tab) : UBOGON;
+         }       
+         else {                /* CS3 not set up */
+           c = UBOGON;         /* swallow byte, say bogon */
+           if (i < text->size) i++;
+         }
+         break;
+\f
+       default:
+         if (((ku = (c & BITS7) - p1->base_ku) >= p1->max_ku) ||
+             ((ten = (c1 & BITS7) - p1->base_ten) >= p1->max_ten)) c = UBOGON;
+         else if (((c = t1[(ku*p1->max_ten) + ten]) == UBOGON) &&
+                  /* special hack for JIS X 0212: merge rows less than 10 */
+                  ku && (ku < 10) && t3 && p3->base_ten)
+           c = t3[((ku - (p3->base_ku - p1->base_ku))*p3->max_ten) + ten];
+       }
+      }
+                               /* convert if second pass */
+      if (pass) UTF8_WRITE_BMP (s,c,cv,de)
+      else UTF8_COUNT_BMP (ret->size,c,cv,de);
+    }
+    if (!pass) (s = ret->data = (unsigned char *)
+               fs_get (ret->size + 1))[ret->size] =NIL;
+  }
+}
+
+
+/* Convert ASCII + double-byte sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         conversion table
+ *         canonicalization function
+ */
+
+void utf8_text_dbyte (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv,
+                     ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c,c1,ku,ten;
+  struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab;
+  unsigned short *t1 = (unsigned short *) p1->tab;
+  for (ret->size = i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+                               /* special hack for GBK: 0x80 is Euro */
+      if ((c == 0x80) && (t1 == (unsigned short *) gb2312tab)) c = UCS2_EURO;
+      else c = ((i < text->size) && (c1 = text->data[i++]) &&
+               ((ku = c - p1->base_ku) < p1->max_ku) &&
+               ((ten = c1 - p1->base_ten) < p1->max_ten)) ?
+            t1[(ku*p1->max_ten) + ten] : UBOGON;
+    }
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+                               /* special hack for GBK: 0x80 is Euro */
+      if ((c == 0x80) && (t1 == (unsigned short *) gb2312tab)) c = UCS2_EURO;
+      else c = ((i < text->size) && (c1 = text->data[i++]) &&
+               ((ku = c - p1->base_ku) < p1->max_ku) &&
+               ((ten = c1 - p1->base_ten) < p1->max_ten)) ?
+            t1[(ku*p1->max_ten) + ten] : UBOGON;
+    }
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+\f
+/* Convert ASCII + double byte 2 plane sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         conversion table
+ *         canonicalization function
+ */
+
+void utf8_text_dbyte2 (SIZEDTEXT *text,SIZEDTEXT *ret,void *tab,ucs4cn_t cv,
+                      ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c,c1,ku,ten;
+  struct utf8_eucparam *p1 = (struct utf8_eucparam *) tab;
+  struct utf8_eucparam *p2 = p1 + 1;
+  unsigned short *t = (unsigned short *) p1->tab;
+  for (ret->size = i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+      if ((i >= text->size) || !(c1 = text->data[i++]))
+       c = UBOGON;             /* out of space or bogon */
+      else if (c1 & BIT8)      /* high vs. low plane */
+       c = ((ku = c - p2->base_ku) < p2->max_ku &&
+            ((ten = c1 - p2->base_ten) < p2->max_ten)) ?
+              t[(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten] :UBOGON;
+      else c = ((ku = c - p1->base_ku) < p1->max_ku &&
+               ((ten = c1 - p1->base_ten) < p1->max_ten)) ?
+                 t[(ku*(p1->max_ten + p2->max_ten)) + ten] : UBOGON;
+    }
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+      if ((i >= text->size) || !(c1 = text->data[i++]))
+       c = UBOGON;             /* out of space or bogon */
+      else if (c1 & BIT8)      /* high vs. low plane */
+       c = ((ku = c - p2->base_ku) < p2->max_ku &&
+            ((ten = c1 - p2->base_ten) < p2->max_ten)) ?
+              t[(ku*(p1->max_ten + p2->max_ten)) + p1->max_ten + ten] :UBOGON;
+      else c = ((ku = c - p1->base_ku) < p1->max_ku &&
+               ((ten = c1 - p1->base_ten) < p1->max_ten)) ?
+                 t[(ku*(p1->max_ten + p2->max_ten)) + ten] : UBOGON;
+    }
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+\f
+#ifdef JISTOUNICODE            /* Japanese */
+/* Convert Shift JIS sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to return sized text
+ *         canonicalization function
+ */
+
+void utf8_text_sjis (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,
+                    ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c,c1,ku,ten;
+  for (ret->size = i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+                               /* half-width katakana */
+      if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) c += KANA_8;
+      else if (i >= text->size) c = UBOGON;
+      else {                   /* Shift-JIS */
+       c1 = text->data[i++];
+       SJISTOJIS (c,c1);
+       c = JISTOUNICODE (c,c1,ku,ten);
+      }
+    }
+                               /* compromise - do yen sign but not overline */
+    else if (c == JISROMAN_YEN) c = UCS2_YEN;
+    UTF8_COUNT_BMP (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (i = 0; i < text->size;) {
+    if ((c = text->data[i++]) & BIT8) {
+                               /* half-width katakana */
+      if ((c >= MIN_KANA_8) && (c < MAX_KANA_8)) c += KANA_8;
+      else {                   /* Shift-JIS */
+       c1 = text->data[i++];
+       SJISTOJIS (c,c1);
+       c = JISTOUNICODE (c,c1,ku,ten);
+      }
+    }
+                               /* compromise - do yen sign but not overline */
+    else if (c == JISROMAN_YEN) c = UCS2_YEN;
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+}
+#endif
+\f
+/* Convert ISO-2022 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_2022 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int pass,state,c,co,gi,gl,gr,g[4],ku,ten;
+  for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) {
+    gi = 0;                    /* quell compiler warnings */
+    state = I2S_CHAR;          /* initialize engine */
+    g[0]= g[2] = I2CS_ASCII;   /* G0 and G2 are ASCII */
+    g[1]= g[3] = I2CS_ISO8859_1;/* G1 and G3 are ISO-8850-1 */
+    gl = I2C_G0; gr = I2C_G1;  /* left is G0, right is G1 */
+    for (i = 0; i < text->size;) {
+      c = text->data[i++];
+      switch (state) {         /* dispatch based upon engine state */
+      case I2S_ESC:            /* ESC seen */
+       switch (c) {            /* process intermediate character */
+       case I2C_MULTI:         /* multibyte character? */
+         state = I2S_MUL;      /* mark multibyte flag seen */
+         break;
+        case I2C_SS2:          /* single shift GL to G2 */
+       case I2C_SS2_ALT:       /* Taiwan SeedNet */
+         gl |= I2C_SG2;
+         break;
+        case I2C_SS3:          /* single shift GL to G3 */
+       case I2C_SS3_ALT:       /* Taiwan SeedNet */
+         gl |= I2C_SG3;
+         break;
+        case I2C_LS2:          /* shift GL to G2 */
+         gl = I2C_G2;
+         break;
+        case I2C_LS3:          /* shift GL to G3 */
+         gl = I2C_G3;
+         break;
+        case I2C_LS1R:         /* shift GR to G1 */
+         gr = I2C_G1;
+         break;
+        case I2C_LS2R:         /* shift GR to G2 */
+         gr = I2C_G2;
+         break;
+        case I2C_LS3R:         /* shift GR to G3 */
+         gr = I2C_G3;
+         break;
+       case I2C_G0_94: case I2C_G1_94: case I2C_G2_94: case I2C_G3_94:
+         g[gi = c - I2C_G0_94] = (state == I2S_MUL) ? I2CS_94x94 : I2CS_94;
+         state = I2S_INT;      /* ready for character set */
+         break;
+       case I2C_G0_96: case I2C_G1_96: case I2C_G2_96: case I2C_G3_96:
+         g[gi = c - I2C_G0_96] = (state == I2S_MUL) ? I2CS_96x96 : I2CS_96;
+         state = I2S_INT;      /* ready for character set */
+         break;
+       default:                /* bogon */
+         if (pass) *s++ = I2C_ESC,*s++ = c;
+         else ret->size += 2;
+         state = I2S_CHAR;     /* return to previous state */
+       }
+       break;
+\f
+      case I2S_MUL:            /* ESC $ */
+       switch (c) {            /* process multibyte intermediate character */
+       case I2C_G0_94: case I2C_G1_94: case I2C_G2_94: case I2C_G3_94:
+         g[gi = c - I2C_G0_94] = I2CS_94x94;
+         state = I2S_INT;      /* ready for character set */
+         break;
+       case I2C_G0_96: case I2C_G1_96: case I2C_G2_96: case I2C_G3_96:
+         g[gi = c - I2C_G0_96] = I2CS_96x96;
+         state = I2S_INT;      /* ready for character set */
+         break;
+       default:                /* probably omitted I2CS_94x94 */
+         g[gi = I2C_G0] = I2CS_94x94 | c;
+         state = I2S_CHAR;     /* return to character state */
+       }
+       break;
+      case I2S_INT:
+       state = I2S_CHAR;       /* return to character state */
+       g[gi] |= c;             /* set character set */
+       break;
+\f
+      case I2S_CHAR:           /* character data */
+       switch (c) {
+       case I2C_ESC:           /* ESC character */
+         state = I2S_ESC;      /* see if ISO-2022 prefix */
+         break;
+       case I2C_SI:            /* shift GL to G0 */
+         gl = I2C_G0;
+         break;
+       case I2C_SO:            /* shift GL to G1 */
+         gl = I2C_G1;
+         break;
+        case I2C_SS2_ALT:      /* single shift GL to G2 */
+       case I2C_SS2_ALT_7:
+         gl |= I2C_SG2;
+         break;
+        case I2C_SS3_ALT:      /* single shift GL to G3 */
+       case I2C_SS3_ALT_7:
+         gl |= I2C_SG3;
+         break;
+\f
+       default:                /* ordinary character */
+         co = c;               /* note original character */
+         if (gl & (3 << 2)) {  /* single shifted? */
+           gi = g[gl >> 2];    /* get shifted character set */
+           gl &= 0x3;          /* cancel shift */
+         }
+                               /* select left or right half */
+         else gi = (c & BIT8) ? g[gr] : g[gl];
+         c &= BITS7;           /* make 7-bit */
+         switch (gi) {         /* interpret in character set */
+         case I2CS_ASCII:      /* ASCII */
+           break;              /* easy! */
+         case I2CS_BRITISH:    /* British ASCII */
+                               /* Pound sterling sign */
+           if (c == BRITISH_POUNDSTERLING) c = UCS2_POUNDSTERLING;
+           break;
+         case I2CS_JIS_ROMAN:  /* JIS Roman */
+         case I2CS_JIS_BUGROM: /* old bugs */
+           switch (c) {        /* two exceptions to ASCII */
+           case JISROMAN_YEN:  /* Yen sign */
+             c = UCS2_YEN;
+             break;
+                               /* overline */
+           case JISROMAN_OVERLINE:
+             c = UCS2_OVERLINE;
+             break;
+           }
+           break;
+         case I2CS_JIS_KANA:   /* JIS hankaku katakana */
+           if ((c >= MIN_KANA_7) && (c < MAX_KANA_7)) c += KANA_7;
+           break;
+\f
+         case I2CS_ISO8859_1:  /* Latin-1 (West European) */
+           c |= BIT8;          /* just turn on high bit */
+           break;
+         case I2CS_ISO8859_2:  /* Latin-2 (Czech, Slovak) */
+           c = iso8859_2tab[c];
+           break;
+         case I2CS_ISO8859_3:  /* Latin-3 (Dutch, Turkish) */
+           c = iso8859_3tab[c];
+           break;
+         case I2CS_ISO8859_4:  /* Latin-4 (Scandinavian) */
+           c = iso8859_4tab[c];
+           break;
+         case I2CS_ISO8859_5:  /* Cyrillic */
+           c = iso8859_5tab[c];
+           break;
+         case I2CS_ISO8859_6:  /* Arabic */
+           c = iso8859_6tab[c];
+           break;
+         case I2CS_ISO8859_7:  /* Greek */
+           c = iso8859_7tab[c];
+           break;
+         case I2CS_ISO8859_8:  /* Hebrew */
+           c = iso8859_8tab[c];
+           break;
+         case I2CS_ISO8859_9:  /* Latin-5 (Finnish, Portuguese) */
+           c = iso8859_9tab[c];
+           break;
+         case I2CS_TIS620:     /* Thai */
+           c = tis620tab[c];
+           break;
+         case I2CS_ISO8859_10: /* Latin-6 (Northern Europe) */
+           c = iso8859_10tab[c];
+           break;
+         case I2CS_ISO8859_13: /* Latin-7 (Baltic) */
+           c = iso8859_13tab[c];
+           break;
+         case I2CS_VSCII:      /* Vietnamese */
+           c = visciitab[c];
+           break;
+         case I2CS_ISO8859_14: /* Latin-8 (Celtic) */
+           c = iso8859_14tab[c];
+           break;
+         case I2CS_ISO8859_15: /* Latin-9 (Euro) */
+           c = iso8859_15tab[c];
+           break;
+         case I2CS_ISO8859_16: /* Latin-10 (Baltic) */
+           c = iso8859_16tab[c];
+           break;
+\f
+         default:              /* all other character sets */
+                               /* multibyte character set */
+           if ((gi & I2CS_MUL) && !(c & BIT8) && isgraph (c)) {
+             c = (i < text->size) ? text->data[i++] : 0;
+             switch (gi) {
+#ifdef GBTOUNICODE
+             case I2CS_GB:     /* GB 2312 */
+               co |= BIT8;     /* make into EUC */
+               c |= BIT8;
+               c = GBTOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef JISTOUNICODE
+             case I2CS_JIS_OLD:/* JIS X 0208-1978 */
+             case I2CS_JIS_NEW:/* JIS X 0208-1983 */
+               c = JISTOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef JIS0212TOUNICODE
+             case I2CS_JIS_EXT:/* JIS X 0212-1990 */
+               c = JIS0212TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef KSCTOUNICODE
+             case I2CS_KSC:    /* KSC 5601 */
+               co |= BIT8;     /* make into EUC */
+               c |= BIT8;
+               c = KSCTOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS1TOUNICODE
+             case I2CS_CNS1:   /* CNS 11643 plane 1 */
+               c = CNS1TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS2TOUNICODE
+             case I2CS_CNS2:   /* CNS 11643 plane 2 */
+               c = CNS2TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS3TOUNICODE
+             case I2CS_CNS3:   /* CNS 11643 plane 3 */
+               c = CNS3TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS4TOUNICODE
+             case I2CS_CNS4:   /* CNS 11643 plane 4 */
+               c = CNS4TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS5TOUNICODE
+             case I2CS_CNS5:   /* CNS 11643 plane 5 */
+               c = CNS5TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS6TOUNICODE
+             case I2CS_CNS6:   /* CNS 11643 plane 6 */
+               c = CNS6TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+#ifdef CNS7TOUNICODE
+             case I2CS_CNS7:   /* CNS 11643 plane 7 */
+               c = CNS7TOUNICODE (co,c,ku,ten);
+               break;
+#endif
+             default:          /* unknown multibyte, treat as UCS-2 */
+               c |= (co << 8); /* wrong, but nothing else to do */
+               break;
+             }
+           }
+           else c = co;        /* unknown single byte, treat as 8859-1 */
+         }
+                               /* convert if second pass */
+         if (pass) UTF8_WRITE_BMP (s,c,cv,de)
+         else UTF8_COUNT_BMP (ret->size,c,cv,de);
+       }
+      }
+    }
+    if (!pass) (s = ret->data = (unsigned char *)
+               fs_get (ret->size + 1))[ret->size] = NIL;
+    else if (((unsigned long) (s - ret->data)) != ret->size)
+      fatal ("ISO-2022 to UTF-8 botch");
+  }
+}
+\f
+/* Convert UTF-7 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_utf7 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s;
+  unsigned int c,c1,d,uc,pass,e,e1,state,surrh;
+  for (pass = 0,s = NIL,ret->size = 0; pass <= 1; pass++) {
+    c1 = d = uc = e = e1 = 0;
+    for (i = 0,state = NIL; i < text->size;) {
+      c = text->data[i++];     /* get next byte */
+      switch (state) {
+      case U7_PLUS:            /* previous character was + */
+       if (c == '-') {         /* +- means textual + */
+         c = '+';
+         state = U7_ASCII;     /* revert to ASCII */
+         break;
+       }
+       state = U7_UNICODE;     /* enter Unicode state */
+       e = e1 = 0;             /* initialize Unicode quantum position */
+      case U7_UNICODE:         /* Unicode state */
+       if (c == '-') state = U7_MINUS;
+       else {                  /* decode Unicode */
+         /* don't use isupper/islower since this is ASCII only */
+         if ((c >= 'A') && (c <= 'Z')) c -= 'A';
+         else if ((c >= 'a') && (c <= 'z')) c -= 'a' - 26;
+         else if (isdigit (c)) c -= '0' - 52;
+         else if (c == '+') c = 62;
+         else if (c == '/') c = 63;
+         else state = U7_ASCII;/* end of modified BASE64 */
+       }
+       break;
+      case U7_MINUS:           /* previous character was absorbed - */
+       state = U7_ASCII;       /* revert to ASCII */
+      case U7_ASCII:           /* ASCII state */
+       if (c == '+') state = U7_PLUS;
+       break;
+      }
+\f
+      switch (state) {         /* store character if in character mode */
+      case U7_UNICODE:         /* Unicode */
+       switch (e++) {          /* install based on BASE64 state */
+       case 0:
+         c1 = c << 2;          /* byte 1: high 6 bits */
+         break;
+       case 1:
+         d = c1 | (c >> 4);    /* byte 1: low 2 bits */
+         c1 = c << 4;          /* byte 2: high 4 bits */
+         break;
+       case 2:
+         d = c1 | (c >> 2);    /* byte 2: low 4 bits */
+         c1 = c << 6;          /* byte 3: high 2 bits */
+         break;
+       case 3:
+         d = c | c1;           /* byte 3: low 6 bits */
+         e = 0;                /* reinitialize mechanism */
+         break;
+       }
+       if (e == 1) break;      /* done if first BASE64 state */
+       if (!e1) {              /* first byte of UCS-2 character */
+         uc = (d & 0xff) << 8; /* note first byte */
+         e1 = T;               /* enter second UCS-2 state */
+         break;                /* done */
+       }
+       c = uc | (d & 0xff);    /* build UCS-2 character */
+       e1 = NIL;               /* back to first UCS-2 state, drop in */
+                               /* surrogate pair?  */
+       if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) {
+                               /* save high surrogate for later */
+         if (c < UTF16_SURRL) surrh = c;
+         else c = UTF16_BASE + ((surrh & UTF16_MASK) << UTF16_SHIFT) +
+                (c & UTF16_MASK);
+         break;                /* either way with surrogates, we're done */
+       }
+      case U7_ASCII:           /* just install if ASCII */
+                               /* convert if second pass */
+       if (pass) UTF8_WRITE_BMP (s,c,cv,de)
+       else UTF8_COUNT_BMP (ret->size,c,cv,de);
+      }
+    }
+    if (!pass) (s = ret->data = (unsigned char *)
+               fs_get (ret->size + 1))[ret->size] = NIL;
+    else if (((unsigned long) (s - ret->data)) != ret->size)
+      fatal ("UTF-7 to UTF-8 botch");
+  }
+}
+
+
+/* Convert UTF-8 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_utf8 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i,c;
+  unsigned char *s,*t;
+  for (ret->size = 0, t = text->data, i = text->size; i;) {
+    if ((c = utf8_get (&t,&i)) & U8G_ERROR) {
+      ret->data = text->data;  /* conversion failed */
+      ret->size = text->size;
+      return;
+    }
+    UTF8_COUNT (ret->size,c,cv,de)
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] =NIL;
+  for (t = text->data, i = text->size; i;) {
+    c = utf8_get (&t,&i);
+    UTF8_WRITE (s,c,cv,de)     /* convert UCS-4 to UTF-8 */
+  }
+  if (((unsigned long) (s - ret->data)) != ret->size)
+    fatal ("UTF-8 to UTF-8 botch");
+}
+\f
+/* Convert UCS-2 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_ucs2 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s,*t;
+  unsigned int c;
+  for (ret->size = 0, t = text->data, i = text->size / 2; i; --i) {
+    c = *t++ << 8;
+    c |= *t++;
+    UTF8_COUNT_BMP (ret->size,c,cv,de);
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (t = text->data, i = text->size / 2; i; --i) {
+    c = *t++ << 8;
+    c |= *t++;
+    UTF8_WRITE_BMP (s,c,cv,de) /* convert UCS-2 to UTF-8 */
+  }
+  if (((unsigned long) (s - ret->data)) != ret->size)
+    fatal ("UCS-2 to UTF-8 botch");
+}
+
+
+/* Convert UCS-4 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_ucs4 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s,*t;
+  unsigned long c;
+  for (ret->size = 0, t = text->data, i = text->size / 4; i; --i) {
+    c = *t++ << 24; c |= *t++ << 16; c |= *t++ << 8; c |= *t++;
+    UTF8_COUNT (ret->size,c,cv,de);
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (t = text->data, i = text->size / 2; i; --i) {
+    c = *t++ << 24; c |= *t++ << 16; c |= *t++ << 8; c |= *t++;
+    UTF8_WRITE (s,c,cv,de)     /* convert UCS-4 to UTF-8 */
+  }
+  if (((unsigned long) (s - ret->data)) != ret->size)
+    fatal ("UCS-4 to UTF-8 botch");
+}
+\f
+/* Convert UTF-16 sized text to UTF-8
+ * Accepts: source sized text
+ *         pointer to returned sized text
+ *         canonicalization function
+ */
+
+void utf8_text_utf16 (SIZEDTEXT *text,SIZEDTEXT *ret,ucs4cn_t cv,ucs4de_t de)
+{
+  unsigned long i;
+  unsigned char *s,*t;
+  unsigned long c,d;
+  for (ret->size = 0, t = text->data, i = text->size / 2; i; --i) {
+    c = *t++ << 8;
+    c |= *t++;
+                               /* possible surrogate? */
+    if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) {
+                               /* invalid first surrogate */
+      if ((c > UTF16_SURRHEND) || !i) c = UBOGON;
+      else {                   /* get second surrogate */
+       d = *t++ << 8;
+       d |= *t++;
+       --i;                    /* swallowed another 16-bits */
+                               /* invalid second surrogate */
+       if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) c = UBOGON;
+       else c = UTF16_BASE + ((c & UTF16_MASK) << UTF16_SHIFT) +
+              (d & UTF16_MASK);
+      }
+    }
+    UTF8_COUNT (ret->size,c,cv,de);
+  }
+  (s = ret->data = (unsigned char *) fs_get (ret->size + 1))[ret->size] = NIL;
+  for (t = text->data, i = text->size / 2; i; --i) {
+    c = *t++ << 8;
+    c |= *t++;
+                               /* possible surrogate? */
+    if ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR)) {
+                               /* invalid first surrogate */
+      if ((c > UTF16_SURRHEND) || !i) c = UBOGON;
+      else {                   /* get second surrogate */
+       d = *t++ << 8;
+       d |= *t++;
+       --i;                    /* swallowed another 16-bits */
+                               /* invalid second surrogate */
+       if ((d < UTF16_SURRL) || (d > UTF16_SURRLEND)) c = UBOGON;
+       else c = UTF16_BASE + ((c & UTF16_MASK) << UTF16_SHIFT) +
+              (d & UTF16_MASK);
+      }
+    }
+    UTF8_WRITE (s,c,cv,de)     /* convert UCS-4 to UTF-8 */
+  }
+  if (((unsigned long) (s - ret->data)) != ret->size)
+    fatal ("UTF-16 to UTF-8 botch");
+}
+\f
+/* Size of UCS-4 character, possibly not in BMP, as UTF-8 octets
+ * Accepts: character
+ * Returns: size (0 means bogon)
+ *
+ * Use UTF8_SIZE macro if known to be in the BMP
+ */
+
+unsigned long utf8_size (unsigned long c)
+{
+  if (c < 0x80) return 1;
+  else if (c < 0x800) return 2;
+  else if (c < 0x10000) return 3;
+  else if (c < 0x200000) return 4;
+  else if (c < 0x4000000) return 5;
+  else if (c < 0x80000000) return 6;
+  return 0;
+}
+
+
+/* Put UCS-4 character, possibly not in BMP, as UTF-8 octets
+ * Accepts: destination string pointer
+ *         character
+ * Returns: updated destination pointer
+ *
+ * Use UTF8_PUT_BMP macro if known to be in the BMP
+ */
+
+unsigned char *utf8_put (unsigned char *s,unsigned long c)
+{
+  unsigned char mark[6] = {0x00,0xc0,0xe0,0xf0,0xf8,0xfc};
+  unsigned long size = utf8_size (c);
+  switch (size) {
+  case 6:
+    s[5] = 0x80 | (unsigned char) (c & 0x3f);
+    c >>= 6;
+  case 5:
+    s[4] = 0x80 | (unsigned char) (c & 0x3f);
+    c >>= 6;
+  case 4:
+    s[3] = 0x80 | (unsigned char) (c & 0x3f);
+    c >>= 6;
+  case 3:
+    s[2] = 0x80 | (unsigned char) (c & 0x3f);
+    c >>= 6;
+  case 2:
+    s[1] = 0x80 | (unsigned char) (c & 0x3f);
+    c >>= 6;
+  case 1:
+    *s = mark[size-1] | (unsigned char) (c & 0x7f);
+    break;
+  }
+  return s + size;
+}
+\f
+/* Return title case of a fixed-width UCS-4 character
+ * Accepts: character
+ * Returns: title case of character
+ */
+
+unsigned long ucs4_titlecase (unsigned long c)
+{
+  if (c <= UCS4_TMAPMAX) return ucs4_tmaptab[c];
+  if (c < UCS4_TMAPHIMIN) return c;
+  if (c <= UCS4_TMAPHIMAX) return c - UCS4_TMAPHIMAP;
+  if (c < UCS4_TMAPDESERETMIN) return c;
+  if (c <= UCS4_TMAPDESERETMAX) return c - UCS4_TMAPDESERETMAP;
+  return c;
+}
+
+
+/* Return width of a fixed-width UCS-4 character in planes 0-2
+ * Accepts: character
+ * Returns: width (0, 1, 2) or negative error condition if not valid
+ */
+
+long ucs4_width (unsigned long c)
+{
+  long ret;
+                               /* out of range, not-a-char, or surrogates */
+  if ((c > UCS4_MAXUNICODE) || ((c & 0xfffe) == 0xfffe) ||
+      ((c >= UTF16_SURR) && (c <= UTF16_MAXSURR))) ret = U4W_NOTUNCD;
+                               /* private-use */
+  else if (c >= UCS4_PVTBASE) ret = U4W_PRIVATE;
+                               /* SSP are not printing characters */
+  else if (c >= UCS4_SSPBASE) ret = U4W_SSPCHAR;
+                               /* unassigned planes */
+  else if (c >= UCS4_UNABASE) ret = U4W_UNASSGN;
+                               /* SIP and reserved plane 3 are wide */
+  else if (c >= UCS4_SIPBASE) ret = 2;
+#if (UCS4_WIDLEN != UCS4_SIPBASE)
+#error "UCS4_WIDLEN != UCS4_SIPBASE"
+#endif
+                               /* C0/C1 controls */
+  else if ((c <= UCS2_C0CONTROLEND) ||
+          ((c >= UCS2_C1CONTROL) && (c <= UCS2_C1CONTROLEND)))
+    ret = U4W_CONTROL;
+                               /* BMP and SMP get value from table */
+  else switch (ret = (ucs4_widthtab[(c >> 2)] >> ((3 - (c & 0x3)) << 1)) &0x3){
+  case 0:                      /* zero-width */
+    if (c == 0x00ad) ret = 1;  /* force U+00ad (SOFT HYPHEN) to width 1 */
+  case 1:                      /* single-width */
+  case 2:                      /* double-width */
+    break;
+  case 3:                      /* ambiguous width */
+    ret = (c >= 0x2100) ? 2 : 1;/* need to do something better than this */
+    break;
+  }
+  return ret;
+}
+\f
+/* Return screen width of UTF-8 string
+ * Accepts: string
+ * Returns: width or negative if not valid UTF-8
+ */
+
+long utf8_strwidth (unsigned char *s)
+{
+  unsigned long c,i,ret;
+                               /* go through string */
+  for (ret = 0; *s; ret += ucs4_width (c)) {
+    /* It's alright to give a fake value for the byte count to utf8_get()
+     * since the null of a null-terminated string will stop processing anyway.
+     */
+    i = 6;                     /* fake value */
+    if ((c = utf8_get (&s,&i)) & U8G_ERROR) return -1;
+  }
+  return ret;
+}
+
+
+/* Return screen width of UTF-8 text
+ * Accepts: SIZEDTEXT to string
+ * Returns: width or negative if not valid UTF-8
+ */
+
+long utf8_textwidth (SIZEDTEXT *utf8)
+{
+  unsigned long c;
+  unsigned char *s = utf8->data;
+  unsigned long i = utf8->size;
+  unsigned long ret = 0;
+  while (i) {                  /* while there's a string to process */
+    if ((c = utf8_get (&s,&i)) & U8G_ERROR) return -1;
+    ret += ucs4_width (c);
+  }
+  return ret;
+}
+\f
+/* Decomposition (phew!) */
+
+#define MORESINGLE 1           /* single UCS-4 tail value */
+#define MOREMULTIPLE 2         /* multiple UCS-2 tail values */
+
+struct decomposemore {
+  short type;                  /* type of more */
+  union {
+    unsigned long single;      /* single decomposed value */
+    struct {                   /* multiple BMP values */
+      unsigned short *next;
+      unsigned long count;
+    } multiple;
+  } data;
+};
+
+#define RECURSIVEMORE struct recursivemore
+
+RECURSIVEMORE {
+  struct decomposemore *more;
+  RECURSIVEMORE *next;
+};
+
+
+/* Return decomposition of a UCS-4 character
+ * Accepts: character or U8G_ERROR to return next from "more"
+ *         pointer to returned more
+ * Returns: [next] decomposed value, more set if still more decomposition
+ */
+
+unsigned long ucs4_decompose (unsigned long c,void **more)
+{
+  unsigned long i,ix,ret;
+  struct decomposemore *m;
+  if (c & U8G_ERROR) {         /* want to chase more? */
+                               /* do sanity check */
+    if (m = (struct decomposemore *) *more) switch (m->type) {
+    case MORESINGLE:           /* single value */
+      ret = m->data.single;
+      fs_give (more);          /* no more decomposition */
+      break;
+    case MOREMULTIPLE:         /* multiple value */
+      ret = *m->data.multiple.next++;
+      if (!--m->data.multiple.count) fs_give (more);
+      break;
+    default:                   /* uh-oh */
+      fatal ("invalid more block argument to ucs4_decompose!");
+    }
+    else fatal ("no more block provided to ucs4_decompose!");
+  }
+\f
+  else {                       /* start decomposition */
+    *more = NIL;               /* initially set no more */
+                               /* BMP low decompositions */
+    if (c < UCS4_BMPLOMIN) ret = c;
+                               /* fix this someday */
+    else if (c == UCS4_BMPLOMIN) ret = ucs4_dbmplotab[0];
+    else if (c <= UCS4_BMPLOMAX) {
+                               /* within range - have a decomposition? */
+      if (i = ucs4_dbmploixtab[c - UCS4_BMPLOMIN]) {
+                               /* get first value of decomposition */
+       ret = ucs4_dbmplotab[ix = i & UCS4_BMPLOIXMASK];
+                               /* has continuation? */
+       if (i & UCS4_BMPLOSIZEMASK) {
+         m = (struct decomposemore *)
+           (*more = memset (fs_get (sizeof (struct decomposemore)),0,
+                           sizeof (struct decomposemore)));
+         m->type = MOREMULTIPLE;
+         m->data.multiple.next = &ucs4_dbmplotab[++ix];
+         m->data.multiple.count = i >> UCS4_BMPLOSIZESHIFT;
+       }
+      }
+      else ret = c;            /* in range but doesn't decompose */
+    }
+                               /* BMP CJK compatibility */
+    else if (c < UCS4_BMPCJKMIN) ret = c;
+    else if (c <= UCS4_BMPCJKMAX) {
+      if (!(ret = ucs4_bmpcjk1decomptab[c - UCS4_BMPCJKMIN])) ret = c;
+    }
+                               /* BMP CJK compatibility - some not in BMP */
+#if UCS4_BMPCJK2MIN - (UCS4_BMPCJKMAX + 1)
+    else if (c < UCS4_BMPCJK2MIN) ret = c;
+#endif
+    else if (c <= UCS4_BMPCJK2MAX)
+      ret = ucs4_bmpcjk2decomptab[c - UCS4_BMPCJK2MIN];
+                               /* BMP high decompositions */
+    else if (c < UCS4_BMPHIMIN) ret = c;
+    else if (c <= UCS4_BMPHIMAX) {
+                               /* within range - have a decomposition? */
+      if (i = ucs4_dbmphiixtab[c - UCS4_BMPHIMIN]) {
+                               /* get first value of decomposition */
+       ret = ucs4_dbmphitab[ix = i & UCS4_BMPHIIXMASK];
+                               /* has continuation? */
+       if (i & UCS4_BMPHISIZEMASK) {
+         m = (struct decomposemore *)
+           (*more = memset (fs_get (sizeof (struct decomposemore)),0,
+                           sizeof (struct decomposemore)));
+         m->type = MOREMULTIPLE;
+         m->data.multiple.next = &ucs4_dbmphitab[++ix];
+         m->data.multiple.count = i >> UCS4_BMPHISIZESHIFT;
+       }
+      }
+      else ret = c;            /* in range but doesn't decompose */
+    }
+\f
+                               /* BMP half and full width forms */
+    else if (c < UCS4_BMPHALFFULLMIN) ret = c;
+    else if (c <= UCS4_BMPHALFFULLMAX) {
+      if (!(ret = ucs4_bmphalffulldecomptab[c - UCS4_BMPHALFFULLMIN])) ret = c;
+    }
+                               /* SMP music */
+    else if (c < UCS4_SMPMUSIC1MIN) ret = c;
+    else if (c <= UCS4_SMPMUSIC1MAX) {
+      ret = ucs4_smpmusic1decomptab[c -= UCS4_SMPMUSIC1MIN][0];
+      m = (struct decomposemore *)
+       (*more = memset (fs_get (sizeof (struct decomposemore)),0,
+                        sizeof (struct decomposemore)));
+      m->type = MORESINGLE;
+      m->data.single = ucs4_smpmusic1decomptab[c][1];
+    }
+    else if (c < UCS4_SMPMUSIC2MIN) ret = c;
+    else if (c <= UCS4_SMPMUSIC2MAX) {
+      ret = ucs4_smpmusic2decomptab[c -= UCS4_SMPMUSIC2MIN][0];
+      m = (struct decomposemore *)
+       (*more = memset (fs_get (sizeof (struct decomposemore)),0,
+                        sizeof (struct decomposemore)));
+      m->type = MORESINGLE;
+      m->data.single = ucs4_smpmusic2decomptab[c][1];
+    }
+                               /* SMP mathematical forms */
+    else if (c < UCS4_SMPMATHMIN) ret = c;
+    else if (c <= UCS4_SMPMATHMAX) {
+      if (!(ret = ucs4_smpmathdecomptab[c - UCS4_SMPMATHMIN])) ret = c;
+    }
+                               /* CJK compatibility ideographs in SIP */
+    else if (!(ret = ((c >= UCS4_SIPMIN) && (c <= UCS4_SIPMAX)) ?
+              ucs4_sipdecomptab[c - UCS4_SIPMIN] : c)) ret = c;
+  }
+  return ret;
+}
+\f
+/* Return recursive decomposition of a UCS-4 character
+ * Accepts: character or U8G_ERROR to return next from "more"
+ *         pointer to returned more
+ * Returns: [next] decomposed value, more set if still more decomposition
+ */
+
+unsigned long ucs4_decompose_recursive (unsigned long c,void **more)
+{
+  unsigned long c1;
+  void *m,*mn;
+  RECURSIVEMORE *mr;
+  if (c & U8G_ERROR) {         /* want to chase more? */
+    mn = NIL;
+    if (mr = (RECURSIVEMORE *) *more) switch (mr->more->type) {
+    case MORESINGLE:           /* decompose single value */
+      c = ucs4_decompose_recursive (mr->more->data.single,&mn);
+      *more = mr->next;                /* done with this more, remove it */
+      fs_give ((void **) &mr->more);
+      fs_give ((void **) &mr);
+      break;
+    case MOREMULTIPLE:         /* decompose current value in multiple */
+      c = ucs4_decompose_recursive (*mr->more->data.multiple.next++,&mn);
+                               /* if done with this multiple decomposition */
+      if (!--mr->more->data.multiple.count) {
+       *more = mr->next;       /* done with this more, remove it */
+       fs_give ((void **) &mr->more);
+       fs_give ((void **) &mr);
+      }
+      break;
+    default:                   /* uh-oh */
+      fatal ("invalid more block argument to ucs4_decompose_recursive!");
+    }
+    else fatal ("no more block provided to ucs4_decompose_recursive!");
+    if (mr = mn) {             /* did this value recurse on us? */
+      mr->next = *more;                /* yes, insert new more at head */
+      *more = mr;
+    }
+  }
+  else {                       /* start decomposition */
+    *more = NIL;               /* initially set no more */
+    mr = NIL;
+    do {                       /* repeatedly decompose this codepoint */
+      c = ucs4_decompose (c1 = c,&m);
+      if (m) {                 /* multi-byte decomposition */
+       if (c1 == c) fatal ("endless multiple decomposition!");
+                               /* create a block to stash this more */
+       mr = memset (fs_get (sizeof (RECURSIVEMORE)),0,sizeof (RECURSIVEMORE));
+       mr->more = m;           /* note the expansion */
+       mr->next = *more;       /* old list is the tail */
+       *more = mr;             /* and this is the new head */
+      }
+    } while (c1 != c);         /* until nothing more to decompose */
+  }
+  return c;
+}