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, 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 
1189   if ((attr = ippFindAttribute(response, "media-bottom-margin-supported",
1190 			       IPP_TAG_INTEGER)) != NULL) {
1191     for (i = 1, *bottom = ippGetInteger(attr, 0), count = ippGetCount(attr);
1192 	 i < count; i ++)
1193       if (i == 1 || ippGetInteger(attr, i) < *bottom)
1194         *bottom = ippGetInteger(attr, i);
1195   } else
1196     *bottom = 1270;
1197 
1198   if ((attr = ippFindAttribute(response, "media-left-margin-supported",
1199 			       IPP_TAG_INTEGER)) != NULL) {
1200     for (i = 1, *left = ippGetInteger(attr, 0), count = ippGetCount(attr);
1201 	 i < count; i ++)
1202       if (i == 1 || ippGetInteger(attr, i) < *left)
1203         *left = ippGetInteger(attr, i);
1204   } else
1205     *left = 635;
1206 
1207   if ((attr = ippFindAttribute(response, "media-right-margin-supported",
1208 			       IPP_TAG_INTEGER)) != NULL) {
1209     for (i = 1, *right = ippGetInteger(attr, 0), count = ippGetCount(attr);
1210 	 i < count; i ++)
1211       if (i == 1 || ippGetInteger(attr, i) < *right)
1212         *right = ippGetInteger(attr, i);
1213   } else
1214     *right = 635;
1215 
1216   if ((attr = ippFindAttribute(response, "media-top-margin-supported",
1217 			       IPP_TAG_INTEGER)) != NULL) {
1218     for (i = 1, *top = ippGetInteger(attr, 0), count = ippGetCount(attr);
1219 	 i < count; i ++)
1220       if (i == 1 || ippGetInteger(attr, i) < *top)
1221         *top = ippGetInteger(attr, i);
1222   } else
1223     *top = 1270;
1224 
1225   if ((*defattr = ippFindAttribute(response, "media-col-default",
1226 				   IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1227     if ((attr = ippFindAttribute(ippGetCollection(*defattr, 0), "media-size",
1228 				 IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1229       media_size = ippGetCollection(attr, 0);
1230       x_dim      = ippFindAttribute(media_size, "x-dimension", IPP_TAG_INTEGER);
1231       y_dim      = ippFindAttribute(media_size, "y-dimension", IPP_TAG_INTEGER);
1232 
1233       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1234 				     "media-bottom-margin", IPP_TAG_INTEGER))
1235 	  != NULL)
1236 	bottom_def = ippGetInteger(margin, 0);
1237       else
1238 	bottom_def = *bottom;
1239 
1240       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1241 				     "media-left-margin", IPP_TAG_INTEGER))
1242 	  != NULL)
1243 	left_def = ippGetInteger(margin, 0);
1244       else
1245 	left_def = *left;
1246 
1247       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1248 				     "media-right-margin", IPP_TAG_INTEGER))
1249 	  != NULL)
1250 	right_def = ippGetInteger(margin, 0);
1251       else
1252 	right_def = *right;
1253 
1254       if ((margin = ippFindAttribute(ippGetCollection(*defattr, 0),
1255 				     "media-top-margin", IPP_TAG_INTEGER))
1256 	  != NULL)
1257 	top_def = ippGetInteger(margin, 0);
1258       else
1259 	top_def = *top;
1260 
1261       if (x_dim && y_dim &&
1262 	  (pwg = pwgMediaForSize(ippGetInteger(x_dim, 0),
1263 				 ippGetInteger(y_dim, 0))) != NULL) {
1264 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1265         if (bottom_def == 0 && left_def == 0 && right_def == 0 && top_def == 0)
1266           snprintf(ppdname, PPD_MAX_NAME, "%s.Borderless", psname);
1267         else
1268           strlcpy(ppdname, psname, PPD_MAX_NAME);
1269       } else
1270 	strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1271     } else
1272       strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1273   } else if ((pwg =
1274 	      pwgMediaForPWG(ippGetString(ippFindAttribute(response,
1275 							   "media-default",
1276 							   IPP_TAG_ZERO), 0,
1277 					  NULL))) != NULL) {
1278     psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1279     strlcpy(ppdname, psname, PPD_MAX_NAME);
1280   } else
1281     strlcpy(ppdname, "Unknown", PPD_MAX_NAME);
1282 
1283   sizes = cupsArrayNew3((cups_array_func_t)pwg_compare_sizes, NULL, NULL, 0,
1284 			(cups_acopy_func_t)pwg_copy_size,
1285 			(cups_afree_func_t)free);
1286 
1287   if ((attr = ippFindAttribute(response, "media-col-database",
1288 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1289     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1290       cups_size_t temp;   /* Current size */
1291 
1292       media_col   = ippGetCollection(attr, i);
1293       media_size  =
1294 	ippGetCollection(ippFindAttribute(media_col, "media-size",
1295 					  IPP_TAG_BEGIN_COLLECTION), 0);
1296       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
1297       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
1298       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0),
1299 				    ippGetInteger(y_dim, 0));
1300 
1301       if (pwg) {
1302 	temp.width  = pwg->width;
1303 	temp.length = pwg->length;
1304 
1305 	if ((margin = ippFindAttribute(media_col, "media-bottom-margin",
1306 				       IPP_TAG_INTEGER)) != NULL)
1307 	  temp.bottom = ippGetInteger(margin, 0);
1308 	else
1309 	  temp.bottom = *bottom;
1310 
1311 	if ((margin = ippFindAttribute(media_col, "media-left-margin",
1312 				       IPP_TAG_INTEGER)) != NULL)
1313 	  temp.left = ippGetInteger(margin, 0);
1314 	else
1315 	  temp.left = *left;
1316 
1317 	if ((margin = ippFindAttribute(media_col, "media-right-margin",
1318 				       IPP_TAG_INTEGER)) != NULL)
1319 	  temp.right = ippGetInteger(margin, 0);
1320 	else
1321 	  temp.right = *right;
1322 
1323 	if ((margin = ippFindAttribute(media_col, "media-top-margin",
1324 				       IPP_TAG_INTEGER)) != NULL)
1325 	  temp.top = ippGetInteger(margin, 0);
1326 	else
1327 	  temp.top = *top;
1328 
1329 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1330 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1331 	    temp.top == 0)
1332 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1333 	else
1334 	  strlcpy(temp.media, psname, sizeof(temp.media));
1335 
1336 	if (!cupsArrayFind(sizes, &temp))
1337 	  cupsArrayAdd(sizes, &temp);
1338       } else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE ||
1339 		 ippGetValueTag(y_dim) == IPP_TAG_RANGE) {
1340 	/*
1341 	 * Custom size - record the min/max values...
1342 	 */
1343 
1344 	int lower, upper;   /* Range values */
1345 
1346 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
1347 	  lower = ippGetRange(x_dim, 0, &upper);
1348 	else
1349 	  lower = upper = ippGetInteger(x_dim, 0);
1350 
1351 	if (lower < *min_width)
1352 	  *min_width = lower;
1353 	if (upper > *max_width)
1354 	  *max_width = upper;
1355 
1356 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
1357 	  lower = ippGetRange(y_dim, 0, &upper);
1358 	else
1359 	  lower = upper = ippGetInteger(y_dim, 0);
1360 
1361 	if (lower < *min_length)
1362 	  *min_length = lower;
1363 	if (upper > *max_length)
1364 	  *max_length = upper;
1365       }
1366     }
1367   }
1368   if ((attr = ippFindAttribute(response, "media-size-supported",
1369 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
1370     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1371       cups_size_t temp;   /* Current size */
1372 
1373       media_size  = ippGetCollection(attr, i);
1374       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
1375       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
1376       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0),
1377 				    ippGetInteger(y_dim, 0));
1378 
1379       if (pwg) {
1380 	temp.width  = pwg->width;
1381 	temp.length = pwg->length;
1382 	temp.bottom = *bottom;
1383 	temp.left   = *left;
1384 	temp.right  = *right;
1385 	temp.top    = *top;
1386 
1387 	psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1388 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1389 	    temp.top == 0)
1390 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1391 	else
1392 	  strlcpy(temp.media, psname, sizeof(temp.media));
1393 
1394 	if (!cupsArrayFind(sizes, &temp))
1395 	  cupsArrayAdd(sizes, &temp);
1396       } else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE ||
1397 		 ippGetValueTag(y_dim) == IPP_TAG_RANGE) {
1398 	/*
1399 	 * Custom size - record the min/max values...
1400 	 */
1401 
1402 	int lower, upper;   /* Range values */
1403 
1404 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
1405 	  lower = ippGetRange(x_dim, 0, &upper);
1406 	else
1407 	  lower = upper = ippGetInteger(x_dim, 0);
1408 
1409 	if (lower < *min_width)
1410 	  *min_width = lower;
1411 	if (upper > *max_width)
1412 	  *max_width = upper;
1413 
1414 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
1415 	  lower = ippGetRange(y_dim, 0, &upper);
1416 	else
1417 	  lower = upper = ippGetInteger(y_dim, 0);
1418 
1419 	if (lower < *min_length)
1420 	  *min_length = lower;
1421 	if (upper > *max_length)
1422 	  *max_length = upper;
1423       }
1424     }
1425   }
1426   if ((attr = ippFindAttribute(response, "media-supported", IPP_TAG_ZERO))
1427       != NULL) {
1428     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1429       const char  *pwg_size = ippGetString(attr, i, NULL);
1430       /* PWG size name */
1431       cups_size_t temp, *temp2; /* Current size, found size */
1432 
1433       if ((pwg = pwgMediaForPWG(pwg_size)) != NULL) {
1434         if (strstr(pwg_size, "_max_") || strstr(pwg_size, "_max.")) {
1435           if (pwg->width > *max_width)
1436             *max_width = pwg->width;
1437           if (pwg->length > *max_length)
1438             *max_length = pwg->length;
1439         } else if (strstr(pwg_size, "_min_") || strstr(pwg_size, "_min.")) {
1440           if (pwg->width < *min_width)
1441             *min_width = pwg->width;
1442           if (pwg->length < *min_length)
1443             *min_length = pwg->length;
1444         } else {
1445 	  temp.width  = pwg->width;
1446 	  temp.length = pwg->length;
1447 	  temp.bottom = *bottom;
1448 	  temp.left   = *left;
1449 	  temp.right  = *right;
1450 	  temp.top    = *top;
1451 
1452 	  psname = (pwg->ppd != NULL ? pwg->ppd : pwg->pwg);
1453 	  if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 &&
1454 	      temp.top == 0)
1455 	    snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1456 	  else
1457 	    strlcpy(temp.media, psname, sizeof(temp.media));
1458 
1459 	  /* Add the printer's original IPP name to an already found size */
1460 	  if ((temp2 = cupsArrayFind(sizes, &temp)) != NULL) {
1461 	    snprintf(temp2->media + strlen(temp2->media),
1462 		     sizeof(temp2->media) - strlen(temp2->media),
1463 		     " %s", pwg_size);
1464 	    /* Check if we have also a borderless version of the size and add
1465 	       the original IPP name also there */
1466 	    snprintf(temp.media, sizeof(temp.media), "%s.Borderless", psname);
1467 	    if ((temp2 = cupsArrayFind(sizes, &temp)) != NULL)
1468 	      snprintf(temp2->media + strlen(temp2->media),
1469 		       sizeof(temp2->media) - strlen(temp2->media),
1470 		       " %s", pwg_size);
1471 	  } else
1472 	    cupsArrayAdd(sizes, &temp);
1473 	}
1474       }
1475     }
1476   }
1477   return sizes;
1478 }
1479 
is_colordevice(const char * keyword,ipp_attribute_t * attr)1480 int is_colordevice(const char *keyword,ipp_attribute_t *attr)
1481 {
1482   if (!strcasecmp(keyword, "sgray_16") || !strncmp(keyword, "W8-16", 5) ||
1483       !strncmp(keyword, "W16", 3))
1484     return 1;
1485   else if (!strcasecmp(keyword, "srgb_8") || !strncmp(keyword, "SRGB24", 6) ||
1486 	   !strcmp(keyword, "color"))
1487     return 1;
1488   else if ((!strcasecmp(keyword, "srgb_16") ||
1489 	    !strncmp(keyword, "SRGB48", 6)) &&
1490 	   !ippContainsString(attr, "srgb_8"))
1491     return 1;
1492   else if (!strcasecmp(keyword, "adobe-rgb_16") ||
1493 	   !strncmp(keyword, "ADOBERGB48", 10) ||
1494 	   !strncmp(keyword, "ADOBERGB24-48", 13))
1495     return 1;
1496   else if ((!strcasecmp(keyword, "adobe-rgb_8") ||
1497 	    !strcmp(keyword, "ADOBERGB24")) &&
1498 	   !ippContainsString(attr, "adobe-rgb_16"))
1499     return 1;
1500   else if ((!strcasecmp(keyword, "cmyk_8") &&
1501 	    !ippContainsString(attr, "cmyk_16")) ||
1502 	   !strcmp(keyword, "DEVCMYK32"))
1503     return 1;
1504   else if (!strcasecmp(keyword, "cmyk_16") ||
1505 	   !strcmp(keyword, "DEVCMYK32-64") ||
1506 	   !strcmp(keyword, "DEVCMYK64"))
1507     return 1;
1508   else if ((!strcasecmp(keyword, "rgb_8") &&
1509 	    !ippContainsString(attr, "rgb_16"))
1510 	   || !strcmp(keyword, "DEVRGB24"))
1511     return 1;
1512   else if (!strcasecmp(keyword, "rgb_16") ||
1513 	   !strcmp(keyword, "DEVRGB24-48") ||
1514 	   !strcmp(keyword, "DEVRGB48"))
1515     return 1;
1516   return 0;
1517 }
1518 
1519 /*
1520  * 'ppdCreateFromIPP()' - Create a PPD file describing the capabilities
1521  *                        of an IPP printer (legacy interface).
1522  */
1523 
1524 char *                                           /* O - PPD filename or NULL on
1525 						    error */
ppdCreateFromIPP(char * buffer,size_t bufsize,ipp_t * response,const char * make_model,const char * pdl,int color,int duplex)1526 ppdCreateFromIPP (char         *buffer,          /* I - Filename buffer */
1527 		  size_t       bufsize,          /* I - Size of filename
1528 						        buffer */
1529 		  ipp_t        *response,        /* I - Get-Printer-Attributes
1530 						        response */
1531 		  const char   *make_model,      /* I - Make and model from
1532 						        DNS-SD */
1533 		  const char   *pdl,             /* I - List of PDLs from
1534 						        DNS-SD */
1535 		  int          color,            /* I - Color printer? (from
1536 						        DNS-SD) */
1537 		  int          duplex)           /* I - Duplex printer? (from
1538 						        DNS-SD) */
1539 {
1540   return ppdCreateFromIPP2(buffer, bufsize, response, make_model, pdl,
1541 			   color, duplex, NULL, NULL, NULL, NULL);
1542 }
1543 
1544 /*
1545  * 'ppdCreateFromIPP2()' - Create a PPD file describing the capabilities
1546  *                         of an IPP printer.
1547  */
1548 
1549 char *                                           /* O - PPD filename or NULL on
1550 						    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)1551 ppdCreateFromIPP2(char         *buffer,          /* I - Filename buffer */
1552 		  size_t       bufsize,          /* I - Size of filename
1553 						        buffer */
1554 		  ipp_t        *response,        /* I - Get-Printer-Attributes
1555 						        response */
1556 		  const char   *make_model,      /* I - Make and model from
1557 						        DNS-SD */
1558 		  const char   *pdl,             /* I - List of PDLs from
1559 						        DNS-SD */
1560 		  int          color,            /* I - Color printer? (from
1561 						        DNS-SD) */
1562 		  int          duplex,           /* I - Duplex printer? (from
1563 						        DNS-SD) */
1564 		  cups_array_t *conflicts,       /* I - Array of constraints */
1565 		  cups_array_t *sizes,           /* I - Media sizes we've
1566 						        added */
1567 		  char*        default_pagesize, /* I - Default page size*/
1568 		  const char   *default_cluster_color) /* I - cluster def
1569 							color (if cluster's
1570 							attributes are
1571 							returned) */
1572 {
1573   cups_file_t		*fp;		/* PPD file */
1574   cups_array_t		*printer_sizes;	/* Media sizes we've added */
1575   cups_size_t		*size;		/* Current media size */
1576   ipp_attribute_t	*attr,		/* xxx-supported */
1577                         *attr2,
1578 			*defattr,	/* xxx-default */
1579                         *quality,	/* print-quality-supported */
1580 			*x_dim, *y_dim;	/* Media dimensions */
1581   ipp_t			*media_col,	/* Media collection */
1582 			*media_size;	/* Media size collection */
1583   char			make[256],	/* Make and model */
1584 			*model,		/* Model name */
1585 			ppdname[PPD_MAX_NAME];
1586 		    			/* PPD keyword */
1587   int			i, j,		/* Looping vars */
1588 			count = 0,	/* Number of values */
1589 			bottom,		/* Largest bottom margin */
1590 			left,		/* Largest left margin */
1591 			right,		/* Largest right margin */
1592 			top,		/* Largest top margin */
1593 			max_length = 0,	/* Maximum custom size */
1594 			max_width = 0,
1595 			min_length = INT_MAX,
1596 					/* Minimum custom size */
1597 			min_width = INT_MAX,
1598 			is_apple = 0,	/* Does the printer support Apple
1599 					   Raster? */
1600                         is_pwg = 0,	/* Does the printer support PWG
1601 					   Raster? */
1602                         is_pclm = 0,    /* Does the printer support PCLm? */
1603                         is_pdf = 0;     /* Does the printer support PDF? */
1604   pwg_media_t		*pwg;		/* PWG media size */
1605   int			xres, yres;	/* Resolution values */
1606   cups_array_t          *common_res,    /* Common resolutions of all PDLs */
1607                         *current_res,   /* Resolutions of current PDL */
1608                         *pdl_list;      /* List of PDLs */
1609   res_t                 *common_def,    /* Common default resolution */
1610                         *current_def,   /* Default resolution of current PDL */
1611                         *min_res,       /* Minimum common resolution */
1612                         *max_res;       /* Maximum common resolution */
1613   cups_lang_t		*lang = cupsLangDefault();
1614 					/* Localization info */
1615   struct lconv		*loc = localeconv();
1616 					/* Locale data */
1617   cups_array_t          *printer_opt_strings_catalog = NULL;
1618                                         /* Printer-specific option UI strings */
1619   char                  *human_readable,
1620                         *human_readable2;
1621   const char		*keyword;	/* Keyword value */
1622   cups_array_t		*fin_options = NULL;
1623 					/* Finishing options */
1624   char			buf[256],
1625                         filter_path[1024];
1626                                         /* Path to filter executable */
1627   const char		*cups_serverbin;/* CUPS_SERVERBIN environment
1628 					   variable */
1629   char			*defaultoutbin = NULL;
1630   const char		*outbin;
1631   char			outbin_properties[1024];
1632   int			octet_str_len;
1633   void			*outbin_properties_octet;
1634   int			outputorderinfofound = 0,
1635 			faceupdown = 1,
1636 			firsttolast = 1;
1637   int			manual_copies = -1,
1638 			is_fax = 0;
1639 
1640  /*
1641   * Range check input...
1642   */
1643 
1644   if (buffer)
1645     *buffer = '\0';
1646 
1647   if (!buffer || bufsize < 1) {
1648     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1649     return (NULL);
1650   }
1651 
1652   if (!response) {
1653     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No IPP attributes."), 1);
1654     return (NULL);
1655   }
1656 
1657  /*
1658   * Open a temporary file for the PPD...
1659   */
1660 
1661   if ((fp = cupsTempFile2(buffer, (int)bufsize)) == NULL) {
1662     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
1663     return (NULL);
1664   }
1665 
1666  /*
1667   * Standard stuff for PPD file...
1668   */
1669 
1670   cupsFilePuts(fp, "*PPD-Adobe: \"4.3\"\n");
1671   cupsFilePuts(fp, "*FormatVersion: \"4.3\"\n");
1672   cupsFilePrintf(fp, "*FileVersion: \"%s\"\n", VERSION);
1673   cupsFilePuts(fp, "*LanguageVersion: English\n");
1674   cupsFilePuts(fp, "*LanguageEncoding: ISOLatin1\n");
1675   cupsFilePuts(fp, "*PSVersion: \"(3010.000) 0\"\n");
1676   cupsFilePuts(fp, "*LanguageLevel: \"3\"\n");
1677   cupsFilePuts(fp, "*FileSystem: False\n");
1678   cupsFilePuts(fp, "*PCFileName: \"drvless.ppd\"\n");
1679 
1680   if ((attr = ippFindAttribute(response, "ipp-features-supported",
1681 			       IPP_TAG_KEYWORD)) != NULL &&
1682       ippContainsString(attr, "faxout"))
1683   {
1684     attr = ippFindAttribute(response, "printer-uri-supported",
1685 			    IPP_TAG_URI);
1686     if (attr)
1687     {
1688       ippAttributeString(attr, buf, sizeof(buf));
1689       if (strcasestr(buf, "faxout"))
1690 	is_fax = 1;
1691     }
1692   }
1693 
1694   if ((attr = ippFindAttribute(response, "printer-make-and-model",
1695 			       IPP_TAG_TEXT)) != NULL)
1696     strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make));
1697   else if (make_model && make_model[0] != '\0')
1698     strlcpy(make, make_model, sizeof(make));
1699   else
1700     strlcpy(make, "Unknown Printer", sizeof(make));
1701 
1702   if (!_cups_strncasecmp(make, "Hewlett Packard ", 16) ||
1703       !_cups_strncasecmp(make, "Hewlett-Packard ", 16)) {
1704     model = make + 16;
1705     strlcpy(make, "HP", sizeof(make));
1706   }
1707   else if ((model = strchr(make, ' ')) != NULL)
1708     *model++ = '\0';
1709   else
1710     model = make;
1711 
1712   cupsFilePrintf(fp, "*Manufacturer: \"%s\"\n", make);
1713   cupsFilePrintf(fp, "*ModelName: \"%s %s\"\n", make, model);
1714   cupsFilePrintf(fp, "*Product: \"(%s %s)\"\n", make, model);
1715   cupsFilePrintf(fp, "*NickName: \"%s %s, %sdriverless, cups-filters %s\"\n",
1716 		 make, model, (is_fax ? "Fax, " : ""), VERSION);
1717   cupsFilePrintf(fp, "*ShortNickName: \"%s %s\"\n", make, model);
1718 
1719   /* Which is the default output bin? */
1720   if ((attr = ippFindAttribute(response, "output-bin-default", IPP_TAG_ZERO))
1721       != NULL)
1722     defaultoutbin = strdup(ippGetString(attr, 0, NULL));
1723   /* Find out on which position of the list of output bins the default one is,
1724      if there is no default bin, take the first of this list */
1725   i = 0;
1726   if ((attr = ippFindAttribute(response, "output-bin-supported",
1727 			       IPP_TAG_ZERO)) != NULL) {
1728     count = ippGetCount(attr);
1729     for (i = 0; i < count; i ++) {
1730       outbin = ippGetString(attr, i, NULL);
1731       if (outbin == NULL)
1732 	continue;
1733       if (defaultoutbin == NULL) {
1734 	defaultoutbin = strdup(outbin);
1735 	break;
1736       } else if (strcasecmp(outbin, defaultoutbin) == 0)
1737 	break;
1738     }
1739   }
1740   if ((attr = ippFindAttribute(response, "printer-output-tray",
1741 			       IPP_TAG_STRING)) != NULL &&
1742       i < ippGetCount(attr)) {
1743     outbin_properties_octet = ippGetOctetString(attr, i, &octet_str_len);
1744     memset(outbin_properties, 0, sizeof(outbin_properties));
1745     memcpy(outbin_properties, outbin_properties_octet,
1746 	   ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
1747 	    (size_t)octet_str_len : sizeof(outbin_properties) - 1));
1748     if (strcasestr(outbin_properties, "pagedelivery=faceUp")) {
1749       outputorderinfofound = 1;
1750       faceupdown = -1;
1751     }
1752     if (strcasestr(outbin_properties, "stackingorder=lastToFirst"))
1753       firsttolast = -1;
1754   }
1755   if (outputorderinfofound == 0 && defaultoutbin &&
1756       strcasestr(defaultoutbin, "face-up"))
1757     faceupdown = -1;
1758   if (defaultoutbin)
1759     free (defaultoutbin);
1760   if (firsttolast * faceupdown < 0)
1761     cupsFilePuts(fp, "*DefaultOutputOrder: Reverse\n");
1762   else
1763     cupsFilePuts(fp, "*DefaultOutputOrder: Normal\n");
1764 
1765   /* To decide whether the printer is coloured or not we see the various
1766      colormodel supported by the printer*/
1767   if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD))
1768       == NULL)
1769     if ((attr = ippFindAttribute(response,
1770 				 "pwg-raster-document-type-supported",
1771 				 IPP_TAG_KEYWORD)) == NULL)
1772       if ((attr = ippFindAttribute(response, "print-color-mode-supported",
1773 				   IPP_TAG_KEYWORD)) == NULL)
1774         attr = ippFindAttribute(response, "output-mode-supported",
1775 				IPP_TAG_KEYWORD);
1776   if (attr==NULL || !ippGetCount(attr)) {
1777     if ((attr = ippFindAttribute(response, "color-supported", IPP_TAG_BOOLEAN))
1778 	!= NULL) {
1779       if (ippGetBoolean(attr, 0))
1780 	cupsFilePuts(fp, "*ColorDevice: True\n");
1781       else
1782 	cupsFilePuts(fp, "*ColorDevice: False\n");
1783     } else {
1784       if(color)
1785 	cupsFilePuts(fp, "*ColorDevice: True\n");
1786       else
1787 	cupsFilePuts(fp, "*ColorDevice: False\n");
1788     }
1789   } else {
1790     int   colordevice = 0;
1791     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1792       keyword = ippGetString(attr, i, NULL);
1793       colordevice = is_colordevice(keyword,attr);
1794       if (colordevice) {
1795 	cupsFilePuts(fp, "*ColorDevice: True\n");
1796 	break;
1797       }
1798     }
1799     if (colordevice == 0)
1800       cupsFilePuts(fp, "*ColorDevice: False\n");
1801   }
1802 
1803   cupsFilePrintf(fp, "*cupsVersion: %d.%d\n", CUPS_VERSION_MAJOR,
1804 		 CUPS_VERSION_MINOR);
1805   cupsFilePuts(fp, "*cupsSNMPSupplies: False\n");
1806   cupsFilePuts(fp, "*cupsLanguages: \"en\"\n");
1807 
1808   if ((attr = ippFindAttribute(response, "printer-more-info", IPP_TAG_URI)) !=
1809       NULL)
1810     cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL));
1811 
1812   if ((attr = ippFindAttribute(response, "printer-charge-info-uri",
1813 			       IPP_TAG_URI)) != NULL)
1814     cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0,
1815 								    NULL));
1816 
1817   /* Message catalogs for UI strings */
1818   if (opt_strings_catalog == NULL) {
1819     opt_strings_catalog = optArrayNew();
1820     load_opt_strings_catalog(NULL, opt_strings_catalog);
1821   }
1822   if ((attr = ippFindAttribute(response, "printer-strings-uri",
1823 			       IPP_TAG_URI)) != NULL) {
1824     printer_opt_strings_catalog = optArrayNew();
1825     load_opt_strings_catalog(ippGetString(attr, 0, NULL),
1826 			     printer_opt_strings_catalog);
1827     if (printer_opt_strings_catalog)
1828       cupsFilePrintf(fp, "*cupsStringsURI: \"%s\"\n", ippGetString(attr, 0,
1829 								   NULL));
1830   }
1831 
1832  /*
1833   * Accounting...
1834   */
1835 
1836   if (ippGetBoolean(ippFindAttribute(response, "job-account-id-supported",
1837 				     IPP_TAG_BOOLEAN), 0))
1838     cupsFilePuts(fp, "*cupsJobAccountId: True\n");
1839 
1840   if (ippGetBoolean(ippFindAttribute(response,
1841 				     "job-accounting-user-id-supported",
1842 				     IPP_TAG_BOOLEAN), 0))
1843     cupsFilePuts(fp, "*cupsJobAccountingUserId: True\n");
1844 
1845  /*
1846   * Password/PIN printing...
1847   */
1848 
1849   if ((attr = ippFindAttribute(response, "job-password-supported",
1850 			       IPP_TAG_INTEGER)) != NULL) {
1851     char	pattern[33];		/* Password pattern */
1852     int		maxlen = ippGetInteger(attr, 0);
1853 					/* Maximum length */
1854     const char	*repertoire =
1855       ippGetString(ippFindAttribute(response,
1856 				    "job-password-repertoire-configured",
1857 				    IPP_TAG_KEYWORD), 0, NULL);
1858 					/* Type of password */
1859 
1860     if (maxlen > (int)(sizeof(pattern) - 1))
1861       maxlen = (int)sizeof(pattern) - 1;
1862 
1863     if (!repertoire || !strcmp(repertoire, "iana_us-ascii_digits"))
1864       memset(pattern, '1', (size_t)maxlen);
1865     else if (!strcmp(repertoire, "iana_us-ascii_letters"))
1866       memset(pattern, 'A', (size_t)maxlen);
1867     else if (!strcmp(repertoire, "iana_us-ascii_complex"))
1868       memset(pattern, 'C', (size_t)maxlen);
1869     else if (!strcmp(repertoire, "iana_us-ascii_any"))
1870       memset(pattern, '.', (size_t)maxlen);
1871     else if (!strcmp(repertoire, "iana_utf-8_digits"))
1872       memset(pattern, 'N', (size_t)maxlen);
1873     else if (!strcmp(repertoire, "iana_utf-8_letters"))
1874       memset(pattern, 'U', (size_t)maxlen);
1875     else
1876       memset(pattern, '*', (size_t)maxlen);
1877 
1878     pattern[maxlen] = '\0';
1879 
1880     cupsFilePrintf(fp, "*cupsJobPassword: \"%s\"\n", pattern);
1881   }
1882 
1883 
1884 
1885  /*
1886   * PDLs and common resolutions ...
1887   */
1888 
1889   common_res = NULL;
1890   current_res = NULL;
1891   common_def = NULL;
1892   current_def = NULL;
1893   min_res = NULL;
1894   max_res = NULL;
1895   /* Put all available PDls into a simple case-insensitevely searchable
1896      sorted string list */
1897   if ((pdl_list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0,
1898 				(cups_acopy_func_t)strdup,
1899 				(cups_afree_func_t)free)) == NULL)
1900     goto bad_ppd;
1901   int formatfound = 0;
1902 
1903   if (((attr = ippFindAttribute(response, "document-format-supported",
1904 				IPP_TAG_MIMETYPE)) != NULL) ||
1905       (pdl && pdl[0] != '\0')) {
1906     const char *format = pdl;
1907     i = 0;
1908     count = ippGetCount(attr);
1909     while ((attr && i < count) || /* Go through formats in attribute */
1910 	   (!attr && pdl && pdl[0] != '\0' && format[0] != '\0')) {
1911       /* Go through formats in pdl string (from DNS-SD record) */
1912 
1913       /* Pick next format from attribute */
1914       if (attr) format = ippGetString(attr, i, NULL);
1915       /* Add format to list of supported PDLs, skip duplicates */
1916       if (!cupsArrayFind(pdl_list, (void *)format))
1917 	cupsArrayAdd(pdl_list, (void *)format);
1918       if (attr)
1919 	/* Next format in attribute */
1920 	i ++;
1921       else {
1922 	/* Find the next format in the string pdl, if there is none left,
1923 	   go to the terminating zero */
1924 	while (!isspace(*format) && *format != ',' && *format != '\0')
1925 	  format ++;
1926 	while ((isspace(*format) || *format == ',') && *format != '\0')
1927 	  format ++;
1928       }
1929     }
1930   }
1931 
1932   /*
1933    * Fax
1934    */
1935 
1936   if (is_fax)
1937     cupsFilePuts(fp, "*cupsIPPFaxOut: True\n");
1938 
1939   /* Check for each CUPS/cups-filters-supported PDL, starting with the
1940      most desirable going to the least desirable. If a PDL requires a
1941      certain set of resolutions (the raster-based PDLs), find the
1942      resolutions and find out which are the common resolutions of all
1943      supported PDLs. Choose the default resolution from the most
1944      desirable of all resolution-requiring PDLs if it is common in all
1945      of them. Skip a resolution-requiring PDL if its resolution list
1946      attribute is missing or contains only broken entries. Use the
1947      general resolution list and default resolution of the printer
1948      only if it does not support any resolution-requiring PDL. Use 300
1949      dpi if there is no resolution info at all in the attributes.
1950      In case of PDF as PDL check whether also the
1951      "application/vnd.cups-pdf" MIME type is accepted. In this case
1952      our printer is a remote CUPS queue which already runs the
1953      pdftopdf filter on the server, so let the PPD take
1954      "application/pdf" as input format so that pdftopdf does not also
1955      get executed on the client, applying option settings twice. See
1956      https://github.com/apple/cups/issues/5361 */
1957   if (cupsArrayFind(pdl_list, "application/vnd.cups-pdf")) {
1958     cupsFilePuts(fp, "*cupsFilter2: \"application/pdf application/pdf 0 -\"\n");
1959     manual_copies = 0;
1960     formatfound = 1;
1961     is_pdf = 1;
1962   } else if (cupsArrayFind(pdl_list, "application/pdf")) {
1963     cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 200 -\"\n");
1964     manual_copies = 0;
1965     formatfound = 1;
1966     is_pdf = 1;
1967   }
1968 #ifdef CUPS_RASTER_HAVE_APPLERASTER
1969   if (cupsArrayFind(pdl_list, "image/urf")) {
1970     if ((attr = ippFindAttribute(response, "urf-supported",
1971 				 IPP_TAG_KEYWORD)) != NULL) {
1972       int lowdpi = 0, hidpi = 0; /* Lower and higher resolution */
1973       for (i = 0, count = ippGetCount(attr); i < count; i ++) {
1974 	const char *rs = ippGetString(attr, i, NULL); /* RS value */
1975 	if (_cups_strncasecmp(rs, "RS", 2))
1976 	  continue;
1977 	lowdpi = atoi(rs + 2);
1978 	if ((rs = strrchr(rs, '-')) != NULL)
1979 	  hidpi = atoi(rs + 1);
1980 	else
1981 	  hidpi = lowdpi;
1982 	break;
1983       }
1984       if (lowdpi == 0) {
1985 	/* Invalid "urf-supported" value... */
1986 	goto bad_ppd;
1987       } else {
1988 	if ((current_res = resolutionArrayNew()) != NULL) {
1989 	  if ((current_def = resolutionNew(lowdpi, lowdpi)) != NULL)
1990           {
1991 	    cupsArrayAdd(current_res, current_def);
1992             free_resolution(current_def, NULL);
1993           }
1994 	  if (hidpi != lowdpi &&
1995 	      (current_def = resolutionNew(hidpi, hidpi)) != NULL)
1996           {
1997 	    cupsArrayAdd(current_res, current_def);
1998             free_resolution(current_def, NULL);
1999           }
2000 	  current_def = NULL;
2001 	  if (cupsArrayCount(current_res) > 0 &&
2002 	      joinResolutionArrays(&common_res, &current_res, &common_def,
2003 				   &current_def)) {
2004 	    cupsFilePuts(fp, "*cupsFilter2: \"image/urf image/urf 0 -\"\n");
2005 	    if (formatfound == 0) manual_copies = 1;
2006 	    formatfound = 1;
2007 	    is_apple = 1;
2008 	  }
2009 	}
2010       }
2011     }
2012   }
2013 #endif
2014   if (is_apple == 0 && cupsArrayFind(pdl_list, "image/pwg-raster")) {
2015     if ((attr = ippFindAttribute(response,
2016 				 "pwg-raster-document-resolution-supported",
2017 				 IPP_TAG_RESOLUTION)) != NULL) {
2018       current_def = NULL;
2019       if ((current_res = ippResolutionListToArray(attr)) != NULL &&
2020 	  joinResolutionArrays(&common_res, &current_res, &common_def,
2021 			       &current_def)) {
2022 	cupsFilePuts(fp, "*cupsFilter2: \"image/pwg-raster image/pwg-raster 10 -\"\n");
2023 	if (formatfound == 0) manual_copies = 1;
2024 	formatfound = 1;
2025 	is_pwg = 1;
2026       }
2027     }
2028   }
2029 #ifdef QPDF_HAVE_PCLM
2030   if (cupsArrayFind(pdl_list, "application/PCLm")) {
2031     if ((attr = ippFindAttribute(response, "pclm-source-resolution-supported",
2032 				 IPP_TAG_RESOLUTION)) != NULL) {
2033       if ((defattr = ippFindAttribute(response,
2034 				      "pclm-source-resolution-default",
2035 				      IPP_TAG_RESOLUTION)) != NULL)
2036 	current_def = ippResolutionToRes(defattr, 0);
2037       else
2038 	current_def = NULL;
2039       if ((current_res = ippResolutionListToArray(attr)) != NULL &&
2040 	  joinResolutionArrays(&common_res, &current_res, &common_def,
2041 			       &current_def)) {
2042 	cupsFilePuts(fp, "*cupsFilter2: \"application/PCLm application/PCLm 300 -\"\n");
2043 	if (formatfound == 0) manual_copies = 1;
2044 	formatfound = 1;
2045 	is_pclm = 1;
2046       }
2047     }
2048   }
2049 #endif
2050   if (cupsArrayFind(pdl_list, "application/vnd.hp-pclxl")) {
2051     /* Check whether the gstopxl filter is installed,
2052        otherwise ignore the PCL-XL support of the printer */
2053     if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
2054       cups_serverbin = CUPS_SERVERBIN;
2055     snprintf(filter_path, sizeof(filter_path), "%s/filter/gstopxl",
2056 	     cups_serverbin);
2057     if (access(filter_path, X_OK) == 0) {
2058       /* We put a high cost factor here as if a printer supports also
2059 	 another format, like PWG or Apple Raster, we prefer it, as some
2060 	 PCL-XL printers have bugs in their PCL-XL interpreters */
2061       cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/vnd.hp-pclxl 400 gstopxl\"\n");
2062       if (formatfound == 0) manual_copies = 1;
2063       formatfound = 1;
2064     }
2065   }
2066   if (cupsArrayFind(pdl_list, "application/postscript")) {
2067     /* We put a high cost factor here as if a printer supports also
2068        another format, like PWG or Apple Raster, we prefer it, as many
2069        PostScript printers have bugs in their PostScript interpreters */
2070     cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-postscript application/postscript 600 -\"\n");
2071     if (formatfound == 0) manual_copies = 0;
2072     formatfound = 1;
2073   }
2074   if (cupsArrayFind(pdl_list, "application/vnd.hp-pcl")) {
2075     /* We put a high cost factor here as if a printer supports also
2076        another format, like PWG or Apple Raster, we prefer it, as there
2077        are some printers, like HP inkjets which report to accept PCL
2078        but do not support PCL 5c/e or PCL-XL */
2079     cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-raster application/vnd.hp-pcl 800 rastertopclx\"\n");
2080     if (formatfound == 0) manual_copies = 1;
2081     formatfound = 1;
2082   }
2083   if (cupsArrayFind(pdl_list, "image/jpeg"))
2084     cupsFilePuts(fp, "*cupsFilter2: \"image/jpeg image/jpeg 0 -\"\n");
2085   if (cupsArrayFind(pdl_list, "image/png"))
2086     cupsFilePuts(fp, "*cupsFilter2: \"image/png image/png 0 -\"\n");
2087   cupsArrayDelete(pdl_list);
2088   if (manual_copies < 0) manual_copies = 1;
2089   if (formatfound == 0)
2090     goto bad_ppd;
2091 
2092   /* For the case that we will print in a raster format and not in a high-level
2093      format, we need to create multiple copies on the client. We add a line to
2094      the PPD which tells the pdftopdf filter to generate the copies */
2095   if (manual_copies == 1)
2096     cupsFilePuts(fp, "*cupsManualCopies: True\n");
2097 
2098   /* No resolution requirements by any of the supported PDLs?
2099      Use "printer-resolution-supported" attribute */
2100   if (common_res == NULL) {
2101     if ((attr = ippFindAttribute(response, "printer-resolution-supported",
2102 				 IPP_TAG_RESOLUTION)) != NULL) {
2103       if ((defattr = ippFindAttribute(response, "printer-resolution-default",
2104 				      IPP_TAG_RESOLUTION)) != NULL)
2105 	current_def = ippResolutionToRes(defattr, 0);
2106       else
2107 	current_def = NULL;
2108       if ((current_res = ippResolutionListToArray(attr)) != NULL)
2109 	joinResolutionArrays(&common_res, &current_res, &common_def,
2110 			     &current_def);
2111     }
2112   }
2113   /* Still no resolution found? Default to 300 dpi */
2114   if (common_res == NULL) {
2115     if ((common_res = resolutionArrayNew()) != NULL) {
2116       if ((current_def = resolutionNew(300, 300)) != NULL)
2117       {
2118 	cupsArrayAdd(common_res, current_def);
2119         free_resolution(current_def, NULL);
2120       }
2121       current_def = NULL;
2122     } else
2123       goto bad_ppd;
2124   }
2125   /* No default resolution determined yet */
2126   if (common_def == NULL) {
2127     if ((defattr = ippFindAttribute(response, "printer-resolution-default",
2128 				    IPP_TAG_RESOLUTION)) != NULL) {
2129       common_def = ippResolutionToRes(defattr, 0);
2130       if (!cupsArrayFind(common_res, common_def)) {
2131 	free(common_def);
2132 	common_def = NULL;
2133       }
2134     }
2135     if (common_def == NULL) {
2136       count = cupsArrayCount(common_res);
2137       common_def = copy_resolution(cupsArrayIndex(common_res, count / 2), NULL);
2138     }
2139   }
2140   /* Get minimum and maximum resolution */
2141   min_res = copy_resolution(cupsArrayFirst(common_res), NULL);
2142   max_res = copy_resolution(cupsArrayLast(common_res), NULL);
2143   cupsArrayDelete(common_res);
2144 
2145 #ifdef QPDF_HAVE_PCLM
2146  /*
2147   * Generically check for PCLm attributes in IPP response
2148   * and ppdize them one by one
2149   */
2150 
2151   if (is_pclm) {
2152     attr = ippFirstAttribute(response); /* first attribute */
2153     while (attr) {                      /* loop through all the attributes */
2154       if (_cups_strncasecmp(ippGetName(attr), "pclm", 4) == 0) {
2155 	pwg_ppdize_name(ippGetName(attr), ppdname, sizeof(ppdname));
2156 	cupsFilePrintf(fp, "*cups%s: ", ppdname);
2157 	ipp_tag_t tag = ippGetValueTag(attr);
2158 	count = ippGetCount(attr);
2159 
2160 	if (tag == IPP_TAG_RESOLUTION) { /* ppdize values of type resolution */
2161 	  if ((current_res = ippResolutionListToArray(attr)) != NULL) {
2162 	    count = cupsArrayCount(current_res);
2163 	    if (count > 1)
2164 	      cupsFilePuts(fp, "\"");
2165 	    for (i = 0, current_def = cupsArrayFirst(current_res);
2166 		 current_def;
2167 		 i ++, current_def = cupsArrayNext(current_res)) {
2168 	      int x = current_def->x;
2169 	      int y = current_def->y;
2170 	      if (x == y)
2171 		cupsFilePrintf(fp, "%ddpi", x);
2172 	      else
2173 		cupsFilePrintf(fp, "%dx%ddpi", x, y);
2174 	      if (i < count - 1)
2175 		cupsFilePuts(fp, ",");
2176 	    }
2177 	    if (count > 1)
2178 	      cupsFilePuts(fp, "\"");
2179 	    cupsFilePuts(fp, "\n");
2180 	  } else
2181 	    cupsFilePuts(fp, "\"\"\n");
2182 	  cupsArrayDelete(current_res);
2183 	} else {
2184 	  ippAttributeString(attr, ppdname, sizeof(ppdname));
2185 	  if (count > 1 || /* quotes around multi-valued and string
2186 			      attributes */
2187 	      tag == IPP_TAG_STRING ||
2188 	      tag == IPP_TAG_TEXT ||
2189 	      tag == IPP_TAG_TEXTLANG)
2190 	    cupsFilePrintf(fp, "\"%s\"\n", ppdname);
2191 	  else
2192 	    cupsFilePrintf(fp, "%s\n", ppdname);
2193 	}
2194       }
2195       attr = ippNextAttribute(response);
2196     }
2197   }
2198 #endif
2199 
2200  /*
2201   * PageSize/PageRegion/ImageableArea/PaperDimension
2202   */
2203   printer_sizes = generate_sizes(response, &defattr, &min_length, &min_width,
2204 				 &max_length, &max_width,
2205 				 &bottom, &left, &right, &top, ppdname);
2206   if (sizes==NULL) {
2207     sizes = printer_sizes;
2208   } else
2209     strcpy(ppdname, default_pagesize);
2210 
2211   if (cupsArrayCount(sizes) > 0) {
2212    /*
2213     * List all of the standard sizes...
2214     */
2215 
2216     char	tleft[256],		/* Left string */
2217 		tbottom[256],		/* Bottom string */
2218 		tright[256],		/* Right string */
2219 		ttop[256],		/* Top string */
2220 		twidth[256],		/* Width string */
2221 		tlength[256],		/* Length string */
2222 		ppdsizename[128];
2223     char        *ippsizename,
2224                 *suffix;
2225     int         all_borderless = 1;
2226 
2227     /* Find a page size without ".Borderless" suffix */
2228     /* (if all are ".Borderless" we drop the suffix in the PPD) */
2229     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2230 	 size = (cups_size_t *)cupsArrayNext(sizes))
2231       if (strcasestr(size->media, ".Borderless") == NULL)
2232 	break;
2233     if (size)
2234       all_borderless = 0;
2235 
2236     if (all_borderless) {
2237       suffix = strcasestr(ppdname, ".Borderless");
2238       if (suffix)
2239 	*suffix = '\0';
2240     }
2241 
2242     cupsFilePrintf(fp, "*OpenUI *PageSize/%s: PickOne\n"
2243 		   "*OrderDependency: 10 AnySetup *PageSize\n"
2244 		   "*DefaultPageSize: %s\n", "Media Size", ppdname);
2245     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2246 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2247       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2248 		      size->width * 72.0 / 2540.0, loc);
2249       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2250 		      size->length * 72.0 / 2540.0, loc);
2251       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2252       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL) {
2253 	*ippsizename = '\0';
2254 	ippsizename ++;
2255       }
2256 
2257       if (ippsizename)
2258 	human_readable = lookup_choice(ippsizename, "media",
2259 				       opt_strings_catalog,
2260 				       printer_opt_strings_catalog);
2261       else
2262 	human_readable = NULL;
2263       if (!human_readable) {
2264 	pwg = pwgMediaForSize(size->width, size->length);
2265 	if (pwg)
2266 	  human_readable = lookup_choice((char *)pwg->pwg, "media",
2267 					 opt_strings_catalog,
2268 					 printer_opt_strings_catalog);
2269       }
2270 
2271       if (all_borderless) {
2272 	suffix = strcasestr(ppdsizename, ".Borderless");
2273 	if (suffix)
2274 	  *suffix = '\0';
2275       }
2276 
2277       cupsFilePrintf(fp, "*PageSize %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
2278 		     ppdsizename,
2279 		     (human_readable ? "/" : ""),
2280 		     (human_readable ? human_readable : ""),
2281 		     (human_readable && strstr(ppdsizename, ".Borderless") ?
2282 		      " (Borderless)" : ""),
2283 		     twidth, tlength);
2284     }
2285     cupsFilePuts(fp, "*CloseUI: *PageSize\n");
2286 
2287     cupsFilePrintf(fp, "*OpenUI *PageRegion/%s: PickOne\n"
2288 		   "*OrderDependency: 10 AnySetup *PageRegion\n"
2289 		   "*DefaultPageRegion: %s\n", "Media Size", ppdname);
2290     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2291 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2292       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2293 		      size->width * 72.0 / 2540.0, loc);
2294       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2295 		      size->length * 72.0 / 2540.0, loc);
2296       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2297       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL) {
2298 	*ippsizename = '\0';
2299 	ippsizename ++;
2300       }
2301 
2302       if (ippsizename)
2303 	human_readable = lookup_choice(ippsizename, "media",
2304 				       opt_strings_catalog,
2305 				       printer_opt_strings_catalog);
2306       else
2307 	human_readable = NULL;
2308       if (!human_readable) {
2309 	pwg = pwgMediaForSize(size->width, size->length);
2310 	if (pwg)
2311 	  human_readable = lookup_choice((char *)pwg->pwg, "media",
2312 					 opt_strings_catalog,
2313 					 printer_opt_strings_catalog);
2314       }
2315 
2316       if (all_borderless) {
2317 	suffix = strcasestr(ppdsizename, ".Borderless");
2318 	if (suffix)
2319 	  *suffix = '\0';
2320       }
2321 
2322       cupsFilePrintf(fp, "*PageRegion %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
2323 		     ppdsizename,
2324 		     (human_readable ? "/" : ""),
2325 		     (human_readable ? human_readable : ""),
2326 		     (human_readable && strstr(ppdsizename, ".Borderless") ?
2327 		      " (Borderless)" : ""),
2328 		     twidth, tlength);
2329     }
2330     cupsFilePuts(fp, "*CloseUI: *PageRegion\n");
2331 
2332     cupsFilePrintf(fp, "*DefaultImageableArea: %s\n"
2333 		   "*DefaultPaperDimension: %s\n", ppdname, ppdname);
2334 
2335     for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
2336 	 size = (cups_size_t *)cupsArrayNext(sizes)) {
2337       _cupsStrFormatd(tleft, tleft + sizeof(tleft),
2338 		      size->left * 72.0 / 2540.0, loc);
2339       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom),
2340 		      size->bottom * 72.0 / 2540.0, loc);
2341       _cupsStrFormatd(tright, tright + sizeof(tright),
2342 		      (size->width - size->right) * 72.0 / 2540.0, loc);
2343       _cupsStrFormatd(ttop, ttop + sizeof(ttop),
2344 		      (size->length - size->top) * 72.0 / 2540.0, loc);
2345       _cupsStrFormatd(twidth, twidth + sizeof(twidth),
2346 		      size->width * 72.0 / 2540.0, loc);
2347       _cupsStrFormatd(tlength, tlength + sizeof(tlength),
2348 		      size->length * 72.0 / 2540.0, loc);
2349       strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
2350       if ((ippsizename = strchr(ppdsizename, ' ')) != NULL)
2351 	*ippsizename = '\0';
2352 
2353       if (all_borderless) {
2354 	suffix = strcasestr(ppdsizename, ".Borderless");
2355 	if (suffix)
2356 	  *suffix = '\0';
2357       }
2358 
2359       cupsFilePrintf(fp, "*ImageableArea %s: \"%s %s %s %s\"\n", ppdsizename,
2360 		     tleft, tbottom, tright, ttop);
2361       cupsFilePrintf(fp, "*PaperDimension %s: \"%s %s\"\n", ppdsizename,
2362 		     twidth, tlength);
2363     }
2364 
2365    /*
2366     * Custom size support...
2367     */
2368 
2369     if (max_width > 0 && min_width < INT_MAX && max_length > 0 &&
2370 	min_length < INT_MAX) {
2371       char	tmax[256], tmin[256];	/* Min/max values */
2372 
2373       _cupsStrFormatd(tleft, tleft + sizeof(tleft), left * 72.0 / 2540.0, loc);
2374       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom),
2375 		      bottom * 72.0 / 2540.0, loc);
2376       _cupsStrFormatd(tright, tright + sizeof(tright), right * 72.0 / 2540.0,
2377 		      loc);
2378       _cupsStrFormatd(ttop, ttop + sizeof(ttop), top * 72.0 / 2540.0, loc);
2379 
2380       cupsFilePrintf(fp, "*HWMargins: \"%s %s %s %s\"\n", tleft, tbottom,
2381 		     tright, ttop);
2382 
2383       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_width * 72.0 / 2540.0,
2384 		      loc);
2385       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_width * 72.0 / 2540.0,
2386 		      loc);
2387       cupsFilePrintf(fp, "*ParamCustomPageSize Width: 1 points %s %s\n", tmin,
2388 		     tmax);
2389 
2390       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_length * 72.0 / 2540.0,
2391 		      loc);
2392       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_length * 72.0 / 2540.0,
2393 		      loc);
2394       cupsFilePrintf(fp, "*ParamCustomPageSize Height: 2 points %s %s\n", tmin,
2395 		     tmax);
2396 
2397       cupsFilePuts(fp, "*ParamCustomPageSize WidthOffset: 3 points 0 0\n");
2398       cupsFilePuts(fp, "*ParamCustomPageSize HeightOffset: 4 points 0 0\n");
2399       cupsFilePuts(fp, "*ParamCustomPageSize Orientation: 5 int 0 3\n");
2400       cupsFilePuts(fp, "*CustomPageSize True: \"pop pop pop <</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice\"\n");
2401     }
2402   } else {
2403     cupsFilePrintf(fp,
2404 		   "*%% Printer did not supply page size info via IPP, using defaults\n"
2405 		   "*OpenUI *PageSize/Media Size: PickOne\n"
2406 		   "*OrderDependency: 10 AnySetup *PageSize\n"
2407 		   "*DefaultPageSize: Letter\n"
2408 		   "*PageSize Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
2409 		   "*PageSize Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
2410 		   "*PageSize Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
2411 		   "*PageSize Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
2412 		   "*PageSize A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
2413 		   "*PageSize A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
2414 		   "*PageSize A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
2415 		   "*PageSize B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
2416 		   "*PageSize EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
2417 		   "*PageSize Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
2418 		   "*PageSize EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
2419 		   "*PageSize EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
2420 		   "*PageSize EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
2421 		   "*CloseUI: *PageSize\n"
2422 		   "*OpenUI *PageRegion/Media Size: PickOne\n"
2423 		   "*OrderDependency: 10 AnySetup *PageRegion\n"
2424 		   "*DefaultPageRegion: Letter\n"
2425 		   "*PageRegion Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
2426 		   "*PageRegion Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
2427 		   "*PageRegion Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
2428 		   "*PageRegion Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
2429 		   "*PageRegion A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
2430 		   "*PageRegion A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
2431 		   "*PageRegion A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
2432 		   "*PageRegion B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
2433 		   "*PageRegion EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
2434 		   "*PageRegion Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
2435 		   "*PageRegion EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
2436 		   "*PageRegion EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
2437 		   "*PageRegion EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
2438 		   "*CloseUI: *PageSize\n"
2439 		   "*DefaultImageableArea: Letter\n"
2440 		   "*ImageableArea Letter/US Letter: \"18 12 594 780\"\n"
2441 		   "*ImageableArea Legal/US Legal: \"18 12 594 996\"\n"
2442 		   "*ImageableArea Executive/Executive: \"18 12 504 744\"\n"
2443 		   "*ImageableArea Tabloid/Tabloid: \"18 12 774 1212\"\n"
2444 		   "*ImageableArea A3/A3: \"18 12 824 1179\"\n"
2445 		   "*ImageableArea A4/A4: \"18 12 577 830\"\n"
2446 		   "*ImageableArea A5/A5: \"18 12 402 583\"\n"
2447 		   "*ImageableArea B5/JIS B5: \"18 12 498 717\"\n"
2448 		   "*ImageableArea EnvISOB5/Envelope B5: \"18 12 481 697\"\n"
2449 		   "*ImageableArea Env10/Envelope #10 : \"18 12 279 672\"\n"
2450 		   "*ImageableArea EnvC5/Envelope C5: \"18 12 441 637\"\n"
2451 		   "*ImageableArea EnvDL/Envelope DL: \"18 12 294 612\"\n"
2452 		   "*ImageableArea EnvMonarch/Envelope Monarch: \"18 12 261 528\"\n"
2453 		   "*DefaultPaperDimension: Letter\n"
2454 		   "*PaperDimension Letter/US Letter: \"612 792\"\n"
2455 		   "*PaperDimension Legal/US Legal: \"612 1008\"\n"
2456 		   "*PaperDimension Executive/Executive: \"522 756\"\n"
2457 		   "*PaperDimension Tabloid/Tabloid: \"792 1224\"\n"
2458 		   "*PaperDimension A3/A3: \"842 1191\"\n"
2459 		   "*PaperDimension A4/A4: \"595 842\"\n"
2460 		   "*PaperDimension A5/A5: \"420 595\"\n"
2461 		   "*PaperDimension B5/JIS B5: \"516 729\"\n"
2462 		   "*PaperDimension EnvISOB5/Envelope B5: \"499 709\"\n"
2463 		   "*PaperDimension Env10/Envelope #10 : \"297 684\"\n"
2464 		   "*PaperDimension EnvC5/Envelope C5: \"459 649\"\n"
2465 		   "*PaperDimension EnvDL/Envelope DL: \"312 624\"\n"
2466 		   "*PaperDimension EnvMonarch/Envelope Monarch: \"279 540\"\n");
2467   }
2468 
2469   cupsArrayDelete(printer_sizes);
2470 
2471  /*
2472   * InputSlot...
2473   */
2474 
2475   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-source",
2476 			       IPP_TAG_KEYWORD)) != NULL)
2477     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
2478   else
2479     ppdname[0] = '\0';
2480 
2481   if ((attr = ippFindAttribute(response, "media-source-supported",
2482 			       IPP_TAG_KEYWORD)) != NULL &&
2483       (count = ippGetCount(attr)) > 1) {
2484     int have_default = ppdname[0] != '\0';
2485 					/* Do we have a default InputSlot? */
2486     static const char * const sources[][2] =
2487     {					/* "media-source" strings */
2488       { "Auto", _("Automatic") },
2489       { "Main", _("Main") },
2490       { "Alternate", _("Alternate") },
2491       { "LargeCapacity", _("Large Capacity") },
2492       { "Manual", _("Manual") },
2493       { "Envelope", _("Envelope") },
2494       { "Disc", _("Disc") },
2495       { "Photo", _("Photo") },
2496       { "Hagaki", _("Hagaki") },
2497       { "MainRoll", _("Main Roll") },
2498       { "AlternateRoll", _("Alternate Roll") },
2499       { "Top", _("Top") },
2500       { "Middle", _("Middle") },
2501       { "Bottom", _("Bottom") },
2502       { "Side", _("Side") },
2503       { "Left", _("Left") },
2504       { "Right", _("Right") },
2505       { "Center", _("Center") },
2506       { "Rear", _("Rear") },
2507       { "ByPassTray", _("Multipurpose") },
2508       { "Tray1", _("Tray 1") },
2509       { "Tray2", _("Tray 2") },
2510       { "Tray3", _("Tray 3") },
2511       { "Tray4", _("Tray 4") },
2512       { "Tray5", _("Tray 5") },
2513       { "Tray6", _("Tray 6") },
2514       { "Tray7", _("Tray 7") },
2515       { "Tray8", _("Tray 8") },
2516       { "Tray9", _("Tray 9") },
2517       { "Tray10", _("Tray 10") },
2518       { "Tray11", _("Tray 11") },
2519       { "Tray12", _("Tray 12") },
2520       { "Tray13", _("Tray 13") },
2521       { "Tray14", _("Tray 14") },
2522       { "Tray15", _("Tray 15") },
2523       { "Tray16", _("Tray 16") },
2524       { "Tray17", _("Tray 17") },
2525       { "Tray18", _("Tray 18") },
2526       { "Tray19", _("Tray 19") },
2527       { "Tray20", _("Tray 20") },
2528       { "Roll1", _("Roll 1") },
2529       { "Roll2", _("Roll 2") },
2530       { "Roll3", _("Roll 3") },
2531       { "Roll4", _("Roll 4") },
2532       { "Roll5", _("Roll 5") },
2533       { "Roll6", _("Roll 6") },
2534       { "Roll7", _("Roll 7") },
2535       { "Roll8", _("Roll 8") },
2536       { "Roll9", _("Roll 9") },
2537       { "Roll10", _("Roll 10") }
2538     };
2539 
2540     human_readable = lookup_option("media-source", opt_strings_catalog,
2541 				   printer_opt_strings_catalog);
2542     cupsFilePrintf(fp, "*OpenUI *InputSlot/%s: PickOne\n"
2543 		   "*OrderDependency: 10 AnySetup *InputSlot\n",
2544 		   (human_readable ? human_readable : "Media Source"));
2545     if (have_default)
2546       cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
2547     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
2548       keyword = ippGetString(attr, i, NULL);
2549 
2550       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
2551 
2552       if (i == 0 && !have_default)
2553 	cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
2554 
2555       human_readable = lookup_choice((char *)keyword, "media-source",
2556 				     opt_strings_catalog,
2557 				     printer_opt_strings_catalog);
2558       for (j = (int)(sizeof(sources) / sizeof(sources[0])) - 1; j >= 0; j --)
2559         if (!strcmp(sources[j][0], ppdname)) {
2560 	  if (human_readable == NULL)
2561 	    human_readable = (char *)_cupsLangString(lang, sources[j][1]);
2562 	  break;
2563 	}
2564       if (j >= 0)
2565 	cupsFilePrintf(fp, "*InputSlot %s/%s: \"<</MediaPosition %d>>setpagedevice\"\n",
2566 		       ppdname, human_readable, j);
2567       else
2568 	cupsFilePrintf(fp, "*InputSlot %s%s%s: \"\"\n",
2569 		       ppdname,
2570 		       (human_readable ? "/" : ""),
2571 		       (human_readable ? human_readable : ""));
2572     }
2573     cupsFilePuts(fp, "*CloseUI: *InputSlot\n");
2574   }
2575 
2576  /*
2577   * MediaType...
2578   */
2579 
2580   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-type",
2581 			       IPP_TAG_ZERO)) != NULL)
2582     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
2583   else
2584     strlcpy(ppdname, "Unknown", sizeof(ppdname));
2585 
2586   if ((attr = ippFindAttribute(response, "media-type-supported",
2587 			       IPP_TAG_ZERO)) != NULL &&
2588       (count = ippGetCount(attr)) > 1) {
2589     static const char * const media_types[][2] =
2590     {					/* "media-type" strings */
2591       { "aluminum", _("Aluminum") },
2592       { "auto", _("Automatic") },
2593       { "back-print-film", _("Back Print Film") },
2594       { "cardboard", _("Cardboard") },
2595       { "cardstock", _("Cardstock") },
2596       { "cd", _("CD") },
2597       { "com.hp.advanced-photo", _("Advanced Photo Paper") }, /* HP */
2598       { "com.hp.brochure-glossy", _("Glossy Brochure Paper") }, /* HP */
2599       { "com.hp.brochure-matte", _("Matte Brochure Paper") }, /* HP */
2600       { "com.hp.cover-matte", _("Matte Cover Paper") }, /* HP */
2601       { "com.hp.ecosmart-lite", _("Office Recycled Paper") }, /* HP */
2602       { "com.hp.everyday-glossy", _("Everyday Glossy Photo Paper") }, /* HP */
2603       { "com.hp.everyday-matte", _("Everyday Matte Paper") }, /* HP */
2604       { "com.hp.extra-heavy", _("Extra Heavyweight Paper") }, /* HP */
2605       { "com.hp.intermediate", _("Multipurpose Paper") }, /* HP */
2606       { "com.hp.mid-weight", _("Mid-Weight Paper") }, /* HP */
2607       { "com.hp.premium-inkjet", _("Premium Inkjet Paper") }, /* HP */
2608       { "com.hp.premium-photo", _("Premium Photo Glossy Paper") }, /* HP */
2609       { "com.hp.premium-presentation-matte", _("Premium Presentation Matte Paper") }, /* HP */
2610       { "continuous", _("Continuous") },
2611       { "continuous-long", _("Continuous Long") },
2612       { "continuous-short", _("Continuous Short") },
2613       { "disc", _("Optical Disc") },
2614       { "disc-glossy", _("Glossy Optical Disc") },
2615       { "disc-high-gloss", _("High Gloss Optical Disc") },
2616       { "disc-matte", _("Matte Optical Disc") },
2617       { "disc-satin", _("Satin Optical Disc") },
2618       { "disc-semi-gloss", _("Semi-Gloss Optical Disc") },
2619       { "double-wall", _("Double Wall Cardboard") },
2620       { "dry-film", _("Dry Film") },
2621       { "dvd", _("DVD") },
2622       { "embossing-foil", _("Embossing Foil") },
2623       { "end-board", _("End Board") },
2624       { "envelope", _("Envelope") },
2625       { "envelope-archival", _("Archival Envelope") },
2626       { "envelope-bond", _("Bond Envelope") },
2627       { "envelope-coated", _("Coated Envelope") },
2628       { "envelope-cotton", _("Cotton Envelope") },
2629       { "envelope-fine", _("Fine Envelope") },
2630       { "envelope-heavyweight", _("Heavyweight Envelope") },
2631       { "envelope-inkjet", _("Inkjet Envelope") },
2632       { "envelope-lightweight", _("Lightweight Envelope") },
2633       { "envelope-plain", _("Plain Envelope") },
2634       { "envelope-preprinted", _("Preprinted Envelope") },
2635       { "envelope-window", _("Windowed Envelope") },
2636       { "fabric", _("Fabric") },
2637       { "fabric-archival", _("Archival Fabric") },
2638       { "fabric-glossy", _("Glossy Fabric") },
2639       { "fabric-high-gloss", _("High Gloss Fabric") },
2640       { "fabric-matte", _("Matte Fabric") },
2641       { "fabric-semi-gloss", _("Semi-Gloss Fabric") },
2642       { "fabric-waterproof", _("Waterproof Fabric") },
2643       { "film", _("Film") },
2644       { "flexo-base", _("Flexo Base") },
2645       { "flexo-photo-polymer", _("Flexo Photo Polymer") },
2646       { "flute", _("Flute") },
2647       { "foil", _("Foil") },
2648       { "full-cut-tabs", _("Full Cut Tabs") },
2649       { "glass", _("Glass") },
2650       { "glass-colored", _("Glass Colored") },
2651       { "glass-opaque", _("Glass Opaque") },
2652       { "glass-surfaced", _("Glass Surfaced") },
2653       { "glass-textured", _("Glass Textured") },
2654       { "gravure-cylinder", _("Gravure Cylinder") },
2655       { "image-setter-paper", _("Image Setter Paper") },
2656       { "imaging-cylinder", _("Imaging Cylinder") },
2657       { "jp.co.canon_photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
2658       { "jp.co.canon_photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
2659       { "jp.co.canon-photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
2660       { "jp.co.canon-photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
2661       { "labels", _("Labels") },
2662       { "labels-colored", _("Colored Labels") },
2663       { "labels-glossy", _("Glossy Labels") },
2664       { "labels-high-gloss", _("High Gloss Labels") },
2665       { "labels-inkjet", _("Inkjet Labels") },
2666       { "labels-matte", _("Matte Labels") },
2667       { "labels-permanent", _("Permanent Labels") },
2668       { "labels-satin", _("Satin Labels") },
2669       { "labels-security", _("Security Labels") },
2670       { "labels-semi-gloss", _("Semi-Gloss Labels") },
2671       { "laminating-foil", _("Laminating Foil") },
2672       { "letterhead", _("Letterhead") },
2673       { "metal", _("Metal") },
2674       { "metal-glossy", _("Metal Glossy") },
2675       { "metal-high-gloss", _("Metal High Gloss") },
2676       { "metal-matte", _("Metal Matte") },
2677       { "metal-satin", _("Metal Satin") },
2678       { "metal-semi-gloss", _("Metal Semi Gloss") },
2679       { "mounting-tape", _("Mounting Tape") },
2680       { "multi-layer", _("Multi Layer") },
2681       { "multi-part-form", _("Multi Part Form") },
2682       { "other", _("Other") },
2683       { "paper", _("Paper") },
2684       { "photo", _("Photo Paper") }, /* HP mis-spelling */
2685       { "photographic", _("Photo Paper") },
2686       { "photographic-archival", _("Archival Photo Paper") },
2687       { "photographic-film", _("Photo Film") },
2688       { "photographic-glossy", _("Glossy Photo Paper") },
2689       { "photographic-high-gloss", _("High Gloss Photo Paper") },
2690       { "photographic-matte", _("Matte Photo Paper") },
2691       { "photographic-satin", _("Satin Photo Paper") },
2692       { "photographic-semi-gloss", _("Semi-Gloss Photo Paper") },
2693       { "plastic", _("Plastic") },
2694       { "plastic-archival", _("Plastic Archival") },
2695       { "plastic-colored", _("Plastic Colored") },
2696       { "plastic-glossy", _("Plastic Glossy") },
2697       { "plastic-high-gloss", _("Plastic High Gloss") },
2698       { "plastic-matte", _("Plastic Matte") },
2699       { "plastic-satin", _("Plastic Satin") },
2700       { "plastic-semi-gloss", _("Plastic Semi Gloss") },
2701       { "plate", _("Plate") },
2702       { "polyester", _("Polyester") },
2703       { "pre-cut-tabs", _("Pre Cut Tabs") },
2704       { "roll", _("Roll") },
2705       { "screen", _("Screen") },
2706       { "screen-paged", _("Screen Paged") },
2707       { "self-adhesive", _("Self Adhesive") },
2708       { "self-adhesive-film", _("Self Adhesive Film") },
2709       { "shrink-foil", _("Shrink Foil") },
2710       { "single-face", _("Single Face") },
2711       { "single-wall", _("Single Wall Cardboard") },
2712       { "sleeve", _("Sleeve") },
2713       { "stationery", _("Plain Paper") },
2714       { "stationery-archival", _("Archival Paper") },
2715       { "stationery-coated", _("Coated Paper") },
2716       { "stationery-cotton", _("Cotton Paper") },
2717       { "stationery-fine", _("Vellum Paper") },
2718       { "stationery-heavyweight", _("Heavyweight Paper") },
2719       { "stationery-heavyweight-coated", _("Heavyweight Coated Paper") },
2720       { "stationery-inkjet", _("Inkjet Paper") },
2721       { "stationery-letterhead", _("Letterhead") },
2722       { "stationery-lightweight", _("Lightweight Paper") },
2723       { "stationery-preprinted", _("Preprinted Paper") },
2724       { "stationery-prepunched", _("Punched Paper") },
2725       { "tab-stock", _("Tab Stock") },
2726       { "tractor", _("Tractor") },
2727       { "transfer", _("Transfer") },
2728       { "transparency", _("Transparency") },
2729       { "triple-wall", _("Triple Wall Cardboard") },
2730       { "wet-film", _("Wet Film") }
2731     };
2732 
2733     human_readable = lookup_option("media-type", opt_strings_catalog,
2734 				   printer_opt_strings_catalog);
2735     cupsFilePrintf(fp, "*OpenUI *MediaType/%s: PickOne\n"
2736 		   "*OrderDependency: 10 AnySetup *MediaType\n"
2737 		   "*DefaultMediaType: %s\n",
2738 		   (human_readable ? human_readable : "Media Type"),
2739 		   ppdname);
2740     for (i = 0; i < count; i ++) {
2741       keyword = ippGetString(attr, i, NULL);
2742 
2743       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
2744 
2745       human_readable = lookup_choice((char *)keyword, "media-type",
2746 				     opt_strings_catalog,
2747 				     printer_opt_strings_catalog);
2748       if (human_readable == NULL)
2749 	for (j = 0; j < (int)(sizeof(media_types) / sizeof(media_types[0]));
2750 	     j ++)
2751 	  if (!strcmp(media_types[j][0], keyword)) {
2752 	    human_readable = (char *)_cupsLangString(lang, media_types[j][1]);
2753 	    break;
2754 	  }
2755       cupsFilePrintf(fp, "*MediaType %s%s%s: \"<</MediaType(%s)>>setpagedevice\"\n",
2756 		     ppdname,
2757 		     (human_readable ? "/" : ""),
2758 		     (human_readable ? human_readable : ""),
2759 		     ppdname);
2760     }
2761     cupsFilePuts(fp, "*CloseUI: *MediaType\n");
2762   }
2763 
2764  /*
2765   * ColorModel...
2766   */
2767 
2768   if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) ==
2769       NULL)
2770     if ((attr = ippFindAttribute(response, "print-color-mode-supported",
2771 				 IPP_TAG_KEYWORD)) == NULL)
2772       if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported",
2773 				   IPP_TAG_KEYWORD)) == NULL)
2774         attr = ippFindAttribute(response, "output-mode-supported",
2775 				IPP_TAG_KEYWORD);
2776 
2777   human_readable = lookup_option("print-color-mode", opt_strings_catalog,
2778 				 printer_opt_strings_catalog);
2779   if (attr && ippGetCount(attr) > 0) {
2780     const char *default_color = NULL;	/* Default */
2781     int first_choice = 1,
2782       have_bi_level = 0,
2783       have_mono = 0;
2784 
2785     cupsFilePrintf(fp, "*%% ColorModel from %s\n", ippGetName(attr));
2786 
2787     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
2788       keyword = ippGetString(attr, i, NULL); /* Keyword for color/bit depth */
2789 
2790       if (!have_bi_level &&
2791 	  (!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") ||
2792 	   !strcmp(keyword, "process-bi-level"))) {
2793 	have_bi_level = 1;
2794         if (first_choice) {
2795 	  first_choice = 0;
2796 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2797 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2798 			 (human_readable ? human_readable :
2799 			  _cupsLangString(lang, _("Color Mode"))));
2800 	}
2801 
2802 	human_readable2 = lookup_choice("bi-level", "print-color-mode",
2803 					opt_strings_catalog,
2804 					printer_opt_strings_catalog);
2805         cupsFilePrintf(fp, "*ColorModel FastGray/%s: \"<</cupsColorSpace 3/cupsBitsPerColor 1/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2806 		       (human_readable2 ? human_readable2 :
2807 			_cupsLangString(lang, _("Fast Grayscale"))));
2808 
2809         if (!default_color)
2810 	  default_color = "FastGray";
2811       } else if (!have_mono &&
2812 		 (!strcasecmp(keyword, "sgray_8") ||
2813 		  !strncmp(keyword, "W8", 2) ||
2814 		  !strcmp(keyword, "monochrome") ||
2815 		  !strcmp(keyword, "process-monochrome"))) {
2816 	have_mono = 1;
2817         if (first_choice) {
2818 	  first_choice = 0;
2819 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2820 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2821 			 (human_readable ? human_readable :
2822 			  _cupsLangString(lang, _("Color Mode"))));
2823 	}
2824 
2825 	human_readable2 = lookup_choice("monochrome", "print-color-mode",
2826 					opt_strings_catalog,
2827 					printer_opt_strings_catalog);
2828         cupsFilePrintf(fp, "*ColorModel Gray/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2829 		       (human_readable2 ? human_readable2 :
2830 			_cupsLangString(lang, _("Grayscale"))));
2831 
2832         if (!default_color || !strcmp(default_color, "FastGray"))
2833 	  default_color = "Gray";
2834       } else if (!strcasecmp(keyword, "sgray_16") ||
2835 		 !strncmp(keyword, "W8-16", 5) ||
2836 		 !strncmp(keyword, "W16", 3)) {
2837         if (first_choice) {
2838 	  first_choice = 0;
2839 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2840 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2841 			 (human_readable ? human_readable :
2842 			  _cupsLangString(lang, _("Color Mode"))));
2843 	}
2844 
2845         cupsFilePrintf(fp, "*ColorModel Gray16/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2846 		       _cupsLangString(lang, _("Deep Gray (High Definition Grayscale)")));
2847 
2848         if (!default_color || !strcmp(default_color, "FastGray"))
2849 	  default_color = "Gray16";
2850       } else if (!strcasecmp(keyword, "srgb_8") ||
2851 		 !strncmp(keyword, "SRGB24", 6) ||
2852 		 !strcmp(keyword, "color")) {
2853         if (first_choice) {
2854 	  first_choice = 0;
2855 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2856 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2857 			 (human_readable ? human_readable :
2858 			  _cupsLangString(lang, _("Color Mode"))));
2859 	}
2860 
2861 	human_readable2 = lookup_choice("color", "print-color-mode",
2862 					opt_strings_catalog,
2863 					printer_opt_strings_catalog);
2864         cupsFilePrintf(fp, "*ColorModel RGB/%s: \"<</cupsColorSpace 19/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2865 		       (human_readable2 ? human_readable2 :
2866 			_cupsLangString(lang, _("Color"))));
2867 
2868 	default_color = "RGB";
2869       } else if ((!strcasecmp(keyword, "srgb_16") ||
2870 		  !strncmp(keyword, "SRGB48", 6)) &&
2871 		 !ippContainsString(attr, "srgb_8")) {
2872         if (first_choice) {
2873 	  first_choice = 0;
2874 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2875 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2876 			 (human_readable ? human_readable :
2877 			  _cupsLangString(lang, _("Color Mode"))));
2878 	}
2879 
2880 	human_readable2 = lookup_choice("color", "print-color-mode",
2881 					opt_strings_catalog,
2882 					printer_opt_strings_catalog);
2883         cupsFilePrintf(fp, "*ColorModel RGB/%s: \"<</cupsColorSpace 19/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2884 		       (human_readable2 ? human_readable2 :
2885 			_cupsLangString(lang, _("Color"))));
2886 
2887 	default_color = "RGB";
2888 
2889 	/* Apparently some printers only advertise color support, so make sure
2890            we also do grayscale for these printers... */
2891 	if (!ippContainsString(attr, "sgray_8") &&
2892 	    !ippContainsString(attr, "black_1") &&
2893 	    !ippContainsString(attr, "black_8") &&
2894 	    !ippContainsString(attr, "W8") &&
2895 	    !ippContainsString(attr, "W8-16")) {
2896 	  human_readable2 = lookup_choice("monochrome", "print-color-mode",
2897 					  opt_strings_catalog,
2898 					  printer_opt_strings_catalog);
2899 	  cupsFilePrintf(fp, "*ColorModel Gray/%s: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2900 			 (human_readable2 ? human_readable2 :
2901 			  _cupsLangString(lang, _("Grayscale"))));
2902 	}
2903       } else if (!strcasecmp(keyword, "adobe-rgb_16") ||
2904 		 !strncmp(keyword, "ADOBERGB48", 10) ||
2905 		 !strncmp(keyword, "ADOBERGB24-48", 13)) {
2906         if (first_choice) {
2907 	  first_choice = 0;
2908 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2909 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2910 			 (human_readable ? human_readable :
2911 			  _cupsLangString(lang, _("Color Mode"))));
2912 	}
2913 
2914         cupsFilePrintf(fp, "*ColorModel AdobeRGB/%s: \"<</cupsColorSpace 20/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2915 		       _cupsLangString(lang, _("Deep Color (Wide Color Gamut, AdobeRGB)")));
2916 
2917         if (!default_color)
2918 	  default_color = "AdobeRGB";
2919       } else if ((!strcasecmp(keyword, "adobe-rgb_8") ||
2920 		  !strcmp(keyword, "ADOBERGB24")) &&
2921 		 !ippContainsString(attr, "adobe-rgb_16")) {
2922         if (first_choice) {
2923 	  first_choice = 0;
2924 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2925 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2926 			 (human_readable ? human_readable :
2927 			  _cupsLangString(lang, _("Color Mode"))));
2928 	}
2929 
2930         cupsFilePrintf(fp, "*ColorModel AdobeRGB/%s: \"<</cupsColorSpace 20/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2931 		       _cupsLangString(lang, _("Deep Color (Wide Color Gamut, AdobeRGB)")));
2932 
2933         if (!default_color)
2934 	  default_color = "AdobeRGB";
2935       } else if ((!strcasecmp(keyword, "black_8") &&
2936 		  !ippContainsString(attr, "black_16")) ||
2937 		 !strcmp(keyword, "DEVW8")) {
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 DeviceGray/%s: \"<</cupsColorSpace 0/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2947 		       _cupsLangString(lang, _("Device Gray")));
2948       } else if (!strcasecmp(keyword, "black_16") ||
2949 		 !strcmp(keyword, "DEVW16") ||
2950 		 !strcmp(keyword, "DEVW8-16")) {
2951         if (first_choice) {
2952 	  first_choice = 0;
2953 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2954 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2955 			 (human_readable ? human_readable :
2956 			  _cupsLangString(lang, _("Color Mode"))));
2957 	}
2958 
2959         cupsFilePrintf(fp, "*ColorModel DeviceGray/%s: \"<</cupsColorSpace 0/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2960 		       _cupsLangString(lang, _("Device Gray")));
2961       } else if ((!strcasecmp(keyword, "cmyk_8") &&
2962 		  !ippContainsString(attr, "cmyk_16")) ||
2963 		 !strcmp(keyword, "DEVCMYK32")) {
2964         if (first_choice) {
2965 	  first_choice = 0;
2966 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2967 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2968 			 (human_readable ? human_readable :
2969 			  _cupsLangString(lang, _("Color Mode"))));
2970 	}
2971 
2972         cupsFilePrintf(fp, "*ColorModel CMYK/%s: \"<</cupsColorSpace 6/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2973 		       _cupsLangString(lang, _("Device CMYK")));
2974       } else if (!strcasecmp(keyword, "cmyk_16") ||
2975 		 !strcmp(keyword, "DEVCMYK32-64") ||
2976 		 !strcmp(keyword, "DEVCMYK64")) {
2977         if (first_choice) {
2978 	  first_choice = 0;
2979 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2980 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2981 			 (human_readable ? human_readable :
2982 			  _cupsLangString(lang, _("Color Mode"))));
2983 	}
2984 
2985         cupsFilePrintf(fp, "*ColorModel CMYK/%s: \"<</cupsColorSpace 6/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2986 		       _cupsLangString(lang, _("Device CMYK")));
2987       } else if ((!strcasecmp(keyword, "rgb_8") &&
2988 		  !ippContainsString(attr, "rgb_16")) ||
2989 		 !strcmp(keyword, "DEVRGB24")) {
2990         if (first_choice) {
2991 	  first_choice = 0;
2992 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
2993 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
2994 			 (human_readable ? human_readable :
2995 			  _cupsLangString(lang, _("Color Mode"))));
2996 	}
2997 
2998         cupsFilePrintf(fp, "*ColorModel DeviceRGB/%s: \"<</cupsColorSpace 1/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
2999 		       _cupsLangString(lang, _("Device RGB")));
3000       } else if (!strcasecmp(keyword, "rgb_16") ||
3001 		 !strcmp(keyword, "DEVRGB24-48") ||
3002 		 !strcmp(keyword, "DEVRGB48")) {
3003         if (first_choice) {
3004 	  first_choice = 0;
3005 	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3006 			 "*OrderDependency: 10 AnySetup *ColorModel\n",
3007 			 (human_readable ? human_readable :
3008 			  _cupsLangString(lang, _("Color Mode"))));
3009 	}
3010 
3011         cupsFilePrintf(fp, "*ColorModel DeviceRGB/%s: \"<</cupsColorSpace 1/cupsBitsPerColor 16/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n",
3012 		       _cupsLangString(lang, _("Device RGB")));
3013       }
3014     }
3015 
3016     if (default_pagesize != NULL) {
3017       /* Here we are dealing with a cluster, if the default cluster color
3018          is not supplied we set it Gray*/
3019       if (default_cluster_color != NULL) {
3020 	default_color = default_cluster_color;
3021       } else
3022 	default_color = "Gray";
3023     }
3024 
3025     if (default_color)
3026       cupsFilePrintf(fp, "*DefaultColorModel: %s\n", default_color);
3027     if (!first_choice)
3028       cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
3029   } else {
3030     cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
3031 		   "*OrderDependency: 10 AnySetup *ColorModel\n",
3032 		   (human_readable ? human_readable :
3033 		    _cupsLangString(lang, _("Color Mode"))));
3034     cupsFilePrintf(fp, "*DefaultColorModel: Gray\n");
3035     cupsFilePuts(fp, "*ColorModel FastGray/Fast Grayscale: \"<</cupsColorSpace 3/cupsBitsPerColor 1/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceGray>>setpagedevice\"\n");
3036     cupsFilePuts(fp, "*ColorModel Gray/Grayscale: \"<</cupsColorSpace 18/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceGray>>setpagedevice\"\n");
3037     if (color) {
3038       /* Color printer according to DNS-SD (or unknown) */
3039       cupsFilePuts(fp, "*ColorModel RGB/Color: \"<</cupsColorSpace 19/cupsBitsPerColor 8/cupsColorOrder 0/cupsCompression 0/ProcessColorModel /DeviceRGB>>setpagedevice\"\n");
3040     }
3041     cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
3042   }
3043 
3044  /*
3045   * Duplex...
3046   */
3047 
3048   if (((attr = ippFindAttribute(response, "sides-supported",
3049 				IPP_TAG_KEYWORD)) != NULL &&
3050        ippContainsString(attr, "two-sided-long-edge")) ||
3051       (attr == NULL && duplex)) {
3052     human_readable = lookup_option("sides", opt_strings_catalog,
3053 				   printer_opt_strings_catalog);
3054     cupsFilePrintf(fp, "*OpenUI *Duplex/%s: PickOne\n"
3055 		   "*OrderDependency: 10 AnySetup *Duplex\n"
3056 		   "*DefaultDuplex: None\n",
3057 		   (human_readable ? human_readable :
3058 		    _cupsLangString(lang, _("2-Sided Printing"))));
3059     human_readable = lookup_choice("one-sided", "sides", opt_strings_catalog,
3060 				   printer_opt_strings_catalog);
3061     cupsFilePrintf(fp, "*Duplex None/%s: \"<</Duplex false>>setpagedevice\"\n",
3062 		   (human_readable ? human_readable :
3063 		    _cupsLangString(lang, _("Off (1-Sided)"))));
3064     human_readable = lookup_choice("two-sided-long-edge", "sides",
3065 				   opt_strings_catalog,
3066 				   printer_opt_strings_catalog);
3067     cupsFilePrintf(fp, "*Duplex DuplexNoTumble/%s: \"<</Duplex true/Tumble false>>setpagedevice\"\n",
3068 		   (human_readable ? human_readable :
3069 		    _cupsLangString(lang, _("Long-Edge (Portrait)"))));
3070     human_readable = lookup_choice("two-sided-short-edge", "sides",
3071 				   opt_strings_catalog,
3072 				   printer_opt_strings_catalog);
3073     cupsFilePrintf(fp, "*Duplex DuplexTumble/%s: \"<</Duplex true/Tumble true>>setpagedevice\"\n",
3074 		   (human_readable ? human_readable :
3075 		    _cupsLangString(lang, _("Short-Edge (Landscape)"))));
3076     cupsFilePrintf(fp, "*CloseUI: *Duplex\n");
3077 
3078     if ((attr = ippFindAttribute(response, "urf-supported",
3079 				 IPP_TAG_KEYWORD)) != NULL) {
3080       for (i = 0, count = ippGetCount(attr); i < count; i ++) {
3081 	const char *dm = ippGetString(attr, i, NULL); /* DM value */
3082 
3083 	if (!_cups_strcasecmp(dm, "DM1")) {
3084 	  cupsFilePuts(fp, "*cupsBackSide: Normal\n");
3085 	  break;
3086 	} else if (!_cups_strcasecmp(dm, "DM2")) {
3087 	  cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
3088 	  break;
3089 	} else if (!_cups_strcasecmp(dm, "DM3")) {
3090 	  cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
3091 	  break;
3092 	} else if (!_cups_strcasecmp(dm, "DM4")) {
3093 	  cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
3094 	  break;
3095 	}
3096       }
3097     } else if ((attr = ippFindAttribute(response,
3098 					"pwg-raster-document-sheet-back",
3099 					IPP_TAG_KEYWORD)) != NULL) {
3100       keyword = ippGetString(attr, 0, NULL); /* Keyword value */
3101 
3102       if (!strcmp(keyword, "flipped"))
3103         cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
3104       else if (!strcmp(keyword, "manual-tumble"))
3105         cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
3106       else if (!strcmp(keyword, "normal"))
3107         cupsFilePuts(fp, "*cupsBackSide: Normal\n");
3108       else
3109         cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
3110     }
3111   }
3112 
3113  /*
3114   * Output bin...
3115   */
3116 
3117   if ((attr = ippFindAttribute(response, "output-bin-default",
3118 			       IPP_TAG_ZERO)) != NULL)
3119     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
3120   else
3121     strlcpy(ppdname, "Unknown", sizeof(ppdname));
3122 
3123   if ((attr = ippFindAttribute(response, "output-bin-supported",
3124 			       IPP_TAG_ZERO)) != NULL &&
3125       (count = ippGetCount(attr)) > 0) {
3126     static const char * const output_bins[][2] =
3127     {					/* "output-bin" strings */
3128       { "auto", _("Automatic") },
3129       { "bottom", _("Bottom Tray") },
3130       { "center", _("Center Tray") },
3131       { "face-down", _("Face Down") },
3132       { "face-up", _("Face Up") },
3133       { "large-capacity", _("Large Capacity Tray") },
3134       { "left", _("Left Tray") },
3135       { "mailbox-1", _("Mailbox 1") },
3136       { "mailbox-2", _("Mailbox 2") },
3137       { "mailbox-3", _("Mailbox 3") },
3138       { "mailbox-4", _("Mailbox 4") },
3139       { "mailbox-5", _("Mailbox 5") },
3140       { "mailbox-6", _("Mailbox 6") },
3141       { "mailbox-7", _("Mailbox 7") },
3142       { "mailbox-8", _("Mailbox 8") },
3143       { "mailbox-9", _("Mailbox 9") },
3144       { "mailbox-10", _("Mailbox 10") },
3145       { "middle", _("Middle") },
3146       { "my-mailbox", _("My Mailbox") },
3147       { "rear", _("Rear Tray") },
3148       { "right", _("Right Tray") },
3149       { "side", _("Side Tray") },
3150       { "stacker-1", _("Stacker 1") },
3151       { "stacker-2", _("Stacker 2") },
3152       { "stacker-3", _("Stacker 3") },
3153       { "stacker-4", _("Stacker 4") },
3154       { "stacker-5", _("Stacker 5") },
3155       { "stacker-6", _("Stacker 6") },
3156       { "stacker-7", _("Stacker 7") },
3157       { "stacker-8", _("Stacker 8") },
3158       { "stacker-9", _("Stacker 9") },
3159       { "stacker-10", _("Stacker 10") },
3160       { "top", _("Top Tray") },
3161       { "tray-1", _("Tray 1") },
3162       { "tray-2", _("Tray 2") },
3163       { "tray-3", _("Tray 3") },
3164       { "tray-4", _("Tray 4") },
3165       { "tray-5", _("Tray 5") },
3166       { "tray-6", _("Tray 6") },
3167       { "tray-7", _("Tray 7") },
3168       { "tray-8", _("Tray 8") },
3169       { "tray-9", _("Tray 9") },
3170       { "tray-10", _("Tray 10") }
3171     };
3172 
3173     human_readable = lookup_option("output-bin", opt_strings_catalog,
3174 				   printer_opt_strings_catalog);
3175     cupsFilePrintf(fp, "*OpenUI *OutputBin/%s: PickOne\n"
3176 		   "*OrderDependency: 10 AnySetup *OutputBin\n"
3177 		   "*DefaultOutputBin: %s\n",
3178 		   (human_readable ? human_readable : "Output Bin"),
3179 		   ppdname);
3180     attr2 = ippFindAttribute(response, "printer-output-tray", IPP_TAG_STRING);
3181     for (i = 0; i < count; i ++) {
3182       keyword = ippGetString(attr, i, NULL);
3183 
3184       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
3185 
3186       human_readable = lookup_choice((char *)keyword, "output-bin",
3187 				     opt_strings_catalog,
3188 				     printer_opt_strings_catalog);
3189       if (human_readable == NULL)
3190 	for (j = 0; j < (int)(sizeof(output_bins) / sizeof(output_bins[0]));
3191 	     j ++)
3192 	  if (!strcmp(output_bins[j][0], keyword)) {
3193 	    human_readable = (char *)_cupsLangString(lang, output_bins[j][1]);
3194 	    break;
3195 	  }
3196       cupsFilePrintf(fp, "*OutputBin %s%s%s: \"\"\n",
3197 		     ppdname,
3198 		     (human_readable ? "/" : ""),
3199 		     (human_readable ? human_readable : ""));
3200       outputorderinfofound = 0;
3201       faceupdown = 1;
3202       firsttolast = 1;
3203       if (attr2 && i < ippGetCount(attr2)) {
3204 	outbin_properties_octet = ippGetOctetString(attr2, i, &octet_str_len);
3205 	memset(outbin_properties, 0, sizeof(outbin_properties));
3206 	memcpy(outbin_properties, outbin_properties_octet,
3207 	       ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
3208 		(size_t)octet_str_len : sizeof(outbin_properties) - 1));
3209 	if (strcasestr(outbin_properties, "pagedelivery=faceUp")) {
3210 	  outputorderinfofound = 1;
3211 	  faceupdown = -1;
3212 	} else if (strcasestr(outbin_properties, "pagedelivery=faceDown")) {
3213 	  outputorderinfofound = 1;
3214 	  faceupdown = 1;
3215 	}
3216 	if (strcasestr(outbin_properties, "stackingorder=lastToFirst")) {
3217 	  outputorderinfofound = 1;
3218 	  firsttolast = -1;
3219 	} else if (strcasestr(outbin_properties, "stackingorder=firstToLast")) {
3220 	  outputorderinfofound = 1;
3221 	  firsttolast = 1;
3222 	}
3223       }
3224       if (outputorderinfofound == 0) {
3225 	if (strcasestr(keyword, "face-up")) {
3226 	  outputorderinfofound = 1;
3227 	  faceupdown = -1;
3228 	}
3229 	if (strcasestr(keyword, "face-down")) {
3230 	  outputorderinfofound = 1;
3231 	  faceupdown = 1;
3232 	}
3233       }
3234       if (outputorderinfofound)
3235 	cupsFilePrintf(fp, "*PageStackOrder %s: %s\n",
3236 		       ppdname,
3237 		       (firsttolast * faceupdown < 0 ? "Reverse" : "Normal"));
3238     }
3239     cupsFilePuts(fp, "*CloseUI: *OutputBin\n");
3240   }
3241 
3242  /*
3243   * Finishing options...
3244   */
3245 
3246   if ((attr = ippFindAttribute(response, "finishings-supported",
3247 			       IPP_TAG_ENUM)) != NULL) {
3248     int			value;		/* Enum value */
3249     const char		*ppd_keyword;	/* PPD keyword for enum */
3250     cups_array_t	*names;		/* Names we've added */
3251     static const char * const base_keywords[] =
3252     {					/* Base STD 92 keywords */
3253       NULL,				/* none */
3254       "SingleAuto",			/* staple */
3255       "SingleAuto",			/* punch */
3256       NULL,				/* cover */
3257       "BindAuto",			/* bind */
3258       "SaddleStitch",			/* saddle-stitch */
3259       "EdgeStitchAuto",			/* edge-stitch */
3260       "Auto",				/* fold */
3261       NULL,				/* trim */
3262       NULL,				/* bale */
3263       NULL,				/* booklet-maker */
3264       NULL,				/* jog-offset */
3265       NULL,				/* coat */
3266       NULL				/* laminate */
3267     };
3268     static const char * const finishings[][2] =
3269     {					/* Finishings strings */
3270       { "bale", _("Bale") },
3271       { "bind", _("Bind") },
3272       { "bind-bottom", _("Bind (Reverse Landscape)") },
3273       { "bind-left", _("Bind (Portrait)") },
3274       { "bind-right", _("Bind (Reverse Portrait)") },
3275       { "bind-top", _("Bind (Landscape)") },
3276       { "booklet-maker", _("Booklet Maker") },
3277       { "coat", _("Coat") },
3278       { "cover", _("Cover") },
3279       { "edge-stitch", _("Staple Edge") },
3280       { "edge-stitch-bottom", _("Staple Edge (Reverse Landscape)") },
3281       { "edge-stitch-left", _("Staple Edge (Portrait)") },
3282       { "edge-stitch-right", _("Staple Edge (Reverse Portrait)") },
3283       { "edge-stitch-top", _("Staple Edge (Landscape)") },
3284       { "fold", _("Fold") },
3285       { "fold-accordian", _("Accordian Fold") },
3286       { "fold-double-gate", _("Double Gate Fold") },
3287       { "fold-engineering-z", _("Engineering Z Fold") },
3288       { "fold-gate", _("Gate Fold") },
3289       { "fold-half", _("Half Fold") },
3290       { "fold-half-z", _("Half Z Fold") },
3291       { "fold-left-gate", _("Left Gate Fold") },
3292       { "fold-letter", _("Letter Fold") },
3293       { "fold-parallel", _("Parallel Fold") },
3294       { "fold-poster", _("Poster Fold") },
3295       { "fold-right-gate", _("Right Gate Fold") },
3296       { "fold-z", _("Z Fold") },
3297       { "jog-offset", _("Jog") },
3298       { "laminate", _("Laminate") },
3299       { "punch", _("Punch") },
3300       { "punch-bottom-left", _("Single Punch (Reverse Landscape)") },
3301       { "punch-bottom-right", _("Single Punch (Reverse Portrait)") },
3302       { "punch-double-bottom", _("2-Hole Punch (Reverse Portrait)") },
3303       { "punch-double-left", _("2-Hole Punch (Reverse Landscape)") },
3304       { "punch-double-right", _("2-Hole Punch (Landscape)") },
3305       { "punch-double-top", _("2-Hole Punch (Portrait)") },
3306       { "punch-quad-bottom", _("4-Hole Punch (Reverse Landscape)") },
3307       { "punch-quad-left", _("4-Hole Punch (Portrait)") },
3308       { "punch-quad-right", _("4-Hole Punch (Reverse Portrait)") },
3309       { "punch-quad-top", _("4-Hole Punch (Landscape)") },
3310       { "punch-top-left", _("Single Punch (Portrait)") },
3311       { "punch-top-right", _("Single Punch (Landscape)") },
3312       { "punch-triple-bottom", _("3-Hole Punch (Reverse Landscape)") },
3313       { "punch-triple-left", _("3-Hole Punch (Portrait)") },
3314       { "punch-triple-right", _("3-Hole Punch (Reverse Portrait)") },
3315       { "punch-triple-top", _("3-Hole Punch (Landscape)") },
3316       { "punch-multiple-bottom", _("Multi-Hole Punch (Reverse Landscape)") },
3317       { "punch-multiple-left", _("Multi-Hole Punch (Portrait)") },
3318       { "punch-multiple-right", _("Multi-Hole Punch (Reverse Portrait)") },
3319       { "punch-multiple-top", _("Multi-Hole Punch (Landscape)") },
3320       { "saddle-stitch", _("Saddle Stitch") },
3321       { "staple", _("Staple") },
3322       { "staple-bottom-left", _("Single Staple (Reverse Landscape)") },
3323       { "staple-bottom-right", _("Single Staple (Reverse Portrait)") },
3324       { "staple-dual-bottom", _("Double Staple (Reverse Landscape)") },
3325       { "staple-dual-left", _("Double Staple (Portrait)") },
3326       { "staple-dual-right", _("Double Staple (Reverse Portrait)") },
3327       { "staple-dual-top", _("Double Staple (Landscape)") },
3328       { "staple-top-left", _("Single Staple (Portrait)") },
3329       { "staple-top-right", _("Single Staple (Landscape)") },
3330       { "staple-triple-bottom", _("Triple Staple (Reverse Landscape)") },
3331       { "staple-triple-left", _("Triple Staple (Portrait)") },
3332       { "staple-triple-right", _("Triple Staple (Reverse Portrait)") },
3333       { "staple-triple-top", _("Triple Staple (Landscape)") },
3334       { "trim", _("Cut Media") }
3335     };
3336 
3337     count = ippGetCount(attr);
3338     names = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0,
3339 			  (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
3340     fin_options = cupsArrayNew((cups_array_func_t)strcmp, NULL);
3341 
3342    /*
3343     * Staple/Bind/Stitch
3344     */
3345 
3346     for (i = 0; i < count; i ++) {
3347       value   = ippGetInteger(attr, i);
3348       keyword = ippEnumString("finishings", value);
3349 
3350       if (!strncmp(keyword, "staple-", 7) ||
3351 	  !strncmp(keyword, "bind-", 5) ||
3352 	  !strncmp(keyword, "edge-stitch-", 12) ||
3353 	  !strcmp(keyword, "saddle-stitch"))
3354         break;
3355     }
3356 
3357     if (i < count) {
3358       static const char * const staple_keywords[] =
3359       {					/* StapleLocation keywords */
3360 	"SinglePortrait",
3361 	"SingleRevLandscape",
3362 	"SingleLandscape",
3363 	"SingleRevPortrait",
3364 	"EdgeStitchPortrait",
3365 	"EdgeStitchLandscape",
3366 	"EdgeStitchRevPortrait",
3367 	"EdgeStitchRevLandscape",
3368 	"DualPortrait",
3369 	"DualLandscape",
3370 	"DualRevPortrait",
3371 	"DualRevLandscape",
3372 	"TriplePortrait",
3373 	"TripleLandscape",
3374 	"TripleRevPortrait",
3375 	"TripleRevLandscape"
3376       };
3377       static const char * const bind_keywords[] =
3378       {					/* StapleLocation binding keywords */
3379 	"BindPortrait",
3380 	"BindLandscape",
3381 	"BindRevPortrait",
3382 	"BindRevLandscape"
3383       };
3384 
3385       cupsArrayAdd(fin_options, "*StapleLocation");
3386 
3387       human_readable = lookup_choice("staple", "finishing-template",
3388 				     opt_strings_catalog,
3389 				     printer_opt_strings_catalog);
3390       cupsFilePrintf(fp, "*OpenUI *StapleLocation/%s: PickOne\n",
3391 		     (human_readable ? human_readable :
3392 		      _cupsLangString(lang, _("Staple"))));
3393       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *StapleLocation\n");
3394       cupsFilePuts(fp, "*DefaultStapleLocation: None\n");
3395       cupsFilePrintf(fp, "*StapleLocation None/%s: \"\"\n",
3396 		     _cupsLangString(lang, _("None")));
3397 
3398       for (; i < count; i ++) {
3399         value   = ippGetInteger(attr, i);
3400         keyword = ippEnumString("finishings", value);
3401 
3402         if (strncmp(keyword, "staple-", 7) &&
3403 	    strncmp(keyword, "bind-", 5) &&
3404 	    strncmp(keyword, "edge-stitch-", 12) &&
3405 	    strcmp(keyword, "saddle-stitch"))
3406           continue;
3407 
3408         if (cupsArrayFind(names, (char *)keyword))
3409           continue; /* Already did this finishing template */
3410 
3411         cupsArrayAdd(names, (char *)keyword);
3412 
3413         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3414           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3415         else if (value >= IPP_FINISHINGS_STAPLE_TOP_LEFT &&
3416 		 value <= IPP_FINISHINGS_STAPLE_TRIPLE_BOTTOM)
3417           ppd_keyword = staple_keywords[value - IPP_FINISHINGS_STAPLE_TOP_LEFT];
3418         else if (value >= IPP_FINISHINGS_BIND_LEFT &&
3419 		 value <= IPP_FINISHINGS_BIND_BOTTOM)
3420           ppd_keyword = bind_keywords[value - IPP_FINISHINGS_BIND_LEFT];
3421         else
3422           ppd_keyword = NULL;
3423 
3424         if (!ppd_keyword)
3425           continue;
3426 
3427 	snprintf(buf, sizeof(buf), "%d", value);
3428 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3429 				       printer_opt_strings_catalog);
3430 	if (human_readable == NULL)
3431 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3432 	       j ++)
3433 	    if (!strcmp(finishings[j][0], keyword)) {
3434 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3435 	      break;
3436 	    }
3437 	cupsFilePrintf(fp, "*StapleLocation %s%s%s: \"\"\n", ppd_keyword,
3438 		       (human_readable ? "/" : ""),
3439 		       (human_readable ? human_readable : ""));
3440 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n",
3441 		       value, keyword, ppd_keyword);
3442       }
3443 
3444       cupsFilePuts(fp, "*CloseUI: *StapleLocation\n");
3445     }
3446 
3447    /*
3448     * Fold
3449     */
3450 
3451     for (i = 0; i < count; i ++) {
3452       value   = ippGetInteger(attr, i);
3453       keyword = ippEnumString("finishings", value);
3454 
3455       if (!strncmp(keyword, "cups-fold-", 10) ||
3456 	  !strcmp(keyword, "fold") ||
3457 	  !strncmp(keyword, "fold-", 5))
3458         break;
3459     }
3460 
3461     if (i < count) {
3462       static const char * const fold_keywords[] =
3463       {					/* FoldType keywords */
3464 	"Accordion",
3465 	"DoubleGate",
3466 	"Gate",
3467 	"Half",
3468 	"HalfZ",
3469 	"LeftGate",
3470 	"Letter",
3471 	"Parallel",
3472 	"XFold",
3473 	"RightGate",
3474 	"ZFold",
3475 	"EngineeringZ"
3476       };
3477 
3478       cupsArrayAdd(fin_options, "*FoldType");
3479 
3480       human_readable = lookup_choice("fold", "finishing-template",
3481 				     opt_strings_catalog,
3482 				     printer_opt_strings_catalog);
3483       cupsFilePrintf(fp, "*OpenUI *FoldType/%s: PickOne\n",
3484 		     (human_readable ? human_readable :
3485 		      _cupsLangString(lang, _("Fold"))));
3486       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *FoldType\n");
3487       cupsFilePuts(fp, "*DefaultFoldType: None\n");
3488       cupsFilePrintf(fp, "*FoldType None/%s: \"\"\n",
3489 		     _cupsLangString(lang, _("None")));
3490 
3491       for (; i < count; i ++) {
3492         value   = ippGetInteger(attr, i);
3493         keyword = ippEnumString("finishings", value);
3494 
3495         if (!strncmp(keyword, "cups-fold-", 10))
3496           keyword += 5;
3497         else if (strcmp(keyword, "fold") && strncmp(keyword, "fold-", 5))
3498           continue;
3499 
3500         if (cupsArrayFind(names, (char *)keyword))
3501           continue; /* Already did this finishing template */
3502 
3503         cupsArrayAdd(names, (char *)keyword);
3504 
3505         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3506           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3507         else if (value >= IPP_FINISHINGS_FOLD_ACCORDION &&
3508 		 value <= IPP_FINISHINGS_FOLD_ENGINEERING_Z)
3509           ppd_keyword = fold_keywords[value - IPP_FINISHINGS_FOLD_ACCORDION];
3510         else if (value >= IPP_FINISHINGS_CUPS_FOLD_ACCORDION &&
3511 		 value <= IPP_FINISHINGS_CUPS_FOLD_Z)
3512           ppd_keyword = fold_keywords[value -
3513 				      IPP_FINISHINGS_CUPS_FOLD_ACCORDION];
3514         else
3515           ppd_keyword = NULL;
3516 
3517         if (!ppd_keyword)
3518           continue;
3519 
3520 	snprintf(buf, sizeof(buf), "%d", value);
3521 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3522 				       printer_opt_strings_catalog);
3523 	if (human_readable == NULL)
3524 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3525 	       j ++)
3526 	    if (!strcmp(finishings[j][0], keyword)) {
3527 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3528 	      break;
3529 	    }
3530 	cupsFilePrintf(fp, "*FoldType %s%s%s: \"\"\n", ppd_keyword,
3531 		       (human_readable ? "/" : ""),
3532 		       (human_readable ? human_readable : ""));
3533 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n",
3534 		       value, keyword, ppd_keyword);
3535       }
3536 
3537       cupsFilePuts(fp, "*CloseUI: *FoldType\n");
3538     }
3539 
3540    /*
3541     * Punch
3542     */
3543 
3544     for (i = 0; i < count; i ++) {
3545       value   = ippGetInteger(attr, i);
3546       keyword = ippEnumString("finishings", value);
3547 
3548       if (!strncmp(keyword, "cups-punch-", 11) ||
3549 	  !strncmp(keyword, "punch-", 6))
3550         break;
3551     }
3552 
3553     if (i < count) {
3554       static const char * const punch_keywords[] =
3555       {					/* PunchMedia keywords */
3556 	"SinglePortrait",
3557 	"SingleRevLandscape",
3558 	"SingleLandscape",
3559 	"SingleRevPortrait",
3560 	"DualPortrait",
3561 	"DualLandscape",
3562 	"DualRevPortrait",
3563 	"DualRevLandscape",
3564 	"TriplePortrait",
3565 	"TripleLandscape",
3566 	"TripleRevPortrait",
3567 	"TripleRevLandscape",
3568 	"QuadPortrait",
3569 	"QuadLandscape",
3570 	"QuadRevPortrait",
3571 	"QuadRevLandscape",
3572 	"MultiplePortrait",
3573 	"MultipleLandscape",
3574 	"MultipleRevPortrait",
3575 	"MultipleRevLandscape"
3576       };
3577 
3578       cupsArrayAdd(fin_options, "*PunchMedia");
3579 
3580       human_readable = lookup_choice("punch", "finishing-template",
3581 				     opt_strings_catalog,
3582 				     printer_opt_strings_catalog);
3583       cupsFilePrintf(fp, "*OpenUI *PunchMedia/%s: PickOne\n",
3584 		     (human_readable ? human_readable :
3585 		      _cupsLangString(lang, _("Punch"))));
3586       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *PunchMedia\n");
3587       cupsFilePuts(fp, "*DefaultPunchMedia: None\n");
3588       cupsFilePrintf(fp, "*PunchMedia None/%s: \"\"\n",
3589 		     _cupsLangString(lang, _("None")));
3590 
3591       for (i = 0; i < count; i ++) {
3592         value   = ippGetInteger(attr, i);
3593         keyword = ippEnumString("finishings", value);
3594 
3595         if (!strncmp(keyword, "cups-punch-", 11))
3596           keyword += 5;
3597         else if (strncmp(keyword, "punch-", 6))
3598           continue;
3599 
3600         if (cupsArrayFind(names, (char *)keyword))
3601           continue; /* Already did this finishing template */
3602 
3603         cupsArrayAdd(names, (char *)keyword);
3604 
3605         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
3606           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
3607         else if (value >= IPP_FINISHINGS_PUNCH_TOP_LEFT &&
3608 		 value <= IPP_FINISHINGS_PUNCH_MULTIPLE_BOTTOM)
3609           ppd_keyword = punch_keywords[value - IPP_FINISHINGS_PUNCH_TOP_LEFT];
3610         else if (value >= IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT &&
3611 		 value <= IPP_FINISHINGS_CUPS_PUNCH_QUAD_BOTTOM)
3612           ppd_keyword = punch_keywords[value -
3613 				       IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT];
3614         else
3615           ppd_keyword = NULL;
3616 
3617         if (!ppd_keyword)
3618           continue;
3619 
3620 	snprintf(buf, sizeof(buf), "%d", value);
3621 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3622 				       printer_opt_strings_catalog);
3623 	if (human_readable == NULL)
3624 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3625 	       j ++)
3626 	    if (!strcmp(finishings[j][0], keyword)) {
3627 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3628 	      break;
3629 	    }
3630 	cupsFilePrintf(fp, "*PunchMedia %s%s%s: \"\"\n", ppd_keyword,
3631 		       (human_readable ? "/" : ""),
3632 		       (human_readable ? human_readable : ""));
3633 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n",
3634 		       value, keyword, ppd_keyword);
3635       }
3636 
3637       cupsFilePuts(fp, "*CloseUI: *PunchMedia\n");
3638     }
3639 
3640    /*
3641     * Booklet
3642     */
3643 
3644     if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER)) {
3645       cupsArrayAdd(fin_options, "*Booklet");
3646 
3647       human_readable = lookup_choice("booklet-maker", "finishing-template",
3648 				     opt_strings_catalog,
3649 				     printer_opt_strings_catalog);
3650       cupsFilePrintf(fp, "*OpenUI *Booklet/%s: Boolean\n",
3651 		     (human_readable ? human_readable :
3652 		      _cupsLangString(lang, _("Booklet"))));
3653       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *Booklet\n");
3654       cupsFilePuts(fp, "*DefaultBooklet: False\n");
3655       cupsFilePuts(fp, "*Booklet False: \"\"\n");
3656       cupsFilePuts(fp, "*Booklet True: \"\"\n");
3657       cupsFilePrintf(fp, "*cupsIPPFinishings %d/booklet-maker: \"*Booklet True\"\n",
3658 		     IPP_FINISHINGS_BOOKLET_MAKER);
3659       cupsFilePuts(fp, "*CloseUI: *Booklet\n");
3660     }
3661 
3662    /*
3663     * CutMedia
3664     */
3665 
3666     for (i = 0; i < count; i ++) {
3667       value   = ippGetInteger(attr, i);
3668       keyword = ippEnumString("finishings", value);
3669 
3670       if (!strcmp(keyword, "trim") || !strncmp(keyword, "trim-", 5))
3671         break;
3672     }
3673 
3674     if (i < count) {
3675       static const char * const trim_keywords[] =
3676       {				/* CutMedia keywords */
3677         "EndOfPage",
3678         "EndOfDoc",
3679         "EndOfSet",
3680         "EndOfJob"
3681       };
3682 
3683       cupsArrayAdd(fin_options, "*CutMedia");
3684 
3685       human_readable = lookup_choice("trim", "finishing-template",
3686 				     opt_strings_catalog,
3687 				     printer_opt_strings_catalog);
3688       cupsFilePrintf(fp, "*OpenUI *CutMedia/%s: PickOne\n",
3689 		     (human_readable ? human_readable :
3690 		      _cupsLangString(lang, _("Cut"))));
3691       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *CutMedia\n");
3692       cupsFilePuts(fp, "*DefaultCutMedia: None\n");
3693       cupsFilePrintf(fp, "*CutMedia None/%s: \"\"\n",
3694 		     _cupsLangString(lang, _("None")));
3695 
3696       for (i = 0; i < count; i ++) {
3697         value   = ippGetInteger(attr, i);
3698         keyword = ippEnumString("finishings", value);
3699 
3700 	if (strcmp(keyword, "trim") && strncmp(keyword, "trim-", 5))
3701           continue;
3702 
3703         if (cupsArrayFind(names, (char *)keyword))
3704           continue; /* Already did this finishing template */
3705 
3706         cupsArrayAdd(names, (char *)keyword);
3707 
3708         if (value == IPP_FINISHINGS_TRIM)
3709           ppd_keyword = "Auto";
3710 	else
3711 	  ppd_keyword = trim_keywords[value - IPP_FINISHINGS_TRIM_AFTER_PAGES];
3712 
3713 	snprintf(buf, sizeof(buf), "%d", value);
3714 	human_readable = lookup_choice(buf, "finishings", opt_strings_catalog,
3715 				       printer_opt_strings_catalog);
3716 	if (human_readable == NULL)
3717 	  for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0]));
3718 	       j ++)
3719 	    if (!strcmp(finishings[j][0], keyword)) {
3720 	      human_readable = (char *)_cupsLangString(lang, finishings[j][1]);
3721 	      break;
3722 	    }
3723 	cupsFilePrintf(fp, "*CutMedia %s%s%s: \"\"\n", ppd_keyword,
3724 		       (human_readable ? "/" : ""),
3725 		       (human_readable ? human_readable : ""));
3726 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*CutMedia %s\"\n",
3727 		       value, keyword, ppd_keyword);
3728       }
3729 
3730       cupsFilePuts(fp, "*CloseUI: *CutMedia\n");
3731     }
3732 
3733     cupsArrayDelete(names);
3734   }
3735 
3736   if ((attr = ippFindAttribute(response, "finishings-col-database",
3737 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
3738     ipp_t	*finishing_col;		/* Current finishing collection */
3739     ipp_attribute_t *finishing_attr;	/* Current finishing member attribute */
3740     cups_array_t *templates;		/* Finishing templates */
3741 
3742     cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickOne\n",
3743 		   _cupsLangString(lang, _("Finishing Preset")));
3744     cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n");
3745     cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: none\n");
3746     cupsFilePrintf(fp, "*cupsFinishingTemplate none/%s: \"\"\n",
3747 		   _cupsLangString(lang, _("None")));
3748 
3749     templates = cupsArrayNew((cups_array_func_t)strcmp, NULL);
3750     count     = ippGetCount(attr);
3751 
3752     for (i = 0; i < count; i ++) {
3753       finishing_col = ippGetCollection(attr, i);
3754       keyword = ippGetString(ippFindAttribute(finishing_col,
3755 					      "finishing-template",
3756 					      IPP_TAG_ZERO), 0, NULL);
3757 
3758       if (!keyword || cupsArrayFind(templates, (void *)keyword))
3759         continue;
3760 
3761       if (!strcmp(keyword, "none"))
3762         continue;
3763 
3764       cupsArrayAdd(templates, (void *)keyword);
3765 
3766       human_readable = lookup_choice((char *)keyword, "finishing-template",
3767 				     opt_strings_catalog,
3768 				     printer_opt_strings_catalog);
3769       if (human_readable == NULL)
3770 	human_readable = (char *)keyword;
3771       cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\n", keyword,
3772 		     human_readable);
3773       for (finishing_attr = ippFirstAttribute(finishing_col); finishing_attr;
3774 	   finishing_attr = ippNextAttribute(finishing_col)) {
3775         if (ippGetValueTag(finishing_attr) == IPP_TAG_BEGIN_COLLECTION) {
3776 	  const char *name = ippGetName(finishing_attr);
3777 					/* Member attribute name */
3778 
3779           if (strcmp(name, "media-size"))
3780             cupsFilePrintf(fp, "%% %s\n", name);
3781 	}
3782       }
3783       cupsFilePuts(fp, "\"\n");
3784       cupsFilePuts(fp, "*End\n");
3785     }
3786 
3787     cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");
3788 
3789     if (cupsArrayCount(fin_options)) {
3790       const char	*fin_option;	/* Current finishing option */
3791 
3792       cupsFilePuts(fp, "*cupsUIConstraint finishing-template: \"*cupsFinishingTemplate");
3793       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
3794 	   fin_option = (const char *)cupsArrayNext(fin_options))
3795         cupsFilePrintf(fp, " %s", fin_option);
3796       cupsFilePuts(fp, "\"\n");
3797 
3798       cupsFilePuts(fp, "*cupsUIResolver finishing-template: \"*cupsFinishingTemplate None");
3799       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
3800 	   fin_option = (const char *)cupsArrayNext(fin_options))
3801         cupsFilePrintf(fp, " %s None", fin_option);
3802       cupsFilePuts(fp, "\"\n");
3803     }
3804 
3805     cupsArrayDelete(templates);
3806   }
3807 
3808   cupsArrayDelete(fin_options);
3809 
3810  /*
3811   * DefaultResolution...
3812   */
3813 
3814   xres = common_def->x;
3815   yres = common_def->y;
3816   if (xres == yres)
3817     cupsFilePrintf(fp, "*DefaultResolution: %ddpi\n", xres);
3818   else
3819     cupsFilePrintf(fp, "*DefaultResolution: %dx%ddpi\n", xres, yres);
3820 
3821  /*
3822   * cupsPrintQuality...
3823   */
3824 
3825   if ((quality =
3826        ippFindAttribute(response, "print-quality-supported",
3827 			IPP_TAG_ENUM)) != NULL) {
3828     human_readable = lookup_option("print-quality", opt_strings_catalog,
3829 				   printer_opt_strings_catalog);
3830     cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
3831 		   "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
3832 		   "*DefaultcupsPrintQuality: Normal\n",
3833 		   (human_readable ? human_readable :
3834 		    _cupsLangString(lang, _("Print Quality"))));
3835     if (ippContainsInteger(quality, IPP_QUALITY_DRAFT)) {
3836       human_readable = lookup_choice("3", "print-quality", opt_strings_catalog,
3837 				     printer_opt_strings_catalog);
3838       cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3839 		     (human_readable ? human_readable :
3840 		      _cupsLangString(lang, _("Draft"))),
3841 		     min_res->x, min_res->y);
3842     }
3843     human_readable = lookup_choice("4", "print-quality", opt_strings_catalog,
3844 				   printer_opt_strings_catalog);
3845     cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3846 		   (human_readable ? human_readable :
3847 		    _cupsLangString(lang, _("Normal"))),
3848 		   common_def->x, common_def->y);
3849     if (ippContainsInteger(quality, IPP_QUALITY_HIGH)) {
3850       human_readable = lookup_choice("5", "print-quality", opt_strings_catalog,
3851 				     printer_opt_strings_catalog);
3852       cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
3853 		     (human_readable ? human_readable :
3854 		      _cupsLangString(lang, _("High"))),
3855 		     max_res->x, max_res->y);
3856     }
3857     cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
3858   }
3859 
3860   /* Only add these options if jobs get sent to the printer as PDF,
3861      PWG Raster, or Apple Raster, as only then arbitrary IPP
3862      attributes get passed through from the filter command line
3863      to the printer by the "ipp" CUPS backend. */
3864   if (is_pdf || is_pwg || is_apple) {
3865     /*
3866      * Print Optimization ...
3867      */
3868 
3869     if ((attr = ippFindAttribute(response, "print-content-optimize-default",
3870 				 IPP_TAG_ZERO)) != NULL)
3871       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
3872     else
3873       strlcpy(ppdname, "auto", sizeof(ppdname));
3874 
3875     if ((attr = ippFindAttribute(response, "print-content-optimize-supported",
3876 				 IPP_TAG_ZERO)) != NULL &&
3877 	(count = ippGetCount(attr)) > 1) {
3878       static const char * const content_optimize_types[][2] =
3879       {					/* "print-content-optimize" strings */
3880 	{ "auto", _("Automatic") },
3881 	{ "graphic", _("Graphics") },
3882 	{ "graphics", _("Graphics") },
3883 	{ "photo", _("Photo") },
3884 	{ "text", _("Text") },
3885 	{ "text-and-graphic", _("Text And Graphics") },
3886 	{ "text-and-graphics", _("Text And Graphics") }
3887       };
3888 
3889       human_readable = lookup_option("print-content-optimize",
3890 				     opt_strings_catalog,
3891 				     printer_opt_strings_catalog);
3892       cupsFilePrintf(fp, "*OpenUI *print-content-optimize/%s: PickOne\n"
3893 		     "*OrderDependency: 10 AnySetup *print-content-optimize\n"
3894 		     "*Defaultprint-content-optimize: %s\n",
3895 		     (human_readable ? human_readable : "Print Optimization"),
3896 		     ppdname);
3897       for (i = 0; i < count; i ++) {
3898 	keyword = ippGetString(attr, i, NULL);
3899 
3900 	human_readable = lookup_choice((char *)keyword,
3901 				       "print-content-optimize",
3902 				       opt_strings_catalog,
3903 				       printer_opt_strings_catalog);
3904 	if (human_readable == NULL)
3905 	  for (j = 0;
3906 	       j < (int)(sizeof(content_optimize_types) /
3907 			 sizeof(content_optimize_types[0]));
3908 	       j ++)
3909 	    if (!strcmp(content_optimize_types[j][0], keyword)) {
3910 	      human_readable =
3911 		(char *)_cupsLangString(lang,
3912 					content_optimize_types[j][1]);
3913 	      break;
3914 	    }
3915 	cupsFilePrintf(fp, "*print-content-optimize %s%s%s: \"\"\n",
3916 		       keyword,
3917 		       (human_readable ? "/" : ""),
3918 		       (human_readable ? human_readable : ""));
3919       }
3920       cupsFilePuts(fp, "*CloseUI: *print-content-optimize\n");
3921     }
3922 
3923     /*
3924      * Print Rendering Intent ...
3925      */
3926 
3927     if ((attr = ippFindAttribute(response, "print-rendering-intent-default",
3928 				 IPP_TAG_ZERO)) != NULL)
3929       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
3930     else
3931       strlcpy(ppdname, "auto", sizeof(ppdname));
3932 
3933     if ((attr = ippFindAttribute(response, "print-rendering-intent-supported",
3934 				 IPP_TAG_ZERO)) != NULL &&
3935 	(count = ippGetCount(attr)) > 1) {
3936       static const char * const rendering_intents[][2] =
3937       {					/* "print-rendering-intent" strings */
3938 	{ "auto", _("Automatic") },
3939 	{ "absolute", _("Absolute") },
3940 	{ "perceptual", _("Perceptual") },
3941 	{ "relative", _("Relative") },
3942 	{ "relative-bpc", _("Relative w/Black Point Compensation") },
3943 	{ "saturation", _("Saturation") }
3944       };
3945 
3946       human_readable = lookup_option("print-rendering-intent",
3947 				     opt_strings_catalog,
3948 				     printer_opt_strings_catalog);
3949       cupsFilePrintf(fp, "*OpenUI *print-rendering-intent/%s: PickOne\n"
3950 		     "*OrderDependency: 10 AnySetup *print-rendering-intent\n"
3951 		     "*Defaultprint-rendering-intent: %s\n",
3952 		     (human_readable ? human_readable :
3953 		      "Print Rendering Intent"),
3954 		     ppdname);
3955       for (i = 0; i < count; i ++) {
3956 	keyword = ippGetString(attr, i, NULL);
3957 
3958 	human_readable = lookup_choice((char *)keyword,
3959 				       "print-rendering-intent",
3960 				       opt_strings_catalog,
3961 				       printer_opt_strings_catalog);
3962 	if (human_readable == NULL)
3963 	  for (j = 0;
3964 	       j < (int)(sizeof(rendering_intents) /
3965 			 sizeof(rendering_intents[0]));
3966 	       j ++)
3967 	    if (!strcmp(rendering_intents[j][0], keyword)) {
3968 	      human_readable =
3969 		(char *)_cupsLangString(lang,
3970 					rendering_intents[j][1]);
3971 	      break;
3972 	    }
3973 	cupsFilePrintf(fp, "*print-rendering-intent %s%s%s: \"\"\n",
3974 		       keyword,
3975 		       (human_readable ? "/" : ""),
3976 		       (human_readable ? human_readable : ""));
3977       }
3978       cupsFilePuts(fp, "*CloseUI: *print-rendering-intent\n");
3979     }
3980 
3981     /*
3982      * Print Scaling ...
3983      */
3984 
3985     if ((attr = ippFindAttribute(response, "print-scaling-default",
3986 				 IPP_TAG_ZERO)) != NULL)
3987       strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
3988     else
3989       strlcpy(ppdname, "auto", sizeof(ppdname));
3990 
3991     if ((attr = ippFindAttribute(response, "print-scaling-supported",
3992 				 IPP_TAG_ZERO)) != NULL &&
3993 	(count = ippGetCount(attr)) > 1) {
3994       static const char * const scaling_types[][2] =
3995       {					/* "print-scaling" strings */
3996 	{ "auto", _("Automatic") },
3997 	{ "auto-fit", _("Auto Fit") },
3998 	{ "fill", _("Fill") },
3999 	{ "fit", _("Fit") },
4000 	{ "none", _("None") }
4001       };
4002 
4003       human_readable = lookup_option("print-scaling", opt_strings_catalog,
4004 				     printer_opt_strings_catalog);
4005       cupsFilePrintf(fp, "*OpenUI *print-scaling/%s: PickOne\n"
4006 		     "*OrderDependency: 10 AnySetup *print-scaling\n"
4007 		     "*Defaultprint-scaling: %s\n",
4008 		     (human_readable ? human_readable : "Print Scaling"),
4009 		     ppdname);
4010       for (i = 0; i < count; i ++) {
4011 	keyword = ippGetString(attr, i, NULL);
4012 
4013 	human_readable = lookup_choice((char *)keyword, "print-scaling",
4014 				       opt_strings_catalog,
4015 				       printer_opt_strings_catalog);
4016 	if (human_readable == NULL)
4017 	  for (j = 0;
4018 	       j < (int)(sizeof(scaling_types) /
4019 			 sizeof(scaling_types[0]));
4020 	       j ++)
4021 	    if (!strcmp(scaling_types[j][0], keyword)) {
4022 	      human_readable =
4023 		(char *)_cupsLangString(lang, scaling_types[j][1]);
4024 	      break;
4025 	    }
4026 	cupsFilePrintf(fp, "*print-scaling %s%s%s: \"\"\n",
4027 		       keyword,
4028 		       (human_readable ? "/" : ""),
4029 		       (human_readable ? human_readable : ""));
4030       }
4031       cupsFilePuts(fp, "*CloseUI: *print-scaling\n");
4032     }
4033   }
4034 
4035  /*
4036   * Phone Options for Fax..
4037   */
4038 
4039   if (is_fax) {
4040     human_readable = lookup_option("Phone", opt_strings_catalog,
4041 				   printer_opt_strings_catalog);
4042 
4043     cupsFilePrintf(fp, "*OpenUI *phone/%s: PickOne\n"
4044 		   "*OrderDependency: 10 AnySetup *phone\n"
4045 		   "*Defaultphone: None\n"
4046 		   "*phone None: \"\"\n"
4047 		   "*CloseUI: *phone\n",
4048 		   (human_readable ? human_readable : "Phone Number"));
4049     cupsFilePrintf(fp,"*Customphone True: \"\"\n"
4050 		   "*ParamCustomphone Text: 1 string 0 64\n");
4051 
4052     human_readable = lookup_option("faxPrefix", opt_strings_catalog,
4053 				   printer_opt_strings_catalog);
4054 
4055     cupsFilePrintf(fp, "*OpenUI *faxPrefix/%s: PickOne\n"
4056 		   "*OrderDependency: 10 AnySetup *faxPrefix\n"
4057 		   "*DefaultfaxPrefix: None\n"
4058 		   "*faxPrefix None: \"\"\n"
4059 		   "*CloseUI: *faxPrefix\n",
4060 		   (human_readable ? human_readable : "Pre-Dial Number"));
4061     cupsFilePrintf(fp,"*CustomfaxPrefix True: \"\"\n"
4062 		   "*ParamCustomfaxPrefix Text: 1 string 0 64\n");
4063   }
4064 
4065  /*
4066   * Presets...
4067   */
4068 
4069   if ((attr = ippFindAttribute(response, "job-presets-supported",
4070 			       IPP_TAG_BEGIN_COLLECTION)) != NULL) {
4071     for (i = 0, count = ippGetCount(attr); i < count; i ++) {
4072       ipp_t	*preset = ippGetCollection(attr, i); /* Preset collection */
4073       const char *preset_name =         /* Preset name */
4074 	ippGetString(ippFindAttribute(preset,
4075 				      "preset-name", IPP_TAG_ZERO), 0, NULL),
4076 		 *localized_name;	/* Localized preset name */
4077       ipp_attribute_t *member;		/* Member attribute in preset */
4078       const char *member_name;		/* Member attribute name */
4079       char       member_value[256];	/* Member attribute value */
4080 
4081       if (!preset || !preset_name)
4082         continue;
4083 
4084       if ((localized_name = lookup_option((char *)preset_name,
4085 					  opt_strings_catalog,
4086 					  printer_opt_strings_catalog)) == NULL)
4087         cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", preset_name);
4088       else
4089         cupsFilePrintf(fp, "*APPrinterPreset %s/%s: \"\n", preset_name,
4090 		       localized_name);
4091 
4092       for (member = ippFirstAttribute(preset); member;
4093 	   member = ippNextAttribute(preset)) {
4094         member_name = ippGetName(member);
4095 
4096         if (!member_name || !strcmp(member_name, "preset-name"))
4097           continue;
4098 
4099         if (!strcmp(member_name, "finishings")) {
4100 	  for (i = 0, count = ippGetCount(member); i < count; i ++) {
4101 	    const char *option = NULL;	/* PPD option name */
4102 
4103 	    keyword = ippEnumString("finishings", ippGetInteger(member, i));
4104 
4105 	    if (!strcmp(keyword, "booklet-maker")) {
4106 	      option  = "Booklet";
4107 	      keyword = "True";
4108 	    } else if (!strncmp(keyword, "fold-", 5))
4109 	      option = "FoldType";
4110 	    else if (!strncmp(keyword, "punch-", 6))
4111 	      option = "PunchMedia";
4112 	    else if (!strncmp(keyword, "bind-", 5) ||
4113 		     !strncmp(keyword, "edge-stitch-", 12) ||
4114 		     !strcmp(keyword, "saddle-stitch") ||
4115 		     !strncmp(keyword, "staple-", 7))
4116 	      option = "StapleLocation";
4117 
4118 	    if (option && keyword)
4119 	      cupsFilePrintf(fp, "*%s %s\n", option, keyword);
4120 	  }
4121         } else if (!strcmp(member_name, "finishings-col")) {
4122           ipp_t *fin_col;		/* finishings-col value */
4123 
4124           for (i = 0, count = ippGetCount(member); i < count; i ++) {
4125             fin_col = ippGetCollection(member, i);
4126 
4127             if ((keyword =
4128 		 ippGetString(ippFindAttribute(fin_col,
4129 					       "finishing-template",
4130 					       IPP_TAG_ZERO), 0, NULL)) != NULL)
4131               cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", keyword);
4132           }
4133         } else if (!strcmp(member_name, "media")) {
4134          /*
4135           * Map media to PageSize...
4136           */
4137 
4138           if ((pwg = pwgMediaForPWG(ippGetString(member, 0, NULL))) != NULL &&
4139 	      pwg->ppd)
4140             cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
4141         } else if (!strcmp(member_name, "media-col")) {
4142           media_col = ippGetCollection(member, 0);
4143 
4144           if ((media_size =
4145 	       ippGetCollection(ippFindAttribute(media_col,
4146 						 "media-size",
4147 						 IPP_TAG_BEGIN_COLLECTION),
4148 				0)) != NULL) {
4149             x_dim = ippFindAttribute(media_size, "x-dimension",
4150 				     IPP_TAG_INTEGER);
4151             y_dim = ippFindAttribute(media_size, "y-dimension",
4152 				     IPP_TAG_INTEGER);
4153             if ((pwg = pwgMediaForSize(ippGetInteger(x_dim, 0),
4154 				       ippGetInteger(y_dim, 0))) != NULL &&
4155 		pwg->ppd)
4156 	      cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
4157           }
4158 
4159           if ((keyword = ippGetString(ippFindAttribute(media_col,
4160 						       "media-source",
4161 						       IPP_TAG_ZERO), 0,
4162 				      NULL)) != NULL) {
4163             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4164             cupsFilePrintf(fp, "*InputSlot %s\n", keyword);
4165 	  }
4166 
4167           if ((keyword = ippGetString(ippFindAttribute(media_col, "media-type",
4168 						       IPP_TAG_ZERO), 0,
4169 				      NULL)) != NULL) {
4170             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4171             cupsFilePrintf(fp, "*MediaType %s\n", keyword);
4172 	  }
4173         } else if (!strcmp(member_name, "print-quality")) {
4174 	 /*
4175 	  * Map print-quality to cupsPrintQuality...
4176 	  */
4177 
4178           int qval = ippGetInteger(member, 0);
4179 					/* print-quality value */
4180 	  static const char * const qualities[] = { "Draft", "Normal", "High" };
4181 					/* cupsPrintQuality values */
4182 
4183           if (qval >= IPP_QUALITY_DRAFT && qval <= IPP_QUALITY_HIGH)
4184             cupsFilePrintf(fp, "*cupsPrintQuality %s\n",
4185 			   qualities[qval - IPP_QUALITY_DRAFT]);
4186         } else if (!strcmp(member_name, "output-bin")) {
4187           pwg_ppdize_name(ippGetString(member, 0, NULL), ppdname,
4188 			  sizeof(ppdname));
4189           cupsFilePrintf(fp, "*OutputBin %s\n", ppdname);
4190         } else if (!strcmp(member_name, "sides")) {
4191           keyword = ippGetString(member, 0, NULL);
4192           if (keyword && !strcmp(keyword, "one-sided"))
4193             cupsFilePuts(fp, "*Duplex None\n");
4194 	  else if (keyword && !strcmp(keyword, "two-sided-long-edge"))
4195 	    cupsFilePuts(fp, "*Duplex DuplexNoTumble\n");
4196 	  else if (keyword && !strcmp(keyword, "two-sided-short-edge"))
4197 	    cupsFilePuts(fp, "*Duplex DuplexTumble\n");
4198         } else {
4199          /*
4200           * Add attribute name and value as-is...
4201           */
4202 
4203           ippAttributeString(member, member_value, sizeof(member_value));
4204           cupsFilePrintf(fp, "*%s %s\n", member_name, member_value);
4205 	}
4206       }
4207 
4208       cupsFilePuts(fp, "\"\n*End\n");
4209     }
4210   }
4211 
4212  /*
4213   * constraints
4214   */
4215   if (conflicts != NULL) {
4216     char* constraint;
4217     for (constraint = (char *)cupsArrayFirst(conflicts); constraint;
4218          constraint = (char *)cupsArrayNext(conflicts)) {
4219       cupsFilePrintf(fp, "%s", constraint);
4220     }
4221   }
4222 
4223  /*
4224   * Close up and return...
4225   */
4226 
4227   free(common_def);
4228   free(min_res);
4229   free(max_res);
4230 
4231   snprintf(ppdgenerator_msg, sizeof(ppdgenerator_msg),
4232 	   "%s %sPPD generated.",
4233 	   (is_apple ? "Apple Raster" :
4234 	    (is_pwg ? "PWG Raster" :
4235 	     (is_pdf ? "PDF" :
4236 	      (is_pclm ? "PCLm" :
4237 	       "Legacy IPP printer")))),
4238 	   (is_fax ? "Fax " : ""));
4239 
4240   cupsFileClose(fp);
4241   if (printer_opt_strings_catalog)
4242     cupsArrayDelete(printer_opt_strings_catalog);
4243 
4244   return (buffer);
4245 
4246  /*
4247   * If we get here then there was a problem creating the PPD...
4248   */
4249 
4250  bad_ppd:
4251 
4252   if (common_res) cupsArrayDelete(common_res);
4253   if (common_def) free(common_def);
4254   if (min_res) free(min_res);
4255   if (max_res) free(max_res);
4256 
4257   cupsFileClose(fp);
4258   if (printer_opt_strings_catalog)
4259     cupsArrayDelete(printer_opt_strings_catalog);
4260   unlink(buffer);
4261   *buffer = '\0';
4262 
4263   _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
4264 		_("Printer does not support required IPP attributes or document formats."),
4265 		1);
4266 
4267   return (NULL);
4268 }
4269 
4270 
4271 /*
4272  * '_pwgInputSlotForSource()' - Get the InputSlot name for the given PWG
4273  *                              media-source.
4274  */
4275 
4276 const char *				/* O - InputSlot name */
_pwgInputSlotForSource(const char * media_source,char * name,size_t namesize)4277 _pwgInputSlotForSource(
4278     const char *media_source,		/* I - PWG media-source */
4279     char       *name,			/* I - Name buffer */
4280     size_t     namesize)		/* I - Size of name buffer */
4281 {
4282  /*
4283   * Range check input...
4284   */
4285 
4286   if (!media_source || !name || namesize < PPD_MAX_NAME)
4287     return (NULL);
4288 
4289   if (_cups_strcasecmp(media_source, "main"))
4290     strlcpy(name, "Cassette", namesize);
4291   else if (_cups_strcasecmp(media_source, "alternate"))
4292     strlcpy(name, "Multipurpose", namesize);
4293   else if (_cups_strcasecmp(media_source, "large-capacity"))
4294     strlcpy(name, "LargeCapacity", namesize);
4295   else if (_cups_strcasecmp(media_source, "bottom"))
4296     strlcpy(name, "Lower", namesize);
4297   else if (_cups_strcasecmp(media_source, "middle"))
4298     strlcpy(name, "Middle", namesize);
4299   else if (_cups_strcasecmp(media_source, "top"))
4300     strlcpy(name, "Upper", namesize);
4301   else if (_cups_strcasecmp(media_source, "rear"))
4302     strlcpy(name, "Rear", namesize);
4303   else if (_cups_strcasecmp(media_source, "side"))
4304     strlcpy(name, "Side", namesize);
4305   else if (_cups_strcasecmp(media_source, "envelope"))
4306     strlcpy(name, "Envelope", namesize);
4307   else if (_cups_strcasecmp(media_source, "main-roll"))
4308     strlcpy(name, "Roll", namesize);
4309   else if (_cups_strcasecmp(media_source, "alternate-roll"))
4310     strlcpy(name, "Roll2", namesize);
4311   else
4312     pwg_ppdize_name(media_source, name, namesize);
4313 
4314   return (name);
4315 }
4316 
4317 
4318 /*
4319  * '_pwgMediaTypeForType()' - Get the MediaType name for the given PWG
4320  *                            media-type.
4321  */
4322 
4323 const char *				/* O - MediaType name */
_pwgMediaTypeForType(const char * media_type,char * name,size_t namesize)4324 _pwgMediaTypeForType(
4325     const char *media_type,		/* I - PWG media-type */
4326     char       *name,			/* I - Name buffer */
4327     size_t     namesize)		/* I - Size of name buffer */
4328 {
4329  /*
4330   * Range check input...
4331   */
4332 
4333   if (!media_type || !name || namesize < PPD_MAX_NAME)
4334     return (NULL);
4335 
4336   if (_cups_strcasecmp(media_type, "auto"))
4337     strlcpy(name, "Auto", namesize);
4338   else if (_cups_strcasecmp(media_type, "cardstock"))
4339     strlcpy(name, "Cardstock", namesize);
4340   else if (_cups_strcasecmp(media_type, "envelope"))
4341     strlcpy(name, "Envelope", namesize);
4342   else if (_cups_strcasecmp(media_type, "photographic-glossy"))
4343     strlcpy(name, "Glossy", namesize);
4344   else if (_cups_strcasecmp(media_type, "photographic-high-gloss"))
4345     strlcpy(name, "HighGloss", namesize);
4346   else if (_cups_strcasecmp(media_type, "photographic-matte"))
4347     strlcpy(name, "Matte", namesize);
4348   else if (_cups_strcasecmp(media_type, "stationery"))
4349     strlcpy(name, "Plain", namesize);
4350   else if (_cups_strcasecmp(media_type, "stationery-coated"))
4351     strlcpy(name, "Coated", namesize);
4352   else if (_cups_strcasecmp(media_type, "stationery-inkjet"))
4353     strlcpy(name, "Inkjet", namesize);
4354   else if (_cups_strcasecmp(media_type, "stationery-letterhead"))
4355     strlcpy(name, "Letterhead", namesize);
4356   else if (_cups_strcasecmp(media_type, "stationery-preprinted"))
4357     strlcpy(name, "Preprinted", namesize);
4358   else if (_cups_strcasecmp(media_type, "transparency"))
4359     strlcpy(name, "Transparency", namesize);
4360   else
4361     pwg_ppdize_name(media_type, name, namesize);
4362 
4363   return (name);
4364 }
4365 
4366 
4367 /*
4368  * '_pwgPageSizeForMedia()' - Get the PageSize name for the given media.
4369  */
4370 
4371 const char *				/* O - PageSize name */
_pwgPageSizeForMedia(pwg_media_t * media,char * name,size_t namesize)4372 _pwgPageSizeForMedia(
4373     pwg_media_t *media,		/* I - Media */
4374     char         *name,			/* I - PageSize name buffer */
4375     size_t       namesize)		/* I - Size of name buffer */
4376 {
4377   const char	*sizeptr,		/* Pointer to size in PWG name */
4378 		*dimptr;		/* Pointer to dimensions in PWG name */
4379 
4380 
4381  /*
4382   * Range check input...
4383   */
4384 
4385   if (!media || !name || namesize < PPD_MAX_NAME)
4386     return (NULL);
4387 
4388  /*
4389   * Copy or generate a PageSize name...
4390   */
4391 
4392   if (media->ppd) {
4393    /*
4394     * Use a standard Adobe name...
4395     */
4396 
4397     strlcpy(name, media->ppd, namesize);
4398   }
4399   else if (!media->pwg || !strncmp(media->pwg, "custom_", 7) ||
4400            (sizeptr = strchr(media->pwg, '_')) == NULL ||
4401 	   (dimptr = strchr(sizeptr + 1, '_')) == NULL ||
4402 	   (size_t)(dimptr - sizeptr) > namesize) {
4403    /*
4404     * Use a name of the form "wNNNhNNN"...
4405     */
4406 
4407     snprintf(name, namesize, "w%dh%d", (int)PWG_TO_POINTS(media->width),
4408              (int)PWG_TO_POINTS(media->length));
4409   } else {
4410    /*
4411     * Copy the size name from class_sizename_dimensions...
4412     */
4413 
4414     memcpy(name, sizeptr + 1, (size_t)(dimptr - sizeptr - 1));
4415     name[dimptr - sizeptr - 1] = '\0';
4416   }
4417 
4418   return (name);
4419 }
4420 
4421 
4422 /*
4423  * 'pwg_ppdize_name()' - Convert an IPP keyword to a PPD keyword.
4424  */
4425 
4426 static void
pwg_ppdize_name(const char * ipp,char * name,size_t namesize)4427 pwg_ppdize_name(const char *ipp,	/* I - IPP keyword */
4428                 char       *name,	/* I - Name buffer */
4429 		size_t     namesize)	/* I - Size of name buffer */
4430 {
4431   char	*ptr,				/* Pointer into name buffer */
4432 	*end;				/* End of name buffer */
4433 
4434 
4435   *name = (char)toupper(*ipp++);
4436 
4437   for (ptr = name + 1, end = name + namesize - 1; *ipp && ptr < end;) {
4438     if (*ipp == '-') {
4439       ipp ++;
4440       if (_cups_isalpha(*ipp))
4441 	*ptr++ = (char)toupper(*ipp++ & 255);
4442     } else
4443       *ptr++ = *ipp++;
4444   }
4445 
4446   *ptr = '\0';
4447 }
4448 
4449 
4450 
4451 /*
4452  * 'pwg_ppdize_resolution()' - Convert PWG resolution values to PPD values.
4453  */
4454 
4455 static void
pwg_ppdize_resolution(ipp_attribute_t * attr,int element,int * xres,int * yres,char * name,size_t namesize)4456 pwg_ppdize_resolution(
4457     ipp_attribute_t *attr,		/* I - Attribute to convert */
4458     int             element,		/* I - Element to convert */
4459     int             *xres,		/* O - X resolution in DPI */
4460     int             *yres,		/* O - Y resolution in DPI */
4461     char            *name,		/* I - Name buffer */
4462     size_t          namesize)		/* I - Size of name buffer */
4463 {
4464   ipp_res_t units;			/* Units for resolution */
4465 
4466   *xres = ippGetResolution(attr, element, yres, &units);
4467 
4468   if (units == IPP_RES_PER_CM) {
4469     *xres = (int)(*xres * 2.54);
4470     *yres = (int)(*yres * 2.54);
4471   }
4472 
4473   if (name && namesize > 4) {
4474     if (*xres == *yres)
4475       snprintf(name, namesize, "%ddpi", *xres);
4476     else
4477       snprintf(name, namesize, "%dx%ddpi", *xres, *yres);
4478   }
4479 }
4480 #endif /* HAVE_CUPS_1_6 */
4481