]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - ppdc/ppdc-catalog.cxx
Fix source file header text duplication text duplication.
[thirdparty/cups.git] / ppdc / ppdc-catalog.cxx
index 96cd6e3aae893c65f17c2bc12df33bb79f617820..c791d30dc6f9bc16e3f60fb944b0691ba639684e 100644 (file)
@@ -1,33 +1,47 @@
 //
-// "$Id$"
+// Shared message catalog class for the CUPS PPD Compiler.
 //
-//   Shared message catalog class for the CUPS PPD Compiler.
+// Copyright 2007-2016 by Apple Inc.
+// Copyright 2002-2006 by Easy Software Products.
 //
-//   Copyright 2007-2008 by Apple Inc.
-//   Copyright 2002-2006 by Easy Software Products.
+// These coded instructions, statements, and computer programs are the
+// property of Apple Inc. and are protected by Federal copyright
+// law.  Distribution and use rights are outlined in the file "LICENSE.txt"
+// which should have been included with this file.  If this file is
+// missing or damaged, see the license at "http://www.cups.org/".
 //
-//   These coded instructions, statements, and computer programs are the
-//   property of Apple Inc. and are protected by Federal copyright
-//   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
-//   which should have been included with this file.  If this file is
-//   file is missing or damaged, see the license at "http://www.cups.org/".
+
+//
+// Include necessary headers...
 //
-// Contents:
+
+#include "ppdc-private.h"
+
+
 //
-//   ppdcCatalog::ppdcCatalog()   - Create a shared message catalog.
-//   ppdcCatalog::~ppdcCatalog()  - Destroy a shared message catalog.
-//   ppdcCatalog::add_message()   - Add a new message.
-//   ppdcCatalog::find_message()  - Find a message in a catalog...
-//   ppdcCatalog::load_messages() - Load messages from a .po file.
-//   ppdcCatalog::save_messages() - Save the messages to a .po file.
+// Character encodings...
 //
 
+typedef enum
+{
+  PPDC_CS_AUTO,
+  PPDC_CS_UTF8,
+  PPDC_CS_UTF16BE,
+  PPDC_CS_UTF16LE
+} ppdc_cs_t;
+
+
 //
-// Include necessary headers...
+// Local functions...
 //
 
-#include "ppdc.h"
-#include <cups/globals.h>
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
+static void    apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
+static int     get_utf8(char *&ptr);
+static int     get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
+static int     put_utf8(int ch, char *&ptr, char *end);
+static int     put_utf16(cups_file_t *fp, int ch);
 
 
 //
@@ -38,9 +52,7 @@ ppdcCatalog::ppdcCatalog(const char *l,       // I - Locale
                          const char *f)        // I - Message catalog file
   : ppdcShared()
 {
-  _cups_globals_t      *cg = _cupsGlobals();
-                                       // Global information
-
+  PPDC_NEW;
 
   locale   = new ppdcString(l);
   filename = new ppdcString(f);
@@ -52,7 +64,70 @@ ppdcCatalog::ppdcCatalog(const char *l,      // I - Locale
     char       pofile[1024];           // Message catalog file
 
 
-    snprintf(pofile, sizeof(pofile), "%s/%s/ppdc_%s.po", cg->localedir, l, l);
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
+    char               applelang[256]; // Apple language ID
+    CFURLRef           url;            // URL to cups.strings file
+    CFReadStreamRef    stream = NULL;  // File stream
+    CFPropertyListRef  plist = NULL;   // Localization file
+
+    snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
+    if (access(pofile, 0))
+    {
+      // Try alternate lproj directory names...
+      const char *tl = l;              // Temporary locale string
+
+      if (!strncmp(l, "en", 2))
+       tl = "English";
+      else if (!strncmp(l, "nb", 2))
+        tl = "no";
+      else if (!strncmp(l, "nl", 2))
+       tl = "Dutch";
+      else if (!strncmp(l, "fr", 2))
+       tl = "French";
+      else if (!strncmp(l, "de", 2))
+       tl = "German";
+      else if (!strncmp(l, "it", 2))
+       tl = "Italian";
+      else if (!strncmp(l, "ja", 2))
+       tl = "Japanese";
+      else if (!strncmp(l, "es", 2))
+       tl = "Spanish";
+
+      snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl);
+    }
+
+    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false);
+    if (url)
+    {
+      stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
+
+      if (stream)
+      {
+       /*
+       * Read the property list containing the localization data.
+       */
+
+       CFReadStreamOpen(stream);
+
+       plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
+
+       if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID())
+         CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this);
+
+       if (plist)
+         CFRelease(plist);
+
+       CFRelease(stream);
+      }
+
+      CFRelease(url);
+    }
+
+#else
+    _cups_globals_t    *cg = _cupsGlobals();
+                                       // Global information
+
+    snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l);
 
     if (load_messages(pofile) && strchr(l, '_'))
     {
@@ -61,14 +136,15 @@ ppdcCatalog::ppdcCatalog(const char *l,    // I - Locale
 
 
       strlcpy(baseloc, l, sizeof(baseloc));
-      snprintf(pofile, sizeof(pofile), "%s/%s/ppdc_%s.po", cg->localedir,
+      snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir,
                baseloc, baseloc);
 
       load_messages(pofile);
     }
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
   }
 
