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