1//
2// "$Id: ppdc-catalog.cxx 11801 2014-04-08 19:55:21Z msweet $"
3//
4// Shared message catalog class for the CUPS PPD Compiler.
5//
6// Copyright 2007-2014 by Apple Inc.
7// Copyright 2002-2006 by Easy Software Products.
8//
9// These coded instructions, statements, and computer programs are the
10// property of Apple Inc. and are protected by Federal copyright
11// law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12// which should have been included with this file.  If this file is
13// file is missing or damaged, see the license at "http://www.cups.org/".
14//
15
16//
17// Include necessary headers...
18//
19
20#include "ppdc-private.h"
21
22
23//
24// Character encodings...
25//
26
27typedef enum
28{
29  PPDC_CS_AUTO,
30  PPDC_CS_UTF8,
31  PPDC_CS_UTF16BE,
32  PPDC_CS_UTF16LE
33} ppdc_cs_t;
34
35
36//
37// Local functions...
38//
39
40#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
41static void	apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
42#endif /* __APPLE__ && CUPS_BUNDLEDIR */
43static int	get_utf8(char *&ptr);
44static int	get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
45static int	put_utf8(int ch, char *&ptr, char *end);
46static int	put_utf16(cups_file_t *fp, int ch);
47
48
49//
50// 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog.
51//
52
53ppdcCatalog::ppdcCatalog(const char *l,	// I - Locale
54                         const char *f)	// I - Message catalog file
55  : ppdcShared()
56{
57  PPDC_NEW;
58
59  locale   = new ppdcString(l);
60  filename = new ppdcString(f);
61  messages = new ppdcArray();
62
63  if (l)
64  {
65    // Try loading the base messages for this locale...
66    char	pofile[1024];		// Message catalog file
67
68
69#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
70    char		applelang[256];	// Apple language ID
71    CFURLRef		url;		// URL to cups.strings file
72    CFReadStreamRef	stream = NULL;	// File stream
73    CFPropertyListRef	plist = NULL;	// Localization file
74
75    snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
76    if (access(pofile, 0))
77    {
78      // Try alternate lproj directory names...
79      const char *tl = l;		// Temporary locale string
80
81      if (!strncmp(l, "en", 2))
82	tl = "English";
83      else if (!strncmp(l, "nb", 2) || !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
156ppdcCatalog::~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
170void
171ppdcCatalog::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
212const char *				// O - Message text
213ppdcCatalog::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
236int					// O - 0 on success, -1 on failure
237ppdcCatalog::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 OS X ".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
521int					// O - 0 on success, -1 on error
522ppdcCatalog::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
666static void
667apple_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
686static int				// O  - Unicode character or 0 on EOF
687get_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
743static int				// O  - Unicode character or 0 on EOF
744get_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
878static int				// O  - 0 on success, -1 on failure
879put_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
930static int				// O - 0 on success, -1 on failure
931put_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}
962
963
964//
965// End of "$Id: ppdc-catalog.cxx 11801 2014-04-08 19:55:21Z msweet $".
966//
967