-  if (f)
+  if (f && *f)
     load_messages(f);
 }
 
@@ -79,9 +155,11 @@ ppdcCatalog::ppdcCatalog(const char *l,     // I - Locale
 
 ppdcCatalog::~ppdcCatalog()
 {
-  delete locale;
-  delete filename;
-  delete messages;
+  PPDC_DELETE;
+
+  locale->release();
+  filename->release();
+  messages->release();
 }
 
 
@@ -90,14 +168,16 @@ ppdcCatalog::~ppdcCatalog()
 //
 
 void
-ppdcCatalog::add_message(const char *id)// I - Message ID to add
+ppdcCatalog::add_message(
+    const char *id,                    // I - Message ID to add
+    const char *string)                        // I - Translation string
 {
   ppdcMessage  *m;                     // Current message
   char         text[1024];             // Text to translate
 
 
   // Range check input...
-  if (!id || !*id)
+  if (!id)
     return;
 
   // Verify that we don't already have the message ID...
@@ -105,11 +185,23 @@ ppdcCatalog::add_message(const char *id)// I - Message ID to add
        m;
        m = (ppdcMessage *)messages->next())
     if (!strcmp(m->id->value, id))
+    {
+      if (string)
+      {
+        m->string->release();
+       m->string = new ppdcString(string);
+      }
       return;
+    }
 
   // Add the message...
-  snprintf(text, sizeof(text), "TRANSLATE %s", id);
-  messages->add(new ppdcMessage(id, text));
+  if (!string)
+  {
+    snprintf(text, sizeof(text), "TRANSLATE %s", id);
+    string = text;
+  }
+
+  messages->add(new ppdcMessage(id, string));
 }
 
 
