Imported Upstream version 2.2.7
[platform/upstream/cups.git] / ppdc / ppdc-catalog.cxx
1 //
2 // Shared message catalog class for the CUPS PPD Compiler.
3 //
4 // Copyright 2007-2017 by Apple Inc.
5 // Copyright 2002-2006 by Easy Software Products.
6 //
7 // These coded instructions, statements, and computer programs are the
8 // property of Apple Inc. and are protected by Federal copyright
9 // law.  Distribution and use rights are outlined in the file "LICENSE.txt"
10 // which should have been included with this file.  If this file is
11 // missing or damaged, see the license at "http://www.cups.org/".
12 //
13
14 //
15 // Include necessary headers...
16 //
17
18 #include "ppdc-private.h"
19
20
21 //
22 // Character encodings...
23 //
24
25 typedef enum
26 {
27   PPDC_CS_AUTO,
28   PPDC_CS_UTF8,
29   PPDC_CS_UTF16BE,
30   PPDC_CS_UTF16LE
31 } ppdc_cs_t;
32
33
34 //
35 // Local functions...
36 //
37
38 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
39 static void     apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
40 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
41 static int      get_utf8(char *&ptr);
42 static int      get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
43 static int      put_utf8(int ch, char *&ptr, char *end);
44 static int      put_utf16(cups_file_t *fp, int ch);
45
46
47 //
48 // 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog.
49 //
50
51 ppdcCatalog::ppdcCatalog(const char *l, // I - Locale
52                          const char *f) // I - Message catalog file
53   : ppdcShared()
54 {
55   PPDC_NEW;
56
57   locale   = new ppdcString(l);
58   filename = new ppdcString(f);
59   messages = new ppdcArray();
60
61   if (l && strcmp(l, "en"))
62   {
63     // Try loading the base messages for this locale...
64     char        pofile[1024];           // Message catalog file
65
66
67 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
68     char                applelang[256]; // Apple language ID
69     CFURLRef            url;            // URL to cups.strings file
70     CFReadStreamRef     stream = NULL;  // File stream
71     CFPropertyListRef   plist = NULL;   // Localization file
72
73     snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
74     if (access(pofile, 0))
75     {
76       // Try alternate lproj directory names...
77       const char *tl = l;               // Temporary locale string
78
79       if (!strncmp(l, "en", 2))
80         tl = "English";
81       else if (!strncmp(l, "nb", 2))
82         tl = "no";
83       else if (!strncmp(l, "nl", 2))
84         tl = "Dutch";
85       else if (!strncmp(l, "fr", 2))
86         tl = "French";
87       else if (!strncmp(l, "de", 2))
88         tl = "German";
89       else if (!strncmp(l, "it", 2))
90         tl = "Italian";
91       else if (!strncmp(l, "ja", 2))
92         tl = "Japanese";
93       else if (!strncmp(l, "es", 2))
94         tl = "Spanish";
95
96       snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl);
97     }
98
99     url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false);
100     if (url)
101     {
102       stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
103
104       if (stream)
105       {
106        /*
107         * Read the property list containing the localization data.
108         */
109
110         CFReadStreamOpen(stream);
111
112         plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
113
114         if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID())
115           CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this);
116
117         if (plist)
118           CFRelease(plist);
119
120         CFRelease(stream);
121       }
122
123       CFRelease(url);
124     }
125
126 #else
127     _cups_globals_t     *cg = _cupsGlobals();
128                                         // Global information
129
130     snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l);
131
132     if (load_messages(pofile) && strchr(l, '_'))
133     {
134       // Try the base locale...
135       char      baseloc[3];             // Base locale...
136
137
138       strlcpy(baseloc, l, sizeof(baseloc));
139       snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir,
140                baseloc, baseloc);
141
142       load_messages(pofile);
143     }
144 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
145   }
146
147   if (f && *f)
148     load_messages(f);
149 }
150
151
152 //
153 // 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog.
154 //
155
156 ppdcCatalog::~ppdcCatalog()
157 {
158   PPDC_DELETE;
159
160   locale->release();
161   filename->release();
162   messages->release();
163 }
164
165
166 //
167 // 'ppdcCatalog::add_message()' - Add a new message.
168 //
169
170 void
171 ppdcCatalog::add_message(
172     const char *id,                     // I - Message ID to add
173     const char *string)                 // I - Translation string
174 {
175   ppdcMessage   *m;                     // Current message
176   char          text[1024];             // Text to translate
177
178
179   // Range check input...
180   if (!id)
181     return;
182
183   // Verify that we don't already have the message ID...
184   for (m = (ppdcMessage *)messages->first();
185        m;
186        m = (ppdcMessage *)messages->next())
187     if (!strcmp(m->id->value, id))
188     {
189       if (string)
190       {
191         m->string->release();
192         m->string = new ppdcString(string);
193       }
194       return;
195     }
196
197   // Add the message...
198   if (!string)
199   {
200     snprintf(text, sizeof(text), "TRANSLATE %s", id);
201     string = text;
202   }
203
204   messages->add(new ppdcMessage(id, string));
205 }
206
207
208 //
209 // 'ppdcCatalog::find_message()' - Find a message in a catalog...
210 //
211
212 const char *                            // O - Message text
213 ppdcCatalog::find_message(
214     const char *id)                     // I - Message ID
215 {
216   ppdcMessage   *m;                     // Current message
217
218
219   if (!*id)
220     return (id);
221
222   for (m = (ppdcMessage *)messages->first();
223        m;
224        m = (ppdcMessage *)messages->next())
225     if (!strcmp(m->id->value, id))
226       return (m->string->value);
227
228   return (id);
229 }
230
231
232 //
233 // 'ppdcCatalog::load_messages()' - Load messages from a .po file.
234 //
235
236 int                                     // O - 0 on success, -1 on failure
237 ppdcCatalog::load_messages(
238     const char *f)                      // I - Message catalog file
239 {
240   cups_file_t   *fp;                    // Message file
241   char          line[4096],             // Line buffer
242                 *ptr,                   // Pointer into buffer
243                 id[4096],               // Translation ID
244                 str[4096];              // Translation string
245   int           linenum;                // Line number
246
247
248   // Open the message catalog file...
249   if ((fp = cupsFileOpen(f, "r")) == NULL)
250     return (-1);
251
252   if ((ptr = (char *)strrchr(f, '.')) == NULL)
253     goto unknown_load_format;
254   else if (!strcmp(ptr, ".strings"))
255   {
256    /*
257     * Read messages in macOS ".strings" format, which are either UTF-8/UTF-16
258     * text files of the format:
259     *
260     *     "id" = "str";
261     *
262     * Strings files can also contain C-style comments.
263     */
264
265     ppdc_cs_t   cs = PPDC_CS_AUTO;      // Character set for file
266     int         ch;                     // Current character from file
267     char        *end;                   // End of buffer
268
269
270     id[0]  = '\0';
271     str[0] = '\0';
272     ptr    = NULL;
273     end    = NULL;
274
275     while ((ch = get_utf16(fp, cs)) != 0)
276     {
277       if (ptr)
278       {
279         if (ch == '\\')
280         {
281           if ((ch = get_utf16(fp, cs)) == 0)
282             break;
283
284           if (ch == 'n')
285             ch = '\n';
286           else if (ch == 't')
287             ch = '\t';
288         }
289         else if (ch == '\"')
290         {
291           *ptr = '\0';
292           ptr  = NULL;
293         }
294
295         if (ptr)
296           put_utf8(ch, ptr, end);
297       }
298       else if (ch == '/')
299       {
300         // Start of a comment?
301         if ((ch = get_utf16(fp, cs)) == 0)
302           break;
303
304         if (ch == '*')
305         {
306           // Skip C comment...
307           int lastch = 0;
308
309           while ((ch = get_utf16(fp, cs)) != 0)
310           {
311             if (ch == '/' && lastch == '*')
312               break;
313
314             lastch = ch;
315           }
316         }
317         else if (ch == '/')
318         {
319           // Skip C++ comment...
320           while ((ch = get_utf16(fp, cs)) != 0)
321             if (ch == '\n')
322               break;
323         }
324       }
325       else if (ch == '\"')
326       {
327         // Start quoted string...
328         if (id[0])
329         {
330           ptr = str;
331           end = str + sizeof(str) - 1;
332         }
333         else
334         {
335           ptr = id;
336           end = id + sizeof(id) - 1;
337         }
338       }
339       else if (ch == ';')
340       {
341         // Add string...
342         add_message(id, str);
343         id[0] = '\0';
344       }
345     }
346   }
347   else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz"))
348   {
349    /*
350     * Read messages from the catalog file until EOF...
351     *
352     * The format is the GNU gettext .po format, which is fairly simple:
353     *
354     *     msgid "some text"
355     *     msgstr "localized text"
356     *
357     * The ID and localized text can span multiple lines using the form:
358     *
359     *     msgid ""
360     *     "some long text"
361     *     msgstr ""
362     *     "localized text spanning "
363     *     "multiple lines"
364     */
365
366     int which,                          // In msgid?
367         haveid,                         // Did we get a msgid string?
368         havestr;                        // Did we get a msgstr string?
369
370     linenum = 0;
371     id[0]   = '\0';
372     str[0]  = '\0';
373     haveid  = 0;
374     havestr = 0;
375     which   = 0;
376
377     while (cupsFileGets(fp, line, sizeof(line)))
378     {
379       linenum ++;
380
381       // Skip blank and comment lines...
382       if (line[0] == '#' || !line[0])
383         continue;
384
385       // Strip the trailing quote...
386       if ((ptr = (char *)strrchr(line, '\"')) == NULL)
387       {
388         _cupsLangPrintf(stderr,
389                         _("ppdc: Expected quoted string on line %d of %s."),
390                         linenum, f);
391         cupsFileClose(fp);
392         return (-1);
393       }
394
395       *ptr = '\0';
396
397       // Find start of value...
398       if ((ptr = strchr(line, '\"')) == NULL)
399       {
400         _cupsLangPrintf(stderr,
401                         _("ppdc: Expected quoted string on line %d of %s."),
402                         linenum, f);
403         cupsFileClose(fp);
404         return (-1);
405       }
406
407       ptr ++;
408
409       // Unquote the text...
410       char *sptr, *dptr;                        // Source/destination pointers
411
412       for (sptr = ptr, dptr = ptr; *sptr;)
413       {
414         if (*sptr == '\\')
415         {
416           sptr ++;
417           if (isdigit(*sptr))
418           {
419             *dptr = 0;
420
421             while (isdigit(*sptr))
422             {
423               *dptr = *dptr * 8 + *sptr - '0';
424               sptr ++;
425             }
426
427             dptr ++;
428           }
429           else
430           {
431             if (*sptr == 'n')
432               *dptr++ = '\n';
433             else if (*sptr == 'r')
434               *dptr++ = '\r';
435             else if (*sptr == 't')
436               *dptr++ = '\t';
437             else
438               *dptr++ = *sptr;
439
440             sptr ++;
441           }
442         }
443         else
444           *dptr++ = *sptr++;
445       }
446
447       *dptr = '\0';
448
449       // Create or add to a message...
450       if (!strncmp(line, "msgid", 5))
451       {
452         if (haveid && havestr)
453           add_message(id, str);
454
455         strlcpy(id, ptr, sizeof(id));
456         str[0] = '\0';
457         haveid  = 1;
458         havestr = 0;
459         which   = 1;
460       }
461       else if (!strncmp(line, "msgstr", 6))
462       {
463         if (!haveid)
464         {
465           _cupsLangPrintf(stderr,
466                           _("ppdc: Need a msgid line before any "
467                             "translation strings on line %d of %s."),
468                           linenum, f);
469           cupsFileClose(fp);
470           return (-1);
471         }
472
473         strlcpy(str, ptr, sizeof(str));
474         havestr = 1;
475         which   = 2;
476       }
477       else if (line[0] == '\"' && which == 2)
478         strlcat(str, ptr, sizeof(str));
479       else if (line[0] == '\"' && which == 1)
480         strlcat(id, ptr, sizeof(id));
481       else
482       {
483         _cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."),
484                         linenum, f);
485         cupsFileClose(fp);
486         return (-1);
487       }
488     }
489
490     if (haveid && havestr)
491       add_message(id, str);
492   }
493   else
494     goto unknown_load_format;
495
496  /*
497   * Close the file and return...
498   */
499
500   cupsFileClose(fp);
501
502   return (0);
503
504  /*
505   * Unknown format error...
506   */
507
508   unknown_load_format:
509
510   _cupsLangPrintf(stderr,
511                   _("ppdc: Unknown message catalog format for \"%s\"."), f);
512   cupsFileClose(fp);
513   return (-1);
514 }
515
516
517 //
518 // 'ppdcCatalog::save_messages()' - Save the messages to a .po file.
519 //
520
521 int                                     // O - 0 on success, -1 on error
522 ppdcCatalog::save_messages(
523     const char *f)                      // I - File to save to
524 {
525   cups_file_t   *fp;                    // Message file
526   ppdcMessage   *m;                     // Current message
527   char          *ptr;                   // Pointer into string
528   int           utf16;                  // Output UTF-16 .strings file?
529   int           ch;                     // Current character
530
531
532   // Open the file...
533   if ((ptr = (char *)strrchr(f, '.')) == NULL)
534     return (-1);
535
536   if (!strcmp(ptr, ".gz"))
537     fp = cupsFileOpen(f, "w9");
538   else
539     fp = cupsFileOpen(f, "w");
540
541   if (!fp)
542     return (-1);
543
544   // For .strings files, write a BOM for big-endian output...
545   utf16 = !strcmp(ptr, ".strings");
546
547   if (utf16)
548     put_utf16(fp, 0xfeff);
549
550   // Loop through all of the messages...
551   for (m = (ppdcMessage *)messages->first();
552        m;
553        m = (ppdcMessage *)messages->next())
554   {
555     if (utf16)
556     {
557       put_utf16(fp, '\"');
558
559       ptr = m->id->value;
560       while ((ch = get_utf8(ptr)) != 0)
561         switch (ch)
562         {
563           case '\n' :
564               put_utf16(fp, '\\');
565               put_utf16(fp, 'n');
566               break;
567           case '\\' :
568               put_utf16(fp, '\\');
569               put_utf16(fp, '\\');
570               break;
571           case '\"' :
572               put_utf16(fp, '\\');
573               put_utf16(fp, '\"');
574               break;
575           default :
576               put_utf16(fp, ch);
577               break;
578         }
579
580       put_utf16(fp, '\"');
581       put_utf16(fp, ' ');
582       put_utf16(fp, '=');
583       put_utf16(fp, ' ');
584       put_utf16(fp, '\"');
585
586       ptr = m->string->value;
587       while ((ch = get_utf8(ptr)) != 0)
588         switch (ch)
589         {
590           case '\n' :
591               put_utf16(fp, '\\');
592               put_utf16(fp, 'n');
593               break;
594           case '\\' :
595               put_utf16(fp, '\\');
596               put_utf16(fp, '\\');
597               break;
598           case '\"' :
599               put_utf16(fp, '\\');
600               put_utf16(fp, '\"');
601               break;
602           default :
603               put_utf16(fp, ch);
604               break;
605         }
606
607       put_utf16(fp, '\"');
608       put_utf16(fp, ';');
609       put_utf16(fp, '\n');
610     }
611     else
612     {
613       cupsFilePuts(fp, "msgid \"");
614       for (ptr = m->id->value; *ptr; ptr ++)
615         switch (*ptr)
616         {
617           case '\n' :
618               cupsFilePuts(fp, "\\n");
619               break;
620           case '\\' :
621               cupsFilePuts(fp, "\\\\");
622               break;
623           case '\"' :
624               cupsFilePuts(fp, "\\\"");
625               break;
626           default :
627               cupsFilePutChar(fp, *ptr);
628               break;
629         }
630       cupsFilePuts(fp, "\"\n");
631
632       cupsFilePuts(fp, "msgstr \"");
633       for (ptr = m->string->value; *ptr; ptr ++)
634         switch (*ptr)
635         {
636           case '\n' :
637               cupsFilePuts(fp, "\\n");
638               break;
639           case '\\' :
640               cupsFilePuts(fp, "\\\\");
641               break;
642           case '\"' :
643               cupsFilePuts(fp, "\\\"");
644               break;
645           default :
646               cupsFilePutChar(fp, *ptr);
647               break;
648         }
649       cupsFilePuts(fp, "\"\n");
650
651       cupsFilePutChar(fp, '\n');
652     }
653   }
654
655   cupsFileClose(fp);
656
657   return (0);
658 }
659
660
661 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
662 //
663 // 'apple_add_message()' - Add a message from a localization dictionary.
664 //
665
666 static void
667 apple_add_message(CFStringRef key,      // I - Localization key
668                   CFStringRef val,      // I - Localized value
669                   ppdcCatalog *c)       // I - Message catalog
670 {
671   char  id[1024],                       // Message id
672         str[1024];                      // Localized message
673
674
675   if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) &&
676       CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8))
677     c->add_message(id, str);
678 }
679 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
680
681
682 //
683 // 'get_utf8()' - Get a UTF-8 character.
684 //
685
686 static int                              // O  - Unicode character or 0 on EOF
687 get_utf8(char *&ptr)                    // IO - Pointer to character
688 {
689   int   ch;                             // Current character
690
691
692   if ((ch = *ptr++ & 255) < 0xc0)
693     return (ch);
694
695   if ((ch & 0xe0) == 0xc0)
696   {
697     // Two-byte UTF-8...
698     if ((*ptr & 0xc0) != 0x80)
699       return (0);
700
701     ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f);
702   }
703   else if ((ch & 0xf0) == 0xe0)
704   {
705     // Three-byte UTF-8...
706     if ((*ptr & 0xc0) != 0x80)
707       return (0);
708
709     ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f);
710
711     if ((*ptr & 0xc0) != 0x80)
712       return (0);
713
714     ch = (ch << 6) | (*ptr++ & 0x3f);
715   }
716   else if ((ch & 0xf8) == 0xf0)
717   {
718     // Four-byte UTF-8...
719     if ((*ptr & 0xc0) != 0x80)
720       return (0);
721
722     ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f);
723
724     if ((*ptr & 0xc0) != 0x80)
725       return (0);
726
727     ch = (ch << 6) | (*ptr++ & 0x3f);
728
729     if ((*ptr & 0xc0) != 0x80)
730       return (0);
731
732     ch = (ch << 6) | (*ptr++ & 0x3f);
733   }
734
735   return (ch);
736 }
737
738
739 //
740 // 'get_utf16()' - Get a UTF-16 character...
741 //
742
743 static int                              // O  - Unicode character or 0 on EOF
744 get_utf16(cups_file_t *fp,              // I  - File to read from
745           ppdc_cs_t   &cs)              // IO - Character set of file
746 {
747   int           ch;                     // Current character
748   unsigned char buffer[3];              // Bytes
749
750
751   if (cs == PPDC_CS_AUTO)
752   {
753     // Get byte-order-mark, if present...
754     if (cupsFileRead(fp, (char *)buffer, 2) != 2)
755       return (0);
756
757     if (buffer[0] == 0xfe && buffer[1] == 0xff)
758     {
759       // Big-endian UTF-16...
760       cs = PPDC_CS_UTF16BE;
761
762       if (cupsFileRead(fp, (char *)buffer, 2) != 2)
763         return (0);
764     }
765     else if (buffer[0] == 0xff && buffer[1] == 0xfe)
766     {
767       // Little-endian UTF-16...
768       cs = PPDC_CS_UTF16LE;
769
770       if (cupsFileRead(fp, (char *)buffer, 2) != 2)
771         return (0);
772     }
773     else if (buffer[0] == 0x00 && buffer[1] != 0x00)
774     {
775       // No BOM, assume big-endian UTF-16...
776       cs = PPDC_CS_UTF16BE;
777     }
778     else if (buffer[0] != 0x00 && buffer[1] == 0x00)
779     {
780       // No BOM, assume little-endian UTF-16...
781       cs = PPDC_CS_UTF16LE;
782     }
783     else
784     {
785       // No BOM, assume UTF-8...
786       cs = PPDC_CS_UTF8;
787
788       cupsFileRewind(fp);
789     }
790   }
791   else if (cs != PPDC_CS_UTF8)
792   {
793     if (cupsFileRead(fp, (char *)buffer, 2) != 2)
794       return (0);
795   }
796
797   if (cs == PPDC_CS_UTF8)
798   {
799     // UTF-8 character...
800     if ((ch = cupsFileGetChar(fp)) < 0)
801       return (0);
802
803     if ((ch & 0xe0) == 0xc0)
804     {
805       // Two-byte UTF-8...
806       if (cupsFileRead(fp, (char *)buffer, 1) != 1)
807         return (0);
808
809       if ((buffer[0] & 0xc0) != 0x80)
810         return (0);
811
812       ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f);
813     }
814     else if ((ch & 0xf0) == 0xe0)
815     {
816       // Three-byte UTF-8...
817       if (cupsFileRead(fp, (char *)buffer, 2) != 2)
818         return (0);
819
820       if ((buffer[0] & 0xc0) != 0x80 ||
821           (buffer[1] & 0xc0) != 0x80)
822         return (0);
823
824       ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) |
825            (buffer[1] & 0x3f);
826     }
827     else if ((ch & 0xf8) == 0xf0)
828     {
829       // Four-byte UTF-8...
830       if (cupsFileRead(fp, (char *)buffer, 3) != 3)
831         return (0);
832
833       if ((buffer[0] & 0xc0) != 0x80 ||
834           (buffer[1] & 0xc0) != 0x80 ||
835           (buffer[2] & 0xc0) != 0x80)
836         return (0);
837
838       ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) |
839              (buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f);
840     }
841   }
842   else
843   {
844     // UTF-16 character...
845     if (cs == PPDC_CS_UTF16BE)
846       ch = (buffer[0] << 8) | buffer[1];
847     else
848       ch = (buffer[1] << 8) | buffer[0];
849
850     if (ch >= 0xd800 && ch <= 0xdbff)
851     {
852       // Handle multi-word encoding...
853       int lch;
854
855       if (cupsFileRead(fp, (char *)buffer, 2) != 2)
856         return (0);
857
858       if (cs == PPDC_CS_UTF16BE)
859         lch = (buffer[0] << 8) | buffer[1];
860       else
861         lch = (buffer[1] << 8) | buffer[0];
862
863       if (lch < 0xdc00 || lch >= 0xdfff)
864         return (0);
865
866       ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
867     }
868   }
869
870   return (ch);
871 }
872
873
874 //
875 // 'put_utf8()' - Add a UTF-8 character to a string.
876 //
877
878 static int                              // O  - 0 on success, -1 on failure
879 put_utf8(int  ch,                       // I  - Unicode character
880          char *&ptr,                    // IO - String pointer
881          char *end)                     // I  - End of buffer
882 {
883   if (ch < 0x80)
884   {
885     // One-byte ASCII...
886     if (ptr >= end)
887       return (-1);
888
889     *ptr++ = (char)ch;
890   }
891   else if (ch < 0x800)
892   {
893     // Two-byte UTF-8...
894     if ((ptr + 1) >= end)
895       return (-1);
896
897     *ptr++ = (char)(0xc0 | (ch >> 6));
898     *ptr++ = (char)(0x80 | (ch & 0x3f));
899   }
900   else if (ch < 0x10000)
901   {
902     // Three-byte UTF-8...
903     if ((ptr + 2) >= end)
904       return (-1);
905
906     *ptr++ = (char)(0xe0 | (ch >> 12));
907     *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
908     *ptr++ = (char)(0x80 | (ch & 0x3f));
909   }
910   else
911   {
912     // Four-byte UTF-8...
913     if ((ptr + 3) >= end)
914       return (-1);
915
916     *ptr++ = (char)(0xf0 | (ch >> 18));
917     *ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
918     *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
919     *ptr++ = (char)(0x80 | (ch & 0x3f));
920   }
921
922   return (0);
923 }
924
925
926 //
927 // 'put_utf16()' - Write a UTF-16 character to a file.
928 //
929
930 static int                              // O - 0 on success, -1 on failure
931 put_utf16(cups_file_t *fp,              // I - File to write to
932           int         ch)               // I - Unicode character
933 {
934   unsigned char buffer[4];              // Output buffer
935
936
937   if (ch < 0x10000)
938   {
939     // One-word UTF-16 big-endian...
940     buffer[0] = (unsigned char)(ch >> 8);
941     buffer[1] = (unsigned char)ch;
942
943     if (cupsFileWrite(fp, (char *)buffer, 2) == 2)
944       return (0);
945   }
946   else
947   {
948     // Two-word UTF-16 big-endian...
949     ch -= 0x10000;
950
951     buffer[0] = (unsigned char)(0xd8 | (ch >> 18));
952     buffer[1] = (unsigned char)(ch >> 10);
953     buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03));
954     buffer[3] = (unsigned char)ch;
955
956     if (cupsFileWrite(fp, (char *)buffer, 4) == 4)
957       return (0);
958   }
959
960   return (-1);
961 }