• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *   PWG Raster/Apple Raster/PCLm/PDF/IPP legacy PPD generator
3  *
4  *   Copyright 2016-2019 by Till Kamppeter.
5  *   Copyright 2017-2019 by Sahil Arora.
6  *   Copyright 2018-2019 by Deepak Patankar.
7  *
8  *   The PPD generator is based on the PPD generator for the CUPS
9  *   "lpadmin -m everywhere" functionality in the cups/ppd-cache.c
10  *   file. The copyright of this file is:
11  *
12  *   Copyright 2010-2016 by Apple Inc.
13  *
14  *   These coded instructions, statements, and computer programs are the
15  *   property of Apple Inc. and are protected by Federal copyright
16  *   law.  Distribution and use rights are outlined in the file "COPYING"
17  *   which should have been included with this file.
18  */
19 
20 #include <config.h>
21 #include <limits.h>
22 #include <cups/cups.h>
23 #include <cups/dir.h>
24 #include <cupsfilters/ppdgenerator.h>
25 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
26 #define HAVE_CUPS_1_6 1
27 #endif
28 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
29 #define HAVE_CUPS_1_7 1
30 #endif
31 
32 
33 /*
34  * Include necessary headers.
35  */
36 
37 #include <errno.h>
38 #include "driver.h"
39 #include <string.h>
40 #include <ctype.h>
41 #ifdef HAVE_CUPS_1_7
42 #include <cups/pwg.h>
43 #endif /* HAVE_CUPS_1_7 */
44 
45 
46 /*
47  * Macros to work around typos in older libcups version
48  */
49 
50 #if (CUPS_VERSION_MAJOR < 2) || ((CUPS_VERSION_MAJOR == 2) && ((CUPS_VERSION_MINOR < 3) || ((CUPS_VERSION_MINOR == 3) && (CUPS_VERSION_PATCH < 1))))
51 #define IPP_FINISHINGS_CUPS_FOLD_ACCORDION IPP_FINISHINGS_CUPS_FOLD_ACCORDIAN
52 #define IPP_FINISHINGS_FOLD_ACCORDION IPP_FINISHINGS_FOLD_ACCORDIAN
53 #endif
54 
55 
56 #ifdef HAVE_CUPS_1_6
57 /* The following code uses a lot of CUPS >= 1.6 specific stuff.
58    It needed for create_local_queue() in cups-browsed
59    to set up local queues for non-CUPS printer broadcasts
60    that is disabled in create_local_queue() for older CUPS <= 1.5.4.
61    Accordingly the following code is also disabled here for CUPS < 1.6. */
62 
63 /*
64  * The code below is borrowed from the CUPS 2.2.x upstream repository
65  * (via patches attached to https://www.cups.org/str.php?L4258). This
66  * allows for automatic PPD generation already with CUPS versions older
67  * than CUPS 2.2.x. We have also an additional test and development
68  * platform for this code. Taken from cups/ppd-cache.c,
69  * cups/string-private.h, cups/string.c.
70  *
71  * The advantage of PPD generation instead of working with System V
72  * interface scripts is that the print dialogs of the clients do not
73  * need to ask the printer for its options via IPP. So we have access
74  * to the options with the current PPD-based dialogs and can even share
75  * the automatically created print queue to other CUPS-based machines
76  * without problems.
77  */
78 
79 
80 cups_array_t *opt_strings_catalog = NULL;
81 char ppdgenerator_msg[1024];
82 
83 typedef struct _pwg_finishings_s	/**** PWG finishings mapping data ****/
84 {
85   ipp_finishings_t	value;		/* finishings value */
86   int			num_options;	/* Number of options to apply */
87   cups_option_t		*options;	/* Options to apply */
88 } _pwg_finishings_t;
89 
90 #define _PWG_EQUIVALENT(x, y)	(abs((x)-(y)) < 2)
91 
92 static void	pwg_ppdize_name(const char *ipp, char *name, size_t namesize);
93 static void	pwg_ppdize_resolution(ipp_attribute_t *attr, int element,
94                                  int *xres, int *yres, char *name, size_t namesize);
95 
96 /*
97  * '_cupsSetError()' - Set the last PPD generator status-message.
98  *
99  * This function replaces the original _cupsSetError() of the private
100  * API of the CUPS library. The #define and the renamed function prevent
101  * from the linker using the original function of the CUPS library instead
102  * of this replacement function.
103  */
104 
105 #define _cupsSetError(x, y, z) _CFcupsSetError(x, y, z)
106 
107 void
_CFcupsSetError(ipp_status_t status,const char * message,int localize)108 _CFcupsSetError(ipp_status_t status,	/* I - IPP status code
109 					   (for compatibility, ignored) */
110 		const char   *message,	/* I - status-message value */
111 		int          localize)	/* I - Localize the message?
112 					   (for compatibility, ignored) */
113 {
114   (void)status;
115   (void)localize;
116 
117   if (!message && errno)
118     message  = strerror(errno);
119 
120   if (message)
121     snprintf(ppdgenerator_msg, sizeof(ppdgenerator_msg), "%s", message);
122 }
123 
124 int			/* O - 1 on match, 0 otherwise */
_cups_isalnum(int ch)125 _cups_isalnum(int ch)			/* I - Character to test */
126 {
127   return ((ch >= '0' && ch <= '9') ||
128           (ch >= 'A' && ch <= 'Z') ||
129           (ch >= 'a' && ch <= 'z'));
130 }
131 
132 int			/* O - 1 on match, 0 otherwise */
_cups_isalpha(int ch)133 _cups_isalpha(int ch)			/* I - Character to test */
134 {
135   return ((ch >= 'A' && ch <= 'Z') ||
136           (ch >= 'a' && ch <= 'z'));
137 }
138 
139 int			/* O - 1 on match, 0 otherwise */
_cups_islower(int ch)140 _cups_islower(int ch)			/* I - Character to test */
141 {
142   return (ch >= 'a' && ch <= 'z');
143 }
144 
145 int			/* O - 1 on match, 0 otherwise */
_cups_isspace(int ch)146 _cups_isspace(int ch)			/* I - Character to test */
147 {
148   return (ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' ||
149           ch == '\v');
150 }
151 
152 int			/* O - 1 on match, 0 otherwise */
_cups_isupper(int ch)153 _cups_isupper(int ch)			/* I - Character to test */
154 {
155   return (ch >= 'A' && ch <= 'Z');
156 }
157 
158 int			/* O - Converted character */
_cups_tolower(int ch)159 _cups_tolower(int ch)			/* I - Character to convert */
160 {
161   return (_cups_isupper(ch) ? ch - 'A' + 'a' : ch);
162 }
163 
164 int			/* O - Converted character */
_cups_toupper(int ch)165 _cups_toupper(int ch)			/* I - Character to convert */
166 {
167   return (_cups_islower(ch) ? ch - 'a' + 'A' : ch);
168 }
169 
170 #ifndef HAVE_STRLCPY
171 /*
172  * '_cups_strlcpy()' - Safely copy two strings.
173  */
174 
175 size_t					/* O - Length of string */
strlcpy(char * dst,const char * src,size_t size)176 strlcpy(char       *dst,		/* O - Destination string */
177 	const char *src,		/* I - Source string */
178 	size_t      size)		/* I - Size of destination string buffer */
179 {
180   size_t	srclen;			/* Length of source string */
181 
182 
183  /*
184   * Figure out how much room is needed...
185   */
186 
187   size --;
188 
189   srclen = strlen(src);
190 
191  /*
192   * Copy the appropriate amount...
193   */
194 
195   if (srclen > size)
196     srclen = size;
197 
198   memmove(dst, src, srclen);
199   dst[srclen] = '\0';
200 
201   return (srclen);
202 }
203 #endif /* !HAVE_STRLCPY */
204 
205 /*
206  * '_cupsStrFormatd()' - Format a floating-point number.
207  */
208 
209 char *					/* O - Pointer to end of string */
_cupsStrFormatd(char * buf,char * bufend,double number,struct lconv * loc)210 _cupsStrFormatd(char         *buf,	/* I - String */
211                 char         *bufend,	/* I - End of string buffer */
212 		double       number,	/* I - Number to format */
213                 struct lconv *loc)	/* I - Locale data */
214 {
215   char		*bufptr,		/* Pointer into buffer */
216 		temp[1024],		/* Temporary string */
217 		*tempdec,		/* Pointer to decimal point */
218 		*tempptr;		/* Pointer into temporary string */
219   const char	*dec;			/* Decimal point */
220   int		declen;			/* Length of decimal point */
221 
222 
223  /*
224   * Format the number using the "%.12f" format and then eliminate
225   * unnecessary trailing 0's.
226   */
227 
228   snprintf(temp, sizeof(temp), "%.12f", number);
229   for (tempptr = temp + strlen(temp) - 1;
230        tempptr > temp && *tempptr == '0';
231        *tempptr-- = '\0');
232 
233  /*
234   * Next, find the decimal point...
235   */
236 
237   if (loc && loc->decimal_point) {
238     dec    = loc->decimal_point;
239     declen = (int)strlen(dec);
240   } else {
241     dec    = ".";
242     declen = 1;
243   }
244 
245   if (declen == 1)
246     tempdec = strchr(temp, *dec);
247   else
248     tempdec = strstr(temp, dec);
249 
250  /*
251   * Copy everything up to the decimal point...
252   */
253 
254   if (tempdec) {
255     for (tempptr = temp, bufptr = buf;
256          tempptr < tempdec && bufptr < bufend;
257 	 *bufptr++ = *tempptr++);
258 
259     tempptr += declen;
260 
261     if (*tempptr && bufptr < bufend) {
262       *bufptr++ = '.';
263 
264       while (*tempptr && bufptr < bufend)
265         *bufptr++ = *tempptr++;
266     }
267 
268     *bufptr = '\0';
269   } else {
270     strlcpy(buf, temp, (size_t)(bufend - buf + 1));
271     bufptr = buf + strlen(buf);
272   }
273 
274   return (bufptr);
275 }
276 
277 
278 /*
279  * '_cups_strcasecmp()' - Do a case-insensitive comparison.
280  */
281 
282 int				/* O - Result of comparison (-1, 0, or 1) */
_cups_strcasecmp(const char * s,const char * t)283 _cups_strcasecmp(const char *s,	/* I - First string */
284                  const char *t)	/* I - Second string */
285 {
286   while (*s != '\0' && *t != '\0') {
287     if (_cups_tolower(*s) < _cups_tolower(*t))
288       return (-1);
289     else if (_cups_tolower(*s) > _cups_tolower(*t))
290       return (1);
291 
292     s ++;
293     t ++;
294   }
295 
296   if (*s == '\0' && *t == '\0')
297     return (0);
298   else if (*s != '\0')
299     return (1);
300   else
301     return (-1);
302 }
303 
304 /*
305  * '_cups_strncasecmp()' - Do a case-insensitive comparison on up to N chars.
306  */
307 
308 int				 /* O - Result of comparison (-1, 0, or 1) */
_cups_strncasecmp(const char * s,const char * t,size_t n)309 _cups_strncasecmp(const char *s, /* I - First string */
310                   const char *t, /* I - Second string */
311 		  size_t     n)	 /* I - Maximum number of characters to
312 				        compare */
313 {
314   while (*s != '\0' && *t != '\0' && n > 0) {
315     if (_cups_tolower(*s) < _cups_tolower(*t))
316       return (-1);
317     else if (_cups_tolower(*s) > _cups_tolower(*t))
318       return (1);
319 
320     s ++;
321     t ++;
322     n --;
323   }
324 
325   if (n == 0)
326     return (0);
327   else if (*s == '\0' && *t == '\0')
328     return (0);
329   else if (*s != '\0')
330     return (1);
331   else
332     return (-1);
333 }
334 
335 
336 /*
337  * 'pwg_compare_sizes()' - Compare two media sizes...
338  */
339 
340 static int				/* O - Result of comparison */
pwg_compare_sizes(cups_size_t * a,cups_size_t * b)341 pwg_compare_sizes(cups_size_t *a,	/* I - First media size */
342                   cups_size_t *b)	/* I - Second media size */
343 {
344   return (strcmp(a->media, b->media));
345 }
346 
347 
348 /*
349  * 'pwg_copy_size()' - Copy a media size.
350  */
351 
352 static cups_size_t *			/* O - New media size */
pwg_copy_size(cups_size_t * size)353 pwg_copy_size(cups_size_t *size)	/* I - Media size to copy */
354 {
355   cups_size_t	*newsize = (cups_size_t *)calloc(1, sizeof(cups_size_t));
356 					/* New media size */
357 
358   if (newsize)
359     memcpy(newsize, size, sizeof(cups_size_t));
360 
361   return (newsize);
362 }
363 
364 static int				/* O  - 1 on success, 0 on failure */
get_url(const char * url,char * name,size_t namesize)365 get_url(const char *url,		/* I  - URL to get */
366 	char       *name,		/* I  - Temporary filename */
367 	size_t     namesize)		/* I  - Size of temporary filename
368 					        buffer */
369 {
370   http_t		*http = NULL;
371   char			scheme[32],	/* URL scheme */
372 			userpass[256],	/* URL username:password */
373 			host[256],	/* URL host */
374 			resource[256];	/* URL resource */
375   int			port;		/* URL port */
376   http_encryption_t	encryption;	/* Type of encryption to use */
377   http_status_t		status;		/* Status of GET request */
378   int			fd;		/* Temporary file */
379 
380 
381   if (httpSeparateURI(HTTP_URI_CODING_ALL, url, scheme, sizeof(scheme),
382 		      userpass, sizeof(userpass), host, sizeof(host), &port,
383 		      resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
384     return (0);
385 
386   if (port == 443 || !strcmp(scheme, "https"))
387     encryption = HTTP_ENCRYPTION_ALWAYS;
388   else
389     encryption = HTTP_ENCRYPTION_IF_REQUESTED;
390 
391   http = httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 5000, NULL);
392 
393   if (!http)
394     return (0);
395 
396   if ((fd = cupsTempFd(name, (int)namesize)) < 0)
397     return (0);
398 
399   status = cupsGetFd(http, resource, fd);
400 
401   close(fd);
402   httpClose(http);
403 
404   if (status != HTTP_STATUS_OK) {
405     unlink(name);
406     *name = '\0';
407     return (0);
408   }
409 
410   return (1);
411 }
412 
413 /*
414  * '_()' - Simplify copying the ppdCreateFromIPP() function from CUPS,
415  *         as we do not do translations of UI strings in cups-browsed
416  */
417 
418 #define _(s) s
419 
420 /*
421  * '_cupsLangString()' - Simplify copying the ppdCreateFromIPP() function
422  *                       from CUPS, as we do not do translations of UI strings
423  *                       in cups-browsed
424  */
425 
426 const char *
_cupsLangString(cups_lang_t * l,const char * s)427 _cupsLangString(cups_lang_t *l, const char *s)
428 {
429   return s;
430 }
431 
432 /*
433  * '_findCUPSMessageCatalog()' - Find a CUPS message catalog file
434  *                               containing human-readable standard
435  *                               option and choice names for IPP
436  *                               printers
437  */
438 
439 const char *
_searchDirForCatalog(const char * dirname)440 _searchDirForCatalog(const char *dirname)
441 {
442   const char *catalog = NULL, *c1, *c2;
443   cups_dir_t *dir = NULL, *subdir;
444   cups_dentry_t *subdirentry, *catalogentry;
445   char subdirpath[1024], catalogpath[2048], lang[8];
446   int i;
447 
448   if (dirname == NULL)
449     return NULL;
450 
451   /* Check first whether we have an English file and prefer this */
452   snprintf(catalogpath, sizeof(catalogpath), "%s/en/cups_en.po", dirname);
453   if (access(catalogpath, R_OK) == 0) {
454     /* Found */
455     catalog = strdup(catalogpath);
456     return catalog;
457   }
458 
459   if ((dir = cupsDirOpen(dirname)) == NULL)
460     return NULL;
461 
462   while ((subdirentry = cupsDirRead(dir)) != NULL) {
463     /* Do we actually have a subdir? */
464     if (!S_ISDIR(subdirentry->fileinfo.st_mode))
465       continue;
466     /* Check format of subdir name */
467     c1 = subdirentry->filename;
468     if (c1[0] < 'a' || c1[0] > 'z' || c1[1] < 'a' || c1[1] > 'z')
469       continue;
470     if (c1[2] >= 'a' && c1[2] <= 'z')
471       i = 3;
472     else
473       i = 2;
474     if (c1[i] == '_') {
475       i ++;
476       if (c1[i] < 'A' || c1[i] > 'Z' || c1[i+1] < 'A' || c1[i+1] > 'Z')
477 	continue;
478       i += 2;
479       if (c1[i] >= 'A' && c1[i] <= 'Z')
480 	i ++;
481     }
482     if (c1[i] != '\0' && c1[i] != '@')
483       continue;
484     strncpy(lang, c1, i);
485     lang[i] = '\0';
486     snprintf(subdirpath, sizeof(subdirpath), "%s/%s", dirname, c1);
487     if ((subdir = cupsDirOpen(subdirpath)) != NULL) {
488       while ((catalogentry = cupsDirRead(subdir)) != NULL) {
489 	/* Do we actually have a regular file? */
490 	if (!S_ISREG(catalogentry->fileinfo.st_mode))
491 	  continue;
492 	/* Check format of catalog name */
493 	c2 = catalogentry->filename;
494 	if (strlen(c2) < 10 || strncmp(c2, "cups_", 5) != 0 ||
495 	    strncmp(c2 + 5, lang, i) != 0 ||
496 	    strcmp(c2 + strlen(c2) - 3, ".po"))
497 	  continue;
498 	/* Is catalog readable ? */
499 	snprintf(catalogpath, sizeof(catalogpath), "%s/%s", subdirpath, c2);
500 	if (access(catalogpath, R_OK) != 0)
501 	  continue;
502 	/* Found */
503 	catalog = strdup(catalogpath);
504 	break;
505       }
506       cupsDirClose(subdir);
507       subdir = NULL;
508       if (catalog != NULL)
509 	break;
510     }
511   }
512 
513   cupsDirClose(dir);
514   return catalog;
515 }
516 
517 const char *
_findCUPSMessageCatalog(const char * preferreddir)518 _findCUPSMessageCatalog(const char *preferreddir)
519 {
520   const char *catalog = NULL, *c;
521   char buf[1024];
522 
523   /* Directory supplied by calling program, from config file,
524      environment variable, ... */
525   if ((catalog = _searchDirForCatalog(preferreddir)) != NULL)
526     goto found;
527 
528   /* Directory supplied by environment variable CUPS_LOCALEDIR */
529   if ((catalog = _searchDirForCatalog(getenv("CUPS_LOCALEDIR"))) != NULL)
530     goto found;
531 
532   /* Determine CUPS datadir (usually /usr/share/cups) */
533   if ((c = getenv("CUPS_DATADIR")) == NULL)
534     c = CUPS_DATADIR;
535 
536   /* Search /usr/share/cups/locale/ (location which
537      Debian/Ubuntu package of CUPS is using) */
538   snprintf(buf, sizeof(buf), "%s/locale", c);
539   if ((catalog = _searchDirForCatalog(buf)) != NULL)
540     goto found;
541 
542   /* Search /usr/(local/)share/locale/ (standard location
543      which CUPS is using on Linux) */
544   snprintf(buf, sizeof(buf), "%s/../locale", c);
545   if ((catalog = _searchDirForCatalog(buf)) != NULL)
546     goto found;
547 
548   /* Search /usr/(local/)lib/locale/ (standard location
549      which CUPS is using on many non-Linux systems) */
550   snprintf(buf, sizeof(buf), "%s/../../lib/locale", c);
551   if ((catalog = _searchDirForCatalog(buf)) != NULL)
552     goto found;
553 
554  found:
555   return catalog;
556 }
557 
558 /* Data structure for IPP choice name and human-readable string */
559 typedef struct ipp_choice_strings_s {
560   char *name, *human_readable;
561 } ipp_choice_strings_t;
562 
563 /* Data structure for IPP option name, human-readable string, and choice list */
564 typedef struct ipp_opt_strings_s {
565   char *name, *human_readable;
566   cups_array_t *choices;
567 } ipp_opt_strings_t;
568 
569 int
compare_choices(void * a,void * b,void * user_data)570 compare_choices(void *a, void *b, void *user_data)
571 {
572   return strcasecmp(((ipp_choice_strings_t *)a)->name,
573 		    ((ipp_choice_strings_t *)b)->name);
574 }
575 
576 int
compare_options(void * a,void * b,void * user_data)577 compare_options(void *a, void *b, void *user_data)
578 {
579   return strcasecmp(((ipp_opt_strings_t *)a)->name,
580 		    ((ipp_opt_strings_t *)b)->name);
581 }
582 
583 void
free_choice_strings(void * entry,void * user_data)584 free_choice_strings(void* entry, void* user_data)
585 {
586   ipp_choice_strings_t *entry_rec = (ipp_choice_strings_t *)entry;
587 
588   if (entry_rec) {
589     if (entry_rec->name) free(entry_rec->name);
590     if (entry_rec->human_readable) free(entry_rec->human_readable);
591     free(entry_rec);
592   }
593 }
594 
595 void
free_opt_strings(void * entry,void * user_data)596 free_opt_strings(void* entry, void* user_data)
597 {
598   ipp_opt_strings_t *entry_rec = (ipp_opt_strings_t *)entry;
599 
600   if (entry_rec) {
601     if (entry_rec->name) free(entry_rec->name);
602     if (entry_rec->human_readable) free(entry_rec->human_readable);
603     if (entry_rec->choices) cupsArrayDelete(entry_rec->choices);
604     free(entry_rec);
605   }
606 }
607 
608 cups_array_t *
optArrayNew()609 optArrayNew()
610 {
611   return cupsArrayNew3(compare_options, NULL, NULL, 0,
612 		       NULL, free_opt_strings);
613 }
614 
615 ipp_opt_strings_t *
find_opt_in_array(cups_array_t * options,char * name)616 find_opt_in_array(cups_array_t *options, char *name)
617 {
618   ipp_opt_strings_t opt;
619 
620   if (!name || !options)
621     return NULL;
622 
623   opt.name = name;
624   return cupsArrayFind(options, &opt);
625 }
626 
627 ipp_choice_strings_t *
find_choice_in_array(cups_array_t * choices,char * name)628 find_choice_in_array(cups_array_t *choices, char *name)
629 {
630   ipp_choice_strings_t choice;
631 
632   if (!name || !choices)
633     return NULL;
634 
635   choice.name = name;
636   return cupsArrayFind(choices, &choice);
637 }
638 
639 ipp_opt_strings_t *
add_opt_to_array(char * name,char * human_readable,cups_array_t * options)640 add_opt_to_array(char *name, char *human_readable, cups_array_t *options)
641 {
642   ipp_opt_strings_t *opt = NULL;
643 
644   if (!name || !options)
645     return NULL;
646 
647   if ((opt = find_opt_in_array(options, name)) == NULL) {
648     opt = calloc(1, sizeof(ipp_opt_strings_t));
649     if (!opt) return NULL;
650     opt->human_readable = NULL;
651     opt->choices = cupsArrayNew3(compare_choices, NULL, NULL, 0,
652 				 NULL, free_choice_strings);
653     if (!opt->choices) {
654       free(opt);
655       return NULL;
656     }
657     opt->name = strdup(name);
658     if (!cupsArrayAdd(options, opt)) {
659       free_opt_strings(opt, NULL);
660       return NULL;
661     }
662   }
663 
664   if (human_readable)
665     opt->human_readable = strdup(human_readable);
666 
667   return opt;
668 }
669 
670 ipp_choice_strings_t *
add_choice_to_array(char * name,char * human_readable,char * opt_name,cups_array_t * options)671 add_choice_to_array(char *name, char *human_readable, char *opt_name,
672 		    cups_array_t *options)
673 {
674   ipp_choice_strings_t *choice = NULL;
675   ipp_opt_strings_t *opt;
676 
677   if (!name || !human_readable || !opt_name || !options)
678     return NULL;
679 
680   opt = add_opt_to_array(opt_name, NULL, options);
681   if (!opt) return NULL;
682 
683   if ((choice = find_choice_in_array(opt->choices, name)) == NULL) {
684     choice = calloc(1, sizeof(ipp_choice_strings_t));
685     if (!choice) return NULL;
686     choice->human_readable = NULL;
687     choice->name = strdup(name);
688     if (!cupsArrayAdd(opt->choices, choice)) {
689       free_choice_strings(choice, NULL);
690       return NULL;
691     }
692   }
693 
694   if (human_readable)
695     choice->human_readable = strdup(human_readable);
696 
697   return choice;
698 
699 }
700 
701 char *
lookup_option(char * name,cups_array_t * options,cups_array_t * printer_options)702 lookup_option(char *name, cups_array_t *options,
703 	      cups_array_t *printer_options)
704 {
705   ipp_opt_strings_t *opt = NULL;
706 
707   if (!name || !options)
708     return NULL;
709 
710   if (printer_options &&
711       (opt = find_opt_in_array(printer_options, name)) != NULL)
712     return opt->human_readable;
713   if ((opt = find_opt_in_array(options, name)) != NULL)
714     return opt->human_readable;
715   else
716     return NULL;
717 }
718 
719 char *
lookup_choice(char * name,char * opt_name,cups_array_t * options,cups_array_t * printer_options)720 lookup_choice(char *name, char *opt_name, cups_array_t *options,
721 	      cups_array_t *printer_options)
722 {
723   ipp_opt_strings_t *opt = NULL;
724   ipp_choice_strings_t *choice = NULL;
725 
726   if (!name || !opt_name || !options)
727     return NULL;
728 
729   if (printer_options &&
730       (opt = find_opt_in_array(printer_options, opt_name)) != NULL &&
731       (choice = find_choice_in_array(opt->choices, name)) != NULL)
732     return choice->human_readable;
733   else if ((opt = find_opt_in_array(options, opt_name)) != NULL &&
734 	   (choice = find_choice_in_array(opt->choices, name)) != NULL)
735     return choice->human_readable;
736   else
737     return NULL;
738 }
739 
740 void
load_opt_strings_catalog(const char * location,cups_array_t * options)741 load_opt_strings_catalog(const char *location, cups_array_t *options)
742 {
743   char tmpfile[1024];
744   const char *filename = NULL;
745   struct stat statbuf;
746   cups_file_t *fp;
747   char line[65536];
748   char *ptr, *start, *start2, *end, *end2, *sep;
749   char *opt_name = NULL, *choice_name = NULL,
750        *human_readable = NULL;
751   int part = -1; /* -1: before first "msgid" or invalid
752 		        line
753 		     0: "msgid"
754 		     1: "msgstr"
755 		     2: "..." = "..."
756 		    10: EOF, save last entry */
757   int digit;
758   int found_in_catalog = 0;
759 
760   if (location == NULL || (strncasecmp(location, "http:", 5) &&
761 			   strncasecmp(location, "https:", 6))) {
762     if (location == NULL ||
763 	(stat(location, &statbuf) == 0 &&
764 	 S_ISDIR(statbuf.st_mode))) /* directory? */
765     {
766       filename = _findCUPSMessageCatalog(location);
767       if (filename)
768         found_in_catalog = 1;
769     }
770     else
771       filename = location;
772   } else {
773     if (get_url(location, tmpfile, sizeof(tmpfile)))
774       filename = tmpfile;
775   }
776   if (!filename)
777     return;
778 
779   if ((fp = cupsFileOpen(filename, "r")) == NULL)
780     return;
781 
782   while (cupsFileGets(fp, line, sizeof(line)) || (part = 10)) {
783     /* Find a pair of quotes delimiting a string in each line
784        and optional "msgid" or "msgstr" keywords, or a
785        "..." = "..." pair. Skip comments ('#') and empty lines. */
786     if (part < 10) {
787       ptr = line;
788       while (isspace(*ptr)) ptr ++;
789       if (*ptr == '#' || *ptr == '\0') continue;
790       if ((start = strchr(ptr, '\"')) == NULL) continue;
791       if ((end = strrchr(ptr, '\"')) == start) continue;
792       if (*(end - 1) == '\\') continue;
793       start2 = NULL;
794       end2 = NULL;
795       if (start > ptr) {
796 	if (*(start - 1) == '\\') continue;
797 	if (strncasecmp(ptr, "msgid", 5) == 0) part = 0;
798 	if (strncasecmp(ptr, "msgstr", 6) == 0) part = 1;
799       } else {
800 	start2 = ptr;
801 	while ((start2 = strchr(start2 + 1, '\"')) < end &&
802 	       *(start2 - 1) == '\\');
803 	if (start2 < end) {
804 	  /* Line with "..." = "..." of text/strings format */
805 	  end2 = end;
806 	  end = start2;
807 	  start2 ++;
808 	  while (isspace(*start2)) start2 ++;
809 	  if (*start2 != '=') continue;
810 	  start2 ++;
811 	  while (isspace(*start2)) start2 ++;
812 	  if (*start2 != '\"') continue;
813 	  start2 ++;
814 	  *end2 = '\0';
815 	  part = 2;
816 	} else
817 	  /* Continuation line in message catalog file */
818 	  start2 = NULL;
819       }
820       start ++;
821       *end = '\0';
822     }
823     /* Read out the strings between the quotes and save entries */
824     if (part == 0 || part == 2 || part == 10) {
825       /* Save previous attribute */
826       if (human_readable) {
827 	if (opt_name) {
828 	  if (choice_name) {
829 	    add_choice_to_array(choice_name, human_readable,
830 				opt_name, options);
831 	    free(choice_name);
832 	  } else
833 	    add_opt_to_array(opt_name, human_readable, options);
834 	  free(opt_name);
835 	}
836 	free(human_readable);
837 	opt_name = NULL;
838 	choice_name = NULL;
839 	human_readable = NULL;
840       }
841       /* Stop the loop after saving the last entry */
842       if (part == 10)
843 	break;
844       /* IPP attribute has to be defined with a single msgid line,
845 	 no continuation lines */
846       if (opt_name) {
847 	free (opt_name);
848 	opt_name = NULL;
849 	if (choice_name) {
850 	  free (choice_name);
851 	  choice_name = NULL;
852 	}
853 	part = -1;
854 	continue;
855       }
856       /* No continuation line in text/strings format */
857       if (part == 2 && (start2 == NULL || end2 == NULL)) {
858 	part = -1;
859 	continue;
860       }
861       /* Check line if it is a valid IPP attribute:
862 	 No spaces, only lowercase letters, digits, '-', '_',
863 	 "option" or "option.choice" */
864       for (ptr = start, sep = NULL; ptr < end; ptr ++)
865 	if (*ptr == '.') { /* Separator between option and choice */
866 	  if (!sep) { /* Only the first '.' counts */
867 	    sep = ptr + 1;
868 	    *ptr = '\0';
869 	  }
870 	} else if (!((*ptr >= 'a' && *ptr <= 'z') ||
871 		     (*ptr >= '0' && *ptr <= '9') ||
872 		     *ptr == '-' || *ptr == '_'))
873 	  break;
874       if (ptr < end) { /* Illegal character found */
875 	part = -1;
876 	continue;
877       }
878       if (strlen(start) > 0) /* Option name found */
879 	opt_name = strdup(start);
880       else { /* Empty option name */
881 	part = -1;
882 	continue;
883       }
884       if (sep && strlen(sep) > 0) /* Choice name found */
885 	choice_name = strdup(sep);
886       else /* Empty choice name */
887 	choice_name = NULL;
888       if (part == 2) { /* Human-readable string in the same line */
889 	start = start2;
890 	end = end2;
891       }
892     }
893     if (part == 1 || part == 2) {
894       /* msgid was not for an IPP attribute, ignore this msgstr */
895       if (!opt_name) continue;
896       /* Empty string */
897       if (start == end) continue;
898       /* Unquote string */
899       ptr = start;
900       end = start;
901       while (*ptr) {
902 	if (*ptr == '\\') {
903 	  ptr ++;
904 	  if (isdigit(*ptr)) {
905 	    digit = 0;
906 	    *end = 0;
907 	    while (isdigit(*ptr) && digit < 3) {
908 	      *end = *end * 8 + *ptr - '0';
909 	      digit ++;
910 	      ptr ++;
911 	    }
912 	    end ++;
913 	  } else {
914 	    if (*ptr == 'n')
915 	      *end ++ = '\n';
916 	    else if (*ptr == 'r')
917 	      *end ++ = '\r';
918 	    else if (*ptr == 't')
919 	      *end ++ = '\t';
920 	    else
921 	      *end ++ = *ptr;
922 	    ptr ++;
923 	  }
924 	} else
925 	  *end ++ = *ptr ++;
926       }
927       *end = '\0';
928       /* Did the unquoting make the string empty? */
929       if (strlen(start) == 0) continue;
930       /* Add the string to our human-readable string */
931       if (human_readable) { /* Continuation line */
932 	human_readable = realloc(human_readable,
933 				 sizeof(char) *
934 				 (strlen(human_readable) +
935 				  strlen(start) + 2));
936 	ptr = human_readable + strlen(human_readable);
937 	*ptr = ' ';
938 	strlcpy(ptr + 1, start, strlen(start) + 1);
939       } else { /* First line */
940 	human_readable = malloc(sizeof(char) *
941 				(strlen(start) + 1));
942 	strlcpy(human_readable, start, strlen(start) + 1);
943       }
944     }
945   }
946   cupsFileClose(fp);
947   if (choice_name != NULL)
948     free(choice_name);
949   if (opt_name != NULL)
950     free(opt_name);
951   if (filename == tmpfile)
952     unlink(filename);
953   if (found_in_catalog)
954     free((char *)filename);
955 }
956 
957 
958 int
compare_resolutions(void * resolution_a,void * resolution_b,void * user_data)959 compare_resolutions(void *resolution_a, void *resolution_b,
960 		    void *user_data)
961 {
962   res_t *res_a = (res_t *)resolution_a;
963   res_t *res_b = (res_t *)resolution_b;
964   int i, a, b;
965 
966   /* Compare the pixels per square inch */
967   a = res_a->x * res_a->y;
968   b = res_b->x * res_b->y;
969   i = (a > b) - (a < b);
970   if (i) return i;
971 
972   /* Compare how much the pixel shape deviates from a square, the
973      more, the worse */
974   a = 100 * res_a->y / res_a->x;
975   if (a > 100) a = 10000 / a;
976   b = 100 * res_b->y / res_b->x;
977   if (b > 100) b = 10000 / b;
978   return (a > b) - (a < b);
979 }
980 
981 void *
copy_resolution(void * resolution,void * user_data)982 copy_resolution(void *resolution, void *user_data)
983 {
984   res_t *res = (res_t *)resolution;
985   res_t *copy;
986 
987   copy = (res_t *)calloc(1, sizeof(res_t));
988   if (copy) {
989     copy->x = res->x;
990     copy->y = res->y;
991   }
992 
993   return copy;
994 }
995 
996 void
free_resolution(void * resolution,void * user_data)997 free_resolution(void *resolution, void *user_data)
998 {
999   res_t *res = (res_t *)resolution;
1000 
1001   if (res) free(res);
1002 }
1003 
1004 cups_array_t *
resolutionArrayNew()1005 resolutionArrayNew()
1006 {
1007   return cupsArrayNew3(compare_resolutions, NULL, NULL, 0,
1008 		       copy_resolution, free_resolution);
1009 }
1010 
1011 res_t *
resolutionNew(int x,int y)1012 resolutionNew(int x, int y)
1013 {
1014   res_t *res = (res_t *)calloc(1, sizeof(res_t));
1015   if (res) {
1016     res->x = x;
1017     res->y = y;
1018   }
1019   return res;
1020 }
1021 
1022 /* Read a single resolution from an IPP attribute, take care of
1023    obviously wrong entries (printer firmware bugs), ignoring
1024    resolutions of less than 75 dpi in at least one dimension and
1025    fixing Brother's "600x2dpi" resolutions. */
1026 res_t *
ippResolutionToRes(ipp_attribute_t * attr,int index)1027 ippResolutionToRes(ipp_attribute_t *attr, int index)
1028 {
1029   res_t *res = NULL;
1030   int x = 0, y = 0;
1031 
1032   if (attr) {
1033     ipp_tag_t tag = ippGetValueTag(attr);
1034     int count = ippGetCount(attr);
1035 
1036     if (tag == IPP_TAG_RESOLUTION && index < count) {
1037       pwg_ppdize_resolution(attr, index, &x, &y, NULL, 0);
1038       if (y == 2) y = x; /* Brother quirk ("600x2dpi") */
1039       if (x >= 75 && y >= 75)
1040 	res = resolutionNew(x, y);
1041     }
1042   }
1043 
1044   return res;
1045 }
1046 
1047 cups_array_t *
ippResolutionListToArray(ipp_attribute_t * attr)1048 ippResolutionListToArray(ipp_attribute_t *attr)
1049 {
1050   cups_array_t *res_array = NULL;
1051   res_t *res;
1052   int i;
1053 
1054   if (attr) {
1055     ipp_tag_t tag = ippGetValueTag(attr);
1056     int count = ippGetCount(attr);
1057 
1058     if (tag == IPP_TAG_RESOLUTION && count > 0) {
1059       res_array = resolutionArrayNew();
1060       if (res_array) {
1061 	for (i = 0; i < count; i ++)
1062 	  if ((res = ippResolutionToRes(attr, i)) != NULL) {
1063 	    if (cupsArrayFind(res_array, res) == NULL)
1064 	      cupsArrayAdd(res_array, res);
1065 	    free_resolution(res, NULL);
1066 	  }
1067       }
1068       if (cupsArrayCount(res_array) == 0) {
1069 	cupsArrayDelete(res_array);
1070 	res_array = NULL;
1071       }
1072     }
1073   }
1074 
1075   return res_array;
1076 }
1077 
1078 /* Build up an array of common resolutions and most desirable default
1079    resolution from multiple arrays of resolutions with an optional
1080    default resolution.
1081    Call this function with each resolution array you find as "new", and
1082    in "current" an array of the common resolutions will be built up.
1083    You do not need to create an empty array for "current" before
1084    starting. Initialize it with NULL.
1085    "current_default" holds the default resolution of the array "current".
1086    It will get replaced by "new_default" if "current_default" is either
1087    NULL or a resolution which is not in "current" any more.
1088    "new" and "new_default" will be deleted/freed and set to NULL after
1089    each, successful or unsuccssful operation.
1090    Note that when calling this function the addresses of the pointers
1091    to the resolution arrays and default resolutions have to be given
1092    (call by reference) as all will get modified by the function. */
1093 
1094 int /* 1 on success, 0 on failure */
joinResolutionArrays(cups_array_t ** current,cups_array_t ** new,res_t ** current_default,res_t ** new_default)1095 joinResolutionArrays(cups_array_t **current, cups_array_t **new,
1096 		     res_t **current_default, res_t **new_default)
1097 {
1098   res_t *res;
1099   int retval;
1100 
1101   if (current == NULL || new == NULL || *new == NULL ||
1102       cupsArrayCount(*new) == 0) {
1103     retval = 0;
1104     goto finish;
1105   }
1106 
1107   if (*current == NULL) {
1108     /* We are adding the very first resolution array, simply make it
1109        our common resolutions array */
1110     *current = *new;
1111     if (current_default) {
1112       if (*current_default)
1113 	free(*current_default);
1114       *current_default = (new_default ? *new_default : NULL);
1115     }
1116     return 1;
1117   } else if (cupsArrayCount(*current) == 0) {
1118     retval = 1;
1119     goto finish;
1120   }
1121 
1122   /* Dry run: Check whether the two array have at least one resolution
1123      in common, if not, do not touch the original array */
1124   for (res = cupsArrayFirst(*current);
1125        res; res = cupsArrayNext(*current))
1126     if (cupsArrayFind(*new, res))
1127       break;
1128 
1129   if (res) {
1130     /* Reduce the original array to the resolutions which are in both
1131        the original and the new array, at least one resolution will
1132        remain. */
1133     for (res = cupsArrayFirst(*current);
1134 	 res; res = cupsArrayNext(*current))
1135       if (!cupsArrayFind(*new, res))
1136 	cupsArrayRemove(*current, res);
1137     if (current_default) {
1138       /* Replace the current default by the new one if the current default
1139 	 is not in the array any more or if it is NULL. If the new default
1140 	 is not in the list or NULL in such a case, set the current default
1141 	 to NULL */
1142       if (*current_default && !cupsArrayFind(*current, *current_default)) {
1143 	free(*current_default);
1144 	*current_default = NULL;
1145       }
1146       if (*current_default == NULL && new_default && *new_default &&
1147 	  cupsArrayFind(*current, *new_default))
1148 	*current_default = copy_resolution(*new_default, NULL);
1149     }
1150     retval = 1;
1151   } else
1152     retval = 0;
1153 
1154  finish:
1155   if (new && *new) {
1156     cupsArrayDelete(*new);
1157     *new = NULL;
1158   }
1159   if (new_default && *new_default) {
1160     free(*new_default);
1161     *new_default = NULL;
1162   }
1163   return retval;
1164 }
1165 
generate_sizes(ipp_t * response,ipp_attribute_t ** defattr,int * min_length,int * min_width,int * max_length,int * max_width,int * bottom,int * left,int * right,int * top,char * ppdname)1166 cups_array_t* generate_sizes(ipp_t *response,
1167                              ipp_attribute_t **defattr,
1168                              int* min_length,
1169                              int* min_width,
1170                              int* max_length,
1171                              int* max_width,
1172                              int* bottom,
1173                              int* left,
1174                              int* right,
1175                              int* top,
1176                              char* ppdname)
1177 {
1178   cups_array_t             *sizes;               /* Media sizes we've added */
1179   ipp_attribute_t          *attr,                /* xxx-supported */
1180                            *x_dim, *y_dim;       /* Media dimensions */
1181   ipp_t                    *media_col,           /* Media collection */
1182                            *media_size;          /* Media size collection */
1183   int                      i, j, count = 0;
1184   pwg_media_t              *pwg;                 /* PWG media size */
1185   int                      left_def, right_def, bottom_def, top_def;
1186   ipp_attribute_t          *margin;  /* media-xxx-margin attribute */
1187   const char               *psname;
1188   // 2 attributes which hold a list of media-col structures. Paesing is the
1189   // same for them, so we use the same code. "media-col-database" is parsed
1190   // first as it is more complete an accurate, "media-col-ready" is more
1191   // a fallback if there is no "media-col-database".
1192   const char * const col_attrs[] =
1193   {
1194     "media-col-database",
1195     "media-col-ready",
1196   };
1197 
1198 
1199   if ((attr = ippFindAttribute(response, "media-bottom-margin-supported",
1200 			       IPP_TAG_INTEGER)) != NULL) {
1201     for (i = 1, *bottom = ippGetInteger(attr, 0), count = ippGetCount(attr);
1202 	 i < count; i ++)
1203       if (ippGetInteger(attr, i) < *bottom)
1204         *bottom = ippGetInteger(attr, i);
1205   } else
1206     *bottom = 1270;
1207 
1208   if ((attr = ippFindAttribute(response, "media-left-margin-supported",
1209 			       IPP_TAG_INTEGER)) != NULL) {
1210     for (i = 1, *left = ippGetInteger(attr, 0), count = ippGetCount(attr);
1211 	 i < count; i ++)
1212       if (ippGetInteger(attr, i) < *left)
1213         *left = ippGetInteger(attr, i);
1214   } else
1215     *left = 635;
1216 
1217   if ((attr = ippFindAttribute(response, "media-right-margin-supported",
1218 			       IPP_TAG_INTEGER)) != NULL) {
1219     for (i = 1, *right = ippGetInteger(attr, 0), count = ippGetCount(attr);
1220 	 i < count; i ++)
1221       if (ippGetInteger(attr, i) < *right)
1222         *right = ippGetInteger(attr, i);
1223   } else
1224     *right = 635;
1225 
1226   if ((attr = ippFindAttribute(response, "media-top-margin-supported",
1227 			       IPP_TAG_INTEGER)) != NULL) {
1228     for (i = 1, *top = ippGetInteger(attr, 0), count = ippGetCount(attr);
1229 	 i < count; i ++)
1230       if (ippGetInteger(attr, i) < *top)
1231         *top = ippGetInteger(attr, i);
1232   } else
1233     *top = 1270;
1234 
1235   if ((*defattr = ippFindAttribute(response, "media-col-default",
1236 				   IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1237     if ((attr = ippFindAttribute(ippGetCollection(*defattr, 0), "media-size",
1238 				 IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1239       media_size = ippGetCollection(attr, 0);
1240       x_dim      = ippFindAttribute(media_size, "x-dimension", IPP_TAG_INTEGER);
1241       y_dim      = ippFindAttribute(media_size, "y-dimension", IPP_TAG_INTEGER);
1242 
1243       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1244 				     "media-bottom-margin", IPP_TAG_INTEGER))
1245 	  != NULL)
1246 	bottom_def = ippGetInteger(margin, 0);
1247       else
1248 	bottom_def = *bottom;
1249 
1250       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1251 				     "media-left-margin", IPP_TAG_INTEGER))
1252 	  != NULL)
1253 	left_def = ippGetInteger(margin, 0);
1254       else
1255 	left_def = *left;
1256 
1257       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1258 				     "media-right-margin", IPP_TAG_INTEGER))
1259 	  != NULL)
1260 	right_def = ippGetInteger(margin, 0);
1261       else
1262 	right_def = *right;
1263 
1264       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1265 				     "media-top-margin", IPP_TAG_INTEGER))
1266 	  != NULL)
1267 	top_def = ippGetInteger(margin, 0);
1268       else
1269 	top_def = *top;
1270 
1271       if (x_dim && y_dim &&
1272 	  (pwg = pwgMediaForSize(ippGetInteger(x_dim, 0),
1273 				 ippGetInteger(y_dim, 0))) != NULL) {
1274 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1275         if (bottom_def == 0 && left_def == 0 && right_def == 0 && top_def == 0)
1276           snprintf(ppdname, PPD_MAX_NAME, "%s.Borderless", psname);
1277         else
1278           strlcpy(ppdname, psname, PPD_MAX_NAME);
1279       } else
1280 	strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1281     } else
1282       strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1283   } else if ((pwg =
1284 	      pwgMediaForPWG(ippGetString(ippFindAttribute(response,
1285 							   "media-default",
1286 							   IPP_TAG_ZERO), 0,
1287 					  NULL))) != NULL) {
1288     psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1289     strlcpy(ppdname, psname, PPD_MAX_NAME);
1290   } else
1291     strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1292 
1293   sizes = cupsArrayNew3((cups_array_func_t)pwg_compare_sizes, NULL, NULL, 0,
1294 			(cups_acopy_func_t)pwg_copy_size,
1295 			(cups_afree_func_t)free);
1296 
1297   // Go through all attributes which are lists of media-col structures
1298   for (j = 0; j < sizeof(col_attrs) / sizeof(col_attrs[0]); j ++)
1299   if ((attr = ippFindAttribute(response, col_attrs[j],
1300 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1301     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1302       cups_size_t temp;   /* Current size */
1303 
1304       media_col   = ippGetCollection(attr, i);
1305       media_size  =
1306 	ippGetCollection(ippFindAttribute(media_col, "media-size",
1307 					  IPP_TAG_BEGIN_COLLECTION), 0);
1308       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
1309       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
1310       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0),
1311 				    ippGetInteger(y_dim, 0));
1312 
1313       if (pwg) {
1314 	temp.width  = pwg->width;
1315 	temp.length = pwg->length;
1316 
1317 	if ((margin = ippFindAttribute(media_col, "media-bottom-margin",
1318 				       IPP_TAG_INTEGER)) != NULL)
1319 	  temp.bottom = ippGetInteger(margin, 0);
1320 	else
1321 	  temp.bottom = *bottom;
1322 
1323 	if ((margin = ippFindAttribute(media_col, "media-left-margin",
1324 				       IPP_TAG_INTEGER)) != NULL)
1325 	  temp.left = ippGetInteger(margin, 0);
1326 	else
1327 	  temp.left = *left;
1328 
1329 	if ((margin = ippFindAttribute(media_col, "media-right-margin",
1330 				       IPP_TAG_INTEGER)) != NULL)
1331 	  temp.right = ippGetInteger(margin, 0);
1332 	else
1333 	  temp.right = *right;
1334 
1335 	if ((margin = ippFindAttribute(media_col, "media-top-margin",
1336 				       IPP_TAG_INTEGER)) != NULL)
1337 	  temp.top = ippGetInteger(margin, 0);
1338 	else
1339 	  temp.top = *top;
1340 
1341 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1342 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1343 	    temp.top == 0)
1344 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1345 	else
1346 	  strlcpy(temp.media, psname, sizeof(temp.media));
1347 
1348 	if (!cupsArrayFind(sizes, &temp))
1349 	  cupsArrayAdd(sizes, &temp);
1350       } else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE ||
1351 		 ippGetValueTag(y_dim) == IPP_TAG_RANGE) {
1352 	/*
1353 	 * Custom size - record the min/max values...
1354 	 */
1355 
1356 	int lower, upper;   /* Range values */
1357 
1358 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
1359 	  lower = ippGetRange(x_dim, 0, &upper);
1360 	else
1361 	  lower = upper = ippGetInteger(x_dim, 0);
1362 
1363 	if (lower < *min_width)
1364 	  *min_width = lower;
1365 	if (upper > *max_width)
1366 	  *max_width = upper;
1367 
1368 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
1369 	  lower = ippGetRange(y_dim, 0, &upper);
1370 	else
1371 	  lower = upper = ippGetInteger(y_dim, 0);
1372 
1373 	if (lower < *min_length)
1374 	  *min_length = lower;
1375 	if (upper > *max_length)
1376 	  *max_length = upper;
1377       }
1378     }
1379   }
1380   if ((attr = ippFindAttribute(response, "media-size-supported",
1381 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1382     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1383       cups_size_t temp;   /* Current size */
1384 
1385       media_size  = ippGetCollection(attr, i);
1386       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
1387       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
1388       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0),
1389 				    ippGetInteger(y_dim, 0));
1390 
1391       if (pwg) {
1392 	temp.width  = pwg->width;
1393 	temp.length = pwg->length;
1394 	temp.bottom = *bottom;
1395 	temp.left   = *left;
1396 	temp.right  = *right;
1397 	temp.top    = *top;
1398 
1399 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1400 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1401 	    temp.top == 0)
1402 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1403 	else
1404 	  strlcpy(temp.media, psname, sizeof(temp.media));
1405 
1406 	if (!cupsArrayFind(sizes, &temp))
1407 	  cupsArrayAdd(sizes, &temp);
1408       } else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE ||
1409 		 ippGetValueTag(y_dim) == IPP_TAG_RANGE) {
1410 	/*
1411 	 * Custom size - record the min/max values...
1412 	 */
1413 
1414 	int lower, upper;   /* Range values */
1415 
1416 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
1417 	  lower = ippGetRange(x_dim, 0, &upper);
1418 	else
1419 	  lower = upper = ippGetInteger(x_dim, 0);
1420 
1421 	if (lower < *min_width)
1422 	  *min_width = lower;
1423 	if (upper > *max_width)
1424 	  *max_width = upper;
1425 
1426 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
1427 	  lower = ippGetRange(y_dim, 0, &upper);
1428 	else
1429 	  lower = upper = ippGetInteger(y_dim, 0);
1430 
1431 	if (lower < *min_length)
1432 	  *min_length = lower;
1433 	if (upper > *max_length)
1434 	  *max_length = upper;
1435       }
1436     }
1437   }
1438   if ((attr = ippFindAttribute(response, "media-supported", IPP_TAG_ZERO))
1439       != NULL) {
1440     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1441       const char  *pwg_size = ippGetString(attr, i, NULL);
1442       /* PWG size name */
1443       cups_size_t temp, *temp2; /* Current size, found size */
1444 
1445       if ((pwg = pwgMediaForPWG(pwg_size)) != NULL) {
1446         if (strstr(pwg_size, "_max_") || strstr(pwg_size, "_max.")) {
1447           if (pwg->width > *max_width)
1448             *max_width = pwg->width;
1449           if (pwg->length > *max_length)
1450             *max_length = pwg->length;
1451         } else if (strstr(pwg_size, "_min_") || strstr(pwg_size, "_min.")) {
1452           if (pwg->width < *min_width)
1453             *min_width = pwg->width;
1454           if (pwg->length < *min_length)
1455             *min_length = pwg->length;
1456         } else {
1457 	  temp.width  = pwg->width;
1458 	  temp.length = pwg->length;
1459 	  temp.bottom = *bottom;
1460 	  temp.left   = *left;
1461 	  temp.right  = *right;
1462 	  temp.top    = *top;
1463 
1464 	  psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1465 	  if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1466 	      temp.top == 0)
1467 	    snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1468 	  else
1469 	    strlcpy(temp.media, psname, sizeof(temp.media));
1470 
1471 	  /* Add the printer's original IPP name to an already found size */
1472 	  if ((temp2 = cupsArrayFind(sizes, &temp)) != NULL) {
1473 	    snprintf(temp2->media + strlen(temp2->media),
1474 		     sizeof(temp2->media) - strlen(temp2->media),
1475 		     " %s", pwg_size);
1476 	    /* Check if we have also a borderless version of the size and add
1477 	       the original IPP name also there */
1478 	    snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1479 	    if ((temp2 = cupsArrayFind(sizes, &temp)) != NULL)
1480 	      snprintf(temp2->media + strlen(temp2->media),
1481 		       sizeof(temp2->media) - strlen(temp2->media),
1482 		       " %s", pwg_size);
1483 	  } else
1484 	    cupsArrayAdd(sizes, &temp);
1485 	}
1486       }
1487     }
1488   }
1489   return sizes;
1490 }
1491 
is_colordevice(const char * keyword,ipp_attribute_t * attr)1492 int is_colordevice(const char *keyword,ipp_attribute_t *attr)
1493 {
1494   if (!strcasecmp(keyword, "sgray_16") || !strncmp(keyword, "W8-16", 5) ||
1495       !strncmp(keyword, "W16", 3))
1496     return 1;
1497   else if (!strcasecmp(keyword, "srgb_8") || !strncmp(keyword, "SRGB24", 6) ||
1498 	   !strcmp(keyword, "color"))
1499     return 1;
1500   else if ((!strcasecmp(keyword, "srgb_16") ||
1501 	    !strncmp(keyword, "SRGB48", 6)) &&
1502 	   !ippContainsString(attr, "srgb_8"))
1503     return 1;
1504   else if (!strcasecmp(keyword, "adobe-rgb_16") ||
1505 	   !strncmp(keyword, "ADOBERGB48", 10) ||
1506 	   !strncmp(keyword, "ADOBERGB24-48", 13))
1507     return 1;
1508   else if ((!strcasecmp(keyword, "adobe-rgb_8") ||
1509 	    !strcmp(keyword, "ADOBERGB24")) &&
1510 	   !ippContainsString(attr, "adobe-rgb_16"))
1511     return 1;
1512   else if ((!strcasecmp(keyword, "cmyk_8") &&
1513 	    !ippContainsString(attr, "cmyk_16")) ||
1514 	   !strcmp(keyword, "DEVCMYK32"))
1515     return 1;
1516   else if (!strcasecmp(keyword, "cmyk_16") ||
1517 	   !strcmp(keyword, "DEVCMYK32-64") ||
1518 	   !strcmp(keyword, "DEVCMYK64"))
1519     return 1;
1520   else if ((!strcasecmp(keyword, "rgb_8") &&
1521 	    !ippContainsString(attr, "rgb_16"))
1522 	   || !strcmp(keyword, "DEVRGB24"))
1523     return 1;
1524   else if (!strcasecmp(keyword, "rgb_16") ||
1525 	   !strcmp(keyword, "DEVRGB24-48") ||
1526 	   !strcmp(keyword, "DEVRGB48"))
1527     return 1;
1528   return 0;
1529 }
1530 
1531 /*
1532  * 'ppdCreateFromIPP()' - Create a PPD file describing the capabilities
1533  *                        of an IPP printer (legacy interface).
1534  */
1535 
1536 char *                                           /* O - PPD filename or NULL on
1537 						    error */
ppdCreateFromIPP(char * buffer,size_t bufsize,ipp_t * response,const char * make_model,const char * pdl,int color,int duplex)1538 ppdCreateFromIPP (char         *buffer,          /* I - Filename buffer */
1539 		  size_t       bufsize,          /* I - Size of filename
1540 						        buffer */
1541 		  ipp_t        *response,        /* I - Get-Printer-Attributes
1542 						        response */
1543 		  const char   *make_model,      /* I - Make and model from
1544 						        DNS-SD */
1545 		  const char   *pdl,             /* I - List of PDLs from
1546 						        DNS-SD */
1547 		  int          color,            /* I - Color printer? (from
1548 						        DNS-SD) */
1549 		  int          duplex)           /* I - Duplex printer? (from
1550 						        DNS-SD) */
1551 {
1552   return ppdCreateFromIPP2(buffer, bufsize, response, make_model, pdl,
1553 			   color, duplex, NULL, NULL, NULL, NULL);
1554 }
1555 
1556 /*
1557  * 'ppdCreateFromIPP2()' - Create a PPD file describing the capabilities
1558  *                         of an IPP printer.
1559  */
1560 
1561 char *                                           /* O - PPD filename or NULL on
1562 						    error */
ppdCreateFromIPP2(char * buffer,size_t bufsize,ipp_t * response,const char * make_model,const char * pdl,int color,int duplex,cups_array_t * conflicts,cups_array_t * sizes,char * default_pagesize,const char * default_cluster_color)1563 ppdCreateFromIPP2(char         *buffer,          /* I - Filename buffer */
1564 		  size_t       bufsize,          /* I - Size of filename
1565 						        buffer */
1566 		  ipp_t        *response,        /* I - Get-Printer-Attributes
1567 						        response */
1568 		  const char   *make_model,      /* I - Make and model from
1569 						        DNS-SD */
1570 		  const char   *pdl,             /* I - List of PDLs from
1571 						        DNS-SD */
1572 		  int          color,            /* I - Color printer? (from
1573 						        DNS-SD) */
1574 		  int          duplex,           /* I - Duplex printer? (from
1575 						        DNS-SD) */
1576 		  cups_array_t *conflicts,       /* I - Array of constraints */
1577 		  cups_array_t *sizes,           /* I - Media sizes we've
1578 						        added */
1579 		  char*        default_pagesize, /* I - Default page size*/
1580 		  const char   *default_cluster_color) /* I - cluster def
1581 							color (if cluster's
1582 							attributes are
1583 							returned) */
1584 {
1585   cups_file_t		*fp;		/* PPD file */
1586   cups_array_t		*printer_sizes;	/* Media sizes we've added */
1587   cups_size_t		*size;		/* Current media size */
1588   ipp_attribute_t	*attr,		/* xxx-supported */
1589                         *attr2,
1590 			*defattr,	/* xxx-default */
1591                         *quality,	/* print-quality-supported */
1592 			*x_dim, *y_dim;	/* Media dimensions */
1593   ipp_t			*media_col,	/* Media collection */
1594 			*media_size;	/* Media size collection */
1595   char			make[256],	/* Make and model */
1596 			*model,		/* Model name */
1597 			ppdname[PPD_MAX_NAME];
1598 		    			/* PPD keyword */
1599   int			i, j,		/* Looping vars */
1600 			count = 0,	/* Number of values */
1601 			bottom,		/* Largest bottom margin */
1602 			left,		/* Largest left margin */
1603 			right,		/* Largest right margin */
1604 			top,		/* Largest top margin */
1605 			max_length = 0,	/* Maximum custom size */
1606 			max_width = 0,
1607 			min_length = INT_MAX,
1608 					/* Minimum custom size */
1609 			min_width = INT_MAX,
1610 			is_apple = 0,	/* Does the printer support Apple
1611 					   Raster? */
1612                         is_pwg = 0,	/* Does the printer support PWG
1613 					   Raster? */
1614                         is_pclm = 0,    /* Does the printer support PCLm? */
1615                         is_pdf = 0;     /* Does the printer support PDF? */
1616   pwg_media_t		*pwg;		/* PWG media size */
1617   int			xres, yres;	/* Resolution values */
1618   cups_array_t          *common_res,    /* Common resolutions of all PDLs */
1619                         *current_res,   /* Resolutions of current PDL */
1620                         *pdl_list;      /* List of PDLs */
1621   res_t                 *common_def,    /* Common default resolution */
1622                         *current_def,   /* Default resolution of current PDL */
1623                         *min_res,       /* Minimum common resolution */
1624                         *max_res;       /* Maximum common resolution */
1625   cups_lang_t		*lang = cupsLangDefault();
1626 					/* Localization info */
1627   struct lconv		*loc = localeconv();
1628 					/* Locale data */
1629   cups_array_t          *printer_opt_strings_catalog = NULL;
1630                                         /* Printer-specific option UI strings */
1631   char                  *human_readable,
1632                         *human_readable2;
1633   const char		*keyword;	/* Keyword value */
1634   cups_array_t		*fin_options = NULL;
1635 					/* Finishing options */
1636   char			buf[256],
1637                         filter_path[1024];
1638                                         /* Path to filter executable */
1639   const char		*cups_serverbin;/* CUPS_SERVERBIN environment
1640 					   variable */
1641   char			*defaultoutbin = NULL;
1642   const char		*outbin;
1643   char			outbin_properties[1024];
1644   int			octet_str_len;
1645   void			*outbin_properties_octet;
1646   int			outputorderinfofound = 0,
1647 			faceupdown = 1,
1648 			firsttolast = 1;
1649   int			manual_copies = -1,
1650 			is_fax = 0;
1651 
1652  /*
1653   * Range check input...
1654   */
1655 
1656   if (buffer)
1657     *buffer = '\0';
1658 
1659   if (!buffer || bufsize < 1) {
1660     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1661     return (NULL);
1662   }
1663 
1664   if (!response) {
1665     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No IPP attributes."), 1);
1666     return (NULL);
1667   }
1668 
1669  /*
1670   * Open a temporary file for the PPD...
1671   */
1672 
1673   if ((fp = cupsTempFile2(buffer, (int)bufsize)) == NULL) {
1674     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
1675     return (NULL);
1676   }
1677 
1678  /*
1679   * Standard stuff for PPD file...
1680   */
1681 
1682   cupsFilePuts(fp, "*PPD-Adobe: \"4.3\"\n");
1683   cupsFilePuts(fp, "*FormatVersion: \"4.3\"\n");
1684   cupsFilePrintf(fp, "*FileVersion: \"%s\"\n", VERSION);
1685   cupsFilePuts(fp, "*LanguageVersion: English\n");
1686   cupsFilePuts(fp, "*LanguageEncoding: ISOLatin1\n");
1687   cupsFilePuts(fp, "*PSVersion: \"(3010.000) 0\"\n");
1688   cupsFilePuts(fp, "*LanguageLevel: \"3\"\n");
1689   cupsFilePuts(fp, "*FileSystem: False\n");
1690   cupsFilePuts(fp, "*PCFileName: \"drvless.ppd\"\n");
1691 
1692   if ((attr = ippFindAttribute(response, "ipp-features-supported",
1693 			       IPP_TAG_KEYWORD)) != NULL &&
1694       ippContainsString(attr, "faxout"))
1695   {
1696     attr = ippFindAttribute(response, "printer-uri-supported",
1697 			    IPP_TAG_URI);
1698     if (attr)
1699     {
1700       ippAttributeString(attr, buf, sizeof(buf));
1701       if (strcasestr(buf, "faxout"))
1702 	is_fax = 1;
1703     }
1704   }
1705 
1706   if ((attr = ippFindAttribute(response, "printer-make-and-model",
1707 			       IPP_TAG_TEXT)) != NULL)
1708     strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make));
1709   else if (make_model && make_model[0] != '\0')
1710     strlcpy(make, make_model, sizeof(make));
1711   else
1712     strlcpy(make, "Unknown Printer", sizeof(make));
1713 
1714   if (!_cups_strncasecmp(make, "Hewlett Packard ", 16) ||
1715       !_cups_strncasecmp(make, "Hewlett-Packard ", 16)) {
1716     model = make + 16;
1717     strlcpy(make, "HP", sizeof(make));
1718   }
1719   else if ((model = strchr(make, ' ')) != NULL)
1720     *model++ = '\0';
1721   else
1722     model = make;
1723 
1724   cupsFilePrintf(fp, "*Manufacturer: \"%s\"\n", make);
1725   cupsFilePrintf(fp, "*ModelName: \"%s %s\"\n", make, model);
1726   cupsFilePrintf(fp, "*Product: \"(%s %s)\"\n", make, model);
1727   cupsFilePrintf(fp, "*NickName: \"%s %s, %sdriverless, cups-filters %s\"\n",
1728 		 make, model, (is_fax ? "Fax, " : ""), VERSION);
1729   cupsFilePrintf(fp, "*ShortNickName: \"%s %s\"\n", make, model);
1730 
1731   /* Which is the default output bin? */
1732   if ((attr = ippFindAttribute(response, "output-bin-default", IPP_TAG_ZERO))
1733       != NULL)
1734     defaultoutbin = strdup(ippGetString(attr, 0, NULL));
1735   /* Find out on which position of the list of output bins the default one is,
1736      if there is no default bin, take the first of this list */
1737   i = 0;
1738   if ((attr = ippFindAttribute(response, "output-bin-supported",
1739 			       IPP_TAG_ZERO)) != NULL) {
1740     count = ippGetCount(attr);
1741     for (i = 0; i < count; i ++) {
1742       outbin = ippGetString(attr, i, NULL);
1743       if (outbin == NULL)
1744 	continue;
1745       if (defaultoutbin == NULL) {
1746 	defaultoutbin = strdup(outbin);
1747 	break;
1748       } else if (strcasecmp(outbin, defaultoutbin) == 0)
1749 	break;
1750     }
1751   }
1752   if ((attr = ippFindAttribute(response, "printer-output-tray",
1753 			       IPP_TAG_STRING)) != NULL &&
1754       i < ippGetCount(attr)) {
1755     outbin_properties_octet = ippGetOctetString(attr, i, &octet_str_len);
1756     memset(outbin_properties, 0, sizeof(outbin_properties));
1757     memcpy(outbin_properties, outbin_properties_octet,
1758 	   ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
1759 	    (size_t)octet_str_len : sizeof(outbin_properties) - 1));
1760     if (strcasestr(outbin_properties, "pagedelivery=faceUp")) {
1761       outputorderinfofound = 1;
1762       faceupdown = -1;
1763     }
1764     if (strcasestr(outbin_properties, "stackingorder=lastToFirst"))
1765       firsttolast = -1;
1766   }
1767   if (outputorderinfofound == 0 && defaultoutbin &&
1768       strcasestr(defaultoutbin, "face-up"))
1769     faceupdown = -1;
1770   if (defaultoutbin)
1771     free (defaultoutbin);
1772   if (firsttolast * faceupdown < 0)
1773     cupsFilePuts(fp, "*DefaultOutputOrder: Reverse\n");
1774   else
1775     cupsFilePuts(fp, "*DefaultOutputOrder: Normal\n");
1776 
1777   /* To decide whether the printer is coloured or not we see the various
1778      colormodel supported by the printer*/
1779   if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD))
1780       == NULL)
1781     if ((attr = ippFindAttribute(response,
1782 				 "pwg-raster-document-type-supported",
1783 				 IPP_TAG_KEYWORD)) == NULL)
1784       if ((attr = ippFindAttribute(response, "print-color-mode-supported",
1785 				   IPP_TAG_KEYWORD)) == NULL)
1786         attr = ippFindAttribute(response, "output-mode-supported",
1787 				IPP_TAG_KEYWORD);
1788   if (attr==NULL || !ippGetCount(attr)) {
1789     if ((attr = ippFindAttribute(response, "color-supported", IPP_TAG_BOOLEAN))
1790 	!= NULL) {
1791       if (ippGetBoolean(attr, 0))
1792 	cupsFilePuts(fp, "*ColorDevice: True\n");
1793       else
1794 	cupsFilePuts(fp, "*ColorDevice: False\n");
1795     } else {
1796       if(color)
1797 	cupsFilePuts(fp, "*ColorDevice: True\n");
1798       else
1799 	cupsFilePuts(fp, "*ColorDevice: False\n");
1800     }
1801   } else {
1802     int   colordevice = 0;
1803     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1804       keyword = ippGetString(attr, i, NULL);
1805       colordevice = is_colordevice(keyword,attr);
1806       if (colordevice) {
1807 	cupsFilePuts(fp, "*ColorDevice: True\n");
1808 	break;
1809       }
1810     }
1811     if (colordevice == 0)
1812       cupsFilePuts(fp, "*ColorDevice: False\n");
1813   }
1814 
1815   cupsFilePrintf(fp, "*cupsVersion: %d.%d\n", CUPS_VERSION_MAJOR,
1816 		 CUPS_VERSION_MINOR);
1817   cupsFilePuts(fp, "*cupsSNMPSupplies: False\n");
1818   cupsFilePuts(fp, "*cupsLanguages: \"en\"\n");
1819 
1820   if ((attr = ippFindAttribute(response, "printer-more-info", IPP_TAG_URI)) !=
1821       NULL)
1822     cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL));
1823 
1824   if ((attr = ippFindAttribute(response, "printer-charge-info-uri",
1825 			       IPP_TAG_URI)) != NULL)
1826     cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0,
1827 								    NULL));
1828 
1829   /* Message catalogs for UI strings */
1830   if (opt_strings_catalog == NULL) {
1831     opt_strings_catalog = optArrayNew();
1832     load_opt_strings_catalog(NULL, opt_strings_catalog);
1833   }
1834   if ((attr = ippFindAttribute(response, "printer-strings-uri",
1835 			       IPP_TAG_URI)) != NULL) {
1836     printer_opt_strings_catalog = optArrayNew();
1837     load_opt_strings_catalog(ippGetString(attr, 0, NULL),
1838 			     printer_opt_strings_catalog);
1839     if (printer_opt_strings_catalog)
1840       cupsFilePrintf(fp, "*cupsStringsURI: \"%s\"\n", ippGetString(attr, 0,
1841 								   NULL));
1842   }
1843 
1844  /*
1845   * Accounting...
1846   */
1847 
1848   if (ippGetBoolean(ippFindAttribute(response, "job-account-id-supported",
1849 				     IPP_TAG_BOOLEAN), 0))
1850     cupsFilePuts(fp, "*cupsJobAccountId: True\n");
1851 
1852   if (ippGetBoolean(ippFindAttribute(response,
1853 				     "job-accounting-user-id-supported",
1854 				     IPP_TAG_BOOLEAN), 0))
1855     cupsFilePuts(fp, "*cupsJobAccountingUserId: True\n");
1856 
1857  /*
1858   * Password/PIN printing...
1859   */
1860 
1861   if ((attr = ippFindAttribute(response, "job-password-supported",
1862 			       IPP_TAG_INTEGER)) != NULL) {
1863     char	pattern[33];		/* Password pattern */
1864     int		maxlen = ippGetInteger(attr, 0);
1865 					/* Maximum length */
1866     const char	*repertoire =
1867       ippGetString(ippFindAttribute(response,
1868 				    "job-password-repertoire-configured",
1869 				    IPP_TAG_KEYWORD), 0, NULL);
1870 					/* Type of password */
1871 
1872     if (maxlen > (int)(sizeof(pattern) - 1))
1873       maxlen = (int)sizeof(pattern) - 1;
1874 
1875     if (!repertoire || !strcmp(repertoire, "iana_us-ascii_digits"))
1876       memset(pattern, '1', (size_t)maxlen);
1877     else if (!strcmp(repertoire, "iana_us-ascii_letters"))
1878       memset(pattern, 'A', (size_t)maxlen);
1879     else if (!strcmp(repertoire, "iana_us-ascii_complex"))
1880       memset(pattern, 'C', (size_t)maxlen);
1881     else if (!strcmp(repertoire, "iana_us-ascii_any"))
1882       memset(pattern, '.', (size_t)maxlen);
1883     else if (!strcmp(repertoire, "iana_utf-8_digits"))
1884       memset(pattern, 'N', (size_t)maxlen);
1885     else if (!strcmp(repertoire, "iana_utf-8_letters"))
1886       memset(pattern, 'U', (size_t)maxlen);
1887     else
1888       memset(pattern, '*', (size_t)maxlen);
1889 
1890     pattern[maxlen] = '\0';
1891 
1892     cupsFilePrintf(fp, "*cupsJobPassword: \"%s\"\n", pattern);
1893   }
1894 
1895 
1896 
1897  /*
1898   * PDLs and common resolutions ...
1899   */
1900 
1901   common_res = NULL;
1902   current_res = NULL;
1903   common_def = NULL;
1904   current_def = NULL;
1905   min_res = NULL;
1906   max_res = NULL;
1907   /* Put all available PDls into a simple case-insensitevely searchable
1908      sorted string list */
1909   if ((pdl_list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0,
1910 				(cups_acopy_func_t)strdup,
1911 				(cups_afree_func_t)free)) == NULL)
1912     goto bad_ppd;
1913   int formatfound = 0;
1914 
1915   if (((attr = ippFindAttribute(response, "document-format-supported",
1916 				IPP_TAG_MIMETYPE)) != NULL) ||
1917       (pdl && pdl[0] != '\0')) {
1918     const char *format = pdl;
1919     i = 0;
1920     count = ippGetCount(attr);
1921     while ((attr && i < count) || /* Go through formats in attribute */
1922 	   (!attr && pdl && pdl[0] != '\0' && format[0] != '\0')) {
1923       /* Go through formats in pdl string (from DNS-SD record) */
1924 
1925       /* Pick next format from attribute */
1926       if (attr) format = ippGetString(attr, i, NULL);
1927       /* Add format to list of supported PDLs, skip duplicates */
1928       if (!cupsArrayFind(pdl_list, (void *)format))
1929 	cupsArrayAdd(pdl_list, (void *)format);
1930       if (attr)
1931 	/* Next format in attribute */
1932 	i ++;
1933       else {
1934 	/* Find the next format in the string pdl, if there is none left,
1935 	   go to the terminating zero */
1936 	while (!isspace(*format) && *format != ',' && *format != '\0')
1937 	  format ++;
1938 	while ((isspace(*format) || *format == ',') && *format != '\0')
1939 	  format ++;
1940       }
1941     }
1942   }
1943 
1944   /*
1945    * Fax
1946    */
1947 
1948   if (is_fax)
1949     cupsFilePuts(fp, "*cupsIPPFaxOut: True\n");
1950 
1951   /* Check for each CUPS/cups-filters-supported PDL, starting with the
1952      most desirable going to the least desirable. If a PDL requires a
1953      certain set of resolutions (the raster-based PDLs), find the
1954      resolutions and find out which are the common resolutions of all
1955      supported PDLs. Choose the default resolution from the most
1956      desirable of all resolution-requiring PDLs if it is common in all
1957      of them. Skip a resolution-requiring PDL if its resolution list
1958      attribute is missing or contains only broken entries. Use the
1959      general resolution list and default resolution of the printer
1960      only if it does not support any resolution-requiring PDL. Use 300
1961      dpi if there is no resolution info at all in the attributes.
1962      In case of PDF as PDL check whether also the
1963      "application/vnd.cups-pdf" MIME type is accepted. In this case
1964      our printer is a remote CUPS queue which already runs the
1965      pdftopdf filter on the server, so let the PPD take
1966      "application/pdf" as input format so that pdftopdf does not also
1967      get executed on the client, applying option settings twice. See
1968      https://github.com/apple/cups/issues/5361 */
1969   if (cupsArrayFind(pdl_list, "application/vnd.cups-pdf")) {
1970     cupsFilePuts(fp, "*cupsFilter2: \"application/pdf application/pdf 0 -\"\n");
1971     manual_copies = 0;
1972     formatfound = 1;
1973     is_pdf = 1;
1974   } else if (cupsArrayFind(pdl_list, "application/pdf")) {
1975     cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 200 -\"\n");
1976     manual_copies = 0;
1977     formatfound = 1;
1978     is_pdf = 1;
1979   }
1980 #ifdef CUPS_RASTER_HAVE_APPLERASTER
1981   if (cupsArrayFind(pdl_list, "image/urf")) {
1982     if ((attr = ippFindAttribute(response, "urf-supported",
1983 				 IPP_TAG_KEYWORD)) != NULL) {
1984       int lowdpi = 0, hidpi = 0; /* Lower and higher resolution */
1985       for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1986 	const char *rs = ippGetString(attr, i, NULL); /* RS value */
1987 	if (_cups_strncasecmp(rs, "RS", 2))
1988 	  continue;
1989 	lowdpi = atoi(rs + 2);
1990 	if ((rs = strrchr(rs, '-')) != NULL)
1991 	  hidpi = atoi(rs + 1);
1992 	else
1993 	  hidpi = lowdpi;
1994 	break;
1995       }
1996       if (lowdpi == 0) {
1997 	/* Invalid "urf-supported" value... */
1998 	goto bad_ppd;
1999       } else {
2000 	if ((current_res = resolutionArrayNew()) != NULL) {
2001 	  if ((current_def = resolutionNew(lowdpi, lowdpi)) != NULL)
2002           {
2003 	    cupsArrayAdd(current_res, current_def);
2004             free_resolution(current_def, NULL);
2005           }
2006 	  if (hidpi != lowdpi &&
2007 	      (current_def = resolutionNew(hidpi, hidpi)) != NULL)
2008           {
2009 	    cupsArrayAdd(current_res, current_def);
2010             free_resolution(current_def, NULL);
2011           }
2012 	  current_def = NULL;
2013 	  if (cupsArrayCount(current_res) > 0 &&
2014 	      joinResolutionArrays(&common_res, &current_res, &common_def,
2015 				   &current_def)) {
2016 	    cupsFilePuts(fp, "*cupsFilter2: \"image/urf image/urf 0 -\"\n");
2017 	    if (formatfound == 0) manual_copies = 1;
2018 	    formatfound = 1;
2019 	    is_apple = 1;
2020 	  }
2021 	}
2022       }
2023     }
2024   }
2025 #endif
2026   if (!is_apple && cupsArrayFind(pdl_list, "image/pwg-raster")) {
2027     if ((attr = ippFindAttribute(response,
2028 				 "pwg-raster-document-resolution-supported",
2029 				 IPP_TAG_RESOLUTION)) != NULL) {
2030       current_def = NULL;
2031       if ((current_res = ippResolutionListToArray(attr)) != NULL &&
2032 	  joinResolutionArrays(&common_res, &current_res, &common_def,
2033 			       &current_def)) {
2034 	cupsFilePuts(fp, "*cupsFilter2: \"image/pwg-raster image/pwg-raster 10 -\"\n");
2035 	if (formatfound == 0) manual_copies = 1;
2036 	formatfound = 1;
2037 	is_pwg = 1;
2038       }
2039     }
2040   }
2041 #ifdef QPDF_HAVE_PCLM
2042   if (!is_apple && !is_pwg && cupsArrayFind(pdl_list, "application/PCLm")) {
2043     if ((attr = ippFindAttribute(response, "pclm-source-resolution-supported",
2044 				 IPP_TAG_RESOLUTION)) != NULL) {
2045       if ((defattr = ippFindAttribute(response,
2046 				      "pclm-source-resolution-default",
2047 				      IPP_TAG_RESOLUTION)) != NULL)
2048 	current_def = ippResolutionToRes(defattr, 0);
2049       else
2050 	current_def = NULL;
2051       if ((current_res = ippResolutionListToArray(attr)) != NULL &&
2052 	  joinResolutionArrays(&common_res, &current_res, &common_def,
2053 			       &current_def)) {
2054 	cupsFilePuts(fp, "*cupsFilter2: \"application/PCLm application/PCLm 300 -\"\n");
2055 	if (formatfound == 0) manual_copies = 1;
2056 	formatfound = 1;
2057 	is_pclm = 1;
2058       }
2059     }
2060   }
2061 #endif
2062   /* Legacy formats only if we have no driverless format */
2063   if (!is_pdf && !is_apple && !is_pwg && !is_pclm) {
2064     if (cupsArrayFind(pdl_list, "application/vnd.hp-pclxl")) {
2065       /* Check whether the gstopxl filter is installed,
2066 	 otherwise ignore the PCL-XL support of the printer */
2067       if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
2068 	cups_serverbin = CUPS_SERVERBIN;
2069       snprintf(filter_path, sizeof(filter_path), "%s/filter/gstopxl",
2070 	       cups_serverbin);
2071       if (access(filter_path, X_OK) == 0) {
2072 	/* We put a high cost factor here as if a printer supports also
2073 	   another format, like PWG or Apple Raster, we prefer it, as some
2074 	   PCL-XL printers have bugs in their PCL-XL interpreters */
2075 	cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/vnd.hp-pclxl 400 gstopxl\"\n");
2076 	if (formatfound == 0) manual_copies = 1;
2077 	formatfound = 1;
2078       }
2079     }
2080     if (cupsArrayFind(pdl_list, "application/postscript")) {
2081       /* We put a high cost factor here as if a printer supports also
2082 	 another format, like PWG or Apple Raster, we prefer it, as many
2083 	 PostScript printers have bugs in their PostScript interpreters */
2084       cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-postscript application/postscript 600 -\"\n");
2085       if (formatfound == 0) manual_copies = 0;
2086       formatfound = 1;
2087     }
2088     if (cupsArrayFind(pdl_list, "application/vnd.hp-pcl")) {
2089       /* We put a high cost factor here as if a printer supports also
2090 	 another format, like PWG or Apple Raster, we prefer it, as there
2091 	 are some printers, like HP inkjets which report to accept PCL
2092 	 but do not support PCL 5c/e or PCL-XL */
2093       cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-raster application/vnd.hp-pcl 800 rastertopclx\"\n");
2094       if (formatfound == 0) manual_copies = 1;
2095       formatfound = 1;
2096     }
2097   }
2098   if (cupsArrayFind(pdl_list, "image/jpeg"))
2099     cupsFilePuts(fp, "*cupsFilter2: \"image/jpeg image/jpeg 0 -\"\n");
2100   if (cupsArrayFind(pdl_list, "image/png"))
2101     cupsFilePuts(fp, "*cupsFilter2: \"image/png image/png 0 -\"\n");
2102   cupsArrayDelete(pdl_list);
2103   if (manual_copies < 0) manual_copies = 1;
2104   if (formatfound == 0)
2105     goto bad_ppd;
2106 
2107   /* For the case that we will print in a raster format and not in a high-level
2108      format, we need to create multiple copies on the client. We add a line to
2109      the PPD which tells the pdftopdf filter to generate the copies */
2110   if (manual_copies == 1)
2111     cupsFilePuts(fp, "*cupsManualCopies: True\n");
2112 
2113   /* No resolution requirements by any of the supported PDLs?
2114      Use "printer-resolution-supported" attribute */
2115   if (common_res == NULL) {
2116     if ((attr = ippFindAttribute(response, "printer-resolution-supported",
2117 				 IPP_TAG_RESOLUTION)) != NULL) {
2118       if ((defattr = ippFindAttribute(response, "printer-resolution-default",
2119 				      IPP_TAG_RESOLUTION)) != NULL)
2120 	current_def = ippResolutionToRes(defattr, 0);
2121       else
2122 	current_def = NULL;
2123       if ((current_res = ippResolutionListToArray(attr)) != NULL)
2124 	joinResolutionArrays(&common_res, &current_res, &common_def,
2125 			     &current_def);
2126     }
2127   }
2128   /* Still no resolution found? Default to 300 dpi */
2129   if (common_res == NULL) {
2130     if ((common_res = resolutionArrayNew()) != NULL) {
2131       if ((current_def = resolutionNew(300, 300)) != NULL)
2132       {
2133 	cupsArrayAdd(common_res, current_def);
2134         free_resolution(current_def, NULL);
2135       }
2136       current_def = NULL;
2137     } else
2138       goto bad_ppd;
2139   }
2140   /* No default resolution determined yet */
2141   if (common_def == NULL) {
2142     if ((defattr = ippFindAttribute(response, "printer-resolution-default",
2143 				    IPP_TAG_RESOLUTION)) != NULL) {
2144       common_def = ippResolutionToRes(defattr, 0);
2145       if (!cupsArrayFind(common_res, common_def)) {
2146 	free(common_def);
2147 	common_def = NULL;
2148       }
2149     }
2150     if (common_def == NULL) {
2151       count = cupsArrayCount(common_res);
2152       common_def = copy_resolution(cupsArrayIndex(common_res, count / 2), NULL);
2153     }
2154   }
2155   /* Get minimum and maximum resolution */
2156   min_res = copy_resolution(cupsArrayFirst(common_res), NULL);
2157   max_res = copy_resolution(cupsArrayLast(common_res), NULL);
2158   cupsArrayDelete(common_res);
2159 
2160 #ifdef QPDF_HAVE_PCLM
2161  /*
2162   * Generically check for PCLm attributes in IPP response
2163   * and ppdize them one by one
2164   */
2165 
2166   if (is_pclm) {
2167     attr = ippFirstAttribute(response); /* first attribute */
2168     while (attr) {                      /* loop through all the attributes */
2169       if (_cups_strncasecmp(ippGetName(attr), "pclm", 4) == 0) {
2170 	pwg_ppdize_name(ippGetName(attr), ppdname, sizeof(ppdname));
2171 	cupsFilePrintf(fp, "*cups%s: ", ppdname);
2172 	ipp_tag_t tag = ippGetValueTag(attr);
2173 	count = ippGetCount(attr);
2174 
2175 	if (tag == IPP_TAG_RESOLUTION) { /* ppdize values of type resolution */
2176 	  if ((current_res = ippResolutionListToArray(attr)) != NULL) {
2177 	    count = cupsArrayCount(current_res);
2178 	    if (count > 1)
2179 	      cupsFilePuts(fp, "\"");
2180 	    for (i = 0, current_def = cupsArrayFirst(current_res);
2181 		 current_def;
2182 		 i ++, current_def = cupsArrayNext(current_res)) {
2183 	      int x = current_def->x;
2184 	      int y = current_def->y;
2185 	      if (x == y)
2186 		cupsFilePrintf(fp, "%ddpi", x);
2187 	      else
2188 		cupsFilePrintf(fp, "%dx%ddpi", x, y);
2189 	      if (i < count - 1)
2190 		cupsFilePuts(fp, ",");
2191 	    }
2192 	    if (count > 1)
2193 	      cupsFilePuts(fp, "\"");
2194 	    cupsFilePuts(fp, "\n");
2195 	  } else
2196 	    cupsFilePuts(fp, "\"\"\n");
2197 	  cupsArrayDelete(current_res);
2198 	} else {
2199 	  ippAttributeString(attr, ppdname, sizeof(ppdname));
2200 	  if (count > 1 || /* quotes around multi-valued and string
2201 			      attributes */
2202 	      tag == IPP_TAG_STRING ||
2203 	      tag == IPP_TAG_TEXT ||
2204 	      tag == IPP_TAG_TEXTLANG)
2205 	    cupsFilePrintf(fp, "\"%s\"\n", ppdname);
2206 	  else
2207 	    cupsFilePrintf(fp, "%s\n", ppdname);
2208 	}
2209       }
2210       attr = ippNextAttribute(response);
2211     }
2212   }
2213 #endif
2214 
2215  /*
2216   * PageSize/PageRegion/ImageableArea/PaperDimension
2217   */
2218   printer_sizes = generate_sizes(response, &defattr, &min_length, &min_width,
2219 				 &max_length, &max_width,
2220 				 &bottom, &left, &right, &top, ppdname);
2221   if (sizes==NULL) {
2222     sizes = printer_sizes;
2223   } else
2224     strcpy(ppdname, default_pagesize);
2225 
2226   if (cupsArrayCount(sizes) > 0) {
2227    /*
2228     * List all of the standard sizes...
2229     */
2230 
2231     char	tleft[256],		/* Left string */
2232 		tbottom[256],		/* Bottom string */
2233 		tright[256],		/* Right string */
2234 		ttop[256],		/* Top string */
2235 		twidth[256],		/* Width string */
2236 		tlength[256],		/* Length string */
2237 		ppdsizename[128];
2238     char        *ippsizename,
2239                 *suffix;
2240     int         all_borderless = 1;
2241 
2242     /* Find a page size without ".Borderless" suffix */
2243     /* (if all are ".Borderless" we drop the suffix in the PPD) */
2244     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2245 	 size = (cups_size_t *)cupsArrayNext(sizes))
2246       if (strcasestr(size->media, ".Borderless") == NULL)
2247 	break;
2248     if (size)
2249       all_borderless = 0;
2250 
2251     if (all_borderless) {
2252       suffix = strcasestr(ppdname, ".Borderless");
2253       if (suffix)
2254 	*suffix = '\0';
2255     }
2256 
2257     cupsFilePrintf(fp, "*OpenUI *PageSize/%s: PickOne\n"
2258 		   "*OrderDependency: 10 AnySetup *PageSize\n"
2259 		   "*DefaultPageSize: %s\n", "Media Size", ppdname);
2260     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2261 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2262       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2263 		      size->width * 72.0 / 2540.0, loc);
2264       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2265 		      size->length * 72.0 / 2540.0, loc);
2266       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2267       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL) {
2268 	*ippsizename = '\0';
2269 	ippsizename ++;
2270       }
2271 
2272       if (ippsizename)
2273 	human_readable = lookup_choice(ippsizename, "media",
2274 				       opt_strings_catalog,
2275 				       printer_opt_strings_catalog);
2276       else
2277 	human_readable = NULL;
2278       if (!human_readable) {
2279 	pwg = pwgMediaForSize(size->width, size->length);
2280 	if (pwg)
2281 	  human_readable = lookup_choice((char *)pwg->pwg, "media",
2282 					 opt_strings_catalog,
2283 					 printer_opt_strings_catalog);
2284       }
2285 
2286       if (all_borderless) {
2287 	suffix = strcasestr(ppdsizename, ".Borderless");
2288 	if (suffix)
2289 	  *suffix = '\0';
2290       }
2291 
2292       cupsFilePrintf(fp, "*PageSize %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
2293 		     ppdsizename,
2294 		     (human_readable ? "/" : ""),
2295 		     (human_readable ? human_readable : ""),
2296 		     (human_readable && strstr(ppdsizename, ".Borderless") ?
2297 		      " (Borderless)" : ""),
2298 		     twidth, tlength);
2299     }
2300     cupsFilePuts(fp, "*CloseUI: *PageSize\n");
2301 
2302     cupsFilePrintf(fp, "*OpenUI *PageRegion/%s: PickOne\n"
2303 		   "*OrderDependency: 10 AnySetup *PageRegion\n"
2304 		   "*DefaultPageRegion: %s\n", "Media Size", ppdname);
2305     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2306 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2307       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2308 		      size->width * 72.0 / 2540.0, loc);
2309       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2310 		      size->length * 72.0 / 2540.0, loc);
2311       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2312       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL) {
2313 	*ippsizename = '\0';
2314 	ippsizename ++;
2315       }
2316 
2317       if (ippsizename)
2318 	human_readable = lookup_choice(ippsizename, "media",
2319 				       opt_strings_catalog,
2320 				       printer_opt_strings_catalog);
2321       else
2322 	human_readable = NULL;
2323       if (!human_readable) {
2324 	pwg = pwgMediaForSize(size->width, size->length);
2325 	if (pwg)
2326 	  human_readable = lookup_choice((char *)pwg->pwg, "media",
2327 					 opt_strings_catalog,
2328 					 printer_opt_strings_catalog);
2329       }
2330 
2331       if (all_borderless) {
2332 	suffix = strcasestr(ppdsizename, ".Borderless");
2333 	if (suffix)
2334 	  *suffix = '\0';
2335       }
2336 
2337       cupsFilePrintf(fp, "*PageRegion %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
2338 		     ppdsizename,
2339 		     (human_readable ? "/" : ""),
2340 		     (human_readable ? human_readable : ""),
2341 		     (human_readable && strstr(ppdsizename, ".Borderless") ?
2342 		      " (Borderless)" : ""),
2343 		     twidth, tlength);
2344     }
2345     cupsFilePuts(fp, "*CloseUI: *PageRegion\n");
2346 
2347     cupsFilePrintf(fp, "*DefaultImageableArea: %s\n"
2348 		   "*DefaultPaperDimension: %s\n", ppdname, ppdname);
2349 
2350     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2351 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2352       _cupsStrFormatd(tleft, tleft + sizeof(tleft),
2353 		      size->left * 72.0 / 2540.0, loc);
2354       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom),
2355 		      size->bottom * 72.0 / 2540.0, loc);
2356       _cupsStrFormatd(tright, tright + sizeof(tright),
2357 		      (size->width - size->right) * 72.0 / 2540.0, loc);
2358       _cupsStrFormatd(ttop, ttop + sizeof(ttop),
2359 		      (size->length - size->top) * 72.0 / 2540.0, loc);
2360       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2361 		      size->width * 72.0 / 2540.0, loc);
2362       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2363 		      size->length * 72.0 / 2540.0, loc);
2364       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2365       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL)
2366 	*ippsizename = '\0';
2367 
2368       if (all_borderless) {
2369 	suffix = strcasestr(ppdsizename, ".Borderless");
2370 	if (suffix)
2371 	  *suffix = '\0';
2372       }
2373 
2374       cupsFilePrintf(fp, "*ImageableArea %s: \"%s %s %s %s\"\n", ppdsizename,
2375 		     tleft, tbottom, tright, ttop);
2376       cupsFilePrintf(fp, "*PaperDimension %s: \"%s %s\"\n", ppdsizename,
2377 		     twidth, tlength);
2378     }
2379 
2380    /*
2381     * Custom size support...
2382     */
2383 
2384     if (max_width > 0 && min_width < INT_MAX && max_length > 0 &&
2385 	min_length < INT_MAX) {
2386       char	tmax[256], tmin[256];	/* Min/max values */
2387 
2388       _cupsStrFormatd(tleft, tleft + sizeof(tleft), left * 72.0 / 2540.0, loc);
2389       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom),
2390 		      bottom * 72.0 / 2540.0, loc);
2391       _cupsStrFormatd(tright, tright + sizeof(tright), right * 72.0 / 2540.0,
2392 		      loc);
2393       _cupsStrFormatd(ttop, ttop + sizeof(ttop), top * 72.0 / 2540.0, loc);
2394 
2395       cupsFilePrintf(fp, "*HWMargins: \"%s %s %s %s\"\n", tleft, tbottom,
2396 		     tright, ttop);
2397 
2398       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_width * 72.0 / 2540.0,
2399 		      loc);
2400       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_width * 72.0 / 2540.0,
2401 		      loc);
2402       cupsFilePrintf(fp, "*ParamCustomPageSize Width: 1 points %s %s\n", tmin,
2403 		     tmax);
2404 
2405       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_length * 72.0 / 2540.0,
2406 		      loc);
2407       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_length * 72.0 / 2540.0,
2408 		      loc);
2409       cupsFilePrintf(fp, "*ParamCustomPageSize Height: 2 points %s %s\n", tmin,
2410 		     tmax);
2411 
2412       cupsFilePuts(fp, "*ParamCustomPageSize WidthOffset: 3 points 0 0\n");
2413       cupsFilePuts(fp, "*ParamCustomPageSize HeightOffset: 4 points 0 0\n");
2414       cupsFilePuts(fp, "*ParamCustomPageSize Orientation: 5 int 0 3\n");
2415       cupsFilePuts(fp, "*CustomPageSize True: \"pop pop pop <</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice\"\n");
2416     }
2417   } else {
2418     cupsFilePrintf(fp,
2419 		   "*%% Printer did not supply page size info via IPP, using defaults\n"
2420 		   "*OpenUI *PageSize/Media Size: PickOne\n"
2421 		   "*OrderDependency: 10 AnySetup *PageSize\n"
2422 		   "*DefaultPageSize: Letter\n"
2423 		   "*PageSize Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
2424 		   "*PageSize Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
2425 		   "*PageSize Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
2426 		   "*PageSize Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
2427 		   "*PageSize A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
2428 		   "*PageSize A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
2429 		   "*PageSize A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
2430 		   "*PageSize B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
2431 		   "*PageSize EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
2432 		   "*PageSize Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
2433 		   "*PageSize EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
2434 		   "*PageSize EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
2435 		   "*PageSize EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
2436 		   "*CloseUI: *PageSize\n"
2437 		   "*OpenUI *PageRegion/Media Size: PickOne\n"
2438 		   "*OrderDependency: 10 AnySetup *PageRegion\n"
2439 		   "*DefaultPageRegion: Letter\n"
2440 		   "*PageRegion Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
2441 		   "*PageRegion Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
2442 		   "*PageRegion Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
2443 		   "*PageRegion Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
2444 		   "*PageRegion A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
2445 		   "*PageRegion A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
2446 		   "*PageRegion A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
2447 		   "*PageRegion B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
2448 		   "*PageRegion EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
2449 		   "*PageRegion Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
2450 		   "*PageRegion EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
2451 		   "*PageRegion EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
2452 		   "*PageRegion EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
2453 		   "*CloseUI: *PageSize\n"
2454 		   "*DefaultImageableArea: Letter\n"
2455 		   "*ImageableArea Letter/US Letter: \"18 12 594 780\"\n"
2456 		   "*ImageableArea Legal/US Legal: \"18 12 594 996\"\n"
2457 		   "*ImageableArea Executive/Executive: \"18 12 504 744\"\n"
2458 		   "*ImageableArea Tabloid/Tabloid: \"18 12 774 1212\"\n"
2459 		   "*ImageableArea A3/A3: \"18 12 824 1179\"\n"
2460 		   "*ImageableArea A4/A4: \"18 12 577 830\"\n"
2461 		   "*ImageableArea A5/A5: \"18 12 402 583\"\n"
2462 		   "*ImageableArea B5/JIS B5: \"18 12 498 717\"\n"
2463 		   "*ImageableArea EnvISOB5/Envelope B5: \"18 12 481 697\"\n"
2464 		   "*ImageableArea Env10/Envelope #10 : \"18 12 279 672\"\n"
2465 		   "*ImageableArea EnvC5/Envelope C5: \"18 12 441 637\"\n"
2466 		   "*ImageableArea EnvDL/Envelope DL: \"18 12 294 612\"\n"
2467 		   "*ImageableArea EnvMonarch/Envelope Monarch: \"18 12 261 528\"\n"
2468 		   "*DefaultPaperDimension: Letter\n"
2469 		   "*PaperDimension Letter/US Letter: \"612 792\"\n"
2470 		   "*PaperDimension Legal/US Legal: \"612 1008\"\n"
2471 		   "*PaperDimension Executive/Executive: \"522 756\"\n"
2472 		   "*PaperDimension Tabloid/Tabloid: \"792 1224\"\n"
2473 		   "*PaperDimension A3/A3: \"842 1191\"\n"
2474 		   "*PaperDimension A4/A4: \"595 842\"\n"
2475 		   "*PaperDimension A5/A5: \"420 595\"\n"
2476 		   "*PaperDimension B5/JIS B5: \"516 729\"\n"
2477 		   "*PaperDimension EnvISOB5/Envelope B5: \"499 709\"\n"
2478 		   "*PaperDimension Env10/Envelope #10 : \"297 684\"\n"
2479 		   "*PaperDimension EnvC5/Envelope C5: \"459 649\"\n"
2480 		   "*PaperDimension EnvDL/Envelope DL: \"312 624\"\n"
2481 		   "*PaperDimension EnvMonarch/Envelope Monarch: \"279 540\"\n");
2482   }
2483 
2484   cupsArrayDelete(printer_sizes);
2485 
2486  /*
2487   * InputSlot...
2488   */
2489 
2490   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-source",
2491 			       IPP_TAG_KEYWORD)) != NULL)
2492     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
2493   else
2494     ppdname[0] = '\0';
2495 
2496   if ((attr = ippFindAttribute(response, "media-source-supported",
2497 			       IPP_TAG_KEYWORD)) != NULL &&
2498       (count = ippGetCount(attr)) > 1) {
2499     int have_default = ppdname[0] != '\0';
2500 					/* Do we have a default InputSlot? */
2501     static const char * const sources[][2] =
2502     {					/* "media-source" strings */
2503       { "Auto", _("Automatic") },
2504       { "Main", _("Main") },
2505       { "Alternate", _("Alternate") },
2506       { "LargeCapacity", _("Large Capacity") },
2507       { "Manual", _("Manual") },
2508       { "Envelope", _("Envelope") },
2509       { "Disc", _("Disc") },
2510       { "Photo", _("Photo") },
2511       { "Hagaki", _("Hagaki") },
2512       { "MainRoll", _("Main Roll") },
2513       { "AlternateRoll", _("Alternate Roll") },
2514       { "Top", _("Top") },
2515       { "Middle", _("Middle") },
2516       { "Bottom", _("Bottom") },
2517       { "Side", _("Side") },
2518       { "Left", _("Left") },
2519       { "Right", _("Right") },
2520       { "Center", _("Center") },
2521       { "Rear", _("Rear") },
2522       { "ByPassTray", _("Multipurpose") },
2523       { "Tray1", _("Tray 1") },
2524       { "Tray2", _("Tray 2") },
2525       { "Tray3", _("Tray 3") },
2526       { "Tray4", _("Tray 4") },
2527       { "Tray5", _("Tray 5") },
2528       { "Tray6", _("Tray 6") },
2529       { "Tray7", _("Tray 7") },
2530       { "Tray8", _("Tray 8") },
2531       { "Tray9", _("Tray 9") },
2532       { "Tray10", _("Tray 10") },
2533       { "Tray11", _("Tray 11") },
2534       { "Tray12", _("Tray 12") },
2535       { "Tray13", _("Tray 13") },
2536       { "Tray14", _("Tray 14") },
2537       { "Tray15", _("Tray 15") },
2538       { "Tray16", _("Tray 16") },
2539       { "Tray17", _("Tray 17") },
2540       { "Tray18", _("Tray 18") },
2541       { "Tray19", _("Tray 19") },
2542       { "Tray20", _("Tray 20") },
2543       { "Roll1", _("Roll 1") },
2544       { "Roll2", _("Roll 2") },
2545       { "Roll3", _("Roll 3") },
2546       { "Roll4", _("Roll 4") },
2547       { "Roll5", _("Roll 5") },
2548       { "Roll6", _("Roll 6") },
2549       { "Roll7", _("Roll 7") },
2550       { "Roll8", _("Roll 8") },
2551       { "Roll9", _("Roll 9") },
2552       { "Roll10", _("Roll 10") }
2553     };
2554 
2555     human_readable = lookup_option("media-source", opt_strings_catalog,
2556 				   printer_opt_strings_catalog);
2557     cupsFilePrintf(fp, "*OpenUI *InputSlot/%s: PickOne\n"
2558 		   "*OrderDependency: 10 AnySetup *InputSlot\n",
2559 		   (human_readable ? human_readable : "Media Source"));
2560     if (have_default)
2561       cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
2562     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
2563       keyword = ippGetString(attr, i, NULL);
2564 
2565       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
2566 
2567       if (i == 0 && !have_default)
2568 	cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
2569 
2570       human_readable = lookup_choice((char *)keyword, "media-source",
2571 				     opt_strings_catalog,
2572 				     printer_opt_strings_catalog);
2573       for (j = (int)(sizeof(sources) / sizeof(sources[0])) - 1; j >= 0; j --)
2574         if (!strcmp(sources[j][0], ppdname)) {
2575 	  if (human_readable == NULL)
2576 	    human_readable = (char *)_cupsLangString(lang, sources[j][1]);
2577 	  break;
2578 	}
2579       if (j >= 0)
2580 	cupsFilePrintf(fp, "*InputSlot %s/%s: \"<</MediaPosition %d>>setpagedevice\"\n",
2581 		       ppdname, human_readable, j);
2582       else
2583 	cupsFilePrintf(fp, "*InputSlot %s%s%s: \"\"\n",
2584 		       ppdname,
2585 		       (human_readable ? "/" : ""),
2586 		       (human_readable ? human_readable : ""));
2587     }
2588     cupsFilePuts(fp, "*CloseUI: *InputSlot\n");
2589   }
2590 
2591  /*
2592   * MediaType...
2593   */
2594 
2595   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-type",
2596 			       IPP_TAG_ZERO)) != NULL)
2597     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
2598   else
2599     strlcpy(ppdname, "Unknown", sizeof(ppdname));
2600 
2601   if ((attr = ippFindAttribute(response, "media-type-supported",
2602 			       IPP_TAG_ZERO)) != NULL &&
2603       (count = ippGetCount(attr)) > 1) {
2604     static const char * const media_types[][2] =
2605     {					/* "media-type" strings */
2606       { "aluminum", _("Aluminum") },
2607       { "auto", _("Automatic") },
2608       { "back-print-film", _("Back Print Film") },
2609       { "cardboard", _("Cardboard") },
2610       { "cardstock", _("Cardstock") },
2611       { "cd", _("CD") },
2612       { "com.hp.advanced-photo", _("Advanced Photo Paper") }, /* HP */
2613       { "com.hp.brochure-glossy", _("Glossy Brochure Paper") }, /* HP */
2614       { "com.hp.brochure-matte", _("Matte Brochure Paper") }, /* HP */
2615       { "com.hp.cover-matte", _("Matte Cover Paper") }, /* HP */
2616       { "com.hp.ecosmart-lite", _("Office Recycled Paper") }, /* HP */
2617       { "com.hp.everyday-glossy", _("Everyday Glossy Photo Paper") }, /* HP */
2618       { "com.hp.everyday-matte", _("Everyday Matte Paper") }, /* HP */
2619       { "com.hp.extra-heavy", _("Extra Heavyweight Paper") }, /* HP */
2620       { "com.hp.intermediate", _("Multipurpose Paper") }, /* HP */
2621       { "com.hp.mid-weight", _("Mid-Weight Paper") }, /* HP */
2622       { "com.hp.premium-inkjet", _("Premium Inkjet Paper") }, /* HP */
2623       { "com.hp.premium-photo", _("Premium Photo Glossy Paper") }, /* HP */
2624       { "com.hp.premium-presentation-matte", _("Premium Presentation Matte Paper") }, /* HP */
2625       { "continuous", _("Continuous") },
2626       { "continuous-long", _("Continuous Long") },
2627       { "continuous-short", _("Continuous Short") },
2628       { "disc", _("Optical Disc") },
2629       { "disc-glossy", _("Glossy Optical Disc") },
2630       { "disc-high-gloss", _("High Gloss Optical Disc") },
2631       { "disc-matte", _("Matte Optical Disc") },
2632       { "disc-satin", _("Satin Optical Disc") },
2633       { "disc-semi-gloss", _("Semi-Gloss Optical Disc") },
2634       { "double-wall", _("Double Wall Cardboard") },
2635       { "dry-film", _("Dry Film") },
2636       { "dvd", _("DVD") },
2637       { "embossing-foil", _("Embossing Foil") },
2638       { "end-board", _("End Board") },
2639       { "envelope", _("Envelope") },
2640       { "envelope-archival", _("Archival Envelope") },
2641       { "envelope-bond", _("Bond Envelope") },
2642       { "envelope-coated", _("Coated Envelope") },
2643       { "envelope-cotton", _("Cotton Envelope") },
2644       { "envelope-fine", _("Fine Envelope") },
2645       { "envelope-heavyweight", _("Heavyweight Envelope") },
2646       { "envelope-inkjet", _("Inkjet Envelope") },
2647       { "envelope-lightweight", _("Lightweight Envelope") },
2648       { "envelope-plain", _("Plain Envelope") },
2649       { "envelope-preprinted", _("Preprinted Envelope") },
2650       { "envelope-window", _("Windowed Envelope") },
2651       { "fabric", _("Fabric") },
2652       { "fabric-archival", _("Archival Fabric") },
2653       { "fabric-glossy", _("Glossy Fabric") },
2654       { "fabric-high-gloss", _("High Gloss Fabric") },
2655       { "fabric-matte", _("Matte Fabric") },
2656       { "fabric-semi-gloss", _("Semi-Gloss Fabric") },
2657       { "fabric-waterproof", _("Waterproof Fabric") },
2658       { "film", _("Film") },
2659       { "flexo-base", _("Flexo Base") },
2660       { "flexo-photo-polymer", _("Flexo Photo Polymer") },
2661       { "flute", _("Flute") },
2662       { "foil", _("Foil") },
2663       { "full-cut-tabs", _("Full Cut Tabs") },
2664       { "glass", _("Glass") },
2665       { "glass-colored", _("Glass Colored") },
2666       { "glass-opaque", _("Glass Opaque") },
2667       { "glass-surfaced", _("Glass Surfaced") },
2668       { "glass-textured", _("Glass Textured") },
2669       { "gravure-cylinder", _("Gravure Cylinder") },
2670       { "image-setter-paper", _("Image Setter Paper") },
2671       { "imaging-cylinder", _("Imaging Cylinder") },
2672       { "jp.co.canon_photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
2673       { "jp.co.canon_photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
2674       { "jp.co.canon-photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
2675       { "jp.co.canon-photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
2676       { "labels", _("Labels") },
2677       { "labels-colored", _("Colored Labels") },
2678       { "labels-glossy", _("Glossy Labels") },
2679       { "labels-high-gloss", _("High Gloss Labels") },
2680       { "labels-inkjet", _("Inkjet Labels") },
2681       { "labels-matte", _("Matte Labels") },
2682       { "labels-permanent", _("Permanent Labels") },
2683       { "labels-satin", _("Satin Labels") },
2684       { "labels-security", _("Security Labels") },
2685       { "labels-semi-gloss", _("Semi-Gloss Labels") },
2686       { "laminating-foil", _("Laminating Foil") },
2687       { "letterhead", _("Letterhead") },
2688       { "metal", _("Metal") },
2689       { "metal-glossy", _("Metal Glossy") },
2690       { "metal-high-gloss", _("Metal High Gloss") },
2691       { "metal-matte", _("Metal Matte") },
2692       { "metal-satin", _("Metal Satin") },
2693       { "metal-semi-gloss", _("Metal Semi Gloss") },
2694       { "mounting-tape", _("Mounting Tape") },
2695       { "multi-layer", _("Multi Layer") },
2696       { "multi-part-form", _("Multi Part Form") },
2697       { "other", _("Other") },
2698       { "paper", _("Paper") },
2699       { "photo", _("Photo Paper") }, /* HP mis-spelling */
2700       { "photographic", _("Photo Paper") },
2701       { "photographic-archival", _("Archival Photo Paper") },
2702       { "photographic-film", _("Photo Film") },
2703       { "photographic-glossy", _("Glossy Photo Paper") },
2704       { "photographic-high-gloss", _("High Gloss Photo Paper") },
2705       { "photographic-matte", _("Matte Photo Paper") },
2706       { "photographic-satin", _("Satin Photo Paper") },
2707       { "photographic-semi-gloss", _("Semi-Gloss Photo Paper") },
2708       { "plastic", _("Plastic") },
2709       { "plastic-archival", _("Plastic Archival") },
2710       { "plastic-colored", _("Plastic Colored") },
2711       { "plastic-glossy", _("Plastic Glossy") },
2712       { "plastic-high-gloss", _("Plastic High Gloss") },
2713       { "plastic-matte", _("Plastic Matte") },
2714       { "plastic-satin", _("Plastic Satin") },
2715       { "plastic-semi-gloss", _("Plastic Semi Gloss") },
2716       { "plate", _("Plate") },
2717       { "polyester", _("Polyester") },
2718       { "pre-cut-tabs", _("Pre Cut Tabs") },
2719       { "roll", _("Roll") },
2720       { "screen", _("Screen") },
2721       { "screen-paged", _("Screen Paged") },
2722       { "self-adhesive", _("Self Adhesive") },
2723       { "self-adhesive-film", _("Self Adhesive Film") },
2724       { "shrink-foil", _("Shrink Foil") },
2725       { "single-face", _("Single Face") },
2726       { "single-wall", _("Single Wall Cardboard") },
2727       { "sleeve", _("Sleeve") },
2728       { "stationery", _("Plain Paper") },
2729       { "stationery-archival", _("Archival Paper") },
2730       { "stationery-coated", _("Coated Paper") },
2731       { "stationery-cotton", _("Cotton Paper") },
2732       { "stationery-fine", _("Vellum Paper") },
2733       { "stationery-heavyweight", _("Heavyweight Paper") },
2734       { "stationery-heavyweight-coated", _("Heavyweight Coated Paper") },
2735       { "stationery-inkjet", _("Inkjet Paper") },
2736       { "stationery-letterhead", _("Letterhead") },
2737       { "stationery-lightweight", _("Lightweight Paper") },
2738       { "stationery-preprinted", _("Preprinted Paper") },
2739       { "stationery-prepunched", _("Punched Paper") },
2740       { "tab-stock", _("Tab Stock") },
2741       { "tractor", _("Tractor") },
2742       { "transfer", _("Transfer") },
2743       { "transparency", _("Transparency") },
2744       { "triple-wall", _("Triple Wall Cardboard") },
2745       { "wet-film", _("Wet Film") }
2746     };
2747 
2748     human_readable = lookup_option("media-type", opt_strings_catalog,
2749 				   printer_opt_strings_catalog);
2750     cupsFilePrintf(fp, "*OpenUI *MediaType/%s: PickOne\n"
2751 		   "*OrderDependency: 10 AnySetup *MediaType\n"
2752 		   "*DefaultMediaType: %s\n",
2753 		   (human_readable ? human_readable : "Media Type"),
2754 		   ppdname);
2755     for (i = 0; i < count; i ++) {
2756       keyword = ippGetString(attr, i, NULL);
2757 
2758       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
2759 
2760       human_readable = lookup_choice((char *)keyword, "media-type",
2761 				     opt_strings_catalog,
2762 				     printer_opt_strings_catalog);
2763       if (human_readable == NULL)
2764 	for (j = 0; j < (int)(sizeof(media_types) / sizeof(media_types[0]));
2765 	     j ++)
2766 	  if (!strcmp(media_types[j][0], keyword)) {
2767 	    human_readable = (char *)_cupsLangString(lang, media_types[j][1]);
2768 	    break;
2769 	  }
2770       cupsFilePrintf(fp, "*MediaType %s%s%s: \"<</MediaType(%s)>>setpagedevice\"\n",
2771 		     ppdname,
2772 		     (human_readable ? "/" : ""),
2773 		     (human_readable ? human_readable : ""),
2774 		     ppdname);
2775     }
2776     cupsFilePuts(fp, "*CloseUI: *MediaType\n");
2777   }
2778 
2779  /*
2780   * ColorModel...
2781   */
2782   if ((defattr = ippFindAttribute(response, "print-color-mode-default",
2783 				  IPP_TAG_KEYWORD)) == NULL)
2784     defattr = ippFindAttribute(response, "output-mode-default",
2785 			       IPP_TAG_KEYWORD);
2786 
2787   if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) ==
2788       NULL)
2789     if ((attr = ippFindAttribute(response, "print-color-mode-supported",
2790 				 IPP_TAG_KEYWORD)) == NULL)
2791       if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported",
2792 				   IPP_TAG_KEYWORD)) == NULL)
2793         attr = ippFindAttribute(response, "output-mode-supported",
2794 				IPP_TAG_KEYWORD);
2795 
2796   human_readable = lookup_option("print-color-mode", opt_strings_catalog,
2797 				 printer_opt_strings_catalog);
2798   if (attr && ippGetCount(attr) > 0) {
2799     const char *default_color = NULL;	/* Default */
2800     int first_choice = 1,
2801       have_bi_level = 0,
2802       have_mono = 0;
2803 
2804     if ((keyword = ippGetString(defattr, 0, NULL)) != NULL)
2805     {
2806       if (!strcmp(keyword, "bi-level"))
2807         default_color = "FastGray";
2808       else if (!strcmp(keyword, "monochrome") ||
2809 	       !strcmp(keyword, "auto-monochrome"))
2810         default_color = "Gray";
2811       else
2812         default_color = "RGB";
2813     }
2814 
2815     cupsFilePrintf(fp, "*%% ColorModel from %s\n", ippGetName(attr));
2816 
2817     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
2818       keyword = ippGetString(attr, i, NULL); /* Keyword for color/bit depth */
2819 
2820       if (!have_bi_level &&
2821 	  (!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") ||
2822 	   !strcmp(keyword, "process-bi-level"))) {
2823 	have_bi_level = 1;
2824         if (first_choice) {
2825 	  first_choice = 0;
2826 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2827 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2828 			 (human_readable ? human_readable :
2829 			  _cupsLangString(lang, _("Color Mode"))));
2830 	}
2831 
2832 	human_readable2 = lookup_choice("bi-level", "print-color-mode",
2833 					opt_strings_catalog,
2834 					printer_opt_strings_catalog);
2835         cupsFilePrintf(fp, "*ColorModel FastGray/%s: \"<</cupsColorSpace 3/cupsBitsPerColor 1/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2836 		       (human_readable2 ? human_readable2 :
2837 			_cupsLangString(lang, _("Fast Grayscale"))));
2838 
2839         if (!default_color)
2840 	  default_color = "FastGray";
2841       } else if (!have_mono &&
2842 		 (!strcasecmp(keyword, "sgray_8") ||
2843 		  !strncmp(keyword, "W8", 2) ||
2844 		  !strcmp(keyword, "monochrome") ||
2845 		  !strcmp(keyword, "process-monochrome"))) {
2846 	have_mono = 1;
2847         if (first_choice) {
2848 	  first_choice = 0;
2849 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2850 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2851 			 (human_readable ? human_readable :
2852 			  _cupsLangString(lang, _("Color Mode"))));
2853 	}
2854 
2855 	human_readable2 = lookup_choice("monochrome", "print-color-mode",
2856 					opt_strings_catalog,
2857 					printer_opt_strings_catalog);
2858         cupsFilePrintf(fp, "*ColorModel Gray/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2859 		       (human_readable2 ? human_readable2 :
2860 			_cupsLangString(lang, _("Grayscale"))));
2861 
2862         if (!default_color || (!defattr && !strcmp(default_color, "FastGray")))
2863 	  default_color = "Gray";
2864       } else if (!strcasecmp(keyword, "sgray_16") ||
2865 		 !strncmp(keyword, "W8-16", 5) ||
2866 		 !strncmp(keyword, "W16", 3)) {
2867         if (first_choice) {
2868 	  first_choice = 0;
2869 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2870 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2871 			 (human_readable ? human_readable :
2872 			  _cupsLangString(lang, _("Color Mode"))));
2873 	}
2874 
2875         cupsFilePrintf(fp, "*ColorModel Gray16/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2876 		       _cupsLangString(lang, _("Deep Gray (High Definition Grayscale)")));
2877 
2878 	if (!default_color || (!defattr && !strcmp(default_color, "FastGray")))
2879 	  default_color = "Gray16";
2880       } else if (!strcasecmp(keyword, "srgb_8") ||
2881 		 !strncmp(keyword, "SRGB24", 6) ||
2882 		 !strcmp(keyword, "color")) {
2883         if (first_choice) {
2884 	  first_choice = 0;
2885 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2886 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2887 			 (human_readable ? human_readable :
2888 			  _cupsLangString(lang, _("Color Mode"))));
2889 	}
2890 
2891 	human_readable2 = lookup_choice("color", "print-color-mode",
2892 					opt_strings_catalog,
2893 					printer_opt_strings_catalog);
2894         cupsFilePrintf(fp, "*ColorModel RGB/%s: \"<</cupsColorSpace 19/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2895 		       (human_readable2 ? human_readable2 :
2896 			_cupsLangString(lang, _("Color"))));
2897 
2898 	if (!defattr)
2899 	  default_color = "RGB";
2900       } else if ((!strcasecmp(keyword, "srgb_16") ||
2901 		  !strncmp(keyword, "SRGB48", 6)) &&
2902 		 !ippContainsString(attr, "srgb_8")) {
2903         if (first_choice) {
2904 	  first_choice = 0;
2905 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2906 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2907 			 (human_readable ? human_readable :
2908 			  _cupsLangString(lang, _("Color Mode"))));
2909 	}
2910 
2911 	human_readable2 = lookup_choice("color", "print-color-mode",
2912 					opt_strings_catalog,
2913 					printer_opt_strings_catalog);
2914         cupsFilePrintf(fp, "*ColorModel RGB/%s: \"<</cupsColorSpace 19/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2915 		       (human_readable2 ? human_readable2 :
2916 			_cupsLangString(lang, _("Color"))));
2917 
2918         if (!defattr)
2919 	  default_color = "RGB";
2920 
2921 	/* Apparently some printers only advertise color support, so make sure
2922            we also do grayscale for these printers... */
2923 	if (!ippContainsString(attr, "sgray_8") &&
2924 	    !ippContainsString(attr, "black_1") &&
2925 	    !ippContainsString(attr, "black_8") &&
2926 	    !ippContainsString(attr, "W8") &&
2927 	    !ippContainsString(attr, "W8-16")) {
2928 	  human_readable2 = lookup_choice("monochrome", "print-color-mode",
2929 					  opt_strings_catalog,
2930 					  printer_opt_strings_catalog);
2931 	  cupsFilePrintf(fp, "*ColorModel Gray/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2932 			 (human_readable2 ? human_readable2 :
2933 			  _cupsLangString(lang, _("Grayscale"))));
2934 	}
2935       } else if (!strcasecmp(keyword, "adobe-rgb_16") ||
2936 		 !strncmp(keyword, "ADOBERGB48", 10) ||
2937 		 !strncmp(keyword, "ADOBERGB24-48", 13)) {
2938         if (first_choice) {
2939 	  first_choice = 0;
2940 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2941 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2942 			 (human_readable ? human_readable :
2943 			  _cupsLangString(lang, _("Color Mode"))));
2944 	}
2945 
2946         cupsFilePrintf(fp, "*ColorModel AdobeRGB/%s: \"<</cupsColorSpace 20/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2947 		       _cupsLangString(lang, _("Deep Color (Wide Color Gamut, AdobeRGB)")));
2948 
2949         if (!default_color)
2950 	  default_color = "AdobeRGB";
2951       } else if ((!strcasecmp(keyword, "adobe-rgb_8") ||
2952 		  !strcmp(keyword, "ADOBERGB24")) &&
2953 		 !ippContainsString(attr, "adobe-rgb_16")) {
2954         if (first_choice) {
2955 	  first_choice = 0;
2956 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2957 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2958 			 (human_readable ? human_readable :
2959 			  _cupsLangString(lang, _("Color Mode"))));
2960 	}
2961 
2962         cupsFilePrintf(fp, "*ColorModel AdobeRGB/%s: \"<</cupsColorSpace 20/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2963 		       _cupsLangString(lang, _("Deep Color (Wide Color Gamut, AdobeRGB)")));
2964 
2965         if (!default_color)
2966 	  default_color = "AdobeRGB";
2967       } else if ((!strcasecmp(keyword, "black_8") &&
2968 		  !ippContainsString(attr, "black_16")) ||
2969 		 !strcmp(keyword, "DEVW8")) {
2970         if (first_choice) {
2971 	  first_choice = 0;
2972 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2973 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2974 			 (human_readable ? human_readable :
2975 			  _cupsLangString(lang, _("Color Mode"))));
2976 	}
2977 
2978         cupsFilePrintf(fp, "*ColorModel DeviceGray/%s: \"<</cupsColorSpace 0/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2979 		       _cupsLangString(lang, _("Device Gray")));
2980       } else if (!strcasecmp(keyword, "black_16") ||
2981 		 !strcmp(keyword, "DEVW16") ||
2982 		 !strcmp(keyword, "DEVW8-16")) {
2983         if (first_choice) {
2984 	  first_choice = 0;
2985 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2986 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2987 			 (human_readable ? human_readable :
2988 			  _cupsLangString(lang, _("Color Mode"))));
2989 	}
2990 
2991         cupsFilePrintf(fp, "*ColorModel DeviceGray/%s: \"<</cupsColorSpace 0/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2992 		       _cupsLangString(lang, _("Device Gray")));
2993       } else if ((!strcasecmp(keyword, "cmyk_8") &&
2994 		  !ippContainsString(attr, "cmyk_16")) ||
2995 		 !strcmp(keyword, "DEVCMYK32")) {
2996         if (first_choice) {
2997 	  first_choice = 0;
2998 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2999 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
3000 			 (human_readable ? human_readable :
3001 			  _cupsLangString(lang, _("Color Mode"))));
3002 	}
3003 
3004         cupsFilePrintf(fp, "*ColorModel CMYK/%s: \"<</cupsColorSpace 6/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
3005 		       _cupsLangString(lang, _("Device CMYK")));
3006       } else if (!strcasecmp(keyword, "cmyk_16") ||
3007 		 !strcmp(keyword, "DEVCMYK32-64") ||
3008 		 !strcmp(keyword, "DEVCMYK64")) {
3009         if (first_choice) {
3010 	  first_choice = 0;
3011 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3012 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
3013 			 (human_readable ? human_readable :
3014 			  _cupsLangString(lang, _("Color Mode"))));
3015 	}
3016 
3017         cupsFilePrintf(fp, "*ColorModel CMYK/%s: \"<</cupsColorSpace 6/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
3018 		       _cupsLangString(lang, _("Device CMYK")));
3019       } else if ((!strcasecmp(keyword, "rgb_8") &&
3020 		  !ippContainsString(attr, "rgb_16")) ||
3021 		 !strcmp(keyword, "DEVRGB24")) {
3022         if (first_choice) {
3023 	  first_choice = 0;
3024 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3025 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
3026 			 (human_readable ? human_readable :
3027 			  _cupsLangString(lang, _("Color Mode"))));
3028 	}
3029 
3030         cupsFilePrintf(fp, "*ColorModel DeviceRGB/%s: \"<</cupsColorSpace 1/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
3031 		       _cupsLangString(lang, _("Device RGB")));
3032       } else if (!strcasecmp(keyword, "rgb_16") ||
3033 		 !strcmp(keyword, "DEVRGB24-48") ||
3034 		 !strcmp(keyword, "DEVRGB48")) {
3035         if (first_choice) {
3036 	  first_choice = 0;
3037 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3038 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
3039 			 (human_readable ? human_readable :
3040 			  _cupsLangString(lang, _("Color Mode"))));
3041 	}
3042 
3043         cupsFilePrintf(fp, "*ColorModel DeviceRGB/%s: \"<</cupsColorSpace 1/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
3044 		       _cupsLangString(lang, _("Device RGB")));
3045       }
3046     }
3047 
3048     if (default_pagesize != NULL) {
3049       /* Here we are dealing with a cluster, if the default cluster color
3050          is not supplied we set it Gray*/
3051       if (default_cluster_color != NULL && (!default_color || !defattr)) {
3052 	default_color = default_cluster_color;
3053       } else
3054 	default_color = "Gray";
3055     }
3056 
3057     if (default_color)
3058       cupsFilePrintf(fp, "*DefaultColorModel: %s\n", default_color);
3059     if (!first_choice)
3060       cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
3061   } else {
3062     cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3063 		   "*OrderDependency: 10 AnySetup *ColorModel\n",
3064 		   (human_readable ? human_readable :
3065 		    _cupsLangString(lang, _("Color Mode"))));
3066     cupsFilePrintf(fp, "*DefaultColorModel: Gray\n");
3067     cupsFilePuts(fp, "*ColorModel FastGray/Fast Grayscale: \"<</cupsColorSpace 3/cupsBitsPerColor 1/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceGray>>setpagedevice\"\n");
3068     cupsFilePuts(fp, "*ColorModel Gray/Grayscale: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceGray>>setpagedevice\"\n");
3069     if (color) {
3070       /* Color printer according to DNS-SD (or unknown) */
3071       cupsFilePuts(fp, "*ColorModel RGB/Color: \"<</cupsColorSpace 19/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceRGB>>setpagedevice\"\n");
3072     }
3073     cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
3074   }
3075 
3076  /*
3077   * Duplex...
3078   */
3079 
3080   if (((attr = ippFindAttribute(response, "sides-supported",
3081 				IPP_TAG_KEYWORD)) != NULL &&
3082        ippContainsString(attr, "two-sided-long-edge")) ||
3083       (attr == NULL && duplex)) {
3084     human_readable = lookup_option("sides", opt_strings_catalog,
3085 				   printer_opt_strings_catalog);
3086     cupsFilePrintf(fp, "*OpenUI *Duplex/%s: PickOne\n"
3087 		   "*OrderDependency: 10 AnySetup *Duplex\n"
3088 		   "*DefaultDuplex: None\n",
3089 		   (human_readable ? human_readable :
3090 		    _cupsLangString(lang, _("2-Sided Printing"))));
3091     human_readable = lookup_choice("one-sided", "sides", opt_strings_catalog,
3092 				   printer_opt_strings_catalog);
3093     cupsFilePrintf(fp, "*Duplex None/%s: \"<</Duplex false>>setpagedevice\"\n",
3094 		   (human_readable ? human_readable :
3095 		    _cupsLangString(lang, _("Off (1-Sided)"))));
3096     human_readable = lookup_choice("two-sided-long-edge", "sides",
3097 				   opt_strings_catalog,
3098 				   printer_opt_strings_catalog);
3099     cupsFilePrintf(fp, "*Duplex DuplexNoTumble/%s: \"<</Duplex true/Tumble false>>setpagedevice\"\n",
3100 		   (human_readable ? human_readable :
3101 		    _cupsLangString(lang, _("Long-Edge (Portrait)"))));
3102     human_readable = lookup_choice("two-sided-short-edge", "sides",
3103 				   opt_strings_catalog,
3104 				   printer_opt_strings_catalog);
3105     cupsFilePrintf(fp, "*Duplex DuplexTumble/%s: \"<</Duplex true/Tumble true>>setpagedevice\"\n",
3106 		   (human_readable ? human_readable :
3107 		    _cupsLangString(lang, _("Short-Edge (Landscape)"))));
3108     cupsFilePrintf(fp, "*CloseUI: *Duplex\n");
3109 
3110     if ((attr = ippFindAttribute(response, "urf-supported",
3111 				 IPP_TAG_KEYWORD)) != NULL) {
3112       for (i = 0, count = ippGetCount(attr); i < count; i ++) {
3113 	const char *dm = ippGetString(attr, i, NULL); /* DM value */
3114 
3115 	if (!_cups_strcasecmp(dm, "DM1")) {
3116 	  cupsFilePuts(fp, "*cupsBackSide: Normal\n");
3117 	  break;
3118 	} else if (!_cups_strcasecmp(dm, "DM2")) {
3119 	  cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
3120 	  break;
3121 	} else if (!_cups_strcasecmp(dm, "DM3")) {
3122 	  cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
3123 	  break;
3124 	} else if (!_cups_strcasecmp(dm, "DM4")) {
3125 	  cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
3126 	  break;
3127 	}
3128       }
3129     } else if ((attr = ippFindAttribute(response,
3130 					"pwg-raster-document-sheet-back",
3131 					IPP_TAG_KEYWORD)) != NULL) {
3132       keyword = ippGetString(attr, 0, NULL); /* Keyword value */
3133 
3134       if (!strcmp(keyword, "flipped"))
3135         cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
3136       else if (!strcmp(keyword, "manual-tumble"))
3137         cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
3138       else if (!strcmp(keyword, "normal"))
3139         cupsFilePuts(fp, "*cupsBackSide: Normal\n");
3140       else
3141         cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
3142     }
3143   }
3144 
3145  /*
3146   * Output bin...
3147   */
3148 
3149   if ((attr = ippFindAttribute(response, "output-bin-default",
3150 			       IPP_TAG_ZERO)) != NULL)
3151     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
3152   else
3153     strlcpy(ppdname, "Unknown", sizeof(ppdname));
3154 
3155   if ((attr = ippFindAttribute(response, "output-bin-supported",
3156 			       IPP_TAG_ZERO)) != NULL &&
3157       (count = ippGetCount(attr)) > 0) {
3158     static const char * const output_bins[][2] =
3159     {					/* "output-bin" strings */
3160       { "auto", _("Automatic") },
3161       { "bottom", _("Bottom Tray") },
3162       { "center", _("Center Tray") },
3163       { "face-down", _("Face Down") },
3164       { "face-up", _("Face Up") },
3165       { "large-capacity", _("Large Capacity Tray") },
3166       { "left", _("Left Tray") },
3167       { "mailbox-1", _("Mailbox 1") },
3168       { "mailbox-2", _("Mailbox 2") },
3169       { "mailbox-3", _("Mailbox 3") },
3170       { "mailbox-4", _("Mailbox 4") },
3171       { "mailbox-5", _("Mailbox 5") },
3172       { "mailbox-6", _("Mailbox 6") },
3173       { "mailbox-7", _("Mailbox 7") },
3174       { "mailbox-8", _("Mailbox 8") },
3175       { "mailbox-9", _("Mailbox 9") },
3176       { "mailbox-10", _("Mailbox 10") },
3177       { "middle", _("Middle") },
3178       { "my-mailbox", _("My Mailbox") },
3179       { "rear", _("Rear Tray") },
3180       { "right", _("Right Tray") },
3181       { "side", _("Side Tray") },
3182       { "stacker-1", _("Stacker 1") },
3183       { "stacker-2", _("Stacker 2") },
3184       { "stacker-3", _("Stacker 3") },
3185       { "stacker-4", _("Stacker 4") },
3186       { "stacker-5", _("Stacker 5") },
3187       { "stacker-6", _("Stacker 6") },
3188       { "stacker-7", _("Stacker 7") },
3189       { "stacker-8", _("Stacker 8") },
3190       { "stacker-9", _("Stacker 9") },
3191       { "stacker-10", _("Stacker 10") },
3192       { "top", _("Top Tray") },
3193       { "tray-1", _("Tray 1") },
3194       { "tray-2", _("Tray 2") },
3195       { "tray-3", _("Tray 3") },
3196       { "tray-4", _("Tray 4") },
3197       { "tray-5", _("Tray 5") },
3198       { "tray-6", _("Tray 6") },
3199       { "tray-7", _("Tray 7") },
3200       { "tray-8", _("Tray 8") },
3201       { "tray-9", _("Tray 9") },
3202       { "tray-10", _("Tray 10") }
3203     };
3204 
3205     human_readable = lookup_option("output-bin", opt_strings_catalog,
3206 				   printer_opt_strings_catalog);
3207     cupsFilePrintf(fp, "*OpenUI *OutputBin/%s: PickOne\n"
3208 		   "*OrderDependency: 10 AnySetup *OutputBin\n"
3209 		   "*DefaultOutputBin: %s\n",
3210 		   (human_readable ? human_readable : "Output Bin"),
3211 		   ppdname);
3212     attr2 = ippFindAttribute(response, "printer-output-tray", IPP_TAG_STRING);
3213     for (i = 0; i < count; i ++) {
3214       keyword = ippGetString(attr, i, NULL);
3215 
3216       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
3217 
3218       human_readable = lookup_choice((char *)keyword, "output-bin",
3219 				     opt_strings_catalog,
3220 				     printer_opt_strings_catalog);
3221       if (human_readable == NULL)
3222 	for (j = 0; j < (int)(sizeof(output_bins) / sizeof(output_bins[0]));
3223 	     j ++)
3224 	  if (!strcmp(output_bins[j][0], keyword)) {
3225 	    human_readable = (char *)_cupsLangString(lang, output_bins[j][1]);
3226 	    break;
3227 	  }
3228       cupsFilePrintf(fp, "*OutputBin %s%s%s: \"\"\n",
3229 		     ppdname,
3230 		     (human_readable ? "/" : ""),
3231 		     (human_readable ? human_readable : ""));
3232       outputorderinfofound = 0;
3233       faceupdown = 1;
3234       firsttolast = 1;
3235       if (attr2 && i < ippGetCount(attr2)) {
3236 	outbin_properties_octet = ippGetOctetString(attr2, i, &octet_str_len);
3237 	memset(outbin_properties, 0, sizeof(outbin_properties));
3238 	memcpy(outbin_properties, outbin_properties_octet,
3239 	       ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
3240 		(size_t)octet_str_len : sizeof(outbin_properties) - 1));
3241 	if (strcasestr(outbin_properties, "pagedelivery=faceUp")) {
3242 	  outputorderinfofound = 1;
3243 	  faceupdown = -1;
3244 	} else if (strcasestr(outbin_properties, "pagedelivery=faceDown")) {
3245 	  outputorderinfofound = 1;
3246 	  faceupdown = 1;
3247 	}
3248 	if (strcasestr(outbin_properties, "stackingorder=lastToFirst")) {
3249 	  outputorderinfofound = 1;
3250 	  firsttolast = -1;
3251 	} else if (strcasestr(outbin_properties, "stackingorder=firstToLast")) {
3252 	  outputorderinfofound = 1;
3253 	  firsttolast = 1;
3254 	}
3255       }
3256       if (outputorderinfofound == 0) {
3257 	if (strcasestr(keyword, "face-up")) {
3258 	  outputorderinfofound = 1;
3259 	  faceupdown = -1;
3260 	}
3261 	if (strcasestr(keyword, "face-down")) {
3262 	  outputorderinfofound = 1;
3263 	  faceupdown = 1;
3264 	}
3265       }
3266       if (outputorderinfofound)
3267 	cupsFilePrintf(fp, "*PageStackOrder %s: %s\n",
3268 		       ppdname,
3269 		       (firsttolast * faceupdown < 0 ? "Reverse" : "Normal"));
3270     }
3271     cupsFilePuts(fp, "*CloseUI: *OutputBin\n");
3272   }
3273 
3274  /*
3275   * Finishing options...
3276   */
3277 
3278   if ((attr = ippFindAttribute(response, "finishings-supported",
3279 			       IPP_TAG_ENUM)) != NULL) {
3280     int			value;		/* Enum value */
3281     const char		*ppd_keyword;	/* PPD keyword for enum */
3282     cups_array_t	*names;		/* Names we've added */
3283     static const char * const base_keywords[] =
3284     {					/* Base STD 92 keywords */
3285       NULL,				/* none */
3286       "SingleAuto",			/* staple */
3287       "SingleAuto",			/* punch */
3288       NULL,				/* cover */
3289       "BindAuto",			/* bind */
3290       "SaddleStitch",			/* saddle-stitch */
3291       "EdgeStitchAuto",			/* edge-stitch */
3292       "Auto",				/* fold */
3293       NULL,				/* trim */
3294       NULL,				/* bale */
3295       NULL,				/* booklet-maker */
3296       NULL,				/* jog-offset */
3297       NULL,				/* coat */
3298       NULL				/* laminate */
3299     };
3300     static const char * const finishings[][2] =
3301     {					/* Finishings strings */
3302       { "bale", _("Bale") },
3303       { "bind", _("Bind") },
3304       { "bind-bottom", _("Bind (Reverse Landscape)") },
3305       { "bind-left", _("Bind (Portrait)") },
3306       { "bind-right", _("Bind (Reverse Portrait)") },
3307       { "bind-top", _("Bind (Landscape)") },
3308       { "booklet-maker", _("Booklet Maker") },
3309       { "coat", _("Coat") },
3310       { "cover", _("Cover") },
3311       { "edge-stitch", _("Staple Edge") },
3312       { "edge-stitch-bottom", _("Staple Edge (Reverse Landscape)") },
3313       { "edge-stitch-left", _("Staple Edge (Portrait)") },
3314       { "edge-stitch-right", _("Staple Edge (Reverse Portrait)") },
3315       { "edge-stitch-top", _("Staple Edge (Landscape)") },
3316       { "fold", _("Fold") },
3317       { "fold-accordian", _("Accordian Fold") },
3318       { "fold-double-gate", _("Double Gate Fold") },
3319       { "fold-engineering-z", _("Engineering Z Fold") },
3320       { "fold-gate", _("Gate Fold") },
3321       { "fold-half", _("Half Fold") },
3322       { "fold-half-z", _("Half Z Fold") },
3323       { "fold-left-gate", _("Left Gate Fold") },
3324       { "fold-letter", _("Letter Fold") },
3325       { "fold-parallel", _("Parallel Fold") },
3326       { "fold-poster", _("Poster Fold") },
3327       { "fold-right-gate", _("Right Gate Fold") },
3328       { "fold-z", _("Z Fold") },
3329       { "jog-offset", _("Jog") },
3330       { "laminate", _("Laminate") },
3331       { "punch", _("Punch") },
3332       { "punch-bottom-left", _("Single Punch (Reverse Landscape)") },
3333       { "punch-bottom-right", _("Single Punch (Reverse Portrait)") },
3334       { "punch-double-bottom", _("2-Hole Punch (Reverse Portrait)") },
3335       { "punch-double-left", _("2-Hole Punch (Reverse Landscape)") },
3336       { "punch-double-right", _("2-Hole Punch (Landscape)") },
3337       { "punch-double-top", _("2-Hole Punch (Portrait)") },
3338       { "punch-quad-bottom", _("4-Hole Punch (Reverse Landscape)") },
3339       { "punch-quad-left", _("4-Hole Punch (Portrait)") },
3340       { "punch-quad-right", _("4-Hole Punch (Reverse Portrait)") },
3341       { "punch-quad-top", _("4-Hole Punch (Landscape)") },
3342       { "punch-top-left", _("Single Punch (Portrait)") },
3343       { "punch-top-right", _("Single Punch (Landscape)") },
3344       { "punch-triple-bottom", _("3-Hole Punch (Reverse Landscape)") },
3345       { "punch-triple-left", _("3-Hole Punch (Portrait)") },
3346       { "punch-triple-right", _("3-Hole Punch (Reverse Portrait)") },
3347       { "punch-triple-top", _("3-Hole Punch (Landscape)") },
3348       { "punch-multiple-bottom", _("Multi-Hole Punch (Reverse Landscape)") },
3349       { "punch-multiple-left", _("Multi-Hole Punch (Portrait)") },
3350       { "punch-multiple-right", _("Multi-Hole Punch (Reverse Portrait)") },
3351       { "punch-multiple-top", _("Multi-Hole Punch (Landscape)") },
3352       { "saddle-stitch", _("Saddle Stitch") },
3353       { "staple", _("Staple") },
3354       { "staple-bottom-left", _("Single Staple (Reverse Landscape)") },
3355       { "staple-bottom-right", _("Single Staple (Reverse Portrait)") },
3356       { "staple-dual-bottom", _("Double Staple (Reverse Landscape)") },
3357       { "staple-dual-left", _("Double Staple (Portrait)") },
3358       { "staple-dual-right", _("Double Staple (Reverse Portrait)") },
3359       { "staple-dual-top", _("Double Staple (Landscape)") },
3360       { "staple-top-left", _("Single Staple (Portrait)") },
3361       { "staple-top-right", _("Single Staple (Landscape)") },
3362       { "staple-triple-bottom", _("Triple Staple (Reverse Landscape)") },
3363       { "staple-triple-left", _("Triple Staple (Portrait)") },
3364       { "staple-triple-right", _("Triple Staple (Reverse Portrait)") },
3365       { "staple-triple-top", _("Triple Staple (Landscape)") },
3366       { "trim", _("Cut Media") }
3367     };
3368 
3369     count = ippGetCount(attr);
3370     names = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0,
3371 			  (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
3372     fin_options = cupsArrayNew((cups_array_func_t)strcmp, NULL);
3373 
3374    /*
3375     * Staple/Bind/Stitch
3376     */
3377 
3378     for (i = 0; i < count; i ++) {
3379       value   = ippGetInteger(attr, i);
3380       keyword = ippEnumString("finishings", value);
3381 
3382       if (!strncmp(keyword, "staple-", 7) ||
3383 	  !strncmp(keyword, "bind-", 5) ||
3384 	  !strncmp(keyword, "edge-stitch-", 12) ||
3385 	  !strcmp(keyword, "saddle-stitch"))
3386         break;
3387     }
3388 
3389     if (i < count) {
3390       static const char * const staple_keywords[] =
3391       {					/* StapleLocation keywords */
3392 	"SinglePortrait",
3393 	"SingleRevLandscape",
3394 	"SingleLandscape",
3395 	"SingleRevPortrait",
3396 	"EdgeStitchPortrait",
3397 	"EdgeStitchLandscape",
3398 	"EdgeStitchRevPortrait",
3399 	"EdgeStitchRevLandscape",
3400 	"DualPortrait",
3401 	"DualLandscape",
3402 	"DualRevPortrait",
3403 	"DualRevLandscape",
3404 	"TriplePortrait",
3405 	"TripleLandscape",
3406 	"TripleRevPortrait",
3407 	"TripleRevLandscape"
3408       };
3409       static const char * const bind_keywords[] =
3410       {					/* StapleLocation binding keywords */
3411 	"BindPortrait",
3412 	"BindLandscape",
3413 	"BindRevPortrait",
3414 	"BindRevLandscape"
3415       };
3416 
3417       cupsArrayAdd(fin_options, "*StapleLocation");
3418 
3419       human_readable = lookup_choice("staple", "finishing-template",
3420 				     opt_strings_catalog,
3421 				     printer_opt_strings_catalog);
3422       cupsFilePrintf(fp, "*OpenUI *StapleLocation/%s: PickOne\n",
3423 		     (human_readable ? human_readable :
3424 		      _cupsLangString(lang, _("Staple"))));
3425       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *StapleLocation\n");
3426       cupsFilePuts(fp, "*DefaultStapleLocation: None\n");
3427       cupsFilePrintf(fp, "*StapleLocation None/%s: \"\"\n",
3428 		     _cupsLangString(lang, _("None")));
3429 
3430       for (; i < count; i ++) {
3431         value   = ippGetInteger(attr, i);
3432         keyword = ippEnumString("finishings", value);
3433 
3434         if (strncmp(keyword, "staple-", 7) &&
3435 	    strncmp(keyword, "bind-", 5) &&
3436 	    strncmp(keyword, "edge-stitch-", 12) &&
3437 	    strcmp(keyword, "saddle-stitch"))
3438           continue;
3439 
3440         if (cupsArrayFind(names, (char *)keyword))
3441           continue; /* Already did this finishing template */
3442 
3443         cupsArrayAdd(names, (char *)keyword);
3444 
3445         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3446           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3447         else if (value >= IPP_FINISHINGS_STAPLE_TOP_LEFT &&
3448 		 value <= IPP_FINISHINGS_STAPLE_TRIPLE_BOTTOM)
3449           ppd_keyword = staple_keywords[value - IPP_FINISHINGS_STAPLE_TOP_LEFT];
3450         else if (value >= IPP_FINISHINGS_BIND_LEFT &&
3451 		 value <= IPP_FINISHINGS_BIND_BOTTOM)
3452           ppd_keyword = bind_keywords[value - IPP_FINISHINGS_BIND_LEFT];
3453         else
3454           ppd_keyword = NULL;
3455 
3456         if (!ppd_keyword)
3457           continue;
3458 
3459 	snprintf(buf, sizeof(buf), "%d", value);
3460 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3461 				       printer_opt_strings_catalog);
3462 	if (human_readable == NULL)
3463 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3464 	       j ++)
3465 	    if (!strcmp(finishings[j][0], keyword)) {
3466 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3467 	      break;
3468 	    }
3469 	cupsFilePrintf(fp, "*StapleLocation %s%s%s: \"\"\n", ppd_keyword,
3470 		       (human_readable ? "/" : ""),
3471 		       (human_readable ? human_readable : ""));
3472 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n",
3473 		       value, keyword, ppd_keyword);
3474       }
3475 
3476       cupsFilePuts(fp, "*CloseUI: *StapleLocation\n");
3477     }
3478 
3479    /*
3480     * Fold
3481     */
3482 
3483     for (i = 0; i < count; i ++) {
3484       value   = ippGetInteger(attr, i);
3485       keyword = ippEnumString("finishings", value);
3486 
3487       if (!strncmp(keyword, "cups-fold-", 10) ||
3488 	  !strcmp(keyword, "fold") ||
3489 	  !strncmp(keyword, "fold-", 5))
3490         break;
3491     }
3492 
3493     if (i < count) {
3494       static const char * const fold_keywords[] =
3495       {					/* FoldType keywords */
3496 	"Accordion",
3497 	"DoubleGate",
3498 	"Gate",
3499 	"Half",
3500 	"HalfZ",
3501 	"LeftGate",
3502 	"Letter",
3503 	"Parallel",
3504 	"XFold",
3505 	"RightGate",
3506 	"ZFold",
3507 	"EngineeringZ"
3508       };
3509 
3510       cupsArrayAdd(fin_options, "*FoldType");
3511 
3512       human_readable = lookup_choice("fold", "finishing-template",
3513 				     opt_strings_catalog,
3514 				     printer_opt_strings_catalog);
3515       cupsFilePrintf(fp, "*OpenUI *FoldType/%s: PickOne\n",
3516 		     (human_readable ? human_readable :
3517 		      _cupsLangString(lang, _("Fold"))));
3518       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *FoldType\n");
3519       cupsFilePuts(fp, "*DefaultFoldType: None\n");
3520       cupsFilePrintf(fp, "*FoldType None/%s: \"\"\n",
3521 		     _cupsLangString(lang, _("None")));
3522 
3523       for (; i < count; i ++) {
3524         value   = ippGetInteger(attr, i);
3525         keyword = ippEnumString("finishings", value);
3526 
3527         if (!strncmp(keyword, "cups-fold-", 10))
3528           keyword += 5;
3529         else if (strcmp(keyword, "fold") && strncmp(keyword, "fold-", 5))
3530           continue;
3531 
3532         if (cupsArrayFind(names, (char *)keyword))
3533           continue; /* Already did this finishing template */
3534 
3535         cupsArrayAdd(names, (char *)keyword);
3536 
3537         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3538           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3539         else if (value >= IPP_FINISHINGS_FOLD_ACCORDION &&
3540 		 value <= IPP_FINISHINGS_FOLD_ENGINEERING_Z)
3541           ppd_keyword = fold_keywords[value - IPP_FINISHINGS_FOLD_ACCORDION];
3542         else if (value >= IPP_FINISHINGS_CUPS_FOLD_ACCORDION &&
3543 		 value <= IPP_FINISHINGS_CUPS_FOLD_Z)
3544           ppd_keyword = fold_keywords[value -
3545 				      IPP_FINISHINGS_CUPS_FOLD_ACCORDION];
3546         else
3547           ppd_keyword = NULL;
3548 
3549         if (!ppd_keyword)
3550           continue;
3551 
3552 	snprintf(buf, sizeof(buf), "%d", value);
3553 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3554 				       printer_opt_strings_catalog);
3555 	if (human_readable == NULL)
3556 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3557 	       j ++)
3558 	    if (!strcmp(finishings[j][0], keyword)) {
3559 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3560 	      break;
3561 	    }
3562 	cupsFilePrintf(fp, "*FoldType %s%s%s: \"\"\n", ppd_keyword,
3563 		       (human_readable ? "/" : ""),
3564 		       (human_readable ? human_readable : ""));
3565 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n",
3566 		       value, keyword, ppd_keyword);
3567       }
3568 
3569       cupsFilePuts(fp, "*CloseUI: *FoldType\n");
3570     }
3571 
3572    /*
3573     * Punch
3574     */
3575 
3576     for (i = 0; i < count; i ++) {
3577       value   = ippGetInteger(attr, i);
3578       keyword = ippEnumString("finishings", value);
3579 
3580       if (!strncmp(keyword, "cups-punch-", 11) ||
3581 	  !strncmp(keyword, "punch-", 6))
3582         break;
3583     }
3584 
3585     if (i < count) {
3586       static const char * const punch_keywords[] =
3587       {					/* PunchMedia keywords */
3588 	"SinglePortrait",
3589 	"SingleRevLandscape",
3590 	"SingleLandscape",
3591 	"SingleRevPortrait",
3592 	"DualPortrait",
3593 	"DualLandscape",
3594 	"DualRevPortrait",
3595 	"DualRevLandscape",
3596 	"TriplePortrait",
3597 	"TripleLandscape",
3598 	"TripleRevPortrait",
3599 	"TripleRevLandscape",
3600 	"QuadPortrait",
3601 	"QuadLandscape",
3602 	"QuadRevPortrait",
3603 	"QuadRevLandscape",
3604 	"MultiplePortrait",
3605 	"MultipleLandscape",
3606 	"MultipleRevPortrait",
3607 	"MultipleRevLandscape"
3608       };
3609 
3610       cupsArrayAdd(fin_options, "*PunchMedia");
3611 
3612       human_readable = lookup_choice("punch", "finishing-template",
3613 				     opt_strings_catalog,
3614 				     printer_opt_strings_catalog);
3615       cupsFilePrintf(fp, "*OpenUI *PunchMedia/%s: PickOne\n",
3616 		     (human_readable ? human_readable :
3617 		      _cupsLangString(lang, _("Punch"))));
3618       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *PunchMedia\n");
3619       cupsFilePuts(fp, "*DefaultPunchMedia: None\n");
3620       cupsFilePrintf(fp, "*PunchMedia None/%s: \"\"\n",
3621 		     _cupsLangString(lang, _("None")));
3622 
3623       for (i = 0; i < count; i ++) {
3624         value   = ippGetInteger(attr, i);
3625         keyword = ippEnumString("finishings", value);
3626 
3627         if (!strncmp(keyword, "cups-punch-", 11))
3628           keyword += 5;
3629         else if (strncmp(keyword, "punch-", 6))
3630           continue;
3631 
3632         if (cupsArrayFind(names, (char *)keyword))
3633           continue; /* Already did this finishing template */
3634 
3635         cupsArrayAdd(names, (char *)keyword);
3636 
3637         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3638           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3639         else if (value >= IPP_FINISHINGS_PUNCH_TOP_LEFT &&
3640 		 value <= IPP_FINISHINGS_PUNCH_MULTIPLE_BOTTOM)
3641           ppd_keyword = punch_keywords[value - IPP_FINISHINGS_PUNCH_TOP_LEFT];
3642         else if (value >= IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT &&
3643 		 value <= IPP_FINISHINGS_CUPS_PUNCH_QUAD_BOTTOM)
3644           ppd_keyword = punch_keywords[value -
3645 				       IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT];
3646         else
3647           ppd_keyword = NULL;
3648 
3649         if (!ppd_keyword)
3650           continue;
3651 
3652 	snprintf(buf, sizeof(buf), "%d", value);
3653 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3654 				       printer_opt_strings_catalog);
3655 	if (human_readable == NULL)
3656 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3657 	       j ++)
3658 	    if (!strcmp(finishings[j][0], keyword)) {
3659 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3660 	      break;
3661 	    }
3662 	cupsFilePrintf(fp, "*PunchMedia %s%s%s: \"\"\n", ppd_keyword,
3663 		       (human_readable ? "/" : ""),
3664 		       (human_readable ? human_readable : ""));
3665 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n",
3666 		       value, keyword, ppd_keyword);
3667       }
3668 
3669       cupsFilePuts(fp, "*CloseUI: *PunchMedia\n");
3670     }
3671 
3672    /*
3673     * Booklet
3674     */
3675 
3676     if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER)) {
3677       cupsArrayAdd(fin_options, "*Booklet");
3678 
3679       human_readable = lookup_choice("booklet-maker", "finishing-template",
3680 				     opt_strings_catalog,
3681 				     printer_opt_strings_catalog);
3682       cupsFilePrintf(fp, "*OpenUI *Booklet/%s: Boolean\n",
3683 		     (human_readable ? human_readable :
3684 		      _cupsLangString(lang, _("Booklet"))));
3685       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *Booklet\n");
3686       cupsFilePuts(fp, "*DefaultBooklet: False\n");
3687       cupsFilePuts(fp, "*Booklet False: \"\"\n");
3688       cupsFilePuts(fp, "*Booklet True: \"\"\n");
3689       cupsFilePrintf(fp, "*cupsIPPFinishings %d/booklet-maker: \"*Booklet True\"\n",
3690 		     IPP_FINISHINGS_BOOKLET_MAKER);
3691       cupsFilePuts(fp, "*CloseUI: *Booklet\n");
3692     }
3693 
3694    /*
3695     * CutMedia
3696     */
3697 
3698     for (i = 0; i < count; i ++) {
3699       value   = ippGetInteger(attr, i);
3700       keyword = ippEnumString("finishings", value);
3701 
3702       if (!strcmp(keyword, "trim") || !strncmp(keyword, "trim-", 5))
3703         break;
3704     }
3705 
3706     if (i < count) {
3707       static const char * const trim_keywords[] =
3708       {				/* CutMedia keywords */
3709         "EndOfPage",
3710         "EndOfDoc",
3711         "EndOfSet",
3712         "EndOfJob"
3713       };
3714 
3715       cupsArrayAdd(fin_options, "*CutMedia");
3716 
3717       human_readable = lookup_choice("trim", "finishing-template",
3718 				     opt_strings_catalog,
3719 				     printer_opt_strings_catalog);
3720       cupsFilePrintf(fp, "*OpenUI *CutMedia/%s: PickOne\n",
3721 		     (human_readable ? human_readable :
3722 		      _cupsLangString(lang, _("Cut"))));
3723       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *CutMedia\n");
3724       cupsFilePuts(fp, "*DefaultCutMedia: None\n");
3725       cupsFilePrintf(fp, "*CutMedia None/%s: \"\"\n",
3726 		     _cupsLangString(lang, _("None")));
3727 
3728       for (i = 0; i < count; i ++) {
3729         value   = ippGetInteger(attr, i);
3730         keyword = ippEnumString("finishings", value);
3731 
3732 	if (strcmp(keyword, "trim") && strncmp(keyword, "trim-", 5))
3733           continue;
3734 
3735         if (cupsArrayFind(names, (char *)keyword))
3736           continue; /* Already did this finishing template */
3737 
3738         cupsArrayAdd(names, (char *)keyword);
3739 
3740         if (value == IPP_FINISHINGS_TRIM)
3741           ppd_keyword = "Auto";
3742 	else
3743 	  ppd_keyword = trim_keywords[value - IPP_FINISHINGS_TRIM_AFTER_PAGES];
3744 
3745 	snprintf(buf, sizeof(buf), "%d", value);
3746 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3747 				       printer_opt_strings_catalog);
3748 	if (human_readable == NULL)
3749 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3750 	       j ++)
3751 	    if (!strcmp(finishings[j][0], keyword)) {
3752 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3753 	      break;
3754 	    }
3755 	cupsFilePrintf(fp, "*CutMedia %s%s%s: \"\"\n", ppd_keyword,
3756 		       (human_readable ? "/" : ""),
3757 		       (human_readable ? human_readable : ""));
3758 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*CutMedia %s\"\n",
3759 		       value, keyword, ppd_keyword);
3760       }
3761 
3762       cupsFilePuts(fp, "*CloseUI: *CutMedia\n");
3763     }
3764 
3765     cupsArrayDelete(names);
3766   }
3767 
3768   if ((attr = ippFindAttribute(response, "finishings-col-database",
3769 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
3770     ipp_t	*finishing_col;		/* Current finishing collection */
3771     ipp_attribute_t *finishing_attr;	/* Current finishing member attribute */
3772     cups_array_t *templates;		/* Finishing templates */
3773 
3774     cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickOne\n",
3775 		   _cupsLangString(lang, _("Finishing Preset")));
3776     cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n");
3777     cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: none\n");
3778     cupsFilePrintf(fp, "*cupsFinishingTemplate none/%s: \"\"\n",
3779 		   _cupsLangString(lang, _("None")));
3780 
3781     templates = cupsArrayNew((cups_array_func_t)strcmp, NULL);
3782     count     = ippGetCount(attr);
3783 
3784     for (i = 0; i < count; i ++) {
3785       finishing_col = ippGetCollection(attr, i);
3786       keyword = ippGetString(ippFindAttribute(finishing_col,
3787 					      "finishing-template",
3788 					      IPP_TAG_ZERO), 0, NULL);
3789 
3790       if (!keyword || cupsArrayFind(templates, (void *)keyword))
3791         continue;
3792 
3793       if (!strcmp(keyword, "none"))
3794         continue;
3795 
3796       cupsArrayAdd(templates, (void *)keyword);
3797 
3798       human_readable = lookup_choice((char *)keyword, "finishing-template",
3799 				     opt_strings_catalog,
3800 				     printer_opt_strings_catalog);
3801       if (human_readable == NULL)
3802 	human_readable = (char *)keyword;
3803       cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\n", keyword,
3804 		     human_readable);
3805       for (finishing_attr = ippFirstAttribute(finishing_col); finishing_attr;
3806 	   finishing_attr = ippNextAttribute(finishing_col)) {
3807         if (ippGetValueTag(finishing_attr) == IPP_TAG_BEGIN_COLLECTION) {
3808 	  const char *name = ippGetName(finishing_attr);
3809 					/* Member attribute name */
3810 
3811           if (strcmp(name, "media-size"))
3812             cupsFilePrintf(fp, "%% %s\n", name);
3813 	}
3814       }
3815       cupsFilePuts(fp, "\"\n");
3816       cupsFilePuts(fp, "*End\n");
3817     }
3818 
3819     cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");
3820 
3821     if (cupsArrayCount(fin_options)) {
3822       const char	*fin_option;	/* Current finishing option */
3823 
3824       cupsFilePuts(fp, "*cupsUIConstraint finishing-template: \"*cupsFinishingTemplate");
3825       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
3826 	   fin_option = (const char *)cupsArrayNext(fin_options))
3827         cupsFilePrintf(fp, " %s", fin_option);
3828       cupsFilePuts(fp, "\"\n");
3829 
3830       cupsFilePuts(fp, "*cupsUIResolver finishing-template: \"*cupsFinishingTemplate None");
3831       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
3832 	   fin_option = (const char *)cupsArrayNext(fin_options))
3833         cupsFilePrintf(fp, " %s None", fin_option);
3834       cupsFilePuts(fp, "\"\n");
3835     }
3836 
3837     cupsArrayDelete(templates);
3838   }
3839 
3840   cupsArrayDelete(fin_options);
3841 
3842  /*
3843   * DefaultResolution...
3844   */
3845 
3846   xres = common_def->x;
3847   yres = common_def->y;
3848   if (xres == yres)
3849     cupsFilePrintf(fp, "*DefaultResolution: %ddpi\n", xres);
3850   else
3851     cupsFilePrintf(fp, "*DefaultResolution: %dx%ddpi\n", xres, yres);
3852 
3853  /*
3854   * cupsPrintQuality...
3855   */
3856 
3857   if ((quality =
3858        ippFindAttribute(response, "print-quality-supported",
3859 			IPP_TAG_ENUM)) != NULL) {
3860     human_readable = lookup_option("print-quality", opt_strings_catalog,
3861 				   printer_opt_strings_catalog);
3862     cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
3863 		   "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
3864 		   "*DefaultcupsPrintQuality: Normal\n",
3865 		   (human_readable ? human_readable :
3866 		    _cupsLangString(lang, _("Print Quality"))));
3867     if (ippContainsInteger(quality, IPP_QUALITY_DRAFT)) {
3868       human_readable = lookup_choice("3", "print-quality", opt_strings_catalog,
3869 				     printer_opt_strings_catalog);
3870       cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3871 		     (human_readable ? human_readable :
3872 		      _cupsLangString(lang, _("Draft"))),
3873 		     min_res->x, min_res->y);
3874     }
3875     human_readable = lookup_choice("4", "print-quality", opt_strings_catalog,
3876 				   printer_opt_strings_catalog);
3877     cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3878 		   (human_readable ? human_readable :
3879 		    _cupsLangString(lang, _("Normal"))),
3880 		   common_def->x, common_def->y);
3881     if (ippContainsInteger(quality, IPP_QUALITY_HIGH)) {
3882       human_readable = lookup_choice("5", "print-quality", opt_strings_catalog,
3883 				     printer_opt_strings_catalog);
3884       cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3885 		     (human_readable ? human_readable :
3886 		      _cupsLangString(lang, _("High"))),
3887 		     max_res->x, max_res->y);
3888     }
3889     cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
3890   }
3891 
3892   /* Only add these options if jobs get sent to the printer as PDF,
3893      PWG Raster, or Apple Raster, as only then arbitrary IPP
3894      attributes get passed through from the filter command line
3895      to the printer by the "ipp" CUPS backend. */
3896   if (is_pdf || is_pwg || is_apple) {
3897     /*
3898      * Print Optimization ...
3899      */
3900 
3901     if ((attr = ippFindAttribute(response, "print-content-optimize-default",
3902 				 IPP_TAG_ZERO)) != NULL)
3903       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
3904     else
3905       strlcpy(ppdname, "auto", sizeof(ppdname));
3906 
3907     if ((attr = ippFindAttribute(response, "print-content-optimize-supported",
3908 				 IPP_TAG_ZERO)) != NULL &&
3909 	(count = ippGetCount(attr)) > 1) {
3910       static const char * const content_optimize_types[][2] =
3911       {					/* "print-content-optimize" strings */
3912 	{ "auto", _("Automatic") },
3913 	{ "graphic", _("Graphics") },
3914 	{ "graphics", _("Graphics") },
3915 	{ "photo", _("Photo") },
3916 	{ "text", _("Text") },
3917 	{ "text-and-graphic", _("Text And Graphics") },
3918 	{ "text-and-graphics", _("Text And Graphics") }
3919       };
3920 
3921       human_readable = lookup_option("print-content-optimize",
3922 				     opt_strings_catalog,
3923 				     printer_opt_strings_catalog);
3924       cupsFilePrintf(fp, "*OpenUI *print-content-optimize/%s: PickOne\n"
3925 		     "*OrderDependency: 10 AnySetup *print-content-optimize\n"
3926 		     "*Defaultprint-content-optimize: %s\n",
3927 		     (human_readable ? human_readable : "Print Optimization"),
3928 		     ppdname);
3929       for (i = 0; i < count; i ++) {
3930 	keyword = ippGetString(attr, i, NULL);
3931 
3932 	human_readable = lookup_choice((char *)keyword,
3933 				       "print-content-optimize",
3934 				       opt_strings_catalog,
3935 				       printer_opt_strings_catalog);
3936 	if (human_readable == NULL)
3937 	  for (j = 0;
3938 	       j < (int)(sizeof(content_optimize_types) /
3939 			 sizeof(content_optimize_types[0]));
3940 	       j ++)
3941 	    if (!strcmp(content_optimize_types[j][0], keyword)) {
3942 	      human_readable =
3943 		(char *)_cupsLangString(lang,
3944 					content_optimize_types[j][1]);
3945 	      break;
3946 	    }
3947 	cupsFilePrintf(fp, "*print-content-optimize %s%s%s: \"\"\n",
3948 		       keyword,
3949 		       (human_readable ? "/" : ""),
3950 		       (human_readable ? human_readable : ""));
3951       }
3952       cupsFilePuts(fp, "*CloseUI: *print-content-optimize\n");
3953     }
3954 
3955     /*
3956      * Print Rendering Intent ...
3957      */
3958 
3959     if ((attr = ippFindAttribute(response, "print-rendering-intent-default",
3960 				 IPP_TAG_ZERO)) != NULL)
3961       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
3962     else
3963       strlcpy(ppdname, "auto", sizeof(ppdname));
3964 
3965     if ((attr = ippFindAttribute(response, "print-rendering-intent-supported",
3966 				 IPP_TAG_ZERO)) != NULL &&
3967 	(count = ippGetCount(attr)) > 1) {
3968       static const char * const rendering_intents[][2] =
3969       {					/* "print-rendering-intent" strings */
3970 	{ "auto", _("Automatic") },
3971 	{ "absolute", _("Absolute") },
3972 	{ "perceptual", _("Perceptual") },
3973 	{ "relative", _("Relative") },
3974 	{ "relative-bpc", _("Relative w/Black Point Compensation") },
3975 	{ "saturation", _("Saturation") }
3976       };
3977 
3978       human_readable = lookup_option("print-rendering-intent",
3979 				     opt_strings_catalog,
3980 				     printer_opt_strings_catalog);
3981       cupsFilePrintf(fp, "*OpenUI *print-rendering-intent/%s: PickOne\n"
3982 		     "*OrderDependency: 10 AnySetup *print-rendering-intent\n"
3983 		     "*Defaultprint-rendering-intent: %s\n",
3984 		     (human_readable ? human_readable :
3985 		      "Print Rendering Intent"),
3986 		     ppdname);
3987       for (i = 0; i < count; i ++) {
3988 	keyword = ippGetString(attr, i, NULL);
3989 
3990 	human_readable = lookup_choice((char *)keyword,
3991 				       "print-rendering-intent",
3992 				       opt_strings_catalog,
3993 				       printer_opt_strings_catalog);
3994 	if (human_readable == NULL)
3995 	  for (j = 0;
3996 	       j < (int)(sizeof(rendering_intents) /
3997 			 sizeof(rendering_intents[0]));
3998 	       j ++)
3999 	    if (!strcmp(rendering_intents[j][0], keyword)) {
4000 	      human_readable =
4001 		(char *)_cupsLangString(lang,
4002 					rendering_intents[j][1]);
4003 	      break;
4004 	    }
4005 	cupsFilePrintf(fp, "*print-rendering-intent %s%s%s: \"\"\n",
4006 		       keyword,
4007 		       (human_readable ? "/" : ""),
4008 		       (human_readable ? human_readable : ""));
4009       }
4010       cupsFilePuts(fp, "*CloseUI: *print-rendering-intent\n");
4011     }
4012 
4013     /*
4014      * Print Scaling ...
4015      */
4016 
4017     if ((attr = ippFindAttribute(response, "print-scaling-default",
4018 				 IPP_TAG_ZERO)) != NULL)
4019       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
4020     else
4021       strlcpy(ppdname, "auto", sizeof(ppdname));
4022 
4023     if ((attr = ippFindAttribute(response, "print-scaling-supported",
4024 				 IPP_TAG_ZERO)) != NULL &&
4025 	(count = ippGetCount(attr)) > 1) {
4026       static const char * const scaling_types[][2] =
4027       {					/* "print-scaling" strings */
4028 	{ "auto", _("Automatic") },
4029 	{ "auto-fit", _("Auto Fit") },
4030 	{ "fill", _("Fill") },
4031 	{ "fit", _("Fit") },
4032 	{ "none", _("None") }
4033       };
4034 
4035       human_readable = lookup_option("print-scaling", opt_strings_catalog,
4036 				     printer_opt_strings_catalog);
4037       cupsFilePrintf(fp, "*OpenUI *print-scaling/%s: PickOne\n"
4038 		     "*OrderDependency: 10 AnySetup *print-scaling\n"
4039 		     "*Defaultprint-scaling: %s\n",
4040 		     (human_readable ? human_readable : "Print Scaling"),
4041 		     ppdname);
4042       for (i = 0; i < count; i ++) {
4043 	keyword = ippGetString(attr, i, NULL);
4044 
4045 	human_readable = lookup_choice((char *)keyword, "print-scaling",
4046 				       opt_strings_catalog,
4047 				       printer_opt_strings_catalog);
4048 	if (human_readable == NULL)
4049 	  for (j = 0;
4050 	       j < (int)(sizeof(scaling_types) /
4051 			 sizeof(scaling_types[0]));
4052 	       j ++)
4053 	    if (!strcmp(scaling_types[j][0], keyword)) {
4054 	      human_readable =
4055 		(char *)_cupsLangString(lang, scaling_types[j][1]);
4056 	      break;
4057 	    }
4058 	cupsFilePrintf(fp, "*print-scaling %s%s%s: \"\"\n",
4059 		       keyword,
4060 		       (human_readable ? "/" : ""),
4061 		       (human_readable ? human_readable : ""));
4062       }
4063       cupsFilePuts(fp, "*CloseUI: *print-scaling\n");
4064     }
4065   }
4066 
4067  /*
4068   * Phone Options for Fax..
4069   */
4070 
4071   if (is_fax) {
4072     human_readable = lookup_option("Phone", opt_strings_catalog,
4073 				   printer_opt_strings_catalog);
4074 
4075     cupsFilePrintf(fp, "*OpenUI *phone/%s: PickOne\n"
4076 		   "*OrderDependency: 10 AnySetup *phone\n"
4077 		   "*Defaultphone: None\n"
4078 		   "*phone None: \"\"\n"
4079 		   "*CloseUI: *phone\n",
4080 		   (human_readable ? human_readable : "Phone Number"));
4081     cupsFilePrintf(fp,"*Customphone True: \"\"\n"
4082 		   "*ParamCustomphone Text: 1 string 0 64\n");
4083 
4084     human_readable = lookup_option("faxPrefix", opt_strings_catalog,
4085 				   printer_opt_strings_catalog);
4086 
4087     cupsFilePrintf(fp, "*OpenUI *faxPrefix/%s: PickOne\n"
4088 		   "*OrderDependency: 10 AnySetup *faxPrefix\n"
4089 		   "*DefaultfaxPrefix: None\n"
4090 		   "*faxPrefix None: \"\"\n"
4091 		   "*CloseUI: *faxPrefix\n",
4092 		   (human_readable ? human_readable : "Pre-Dial Number"));
4093     cupsFilePrintf(fp,"*CustomfaxPrefix True: \"\"\n"
4094 		   "*ParamCustomfaxPrefix Text: 1 string 0 64\n");
4095   }
4096 
4097  /*
4098   * Presets...
4099   */
4100 
4101   if ((attr = ippFindAttribute(response, "job-presets-supported",
4102 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
4103     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
4104       ipp_t	*preset = ippGetCollection(attr, i); /* Preset collection */
4105       const char *preset_name =         /* Preset name */
4106 	ippGetString(ippFindAttribute(preset,
4107 				      "preset-name", IPP_TAG_ZERO), 0, NULL),
4108 		 *localized_name;	/* Localized preset name */
4109       ipp_attribute_t *member;		/* Member attribute in preset */
4110       const char *member_name;		/* Member attribute name */
4111       char       member_value[256];	/* Member attribute value */
4112 
4113       if (!preset || !preset_name)
4114         continue;
4115 
4116       if ((localized_name = lookup_option((char *)preset_name,
4117 					  opt_strings_catalog,
4118 					  printer_opt_strings_catalog)) == NULL)
4119         cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", preset_name);
4120       else
4121         cupsFilePrintf(fp, "*APPrinterPreset %s/%s: \"\n", preset_name,
4122 		       localized_name);
4123 
4124       for (member = ippFirstAttribute(preset); member;
4125 	   member = ippNextAttribute(preset)) {
4126         member_name = ippGetName(member);
4127 
4128         if (!member_name || !strcmp(member_name, "preset-name"))
4129           continue;
4130 
4131         if (!strcmp(member_name, "finishings")) {
4132 	  for (i = 0, count = ippGetCount(member); i < count; i ++) {
4133 	    const char *option = NULL;	/* PPD option name */
4134 
4135 	    keyword = ippEnumString("finishings", ippGetInteger(member, i));
4136 
4137 	    if (!strcmp(keyword, "booklet-maker")) {
4138 	      option  = "Booklet";
4139 	      keyword = "True";
4140 	    } else if (!strncmp(keyword, "fold-", 5))
4141 	      option = "FoldType";
4142 	    else if (!strncmp(keyword, "punch-", 6))
4143 	      option = "PunchMedia";
4144 	    else if (!strncmp(keyword, "bind-", 5) ||
4145 		     !strncmp(keyword, "edge-stitch-", 12) ||
4146 		     !strcmp(keyword, "saddle-stitch") ||
4147 		     !strncmp(keyword, "staple-", 7))
4148 	      option = "StapleLocation";
4149 
4150 	    if (option && keyword)
4151 	      cupsFilePrintf(fp, "*%s %s\n", option, keyword);
4152 	  }
4153         } else if (!strcmp(member_name, "finishings-col")) {
4154           ipp_t *fin_col;		/* finishings-col value */
4155 
4156           for (i = 0, count = ippGetCount(member); i < count; i ++) {
4157             fin_col = ippGetCollection(member, i);
4158 
4159             if ((keyword =
4160 		 ippGetString(ippFindAttribute(fin_col,
4161 					       "finishing-template",
4162 					       IPP_TAG_ZERO), 0, NULL)) != NULL)
4163               cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", keyword);
4164           }
4165         } else if (!strcmp(member_name, "media")) {
4166          /*
4167           * Map media to PageSize...
4168           */
4169 
4170           if ((pwg = pwgMediaForPWG(ippGetString(member, 0, NULL))) != NULL &&
4171 	      pwg->ppd)
4172             cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
4173         } else if (!strcmp(member_name, "media-col")) {
4174           media_col = ippGetCollection(member, 0);
4175 
4176           if ((media_size =
4177 	       ippGetCollection(ippFindAttribute(media_col,
4178 						 "media-size",
4179 						 IPP_TAG_BEGIN_COLLECTION),
4180 				0)) != NULL) {
4181             x_dim = ippFindAttribute(media_size, "x-dimension",
4182 				     IPP_TAG_INTEGER);
4183             y_dim = ippFindAttribute(media_size, "y-dimension",
4184 				     IPP_TAG_INTEGER);
4185             if ((pwg = pwgMediaForSize(ippGetInteger(x_dim, 0),
4186 				       ippGetInteger(y_dim, 0))) != NULL &&
4187 		pwg->ppd)
4188 	      cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
4189           }
4190 
4191           if ((keyword = ippGetString(ippFindAttribute(media_col,
4192 						       "media-source",
4193 						       IPP_TAG_ZERO), 0,
4194 				      NULL)) != NULL) {
4195             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4196             cupsFilePrintf(fp, "*InputSlot %s\n", keyword);
4197 	  }
4198 
4199           if ((keyword = ippGetString(ippFindAttribute(media_col, "media-type",
4200 						       IPP_TAG_ZERO), 0,
4201 				      NULL)) != NULL) {
4202             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4203             cupsFilePrintf(fp, "*MediaType %s\n", keyword);
4204 	  }
4205         } else if (!strcmp(member_name, "print-quality")) {
4206 	 /*
4207 	  * Map print-quality to cupsPrintQuality...
4208 	  */
4209 
4210           int qval = ippGetInteger(member, 0);
4211 					/* print-quality value */
4212 	  static const char * const qualities[] = { "Draft", "Normal", "High" };
4213 					/* cupsPrintQuality values */
4214 
4215           if (qval >= IPP_QUALITY_DRAFT && qval <= IPP_QUALITY_HIGH)
4216             cupsFilePrintf(fp, "*cupsPrintQuality %s\n",
4217 			   qualities[qval - IPP_QUALITY_DRAFT]);
4218         } else if (!strcmp(member_name, "output-bin")) {
4219           pwg_ppdize_name(ippGetString(member, 0, NULL), ppdname,
4220 			  sizeof(ppdname));
4221           cupsFilePrintf(fp, "*OutputBin %s\n", ppdname);
4222         } else if (!strcmp(member_name, "sides")) {
4223           keyword = ippGetString(member, 0, NULL);
4224           if (keyword && !strcmp(keyword, "one-sided"))
4225             cupsFilePuts(fp, "*Duplex None\n");
4226 	  else if (keyword && !strcmp(keyword, "two-sided-long-edge"))
4227 	    cupsFilePuts(fp, "*Duplex DuplexNoTumble\n");
4228 	  else if (keyword && !strcmp(keyword, "two-sided-short-edge"))
4229 	    cupsFilePuts(fp, "*Duplex DuplexTumble\n");
4230         } else {
4231          /*
4232           * Add attribute name and value as-is...
4233           */
4234 
4235           ippAttributeString(member, member_value, sizeof(member_value));
4236           cupsFilePrintf(fp, "*%s %s\n", member_name, member_value);
4237 	}
4238       }
4239 
4240       cupsFilePuts(fp, "\"\n*End\n");
4241     }
4242   }
4243 
4244  /*
4245   * constraints
4246   */
4247   if (conflicts != NULL) {
4248     char* constraint;
4249     for (constraint = (char *)cupsArrayFirst(conflicts); constraint;
4250          constraint = (char *)cupsArrayNext(conflicts)) {
4251       cupsFilePrintf(fp, "%s", constraint);
4252     }
4253   }
4254 
4255  /*
4256   * Close up and return...
4257   */
4258 
4259   free(common_def);
4260   free(min_res);
4261   free(max_res);
4262 
4263   snprintf(ppdgenerator_msg, sizeof(ppdgenerator_msg),
4264 	   "%s %sPPD generated.",
4265 	   (is_apple ? "Apple Raster" :
4266 	    (is_pwg ? "PWG Raster" :
4267 	     (is_pdf ? "PDF" :
4268 	      (is_pclm ? "PCLm" :
4269 	       "Legacy IPP printer")))),
4270 	   (is_fax ? "Fax " : ""));
4271 
4272   cupsFileClose(fp);
4273   if (printer_opt_strings_catalog)
4274     cupsArrayDelete(printer_opt_strings_catalog);
4275 
4276   return (buffer);
4277 
4278  /*
4279   * If we get here then there was a problem creating the PPD...
4280   */
4281 
4282  bad_ppd:
4283 
4284   if (common_res) cupsArrayDelete(common_res);
4285   if (common_def) free(common_def);
4286   if (min_res) free(min_res);
4287   if (max_res) free(max_res);
4288 
4289   cupsFileClose(fp);
4290   if (printer_opt_strings_catalog)
4291     cupsArrayDelete(printer_opt_strings_catalog);
4292   unlink(buffer);
4293   *buffer = '\0';
4294 
4295   _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
4296 		_("Printer does not support required IPP attributes or document formats."),
4297 		1);
4298 
4299   return (NULL);
4300 }
4301 
4302 
4303 /*
4304  * '_pwgInputSlotForSource()' - Get the InputSlot name for the given PWG
4305  *                              media-source.
4306  */
4307 
4308 const char *				/* O - InputSlot name */
_pwgInputSlotForSource(const char * media_source,char * name,size_t namesize)4309 _pwgInputSlotForSource(
4310     const char *media_source,		/* I - PWG media-source */
4311     char       *name,			/* I - Name buffer */
4312     size_t     namesize)		/* I - Size of name buffer */
4313 {
4314  /*
4315   * Range check input...
4316   */
4317 
4318   if (!media_source || !name || namesize < PPD_MAX_NAME)
4319     return (NULL);
4320 
4321   if (_cups_strcasecmp(media_source, "main"))
4322     strlcpy(name, "Cassette", namesize);
4323   else if (_cups_strcasecmp(media_source, "alternate"))
4324     strlcpy(name, "Multipurpose", namesize);
4325   else if (_cups_strcasecmp(media_source, "large-capacity"))
4326     strlcpy(name, "LargeCapacity", namesize);
4327   else if (_cups_strcasecmp(media_source, "bottom"))
4328     strlcpy(name, "Lower", namesize);
4329   else if (_cups_strcasecmp(media_source, "middle"))
4330     strlcpy(name, "Middle", namesize);
4331   else if (_cups_strcasecmp(media_source, "top"))
4332     strlcpy(name, "Upper", namesize);
4333   else if (_cups_strcasecmp(media_source, "rear"))
4334     strlcpy(name, "Rear", namesize);
4335   else if (_cups_strcasecmp(media_source, "side"))
4336     strlcpy(name, "Side", namesize);
4337   else if (_cups_strcasecmp(media_source, "envelope"))
4338     strlcpy(name, "Envelope", namesize);
4339   else if (_cups_strcasecmp(media_source, "main-roll"))
4340     strlcpy(name, "Roll", namesize);
4341   else if (_cups_strcasecmp(media_source, "alternate-roll"))
4342     strlcpy(name, "Roll2", namesize);
4343   else
4344     pwg_ppdize_name(media_source, name, namesize);
4345 
4346   return (name);
4347 }
4348 
4349 
4350 /*
4351  * '_pwgMediaTypeForType()' - Get the MediaType name for the given PWG
4352  *                            media-type.
4353  */
4354 
4355 const char *				/* O - MediaType name */
_pwgMediaTypeForType(const char * media_type,char * name,size_t namesize)4356 _pwgMediaTypeForType(
4357     const char *media_type,		/* I - PWG media-type */
4358     char       *name,			/* I - Name buffer */
4359     size_t     namesize)		/* I - Size of name buffer */
4360 {
4361  /*
4362   * Range check input...
4363   */
4364 
4365   if (!media_type || !name || namesize < PPD_MAX_NAME)
4366     return (NULL);
4367 
4368   if (_cups_strcasecmp(media_type, "auto"))
4369     strlcpy(name, "Auto", namesize);
4370   else if (_cups_strcasecmp(media_type, "cardstock"))
4371     strlcpy(name, "Cardstock", namesize);
4372   else if (_cups_strcasecmp(media_type, "envelope"))
4373     strlcpy(name, "Envelope", namesize);
4374   else if (_cups_strcasecmp(media_type, "photographic-glossy"))
4375     strlcpy(name, "Glossy", namesize);
4376   else if (_cups_strcasecmp(media_type, "photographic-high-gloss"))
4377     strlcpy(name, "HighGloss", namesize);
4378   else if (_cups_strcasecmp(media_type, "photographic-matte"))
4379     strlcpy(name, "Matte", namesize);
4380   else if (_cups_strcasecmp(media_type, "stationery"))
4381     strlcpy(name, "Plain", namesize);
4382   else if (_cups_strcasecmp(media_type, "stationery-coated"))
4383     strlcpy(name, "Coated", namesize);
4384   else if (_cups_strcasecmp(media_type, "stationery-inkjet"))
4385     strlcpy(name, "Inkjet", namesize);
4386   else if (_cups_strcasecmp(media_type, "stationery-letterhead"))
4387     strlcpy(name, "Letterhead", namesize);
4388   else if (_cups_strcasecmp(media_type, "stationery-preprinted"))
4389     strlcpy(name, "Preprinted", namesize);
4390   else if (_cups_strcasecmp(media_type, "transparency"))
4391     strlcpy(name, "Transparency", namesize);
4392   else
4393     pwg_ppdize_name(media_type, name, namesize);
4394 
4395   return (name);
4396 }
4397 
4398 
4399 /*
4400  * '_pwgPageSizeForMedia()' - Get the PageSize name for the given media.
4401  */
4402 
4403 const char *				/* O - PageSize name */
_pwgPageSizeForMedia(pwg_media_t * media,char * name,size_t namesize)4404 _pwgPageSizeForMedia(
4405     pwg_media_t *media,		/* I - Media */
4406     char         *name,			/* I - PageSize name buffer */
4407     size_t       namesize)		/* I - Size of name buffer */
4408 {
4409   const char	*sizeptr,		/* Pointer to size in PWG name */
4410 		*dimptr;		/* Pointer to dimensions in PWG name */
4411 
4412 
4413  /*
4414   * Range check input...
4415   */
4416 
4417   if (!media || !name || namesize < PPD_MAX_NAME)
4418     return (NULL);
4419 
4420  /*
4421   * Copy or generate a PageSize name...
4422   */
4423 
4424   if (media->ppd) {
4425    /*
4426     * Use a standard Adobe name...
4427     */
4428 
4429     strlcpy(name, media->ppd, namesize);
4430   }
4431   else if (!media->pwg || !strncmp(media->pwg, "custom_", 7) ||
4432            (sizeptr = strchr(media->pwg, '_')) == NULL ||
4433 	   (dimptr = strchr(sizeptr + 1, '_')) == NULL ||
4434 	   (size_t)(dimptr - sizeptr) > namesize) {
4435    /*
4436     * Use a name of the form "wNNNhNNN"...
4437     */
4438 
4439     snprintf(name, namesize, "w%dh%d", (int)PWG_TO_POINTS(media->width),
4440              (int)PWG_TO_POINTS(media->length));
4441   } else {
4442    /*
4443     * Copy the size name from class_sizename_dimensions...
4444     */
4445 
4446     memcpy(name, sizeptr + 1, (size_t)(dimptr - sizeptr - 1));
4447     name[dimptr - sizeptr - 1] = '\0';
4448   }
4449 
4450   return (name);
4451 }
4452 
4453 
4454 /*
4455  * 'pwg_ppdize_name()' - Convert an IPP keyword to a PPD keyword.
4456  */
4457 
4458 static void
pwg_ppdize_name(const char * ipp,char * name,size_t namesize)4459 pwg_ppdize_name(const char *ipp,	/* I - IPP keyword */
4460                 char       *name,	/* I - Name buffer */
4461 		size_t     namesize)	/* I - Size of name buffer */
4462 {
4463   char	*ptr,				/* Pointer into name buffer */
4464 	*end;				/* End of name buffer */
4465 
4466 
4467   *name = (char)toupper(*ipp++);
4468 
4469   for (ptr = name + 1, end = name + namesize - 1; *ipp && ptr < end;) {
4470     if (*ipp == '-') {
4471       ipp ++;
4472       if (_cups_isalpha(*ipp))
4473 	*ptr++ = (char)toupper(*ipp++ & 255);
4474     } else
4475       *ptr++ = *ipp++;
4476   }
4477 
4478   *ptr = '\0';
4479 }
4480 
4481 
4482 
4483 /*
4484  * 'pwg_ppdize_resolution()' - Convert PWG resolution values to PPD values.
4485  */
4486 
4487 static void
pwg_ppdize_resolution(ipp_attribute_t * attr,int element,int * xres,int * yres,char * name,size_t namesize)4488 pwg_ppdize_resolution(
4489     ipp_attribute_t *attr,		/* I - Attribute to convert */
4490     int             element,		/* I - Element to convert */
4491     int             *xres,		/* O - X resolution in DPI */
4492     int             *yres,		/* O - Y resolution in DPI */
4493     char            *name,		/* I - Name buffer */
4494     size_t          namesize)		/* I - Size of name buffer */
4495 {
4496   ipp_res_t units;			/* Units for resolution */
4497 
4498   *xres = ippGetResolution(attr, element, yres, &units);
4499 
4500   if (units == IPP_RES_PER_CM) {
4501     *xres = (int)(*xres * 2.54);
4502     *yres = (int)(*yres * 2.54);
4503   }
4504 
4505   if (name && namesize > 4) {
4506     if (*xres == *yres)
4507       snprintf(name, namesize, "%ddpi", *xres);
4508     else
4509       snprintf(name, namesize, "%dx%ddpi", *xres, *yres);
4510   }
4511 }
4512 #endif /* HAVE_CUPS_1_6 */
4513