@@ -124,6 +216,9 @@ ppdcCatalog::find_message(
   ppdcMessage  *m;                     // Current message
 
 
+  if (!*id)
+    return (id);
+
   for (m = (ppdcMessage *)messages->first();
        m;
        m = (ppdcMessage *)messages->next())
@@ -143,7 +238,6 @@ ppdcCatalog::load_messages(
     const char *f)                     // I - Message catalog file
 {
   cups_file_t  *fp;                    // Message file
-  ppdcMessage  *temp;                  // Current message
   char         line[4096],             // Line buffer
                *ptr,                   // Pointer into buffer
                id[4096],               // Translation ID
@@ -155,146 +249,268 @@ ppdcCatalog::load_messages(
   if ((fp = cupsFileOpen(f, "r")) == NULL)
     return (-1);
 
- /*
-  * Read messages from the catalog file until EOF...
-  *
-  * The format is the GNU gettext .po format, which is fairly simple:
-  *
-  *     msgid "some text"
-  *     msgstr "localized text"
-  *
-  * The ID and localized text can span multiple lines using the form:
-  *
-  *     msgid ""
-  *     "some long text"
-  *     msgstr ""
-  *     "localized text spanning "
-  *     "multiple lines"
-  */
+  if ((ptr = (char *)strrchr(f, '.')) == NULL)
+    goto unknown_load_format;
+  else if (!strcmp(ptr, ".strings"))
+  {
+   /*
+    * Read messages in macOS ".strings" format, which are either UTF-8/UTF-16
+    * text files of the format:
+    *
+    *     "id" = "str";
+    *
+    * Strings files can also contain C-style comments.
+    */
+
+    ppdc_cs_t  cs = PPDC_CS_AUTO;      // Character set for file
+    int                ch;                     // Current character from file
+    char       *end;                   // End of buffer
+
+
+    id[0]  = '\0';
+    str[0] = '\0';
+    ptr    = NULL;
+    end    = NULL;
+
+    while ((ch = get_utf16(fp, cs)) != 0)
+    {
+      if (ptr)
+      {
+        if (ch == '\\')
+       {
+         if ((ch = get_utf16(fp, cs)) == 0)
+           break;
 
-  linenum = 0;
-  id[0]   = '\0';
-  str[0]  = '\0';
+         if (ch == 'n')
+           ch = '\n';
+         else if (ch == 't')
+           ch = '\t';
+        }
+       else if (ch == '\"')
+       {
+         *ptr = '\0';
+         ptr  = NULL;
+       }
 
-  while (cupsFileGets(fp, line, sizeof(line)))
-  {
-    linenum ++;
+        if (ptr)
+         put_utf8(ch, ptr, end);
+      }
+      else if (ch == '/')
+      {
+        // Start of a comment?
+       if ((ch = get_utf16(fp, cs)) == 0)
+         break;
 
-    // Skip blank and comment lines...
-    if (line[0] == '#' || !line[0])
-      continue;
+        if (ch == '*')
+       {
+         // Skip C comment...
+         int lastch = 0;
 
-    // Strip the trailing quote...
-    if ((ptr = strrchr(line, '\"')) == NULL)
-    {
-      fprintf(stderr, "load_messages: Expected quoted string on line %d of %s!\n",
-              linenum, f);
-      cupsFileClose(fp);
-      return (-1);
+          while ((ch = get_utf16(fp, cs)) != 0)
+         {
+           if (ch == '/' && lastch == '*')
+             break;
+
+           lastch = ch;
+         }
+       }
+       else if (ch == '/')
+       {
+         // Skip C++ comment...
+         while ((ch = get_utf16(fp, cs)) != 0)
+           if (ch == '\n')
+             break;
+       }
+      }
+      else if (ch == '\"')
+      {
+        // Start quoted string...
+       if (id[0])
+       {
+         ptr = str;
+         end = str + sizeof(str) - 1;
+       }
+       else
+       {
+         ptr = id;
+         end = id + sizeof(id) - 1;
+       }
+      }
+      else if (ch == ';')
+      {
+        // Add string...
+       add_message(id, str);
+       id[0] = '\0';
+      }
     }
+  }
+  else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz"))
+  {
+   /*
+    * Read messages from the catalog file until EOF...
+    *
+    * The format is the GNU gettext .po format, which is fairly simple:
+    *
+    *     msgid "some text"
+    *     msgstr "localized text"
+    *
+    * The ID and localized text can span multiple lines using the form:
+    *
+    *     msgid ""
+    *     "some long text"
+    *     msgstr ""
+    *     "localized text spanning "
+    *     "multiple lines"
+    */
+
+    int        which,                          // In msgid?
+       haveid,                         // Did we get a msgid string?
+       havestr;                        // Did we get a msgstr string?
+
+    linenum = 0;
+    id[0]   = '\0';
+    str[0]  = '\0';
+    haveid  = 0;
+    havestr = 0;
+    which   = 0;
+
+    while (cupsFileGets(fp, line, sizeof(line)))
+    {
+      linenum ++;
 
-    *ptr = '\0';
+      // Skip blank and comment lines...
+      if (line[0] == '#' || !line[0])
+       continue;
 
-    // Find start of value...
-    if ((ptr = strchr(line, '\"')) == NULL)
-    {
-      fprintf(stderr, "load_messages: Expected quoted string on line %d of %s!\n",
-              linenum, f);
-      cupsFileClose(fp);
-      return (-1);
-    }
+      // Strip the trailing quote...
+      if ((ptr = (char *)strrchr(line, '\"')) == NULL)
+      {
+       _cupsLangPrintf(stderr,
+                       _("ppdc: Expected quoted string on line %d of %s."),
+                       linenum, f);
+       cupsFileClose(fp);
+       return (-1);
+      }
+
+      *ptr = '\0';
+
+      // Find start of value...
+      if ((ptr = strchr(line, '\"')) == NULL)
+      {
+       _cupsLangPrintf(stderr,
+                       _("ppdc: Expected quoted string on line %d of %s."),
+                       linenum, f);
+       cupsFileClose(fp);
+       return (-1);
+      }
 
-    ptr ++;
+      ptr ++;
 
-    // Unquote the text...
-    char *sptr, *dptr;                 // Source/destination pointers
+      // Unquote the text...
+      char *sptr, *dptr;                       // Source/destination pointers
 
-    for (sptr = ptr, dptr = ptr; *sptr;)
-    {
-      if (*sptr == '\\')
+      for (sptr = ptr, dptr = ptr; *sptr;)
       {
-       sptr ++;
-       if (isdigit(*sptr))
+       if (*sptr == '\\')
        {
-         *dptr = 0;
+         sptr ++;
+         if (isdigit(*sptr))
+         {
+           *dptr = 0;
 
-         while (isdigit(*sptr))
+           while (isdigit(*sptr))
+           {
+             *dptr = *dptr * 8 + *sptr - '0';
+             sptr ++;
+           }
+
+           dptr ++;
+         }
+         else
          {
-           *dptr = *dptr * 8 + *sptr - '0';
+           if (*sptr == 'n')
+             *dptr++ = '\n';
+           else if (*sptr == 'r')
+             *dptr++ = '\r';
+           else if (*sptr == 't')
+             *dptr++ = '\t';
+           else
+             *dptr++ = *sptr;
+
            sptr ++;
          }
-
-         dptr ++;
        }
        else
-       {
-         if (*sptr == 'n')
-           *dptr++ = '\n';
-         else if (*sptr == 'r')
-           *dptr++ = '\r';
-         else if (*sptr == 't')
-           *dptr++ = '\t';
-         else
-           *dptr++ = *sptr;
-
-         sptr ++;
-       }
+         *dptr++ = *sptr++;
       }
-      else
-       *dptr++ = *sptr++;
-    }
 
-    *dptr = '\0';
+      *dptr = '\0';
 
-    // Create or add to a message...
-    if (!strncmp(line, "msgid", 5))
-    {
-      if (id[0] && str[0])
+      // Create or add to a message...
+      if (!strncmp(line, "msgid", 5))
       {
-       temp = new ppdcMessage(id, str);
-
-       messages->add(temp);
+       if (haveid && havestr)
+         add_message(id, str);
+
+       strlcpy(id, ptr, sizeof(id));
+       str[0] = '\0';
+       haveid  = 1;
+       havestr = 0;
+       which   = 1;
       }
+      else if (!strncmp(line, "msgstr", 6))
+      {
+       if (!haveid)
+       {
+         _cupsLangPrintf(stderr,
+                         _("ppdc: Need a msgid line before any "
+                           "translation strings on line %d of %s."),
+                         linenum, f);
+         cupsFileClose(fp);
+         return (-1);
+       }
 
-      strlcpy(id, ptr, sizeof(id));
-      str[0] = '\0';
-    }
-    else if (!strncmp(line, "msgstr", 6))
-    {
-      if (!id[0])
+       strlcpy(str, ptr, sizeof(str));
+       havestr = 1;
+       which   = 2;
+      }
+      else if (line[0] == '\"' && which == 2)
+       strlcat(str, ptr, sizeof(str));
+      else if (line[0] == '\"' && which == 1)
+       strlcat(id, ptr, sizeof(id));
+      else
       {
-       fprintf(stderr, "load_messages: Need a msgid line before any "
-                       "translation strings on line %d of %s!\n",
-               linenum, f);
+       _cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."),
+                       linenum, f);
        cupsFileClose(fp);
        return (-1);
       }
-
-      strlcpy(str, ptr, sizeof(str));
     }
-    else if (line[0] == '\"' && str[0])
-      strlcat(str, ptr, sizeof(str));
-    else if (line[0] == '\"' && id[0])
-      strlcat(id, ptr, sizeof(id));
-    else
-    {
-      fprintf(stderr, "load_messages: Unexpected text on line %d of %s!\n",
-              linenum, f);
-      cupsFileClose(fp);
-      return (-1);
-    }
-  }
-
-  if (id[0] && str[0])
-  {
-    temp = new ppdcMessage(id, str);
 
-    messages->add(temp);
+    if (haveid && havestr)
+      add_message(id, str);
   }
+  else
+    goto unknown_load_format;
+
+ /*
+  * Close the file and return...
+  */
 
   cupsFileClose(fp);
 
   return (0);
+
+ /*
+  * Unknown format error...
+  */
+
+  unknown_load_format:
+
+  _cupsLangPrintf(stderr,
+                  _("ppdc: Unknown message catalog format for \"%s\"."), f);
+  cupsFileClose(fp);
+  return (-1);
 }
 
 
@@ -308,55 +524,132 @@ ppdcCatalog::save_messages(
 {
   cups_file_t  *fp;                    // Message file
   ppdcMessage  *m;                     // Current message
-  const char   *ptr;                   // Pointer into string
+  char         *ptr;                   // Pointer into string
+  int          utf16;                  // Output UTF-16 .strings file?
+  int          ch;                     // Current character
 
 
-  if ((fp = cupsFileOpen(f, "w")) == NULL)
+  // Open the file...
+  if ((ptr = (char *)strrchr(f, '.')) == NULL)
     return (-1);
 
+  if (!strcmp(ptr, ".gz"))
+    fp = cupsFileOpen(f, "w9");
+  else
+    fp = cupsFileOpen(f, "w");
+
+  if (!fp)
+    return (-1);
+
+  // For .strings files, write a BOM for big-endian output...
+  utf16 = !strcmp(ptr, ".strings");
+
+  if (utf16)
+    put_utf16(fp, 0xfeff);
+
+  // Loop through all of the messages...
   for (m = (ppdcMessage *)messages->first();
        m;
        m = (ppdcMessage *)messages->next())
   {
-    cupsFilePuts(fp, "msgid \"");
-    for (ptr = m->id->value; *ptr; ptr ++)
-      switch (*ptr)
-      {
-        case '\n' :
-           cupsFilePuts(fp, "\\n");
-           break;
-        case '\\' :
-           cupsFilePuts(fp, "\\\\");
-           break;
-        case '\"' :
-           cupsFilePuts(fp, "\\\"");
-           break;
-        default :
-           cupsFilePutChar(fp, *ptr);
-           break;
-      }
-    cupsFilePuts(fp, "\"\n");
+    if (utf16)
+    {
+      put_utf16(fp, '\"');
 
-    cupsFilePuts(fp, "msgstr \"");
-    for (ptr = m->string->value; *ptr; ptr ++)
-      switch (*ptr)
-      {
-        case '\n' :
-           cupsFilePuts(fp, "\\n");
-           break;
-        case '\\' :
-           cupsFilePuts(fp, "\\\\");
-           break;
-        case '\"' :
-           cupsFilePuts(fp, "\\\"");
-           break;
-        default :
-           cupsFilePutChar(fp, *ptr);
-           break;
-      }
-    cupsFilePuts(fp, "\"\n");
+      ptr = m->id->value;
+      while ((ch = get_utf8(ptr)) != 0)
+       switch (ch)
+       {
+         case '\n' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, 'n');
+             break;
+         case '\\' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, '\\');
+             break;
+         case '\"' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, '\"');
+             break;
+         default :
+             put_utf16(fp, ch);
+             break;
+       }
+
+      put_utf16(fp, '\"');
+      put_utf16(fp, ' ');
+      put_utf16(fp, '=');
+      put_utf16(fp, ' ');
+      put_utf16(fp, '\"');
+
+      ptr = m->string->value;
+      while ((ch = get_utf8(ptr)) != 0)
+       switch (ch)
+       {
+         case '\n' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, 'n');
+             break;
+         case '\\' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, '\\');
+             break;
+         case '\"' :
+             put_utf16(fp, '\\');
+             put_utf16(fp, '\"');
+             break;
+         default :
+             put_utf16(fp, ch);
+             break;
+       }
 
-    cupsFilePutChar(fp, '\n');
+      put_utf16(fp, '\"');
+      put_utf16(fp, ';');
+      put_utf16(fp, '\n');
+    }
+    else
+    {
+      cupsFilePuts(fp, "msgid \"");
+      for (ptr = m->id->value; *ptr; ptr ++)
+       switch (*ptr)
+       {
+         case '\n' :
+             cupsFilePuts(fp, "\\n");
+             break;
+         case '\\' :
+             cupsFilePuts(fp, "\\\\");
+             break;
+         case '\"' :
+             cupsFilePuts(fp, "\\\"");
+             break;
+         default :
+             cupsFilePutChar(fp, *ptr);
+             break;
+       }
+      cupsFilePuts(fp, "\"\n");
+
+      cupsFilePuts(fp, "msgstr \"");
+      for (ptr = m->string->value; *ptr; ptr ++)
+       switch (*ptr)
+       {
+         case '\n' :
+             cupsFilePuts(fp, "\\n");
+             break;
+         case '\\' :
+             cupsFilePuts(fp, "\\\\");
+             break;
+         case '\"' :
+             cupsFilePuts(fp, "\\\"");
+             break;
+         default :
+             cupsFilePutChar(fp, *ptr);
+             break;
+       }
+      cupsFilePuts(fp, "\"\n");
+
+      cupsFilePutChar(fp, '\n');
+    }
   }
 
   cupsFileClose(fp);
@@ -365,6 +658,304 @@ ppdcCatalog::save_messages(
 }
 
 
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
 //
-// End of "$Id$".
+// 'apple_add_message()' - Add a message from a localization dictionary.
 //
+
+static void
+apple_add_message(CFStringRef key,     // I - Localization key
+                  CFStringRef val,     // I - Localized value
+                  ppdcCatalog *c)      // I - Message catalog
+{
+  char id[1024],                       // Message id
+       str[1024];                      // Localized message
+
+
+  if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) &&
+      CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8))
+    c->add_message(id, str);
+}
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
+
+
+//
+// 'get_utf8()' - Get a UTF-8 character.
+//
+
+static int                             // O  - Unicode character or 0 on EOF
+get_utf8(char *&ptr)                   // IO - Pointer to character
+{
+  int  ch;                             // Current character
+
+
+  if ((ch = *ptr++ & 255) < 0xc0)
+    return (ch);
+
+  if ((ch & 0xe0) == 0xc0)
+  {
+    // Two-byte UTF-8...
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f);
+  }
+  else if ((ch & 0xf0) == 0xe0)
+  {
+    // Three-byte UTF-8...
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f);
+
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = (ch << 6) | (*ptr++ & 0x3f);
+  }
+  else if ((ch & 0xf8) == 0xf0)
+  {
+    // Four-byte UTF-8...
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f);
+
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = (ch << 6) | (*ptr++ & 0x3f);
+
+    if ((*ptr & 0xc0) != 0x80)
+      return (0);
+
+    ch = (ch << 6) | (*ptr++ & 0x3f);
+  }
+
+  return (ch);
+}
+
+
+//
+// 'get_utf16()' - Get a UTF-16 character...
+//
+
+static int                             // O  - Unicode character or 0 on EOF
+get_utf16(cups_file_t *fp,             // I  - File to read from
+          ppdc_cs_t   &cs)             // IO - Character set of file
+{
+  int          ch;                     // Current character
+  unsigned char        buffer[3];              // Bytes
+
+
+  if (cs == PPDC_CS_AUTO)
+  {
+    // Get byte-order-mark, if present...
+    if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+      return (0);
+
+    if (buffer[0] == 0xfe && buffer[1] == 0xff)
+    {
+      // Big-endian UTF-16...
+      cs = PPDC_CS_UTF16BE;
+
+      if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+       return (0);
+    }
+    else if (buffer[0] == 0xff && buffer[1] == 0xfe)
+    {
+      // Little-endian UTF-16...
+      cs = PPDC_CS_UTF16LE;
+
+      if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+       return (0);
+    }
+    else if (buffer[0] == 0x00 && buffer[1] != 0x00)
+    {
+      // No BOM, assume big-endian UTF-16...
+      cs = PPDC_CS_UTF16BE;
+    }
+    else if (buffer[0] != 0x00 && buffer[1] == 0x00)
+    {
+      // No BOM, assume little-endian UTF-16...
+      cs = PPDC_CS_UTF16LE;
+    }
+    else
+    {
+      // No BOM, assume UTF-8...
+      cs = PPDC_CS_UTF8;
+
+      cupsFileRewind(fp);
+    }
+  }
+  else if (cs != PPDC_CS_UTF8)
+  {
+    if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+      return (0);
+  }
+
+  if (cs == PPDC_CS_UTF8)
+  {
+    // UTF-8 character...
+    if ((ch = cupsFileGetChar(fp)) < 0)
+      return (0);
+
+    if ((ch & 0xe0) == 0xc0)
+    {
+      // Two-byte UTF-8...
+      if (cupsFileRead(fp, (char *)buffer, 1) != 1)
+        return (0);
+
+      if ((buffer[0] & 0xc0) != 0x80)
+        return (0);
+
+      ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f);
+    }
+    else if ((ch & 0xf0) == 0xe0)
+    {
+      // Three-byte UTF-8...
+      if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+        return (0);
+
+      if ((buffer[0] & 0xc0) != 0x80 ||
+          (buffer[1] & 0xc0) != 0x80)
+        return (0);
+
+      ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) |
+           (buffer[1] & 0x3f);
+    }
+    else if ((ch & 0xf8) == 0xf0)
+    {
+      // Four-byte UTF-8...
+      if (cupsFileRead(fp, (char *)buffer, 3) != 3)
+        return (0);
+
+      if ((buffer[0] & 0xc0) != 0x80 ||
+          (buffer[1] & 0xc0) != 0x80 ||
+          (buffer[2] & 0xc0) != 0x80)
+        return (0);
+
+      ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) |
+             (buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f);
+    }
+  }
+  else
+  {
+    // UTF-16 character...
+    if (cs == PPDC_CS_UTF16BE)
+      ch = (buffer[0] << 8) | buffer[1];
+    else
+      ch = (buffer[1] << 8) | buffer[0];
+
+    if (ch >= 0xd800 && ch <= 0xdbff)
+    {
+      // Handle multi-word encoding...
+      int lch;
+
+      if (cupsFileRead(fp, (char *)buffer, 2) != 2)
+        return (0);
+
+      if (cs == PPDC_CS_UTF16BE)
+       lch = (buffer[0] << 8) | buffer[1];
+      else
+       lch = (buffer[1] << 8) | buffer[0];
+
+      if (lch < 0xdc00 || lch >= 0xdfff)
+        return (0);
+
+      ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
+    }
+  }
+
+  return (ch);
+}
+
+
+//
+// 'put_utf8()' - Add a UTF-8 character to a string.
+//
+
+static int                             // O  - 0 on success, -1 on failure
+put_utf8(int  ch,                      // I  - Unicode character
+         char *&ptr,                   // IO - String pointer
+        char *end)                     // I  - End of buffer
+{
+  if (ch < 0x80)
+  {
+    // One-byte ASCII...
+    if (ptr >= end)
+      return (-1);
+
+    *ptr++ = (char)ch;
+  }
+  else if (ch < 0x800)
+  {
+    // Two-byte UTF-8...
+    if ((ptr + 1) >= end)
+      return (-1);
+
+    *ptr++ = (char)(0xc0 | (ch >> 6));
+    *ptr++ = (char)(0x80 | (ch & 0x3f));
+  }
+  else if (ch < 0x10000)
+  {
+    // Three-byte UTF-8...
+    if ((ptr + 2) >= end)
+      return (-1);
+
+    *ptr++ = (char)(0xe0 | (ch >> 12));
+    *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
+    *ptr++ = (char)(0x80 | (ch & 0x3f));
+  }
+  else
+  {
+    // Four-byte UTF-8...
+    if ((ptr + 3) >= end)
+      return (-1);
+
+    *ptr++ = (char)(0xf0 | (ch >> 18));
+    *ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
+    *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
+    *ptr++ = (char)(0x80 | (ch & 0x3f));
+  }
+
+  return (0);
+}
+
+
+//
+// 'put_utf16()' - Write a UTF-16 character to a file.
+//
+
+static int                             // O - 0 on success, -1 on failure
+put_utf16(cups_file_t *fp,             // I - File to write to
+          int         ch)              // I - Unicode character
+{
+  unsigned char        buffer[4];              // Output buffer
+
+
+  if (ch < 0x10000)
+  {
+    // One-word UTF-16 big-endian...
+    buffer[0] = (unsigned char)(ch >> 8);
+    buffer[1] = (unsigned char)ch;
+
+    if (cupsFileWrite(fp, (char *)buffer, 2) == 2)
+      return (0);
+  }
+  else
+  {
+    // Two-word UTF-16 big-endian...
+    ch -= 0x10000;
+
+    buffer[0] = (unsigned char)(0xd8 | (ch >> 18));
+    buffer[1] = (unsigned char)(ch >> 10);
+    buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03));
+    buffer[3] = (unsigned char)ch;
+
+    if (cupsFileWrite(fp, (char *)buffer, 4) == 4)
+      return (0);
+  }
+
+  return (-1);
+}