• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * PPD cache implementation for CUPS.
3  *
4  * Copyright © 2022-2025 by OpenPrinting.
5  * Copyright © 2010-2021 by Apple Inc.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
8  * information.
9  */
10 
11 /*
12  * Include necessary headers...
13  */
14 
15 #include "cups-private.h"
16 #include "ppd-private.h"
17 #include "debug-internal.h"
18 #include <math.h>
19 
20 
21 /*
22  * Macro to test for two almost-equal PWG measurements.
23  */
24 
25 #define _PWG_EQUIVALENT(x, y)	(abs((x)-(y)) < 2)
26 
27 
28 /*
29  * Local functions...
30  */
31 
32 static int	cups_connect(http_t **http, const char *url, char *resource, size_t ressize);
33 static int	cups_get_url(http_t **http, const char *url, char *name, size_t namesize);
34 static const char *ppd_inputslot_for_keyword(_ppd_cache_t *pc, const char *keyword);
35 static void	ppd_put_string(cups_file_t *fp, cups_lang_t *lang, cups_array_t *strings, const char *ppd_option, const char *ppd_choice, const char *pwg_msgid);
36 static void	pwg_add_finishing(cups_array_t *finishings, ipp_finishings_t template, const char *name, const char *value);
37 static void	pwg_add_message(cups_array_t *a, const char *msg, const char *str);
38 static int	pwg_compare_finishings(_pwg_finishings_t *a, _pwg_finishings_t *b);
39 static int	pwg_compare_sizes(cups_size_t *a, cups_size_t *b);
40 static cups_size_t *pwg_copy_size(cups_size_t *size);
41 static void	pwg_free_finishings(_pwg_finishings_t *f);
42 static void	pwg_ppdize_name(const char *ipp, char *name, size_t namesize);
43 static void	pwg_ppdize_resolution(ipp_attribute_t *attr, int element, int *xres, int *yres, char *name, size_t namesize);
44 static void	pwg_unppdize_name(const char *ppd, char *name, size_t namesize,
45 		                  const char *dashchars);
46 
47 
48 /*
49  * '_cupsConvertOptions()' - Convert printer options to standard IPP attributes.
50  *
51  * This functions converts PPD and CUPS-specific options to their standard IPP
52  * attributes and values and adds them to the specified IPP request.
53  */
54 
55 int					/* O - New number of copies */
_cupsConvertOptions(ipp_t * request,ppd_file_t * ppd,_ppd_cache_t * pc,ipp_attribute_t * media_col_sup,ipp_attribute_t * doc_handling_sup,ipp_attribute_t * print_color_mode_sup,const char * user,const char * format,int copies,int num_options,cups_option_t * options)56 _cupsConvertOptions(
57     ipp_t           *request,		/* I - IPP request */
58     ppd_file_t      *ppd,		/* I - PPD file */
59     _ppd_cache_t    *pc,		/* I - PPD cache info */
60     ipp_attribute_t *media_col_sup,	/* I - media-col-supported values */
61     ipp_attribute_t *doc_handling_sup,	/* I - multiple-document-handling-supported values */
62     ipp_attribute_t *print_color_mode_sup,
63                                 	/* I - Printer supports print-color-mode */
64     const char    *user,		/* I - User info */
65     const char    *format,		/* I - document-format value */
66     int           copies,		/* I - Number of copies */
67     int           num_options,		/* I - Number of options */
68     cups_option_t *options)		/* I - Options */
69 {
70   int		i;			/* Looping var */
71   const char	*keyword,		/* PWG keyword */
72 		*password;		/* Password string */
73   pwg_size_t	*size;			/* PWG media size */
74   ipp_t		*media_col,		/* media-col value */
75 		*media_size;		/* media-size value */
76   const char	*media_source,		/* media-source value */
77 		*media_type,		/* media-type value */
78 		*collate_str,		/* multiple-document-handling value */
79 		*color_attr_name,	/* Supported color attribute */
80 		*mandatory,		/* Mandatory attributes */
81 		*finishing_template;	/* Finishing template */
82   int		num_finishings = 0,	/* Number of finishing values */
83 		finishings[10];		/* Finishing enum values */
84   ppd_choice_t	*choice;		/* Marked choice */
85   int           finishings_copies = copies,
86                                         /* Number of copies for finishings */
87                 job_pages = 0,		/* job-pages value */
88 		number_up = 1;		/* number-up value */
89   const char	*value;			/* Option value */
90 
91 
92  /*
93   * Send standard IPP attributes...
94   */
95 
96   if (pc->password && (password = cupsGetOption("job-password", num_options, options)) != NULL && ippGetOperation(request) != IPP_OP_VALIDATE_JOB)
97   {
98     ipp_attribute_t	*attr = NULL;	/* job-password attribute */
99 
100     if ((keyword = cupsGetOption("job-password-encryption", num_options, options)) == NULL)
101       keyword = "none";
102 
103     if (!strcmp(keyword, "none"))
104     {
105      /*
106       * Add plain-text job-password...
107       */
108 
109       attr = ippAddOctetString(request, IPP_TAG_OPERATION, "job-password", password, (int)strlen(password));
110     }
111     else
112     {
113      /*
114       * Add hashed job-password...
115       */
116 
117       unsigned char	hash[64];	/* Hash of password */
118       ssize_t		hashlen;	/* Length of hash */
119 
120       if ((hashlen = cupsHashData(keyword, password, strlen(password), hash, sizeof(hash))) > 0)
121         attr = ippAddOctetString(request, IPP_TAG_OPERATION, "job-password", hash, (int)hashlen);
122     }
123 
124     if (attr)
125       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "job-password-encryption", NULL, keyword);
126   }
127 
128   if (pc->account_id)
129   {
130     if ((keyword = cupsGetOption("job-account-id", num_options, options)) == NULL)
131       keyword = cupsGetOption("job-billing", num_options, options);
132 
133     if (keyword)
134       ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, "job-account-id", NULL, keyword);
135   }
136 
137   if (pc->accounting_user_id)
138   {
139     if ((keyword = cupsGetOption("job-accounting-user-id", num_options, options)) == NULL)
140       keyword = user;
141 
142     if (keyword)
143       ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, "job-accounting-user-id", NULL, keyword);
144   }
145 
146   for (mandatory = (const char *)cupsArrayFirst(pc->mandatory); mandatory; mandatory = (const char *)cupsArrayNext(pc->mandatory))
147   {
148     if (strcmp(mandatory, "copies") &&
149 	strcmp(mandatory, "destination-uris") &&
150 	strcmp(mandatory, "finishings") &&
151 	strcmp(mandatory, "finishings-col") &&
152 	strcmp(mandatory, "finishing-template") &&
153 	strcmp(mandatory, "job-account-id") &&
154 	strcmp(mandatory, "job-accounting-user-id") &&
155 	strcmp(mandatory, "job-password") &&
156 	strcmp(mandatory, "job-password-encryption") &&
157 	strcmp(mandatory, "media") &&
158 	strncmp(mandatory, "media-col", 9) &&
159 	strcmp(mandatory, "multiple-document-handling") &&
160 	strcmp(mandatory, "output-bin") &&
161 	strcmp(mandatory, "print-color-mode") &&
162 	strcmp(mandatory, "print-quality") &&
163 	strcmp(mandatory, "sides") &&
164 	(keyword = cupsGetOption(mandatory, num_options, options)) != NULL)
165     {
166       _ipp_option_t *opt = _ippFindOption(mandatory);
167 				    /* Option type */
168       ipp_tag_t	value_tag = opt ? opt->value_tag : IPP_TAG_NAME;
169 				    /* Value type */
170 
171       switch (value_tag)
172       {
173 	case IPP_TAG_INTEGER :
174 	case IPP_TAG_ENUM :
175 	    ippAddInteger(request, IPP_TAG_JOB, value_tag, mandatory, atoi(keyword));
176 	    break;
177 	case IPP_TAG_BOOLEAN :
178 	    ippAddBoolean(request, IPP_TAG_JOB, mandatory, !_cups_strcasecmp(keyword, "true"));
179 	    break;
180 	case IPP_TAG_RANGE :
181 	    {
182 	      int lower, upper;	/* Range */
183 
184 	      if (sscanf(keyword, "%d-%d", &lower, &upper) != 2)
185 		lower = upper = atoi(keyword);
186 
187 	      ippAddRange(request, IPP_TAG_JOB, mandatory, lower, upper);
188 	    }
189 	    break;
190 	case IPP_TAG_STRING :
191 	    ippAddOctetString(request, IPP_TAG_JOB, mandatory, keyword, (int)strlen(keyword));
192 	    break;
193 	default :
194 	    if (!strcmp(mandatory, "print-color-mode") && !strcmp(keyword, "monochrome"))
195 	    {
196 	      if (ippContainsString(print_color_mode_sup, "auto-monochrome"))
197 		keyword = "auto-monochrome";
198 	      else if (ippContainsString(print_color_mode_sup, "process-monochrome") && !ippContainsString(print_color_mode_sup, "monochrome"))
199 		keyword = "process-monochrome";
200 	    }
201 
202 	    ippAddString(request, IPP_TAG_JOB, value_tag, mandatory, NULL, keyword);
203 	    break;
204       }
205     }
206   }
207 
208   if ((keyword = cupsGetOption("PageSize", num_options, options)) == NULL)
209     keyword = cupsGetOption("media", num_options, options);
210 
211   media_source = _ppdCacheGetSource(pc, cupsGetOption("InputSlot", num_options, options));
212   media_type   = _ppdCacheGetType(pc, cupsGetOption("MediaType", num_options, options));
213   size         = _ppdCacheGetSize(pc, keyword);
214 
215   if (media_col_sup && (size || media_source || media_type))
216   {
217    /*
218     * Add a media-col value...
219     */
220 
221     media_col = ippNew();
222 
223     if (size)
224     {
225       media_size = ippNew();
226       ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER,
227                     "x-dimension", size->width);
228       ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER,
229                     "y-dimension", size->length);
230 
231       ippAddCollection(media_col, IPP_TAG_ZERO, "media-size", media_size);
232     }
233 
234     for (i = 0; i < media_col_sup->num_values; i ++)
235     {
236       if (size && !strcmp(media_col_sup->values[i].string.text, "media-left-margin"))
237 	ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, "media-left-margin", size->left);
238       else if (size && !strcmp(media_col_sup->values[i].string.text, "media-bottom-margin"))
239 	ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, "media-bottom-margin", size->bottom);
240       else if (size && !strcmp(media_col_sup->values[i].string.text, "media-right-margin"))
241 	ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, "media-right-margin", size->right);
242       else if (size && !strcmp(media_col_sup->values[i].string.text, "media-top-margin"))
243 	ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, "media-top-margin", size->top);
244       else if (media_source && !strcmp(media_col_sup->values[i].string.text, "media-source"))
245 	ippAddString(media_col, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "media-source", NULL, media_source);
246       else if (media_type && !strcmp(media_col_sup->values[i].string.text, "media-type"))
247 	ippAddString(media_col, IPP_TAG_ZERO, IPP_TAG_KEYWORD, "media-type", NULL, media_type);
248     }
249 
250     ippAddCollection(request, IPP_TAG_JOB, "media-col", media_col);
251   }
252 
253   if ((keyword = cupsGetOption("output-bin", num_options, options)) == NULL)
254   {
255     if ((choice = ppdFindMarkedChoice(ppd, "OutputBin")) != NULL)
256       keyword = _ppdCacheGetBin(pc, choice->choice);
257   }
258 
259   if (keyword)
260     ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "output-bin", NULL, keyword);
261 
262   color_attr_name = print_color_mode_sup ? "print-color-mode" : "output-mode";
263 
264  /*
265   * If we use PPD with standardized PPD option for color support - ColorModel,
266   * prefer it to don't break color/grayscale support for PPDs, either classic
267   * or the ones generated from IPP Get-Printer-Attributes response.
268   */
269 
270   if ((keyword = cupsGetOption("ColorModel", num_options, options)) == NULL)
271   {
272    /*
273     * No ColorModel in options...
274     */
275 
276     if ((choice = ppdFindMarkedChoice(ppd, "ColorModel")) != NULL)
277     {
278      /*
279       * ColorModel is taken from PPD as its default option.
280       */
281 
282       if (!strcmp(choice->choice, "Gray") || !strcmp(choice->choice, "FastGray") || !strcmp(choice->choice, "DeviceGray"))
283         keyword = "monochrome";
284       else
285         keyword = "color";
286     }
287     else
288      /*
289       * print-color-mode is a default option since 2.4.1, use it as a fallback if there is no
290       * ColorModel in options or PPD...
291       */
292       keyword = cupsGetOption("print-color-mode", num_options, options);
293   }
294   else
295   {
296    /*
297     * ColorModel found in options...
298     */
299 
300     if (!strcmp(keyword, "Gray") || !strcmp(keyword, "FastGray") || !strcmp(keyword, "DeviceGray"))
301       keyword = "monochrome";
302     else
303       keyword = "color";
304   }
305 
306   if (keyword && !strcmp(keyword, "monochrome"))
307   {
308     if (ippContainsString(print_color_mode_sup, "auto-monochrome"))
309       keyword = "auto-monochrome";
310     else if (ippContainsString(print_color_mode_sup, "process-monochrome") && !ippContainsString(print_color_mode_sup, "monochrome"))
311       keyword = "process-monochrome";
312   }
313 
314   if (keyword)
315     ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, color_attr_name, NULL, keyword);
316 
317   if ((keyword = cupsGetOption("print-quality", num_options, options)) != NULL)
318     ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", atoi(keyword));
319   else if ((choice = ppdFindMarkedChoice(ppd, "cupsPrintQuality")) != NULL)
320   {
321     if (!_cups_strcasecmp(choice->choice, "draft"))
322       ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", IPP_QUALITY_DRAFT);
323     else if (!_cups_strcasecmp(choice->choice, "normal"))
324       ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", IPP_QUALITY_NORMAL);
325     else if (!_cups_strcasecmp(choice->choice, "high"))
326       ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", IPP_QUALITY_HIGH);
327   }
328 
329   if ((keyword = cupsGetOption("sides", num_options, options)) != NULL)
330     ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, keyword);
331   else if (pc->sides_option && (choice = ppdFindMarkedChoice(ppd, pc->sides_option)) != NULL)
332   {
333     if (pc->sides_1sided && !_cups_strcasecmp(choice->choice, pc->sides_1sided))
334       ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, "one-sided");
335     else if (pc->sides_2sided_long && !_cups_strcasecmp(choice->choice, pc->sides_2sided_long))
336       ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, "two-sided-long-edge");
337     else if (pc->sides_2sided_short && !_cups_strcasecmp(choice->choice, pc->sides_2sided_short))
338       ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, "two-sided-short-edge");
339   }
340 
341  /*
342   * Copies...
343   */
344 
345   if ((keyword = cupsGetOption("multiple-document-handling", num_options, options)) != NULL)
346   {
347     if (strstr(keyword, "uncollated"))
348       keyword = "false";
349     else
350       keyword = "true";
351   }
352   else if ((keyword = cupsGetOption("collate", num_options, options)) == NULL)
353     keyword = "true";
354 
355   if (format)
356   {
357     if (!_cups_strcasecmp(format, "image/gif") ||
358 	!_cups_strcasecmp(format, "image/jp2") ||
359 	!_cups_strcasecmp(format, "image/jpeg") ||
360 	!_cups_strcasecmp(format, "image/png") ||
361 	!_cups_strcasecmp(format, "image/tiff") ||
362 	!_cups_strncasecmp(format, "image/x-", 8))
363     {
364      /*
365       * Collation makes no sense for single page image formats...
366       */
367 
368       keyword = "false";
369     }
370     else if (!_cups_strncasecmp(format, "image/", 6) ||
371 	     !_cups_strcasecmp(format, "application/vnd.cups-raster"))
372     {
373      /*
374       * Multi-page image formats will have copies applied by the upstream
375       * filters...
376       */
377 
378       copies = 1;
379     }
380   }
381 
382   if (doc_handling_sup)
383   {
384     if (!_cups_strcasecmp(keyword, "true"))
385       collate_str = "separate-documents-collated-copies";
386     else
387       collate_str = "separate-documents-uncollated-copies";
388 
389     for (i = 0; i < doc_handling_sup->num_values; i ++)
390     {
391       if (!strcmp(doc_handling_sup->values[i].string.text, collate_str))
392       {
393 	ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "multiple-document-handling", NULL, collate_str);
394 	break;
395       }
396     }
397 
398     if (i >= doc_handling_sup->num_values)
399       copies = 1;
400   }
401 
402  /*
403   * Map finishing options...
404   */
405 
406   if (copies != finishings_copies)
407   {
408     // Figure out the proper job-pages-per-set value...
409     if ((value = cupsGetOption("job-pages", num_options, options)) == NULL)
410       value = cupsGetOption("com.apple.print.PrintSettings.PMTotalBeginPages..n.", num_options, options);
411 
412     if (value)
413     {
414       if ((job_pages = atoi(value)) < 1)
415         job_pages = 1;
416     }
417 
418     // Adjust for number-up
419     if ((value = cupsGetOption("number-up", num_options, options)) != NULL)
420     {
421       if ((number_up = atoi(value)) < 1)
422         number_up = 1;
423     }
424 
425     job_pages = (job_pages + number_up - 1) / number_up;
426 
427     // When duplex printing, raster data will include an extra (blank) page to
428     // make the total number of pages even.  Make sure this is reflected in the
429     // page count...
430     if ((job_pages & 1) && (keyword = cupsGetOption("sides", num_options, options)) != NULL && strcmp(keyword, "one-sided"))
431       job_pages ++;
432   }
433 
434   if ((finishing_template = cupsGetOption("cupsFinishingTemplate", num_options, options)) == NULL)
435     finishing_template = cupsGetOption("finishing-template", num_options, options);
436 
437   if (finishing_template && strcmp(finishing_template, "none"))
438   {
439     ipp_t *fin_col = ippNew();		/* finishings-col value */
440 
441     ippAddString(fin_col, IPP_TAG_JOB, IPP_TAG_KEYWORD, "finishing-template", NULL, finishing_template);
442     ippAddCollection(request, IPP_TAG_JOB, "finishings-col", fin_col);
443     ippDelete(fin_col);
444 
445     if (copies != finishings_copies && job_pages > 0)
446     {
447      /*
448       * Send job-pages-per-set attribute to apply finishings correctly...
449       */
450 
451       ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-pages-per-set", job_pages);
452     }
453   }
454   else
455   {
456     num_finishings = _ppdCacheGetFinishingValues(ppd, pc, (int)(sizeof(finishings) / sizeof(finishings[0])), finishings);
457     if (num_finishings > 0)
458     {
459       ippAddIntegers(request, IPP_TAG_JOB, IPP_TAG_ENUM, "finishings", num_finishings, finishings);
460 
461       if (copies != finishings_copies && job_pages > 0)
462       {
463        /*
464 	* Send job-pages-per-set attribute to apply finishings correctly...
465 	*/
466 
467 	ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-pages-per-set", job_pages);
468       }
469     }
470   }
471 
472   return (copies);
473 }
474 
475 
476 /*
477  * '_ppdCacheCreateWithFile()' - Create PPD cache and mapping data from a
478  *                               written file.
479  *
480  * Use the @link _ppdCacheWriteFile@ function to write PWG mapping data to a
481  * file.
482  */
483 
484 _ppd_cache_t *				/* O  - PPD cache and mapping data */
_ppdCacheCreateWithFile(const char * filename,ipp_t ** attrs)485 _ppdCacheCreateWithFile(
486     const char *filename,		/* I  - File to read */
487     ipp_t      **attrs)			/* IO - IPP attributes, if any */
488 {
489   cups_file_t	*fp;			/* File */
490   _ppd_cache_t	*pc;			/* PWG mapping data */
491   pwg_size_t	*size;			/* Current size */
492   pwg_map_t	*map;			/* Current map */
493   _pwg_finishings_t *finishings;	/* Current finishings option */
494   int		linenum,		/* Current line number */
495 		num_bins,		/* Number of bins in file */
496 		num_sizes,		/* Number of sizes in file */
497 		num_sources,		/* Number of sources in file */
498 		num_types;		/* Number of types in file */
499   char		line[2048],		/* Current line */
500 		*value,			/* Pointer to value in line */
501 		*valueptr,		/* Pointer into value */
502 		pwg_keyword[128],	/* PWG keyword */
503 		ppd_keyword[PPD_MAX_NAME];
504 					/* PPD keyword */
505   _pwg_print_color_mode_t print_color_mode;
506 					/* Print color mode for preset */
507   _pwg_print_quality_t print_quality;	/* Print quality for preset */
508 
509 
510   DEBUG_printf(("_ppdCacheCreateWithFile(filename=\"%s\")", filename));
511 
512  /*
513   * Range check input...
514   */
515 
516   if (attrs)
517     *attrs = NULL;
518 
519   if (!filename)
520   {
521     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
522     return (NULL);
523   }
524 
525  /*
526   * Open the file...
527   */
528 
529   if ((fp = cupsFileOpen(filename, "r")) == NULL)
530   {
531     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
532     return (NULL);
533   }
534 
535  /*
536   * Read the first line and make sure it has "#CUPS-PPD-CACHE-version" in it...
537   */
538 
539   if (!cupsFileGets(fp, line, sizeof(line)))
540   {
541     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
542     DEBUG_puts("_ppdCacheCreateWithFile: Unable to read first line.");
543     cupsFileClose(fp);
544     return (NULL);
545   }
546 
547   if (strncmp(line, "#CUPS-PPD-CACHE-", 16))
548   {
549     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
550     DEBUG_printf(("_ppdCacheCreateWithFile: Wrong first line \"%s\".", line));
551     cupsFileClose(fp);
552     return (NULL);
553   }
554 
555   if (atoi(line + 16) != _PPD_CACHE_VERSION)
556   {
557     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Out of date PPD cache file."), 1);
558     DEBUG_printf(("_ppdCacheCreateWithFile: Cache file has version %s, "
559                   "expected %d.", line + 16, _PPD_CACHE_VERSION));
560     cupsFileClose(fp);
561     return (NULL);
562   }
563 
564  /*
565   * Allocate the mapping data structure...
566   */
567 
568   if ((pc = calloc(1, sizeof(_ppd_cache_t))) == NULL)
569   {
570     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
571     DEBUG_puts("_ppdCacheCreateWithFile: Unable to allocate _ppd_cache_t.");
572     goto create_error;
573   }
574 
575   pc->max_copies = 9999;
576 
577  /*
578   * Read the file...
579   */
580 
581   linenum     = 0;
582   num_bins    = 0;
583   num_sizes   = 0;
584   num_sources = 0;
585   num_types   = 0;
586 
587   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
588   {
589     DEBUG_printf(("_ppdCacheCreateWithFile: line=\"%s\", value=\"%s\", "
590                   "linenum=%d", line, value, linenum));
591 
592     if (!value)
593     {
594       DEBUG_printf(("_ppdCacheCreateWithFile: Missing value on line %d.",
595                     linenum));
596       _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
597       goto create_error;
598     }
599     else if (!_cups_strcasecmp(line, "Filter"))
600     {
601       if (!pc->filters)
602         pc->filters = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
603 
604       cupsArrayAdd(pc->filters, value);
605     }
606     else if (!_cups_strcasecmp(line, "PreFilter"))
607     {
608       if (!pc->prefilters)
609         pc->prefilters = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
610 
611       cupsArrayAdd(pc->prefilters, value);
612     }
613     else if (!_cups_strcasecmp(line, "Product"))
614     {
615       pc->product = strdup(value);
616     }
617     else if (!_cups_strcasecmp(line, "SingleFile"))
618     {
619       pc->single_file = !_cups_strcasecmp(value, "true");
620     }
621     else if (!_cups_strcasecmp(line, "IPP"))
622     {
623       off_t	pos = cupsFileTell(fp),	/* Position in file */
624 		length = strtol(value, NULL, 10);
625 					/* Length of IPP attributes */
626 
627       if (attrs && *attrs)
628       {
629         DEBUG_puts("_ppdCacheCreateWithFile: IPP listed multiple times.");
630 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
631 	goto create_error;
632       }
633       else if (length <= 0)
634       {
635         DEBUG_puts("_ppdCacheCreateWithFile: Bad IPP length.");
636 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
637 	goto create_error;
638       }
639 
640       if (attrs)
641       {
642        /*
643         * Read IPP attributes into the provided variable...
644 	*/
645 
646         *attrs = ippNew();
647 
648         if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL,
649 		      *attrs) != IPP_STATE_DATA)
650 	{
651 	  DEBUG_puts("_ppdCacheCreateWithFile: Bad IPP data.");
652 	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
653 	  goto create_error;
654 	}
655       }
656       else
657       {
658        /*
659         * Skip the IPP data entirely...
660 	*/
661 
662         cupsFileSeek(fp, pos + length);
663       }
664 
665       if (cupsFileTell(fp) != (pos + length))
666       {
667         DEBUG_puts("_ppdCacheCreateWithFile: Bad IPP data.");
668 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
669 	goto create_error;
670       }
671     }
672     else if (!_cups_strcasecmp(line, "NumBins"))
673     {
674       if (num_bins > 0)
675       {
676         DEBUG_puts("_ppdCacheCreateWithFile: NumBins listed multiple times.");
677 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
678 	goto create_error;
679       }
680 
681       if ((num_bins = atoi(value)) <= 0 || num_bins > 65536)
682       {
683         DEBUG_printf(("_ppdCacheCreateWithFile: Bad NumBins value %d on line "
684 		      "%d.", num_sizes, linenum));
685 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
686 	goto create_error;
687       }
688 
689       if ((pc->bins = calloc((size_t)num_bins, sizeof(pwg_map_t))) == NULL)
690       {
691         DEBUG_printf(("_ppdCacheCreateWithFile: Unable to allocate %d bins.",
692 	              num_sizes));
693 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
694 	goto create_error;
695       }
696     }
697     else if (!_cups_strcasecmp(line, "Bin"))
698     {
699       if (sscanf(value, "%127s%40s", pwg_keyword, ppd_keyword) != 2)
700       {
701         DEBUG_printf(("_ppdCacheCreateWithFile: Bad Bin on line %d.", linenum));
702 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
703 	goto create_error;
704       }
705 
706       if (pc->num_bins >= num_bins)
707       {
708         DEBUG_printf(("_ppdCacheCreateWithFile: Too many Bin's on line %d.",
709 	              linenum));
710 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
711 	goto create_error;
712       }
713 
714       map      = pc->bins + pc->num_bins;
715       map->pwg = strdup(pwg_keyword);
716       map->ppd = strdup(ppd_keyword);
717 
718       pc->num_bins ++;
719     }
720     else if (!_cups_strcasecmp(line, "NumSizes"))
721     {
722       if (num_sizes > 0)
723       {
724         DEBUG_puts("_ppdCacheCreateWithFile: NumSizes listed multiple times.");
725 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
726 	goto create_error;
727       }
728 
729       if ((num_sizes = atoi(value)) < 0 || num_sizes > 65536)
730       {
731         DEBUG_printf(("_ppdCacheCreateWithFile: Bad NumSizes value %d on line "
732 	              "%d.", num_sizes, linenum));
733 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
734 	goto create_error;
735       }
736 
737       if (num_sizes > 0)
738       {
739 	if ((pc->sizes = calloc((size_t)num_sizes, sizeof(pwg_size_t))) == NULL)
740 	{
741 	  DEBUG_printf(("_ppdCacheCreateWithFile: Unable to allocate %d sizes.",
742 			num_sizes));
743 	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
744 	  goto create_error;
745 	}
746       }
747     }
748     else if (!_cups_strcasecmp(line, "Size"))
749     {
750       if (pc->num_sizes >= num_sizes)
751       {
752         DEBUG_printf(("_ppdCacheCreateWithFile: Too many Size's on line %d.",
753 	              linenum));
754 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
755 	goto create_error;
756       }
757 
758       size = pc->sizes + pc->num_sizes;
759 
760       if (sscanf(value, "%127s%40s%d%d%d%d%d%d", pwg_keyword, ppd_keyword,
761 		 &(size->width), &(size->length), &(size->left),
762 		 &(size->bottom), &(size->right), &(size->top)) != 8)
763       {
764         DEBUG_printf(("_ppdCacheCreateWithFile: Bad Size on line %d.",
765 	              linenum));
766 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
767 	goto create_error;
768       }
769 
770       size->map.pwg = strdup(pwg_keyword);
771       size->map.ppd = strdup(ppd_keyword);
772 
773       pc->num_sizes ++;
774     }
775     else if (!_cups_strcasecmp(line, "CustomSize"))
776     {
777       if (pc->custom_max_width > 0)
778       {
779         DEBUG_printf(("_ppdCacheCreateWithFile: Too many CustomSize's on line "
780 	              "%d.", linenum));
781 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
782 	goto create_error;
783       }
784 
785       if (sscanf(value, "%d%d%d%d%d%d%d%d", &(pc->custom_max_width),
786                  &(pc->custom_max_length), &(pc->custom_min_width),
787 		 &(pc->custom_min_length), &(pc->custom_size.left),
788 		 &(pc->custom_size.bottom), &(pc->custom_size.right),
789 		 &(pc->custom_size.top)) != 8)
790       {
791         DEBUG_printf(("_ppdCacheCreateWithFile: Bad CustomSize on line %d.",
792 	              linenum));
793 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
794 	goto create_error;
795       }
796 
797       pwgFormatSizeName(pwg_keyword, sizeof(pwg_keyword), "custom", "max",
798 		        pc->custom_max_width, pc->custom_max_length, NULL);
799       pc->custom_max_keyword = strdup(pwg_keyword);
800 
801       pwgFormatSizeName(pwg_keyword, sizeof(pwg_keyword), "custom", "min",
802 		        pc->custom_min_width, pc->custom_min_length, NULL);
803       pc->custom_min_keyword = strdup(pwg_keyword);
804     }
805     else if (!_cups_strcasecmp(line, "SourceOption"))
806     {
807       pc->source_option = strdup(value);
808     }
809     else if (!_cups_strcasecmp(line, "NumSources"))
810     {
811       if (num_sources > 0)
812       {
813         DEBUG_puts("_ppdCacheCreateWithFile: NumSources listed multiple "
814 	           "times.");
815 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
816 	goto create_error;
817       }
818 
819       if ((num_sources = atoi(value)) <= 0 || num_sources > 65536)
820       {
821         DEBUG_printf(("_ppdCacheCreateWithFile: Bad NumSources value %d on "
822 	              "line %d.", num_sources, linenum));
823 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
824 	goto create_error;
825       }
826 
827       if ((pc->sources = calloc((size_t)num_sources, sizeof(pwg_map_t))) == NULL)
828       {
829         DEBUG_printf(("_ppdCacheCreateWithFile: Unable to allocate %d sources.",
830 	              num_sources));
831 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
832 	goto create_error;
833       }
834     }
835     else if (!_cups_strcasecmp(line, "Source"))
836     {
837       if (sscanf(value, "%127s%40s", pwg_keyword, ppd_keyword) != 2)
838       {
839         DEBUG_printf(("_ppdCacheCreateWithFile: Bad Source on line %d.",
840 	              linenum));
841 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
842 	goto create_error;
843       }
844 
845       if (pc->num_sources >= num_sources)
846       {
847         DEBUG_printf(("_ppdCacheCreateWithFile: Too many Source's on line %d.",
848 	              linenum));
849 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
850 	goto create_error;
851       }
852 
853       map      = pc->sources + pc->num_sources;
854       map->pwg = strdup(pwg_keyword);
855       map->ppd = strdup(ppd_keyword);
856 
857       pc->num_sources ++;
858     }
859     else if (!_cups_strcasecmp(line, "NumTypes"))
860     {
861       if (num_types > 0)
862       {
863         DEBUG_puts("_ppdCacheCreateWithFile: NumTypes listed multiple times.");
864 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
865 	goto create_error;
866       }
867 
868       if ((num_types = atoi(value)) <= 0 || num_types > 65536)
869       {
870         DEBUG_printf(("_ppdCacheCreateWithFile: Bad NumTypes value %d on "
871 	              "line %d.", num_types, linenum));
872 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
873 	goto create_error;
874       }
875 
876       if ((pc->types = calloc((size_t)num_types, sizeof(pwg_map_t))) == NULL)
877       {
878         DEBUG_printf(("_ppdCacheCreateWithFile: Unable to allocate %d types.",
879 	              num_types));
880 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
881 	goto create_error;
882       }
883     }
884     else if (!_cups_strcasecmp(line, "Type"))
885     {
886       if (sscanf(value, "%127s%40s", pwg_keyword, ppd_keyword) != 2)
887       {
888         DEBUG_printf(("_ppdCacheCreateWithFile: Bad Type on line %d.",
889 	              linenum));
890 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
891 	goto create_error;
892       }
893 
894       if (pc->num_types >= num_types)
895       {
896         DEBUG_printf(("_ppdCacheCreateWithFile: Too many Type's on line %d.",
897 	              linenum));
898 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
899 	goto create_error;
900       }
901 
902       map      = pc->types + pc->num_types;
903       map->pwg = strdup(pwg_keyword);
904       map->ppd = strdup(ppd_keyword);
905 
906       pc->num_types ++;
907     }
908     else if (!_cups_strcasecmp(line, "Preset"))
909     {
910      /*
911       * Preset output-mode print-quality name=value ...
912       */
913 
914       print_color_mode = (_pwg_print_color_mode_t)strtol(value, &valueptr, 10);
915       print_quality    = (_pwg_print_quality_t)strtol(valueptr, &valueptr, 10);
916 
917       if (print_color_mode < _PWG_PRINT_COLOR_MODE_MONOCHROME ||
918           print_color_mode >= _PWG_PRINT_COLOR_MODE_MAX ||
919 	  print_quality < _PWG_PRINT_QUALITY_DRAFT ||
920 	  print_quality >= _PWG_PRINT_QUALITY_MAX ||
921 	  valueptr == value || !*valueptr)
922       {
923         DEBUG_printf(("_ppdCacheCreateWithFile: Bad Preset on line %d.",
924 	              linenum));
925 	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
926 	goto create_error;
927       }
928 
929       pc->num_presets[print_color_mode][print_quality] =
930           cupsParseOptions(valueptr, 0,
931 	                   pc->presets[print_color_mode] + print_quality);
932     }
933     else if (!_cups_strcasecmp(line, "SidesOption"))
934       pc->sides_option = strdup(value);
935     else if (!_cups_strcasecmp(line, "Sides1Sided"))
936       pc->sides_1sided = strdup(value);
937     else if (!_cups_strcasecmp(line, "Sides2SidedLong"))
938       pc->sides_2sided_long = strdup(value);
939     else if (!_cups_strcasecmp(line, "Sides2SidedShort"))
940       pc->sides_2sided_short = strdup(value);
941     else if (!_cups_strcasecmp(line, "Finishings"))
942     {
943       if (!pc->finishings)
944 	pc->finishings =
945 	    cupsArrayNew3((cups_array_func_t)pwg_compare_finishings,
946 			  NULL, NULL, 0, NULL,
947 			  (cups_afree_func_t)pwg_free_finishings);
948 
949       if ((finishings = calloc(1, sizeof(_pwg_finishings_t))) == NULL)
950         goto create_error;
951 
952       finishings->value       = (ipp_finishings_t)strtol(value, &valueptr, 10);
953       finishings->num_options = cupsParseOptions(valueptr, 0,
954                                                  &(finishings->options));
955 
956       cupsArrayAdd(pc->finishings, finishings);
957     }
958     else if (!_cups_strcasecmp(line, "FinishingTemplate"))
959     {
960       if (!pc->templates)
961         pc->templates = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
962 
963       cupsArrayAdd(pc->templates, value);
964     }
965     else if (!_cups_strcasecmp(line, "MaxCopies"))
966       pc->max_copies = atoi(value);
967     else if (!_cups_strcasecmp(line, "ChargeInfoURI"))
968       pc->charge_info_uri = strdup(value);
969     else if (!_cups_strcasecmp(line, "JobAccountId"))
970       pc->account_id = !_cups_strcasecmp(value, "true");
971     else if (!_cups_strcasecmp(line, "JobAccountingUserId"))
972       pc->accounting_user_id = !_cups_strcasecmp(value, "true");
973     else if (!_cups_strcasecmp(line, "JobPassword"))
974       pc->password = strdup(value);
975     else if (!_cups_strcasecmp(line, "Mandatory"))
976     {
977       if (pc->mandatory)
978         _cupsArrayAddStrings(pc->mandatory, value, ' ');
979       else
980         pc->mandatory = _cupsArrayNewStrings(value, ' ');
981     }
982     else if (!_cups_strcasecmp(line, "SupportFile"))
983     {
984       if (!pc->support_files)
985         pc->support_files = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
986 
987       cupsArrayAdd(pc->support_files, value);
988     }
989     else
990     {
991       DEBUG_printf(("_ppdCacheCreateWithFile: Unknown %s on line %d.", line,
992 		    linenum));
993     }
994   }
995 
996   if (pc->num_sizes < num_sizes)
997   {
998     DEBUG_printf(("_ppdCacheCreateWithFile: Not enough sizes (%d < %d).",
999                   pc->num_sizes, num_sizes));
1000     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
1001     goto create_error;
1002   }
1003 
1004   if (pc->num_sources < num_sources)
1005   {
1006     DEBUG_printf(("_ppdCacheCreateWithFile: Not enough sources (%d < %d).",
1007                   pc->num_sources, num_sources));
1008     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
1009     goto create_error;
1010   }
1011 
1012   if (pc->num_types < num_types)
1013   {
1014     DEBUG_printf(("_ppdCacheCreateWithFile: Not enough types (%d < %d).",
1015                   pc->num_types, num_types));
1016     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
1017     goto create_error;
1018   }
1019 
1020   cupsFileClose(fp);
1021 
1022   return (pc);
1023 
1024  /*
1025   * If we get here the file was bad - free any data and return...
1026   */
1027 
1028   create_error:
1029 
1030   cupsFileClose(fp);
1031   _ppdCacheDestroy(pc);
1032 
1033   if (attrs)
1034   {
1035     ippDelete(*attrs);
1036     *attrs = NULL;
1037   }
1038 
1039   return (NULL);
1040 }
1041 
1042 
1043 /*
1044  * '_ppdCacheCreateWithPPD()' - Create PWG mapping data from a PPD file.
1045  */
1046 
1047 _ppd_cache_t *				/* O - PPD cache and mapping data */
_ppdCacheCreateWithPPD(ppd_file_t * ppd)1048 _ppdCacheCreateWithPPD(ppd_file_t *ppd)	/* I - PPD file */
1049 {
1050   int			i, j, k;	/* Looping vars */
1051   _ppd_cache_t		*pc;		/* PWG mapping data */
1052   ppd_option_t		*input_slot,	/* InputSlot option */
1053 			*media_type,	/* MediaType option */
1054 			*output_bin,	/* OutputBin option */
1055 			*color_model,	/* ColorModel option */
1056 			*duplex,	/* Duplex option */
1057 			*ppd_option;	/* Other PPD option */
1058   ppd_choice_t		*choice;	/* Current InputSlot/MediaType */
1059   pwg_map_t		*map;		/* Current source/type map */
1060   ppd_attr_t		*ppd_attr;	/* Current PPD preset attribute */
1061   int			num_options;	/* Number of preset options and props */
1062   cups_option_t		*options;	/* Preset options and properties */
1063   ppd_size_t		*ppd_size;	/* Current PPD size */
1064   pwg_size_t		*pwg_size;	/* Current PWG size */
1065   char			pwg_keyword[3 + PPD_MAX_NAME + 1 + 12 + 1 + 12 + 3],
1066 					/* PWG keyword string */
1067 			ppd_name[PPD_MAX_NAME];
1068 					/* Normalized PPD name */
1069   const char		*pwg_name;	/* Standard PWG media name */
1070   pwg_media_t		*pwg_media,	/* PWG media data */
1071 			pwg_mediatemp;	/* Temporary PWG media data */
1072   _pwg_print_color_mode_t pwg_print_color_mode;
1073 					/* print-color-mode index */
1074   _pwg_print_quality_t	pwg_print_quality;
1075 					/* print-quality index */
1076   int			similar;	/* Are the old and new size similar? */
1077   pwg_size_t		*old_size;	/* Current old size */
1078   int			old_imageable,	/* Old imageable length in 2540ths */
1079 			old_borderless,	/* Old borderless state */
1080 			old_known_pwg;	/* Old PWG name is well-known */
1081   int			new_width,	/* New width in 2540ths */
1082 			new_length,	/* New length in 2540ths */
1083 			new_left,	/* New left margin in 2540ths */
1084 			new_bottom,	/* New bottom margin in 2540ths */
1085 			new_right,	/* New right margin in 2540ths */
1086 			new_top,	/* New top margin in 2540ths */
1087 			new_imageable,	/* New imageable length in 2540ths */
1088 			new_borderless,	/* New borderless state */
1089 			new_known_pwg;	/* New PWG name is well-known */
1090   pwg_size_t		*new_size;	/* New size to add, if any */
1091   const char		*filter;	/* Current filter */
1092   _pwg_finishings_t	*finishings;	/* Current finishings value */
1093   char			msg_id[256];	/* Message identifier */
1094 
1095 
1096   DEBUG_printf(("_ppdCacheCreateWithPPD(ppd=%p)", ppd));
1097 
1098  /*
1099   * Range check input...
1100   */
1101 
1102   if (!ppd)
1103     return (NULL);
1104 
1105  /*
1106   * Allocate memory...
1107   */
1108 
1109   if ((pc = calloc(1, sizeof(_ppd_cache_t))) == NULL)
1110   {
1111     DEBUG_puts("_ppdCacheCreateWithPPD: Unable to allocate _ppd_cache_t.");
1112     goto create_error;
1113   }
1114 
1115   pc->strings = _cupsMessageNew(NULL);
1116 
1117  /*
1118   * Copy and convert size data...
1119   */
1120 
1121   if (ppd->num_sizes > 0)
1122   {
1123     if ((pc->sizes = calloc((size_t)ppd->num_sizes, sizeof(pwg_size_t))) == NULL)
1124     {
1125       DEBUG_printf(("_ppdCacheCreateWithPPD: Unable to allocate %d "
1126 		    "pwg_size_t's.", ppd->num_sizes));
1127       goto create_error;
1128     }
1129 
1130     for (i = ppd->num_sizes, pwg_size = pc->sizes, ppd_size = ppd->sizes;
1131 	 i > 0;
1132 	 i --, ppd_size ++)
1133     {
1134      /*
1135       * Don't copy over custom size...
1136       */
1137 
1138       if (!_cups_strcasecmp(ppd_size->name, "Custom"))
1139 	continue;
1140 
1141      /*
1142       * Convert the PPD size name to the corresponding PWG keyword name.
1143       */
1144 
1145       if ((pwg_media = pwgMediaForSize(PWG_FROM_POINTS(ppd_size->width), PWG_FROM_POINTS(ppd_size->length))) != NULL)
1146       {
1147        /*
1148 	* Standard name, do we have conflicts?
1149 	*/
1150 
1151 	for (j = 0; j < pc->num_sizes; j ++)
1152 	  if (!strcmp(pc->sizes[j].map.pwg, pwg_media->pwg))
1153 	  {
1154 	    pwg_media = NULL;
1155 	    break;
1156 	  }
1157       }
1158 
1159       if (pwg_media)
1160       {
1161        /*
1162 	* Standard name and no conflicts, use it!
1163 	*/
1164 
1165 	pwg_name      = pwg_media->pwg;
1166 	new_known_pwg = 1;
1167       }
1168       else
1169       {
1170        /*
1171 	* Not a standard name; convert it to a PWG vendor name of the form:
1172 	*
1173 	*     pp_lowerppd_WIDTHxHEIGHTuu
1174 	*/
1175 
1176 	pwg_name      = pwg_keyword;
1177 	new_known_pwg = 0;
1178 
1179 	pwg_unppdize_name(ppd_size->name, ppd_name, sizeof(ppd_name), "_.");
1180 	pwgFormatSizeName(pwg_keyword, sizeof(pwg_keyword), NULL, ppd_name,
1181 			  PWG_FROM_POINTS(ppd_size->width),
1182 			  PWG_FROM_POINTS(ppd_size->length), NULL);
1183       }
1184 
1185      /*
1186       * If we have a similar paper with non-zero margins then we only want to
1187       * keep it if it has a larger imageable area length.  The NULL check is for
1188       * dimensions that are <= 0...
1189       */
1190 
1191       if ((pwg_media = _pwgMediaNearSize(&pwg_mediatemp, /*keyword*/NULL, /*keysize*/0, /*ppdname*/NULL, /*ppdsize*/0, PWG_FROM_POINTS(ppd_size->width), PWG_FROM_POINTS(ppd_size->length), /*epsilon*/0)) == NULL)
1192 	continue;
1193 
1194       new_width      = pwg_media->width;
1195       new_length     = pwg_media->length;
1196       new_left       = PWG_FROM_POINTS(ppd_size->left);
1197       new_bottom     = PWG_FROM_POINTS(ppd_size->bottom);
1198       new_right      = PWG_FROM_POINTS(ppd_size->width - ppd_size->right);
1199       new_top        = PWG_FROM_POINTS(ppd_size->length - ppd_size->top);
1200       new_imageable  = new_length - new_top - new_bottom;
1201       new_borderless = new_bottom == 0 && new_top == 0 &&
1202 		       new_left == 0 && new_right == 0;
1203 
1204       for (k = pc->num_sizes, similar = 0, old_size = pc->sizes, new_size = NULL;
1205 	   k > 0 && !similar;
1206 	   k --, old_size ++)
1207       {
1208 	old_imageable  = old_size->length - old_size->top - old_size->bottom;
1209 	old_borderless = old_size->left == 0 && old_size->bottom == 0 &&
1210 			 old_size->right == 0 && old_size->top == 0;
1211 	old_known_pwg  = strncmp(old_size->map.pwg, "oe_", 3) &&
1212 			 strncmp(old_size->map.pwg, "om_", 3);
1213 
1214 	similar = old_borderless == new_borderless &&
1215 		  _PWG_EQUIVALENT(old_size->width, new_width) &&
1216 		  _PWG_EQUIVALENT(old_size->length, new_length);
1217 
1218 	if (similar &&
1219 	    (new_known_pwg || (!old_known_pwg && new_imageable > old_imageable)))
1220 	{
1221 	 /*
1222 	  * The new paper has a larger imageable area so it could replace
1223 	  * the older paper.  Regardless of the imageable area, we always
1224 	  * prefer the size with a well-known PWG name.
1225 	  */
1226 
1227 	  new_size = old_size;
1228 	  free(old_size->map.ppd);
1229 	  free(old_size->map.pwg);
1230 	}
1231       }
1232 
1233       if (!similar)
1234       {
1235        /*
1236 	* The paper was unique enough to deserve its own entry so add it to the
1237 	* end.
1238 	*/
1239 
1240 	new_size = pwg_size ++;
1241 	pc->num_sizes ++;
1242       }
1243 
1244       if (new_size)
1245       {
1246        /*
1247 	* Save this size...
1248 	*/
1249 
1250 	new_size->map.ppd = strdup(ppd_size->name);
1251 	new_size->map.pwg = strdup(pwg_name);
1252 	new_size->width   = new_width;
1253 	new_size->length  = new_length;
1254 	new_size->left    = new_left;
1255 	new_size->bottom  = new_bottom;
1256 	new_size->right   = new_right;
1257 	new_size->top     = new_top;
1258       }
1259     }
1260   }
1261 
1262   if (ppd->variable_sizes)
1263   {
1264    /*
1265     * Generate custom size data...
1266     */
1267 
1268     pwgFormatSizeName(pwg_keyword, sizeof(pwg_keyword), "custom", "max",
1269 		      PWG_FROM_POINTS(ppd->custom_max[0]),
1270 		      PWG_FROM_POINTS(ppd->custom_max[1]), NULL);
1271     pc->custom_max_keyword = strdup(pwg_keyword);
1272     pc->custom_max_width   = PWG_FROM_POINTS(ppd->custom_max[0]);
1273     pc->custom_max_length  = PWG_FROM_POINTS(ppd->custom_max[1]);
1274 
1275     pwgFormatSizeName(pwg_keyword, sizeof(pwg_keyword), "custom", "min",
1276 		      PWG_FROM_POINTS(ppd->custom_min[0]),
1277 		      PWG_FROM_POINTS(ppd->custom_min[1]), NULL);
1278     pc->custom_min_keyword = strdup(pwg_keyword);
1279     pc->custom_min_width   = PWG_FROM_POINTS(ppd->custom_min[0]);
1280     pc->custom_min_length  = PWG_FROM_POINTS(ppd->custom_min[1]);
1281 
1282     pc->custom_size.left   = PWG_FROM_POINTS(ppd->custom_margins[0]);
1283     pc->custom_size.bottom = PWG_FROM_POINTS(ppd->custom_margins[1]);
1284     pc->custom_size.right  = PWG_FROM_POINTS(ppd->custom_margins[2]);
1285     pc->custom_size.top    = PWG_FROM_POINTS(ppd->custom_margins[3]);
1286   }
1287 
1288  /*
1289   * Copy and convert InputSlot data...
1290   */
1291 
1292   if ((input_slot = ppdFindOption(ppd, "InputSlot")) == NULL)
1293     input_slot = ppdFindOption(ppd, "HPPaperSource");
1294 
1295   if (input_slot)
1296   {
1297     pc->source_option = strdup(input_slot->keyword);
1298 
1299     if ((pc->sources = calloc((size_t)input_slot->num_choices, sizeof(pwg_map_t))) == NULL)
1300     {
1301       DEBUG_printf(("_ppdCacheCreateWithPPD: Unable to allocate %d "
1302                     "pwg_map_t's for InputSlot.", input_slot->num_choices));
1303       goto create_error;
1304     }
1305 
1306     pc->num_sources = input_slot->num_choices;
1307 
1308     for (i = input_slot->num_choices, choice = input_slot->choices,
1309              map = pc->sources;
1310 	 i > 0;
1311 	 i --, choice ++, map ++)
1312     {
1313       if (!_cups_strncasecmp(choice->choice, "Auto", 4) ||
1314           !_cups_strncasecmp(choice->text, "Auto", 4) ||
1315           !_cups_strcasecmp(choice->choice, "Default") ||
1316           !_cups_strcasecmp(choice->text, "Default"))
1317         pwg_name = "auto";
1318       else if (!_cups_strcasecmp(choice->choice, "Cassette"))
1319         pwg_name = "main";
1320       else if (!_cups_strcasecmp(choice->choice, "PhotoTray"))
1321         pwg_name = "photo";
1322       else if (!_cups_strcasecmp(choice->choice, "CDTray"))
1323         pwg_name = "disc";
1324       else if (!_cups_strncasecmp(choice->choice, "Multipurpose", 12) ||
1325                !_cups_strcasecmp(choice->choice, "MP") ||
1326                !_cups_strcasecmp(choice->choice, "MPTray"))
1327         pwg_name = "by-pass-tray";
1328       else if (!_cups_strcasecmp(choice->choice, "LargeCapacity"))
1329         pwg_name = "large-capacity";
1330       else if (!_cups_strncasecmp(choice->choice, "Lower", 5))
1331         pwg_name = "bottom";
1332       else if (!_cups_strncasecmp(choice->choice, "Middle", 6))
1333         pwg_name = "middle";
1334       else if (!_cups_strncasecmp(choice->choice, "Upper", 5))
1335         pwg_name = "top";
1336       else if (!_cups_strncasecmp(choice->choice, "Side", 4))
1337         pwg_name = "side";
1338       else if (!_cups_strcasecmp(choice->choice, "Roll"))
1339         pwg_name = "main-roll";
1340       else if (!_cups_strcasecmp(choice->choice, "0"))
1341         pwg_name = "tray-1";
1342       else if (!_cups_strcasecmp(choice->choice, "1"))
1343         pwg_name = "tray-2";
1344       else if (!_cups_strcasecmp(choice->choice, "2"))
1345         pwg_name = "tray-3";
1346       else if (!_cups_strcasecmp(choice->choice, "3"))
1347         pwg_name = "tray-4";
1348       else if (!_cups_strcasecmp(choice->choice, "4"))
1349         pwg_name = "tray-5";
1350       else if (!_cups_strcasecmp(choice->choice, "5"))
1351         pwg_name = "tray-6";
1352       else if (!_cups_strcasecmp(choice->choice, "6"))
1353         pwg_name = "tray-7";
1354       else if (!_cups_strcasecmp(choice->choice, "7"))
1355         pwg_name = "tray-8";
1356       else if (!_cups_strcasecmp(choice->choice, "8"))
1357         pwg_name = "tray-9";
1358       else if (!_cups_strcasecmp(choice->choice, "9"))
1359         pwg_name = "tray-10";
1360       else
1361       {
1362        /*
1363         * Convert PPD name to lowercase...
1364 	*/
1365 
1366         pwg_name = pwg_keyword;
1367 	pwg_unppdize_name(choice->choice, pwg_keyword, sizeof(pwg_keyword),
1368 	                  "_");
1369       }
1370 
1371       map->pwg = strdup(pwg_name);
1372       map->ppd = strdup(choice->choice);
1373 
1374      /*
1375       * Add localized text for PWG keyword to message catalog...
1376       */
1377 
1378       snprintf(msg_id, sizeof(msg_id), "media-source.%s", pwg_name);
1379       pwg_add_message(pc->strings, msg_id, choice->text);
1380     }
1381   }
1382 
1383  /*
1384   * Copy and convert MediaType data...
1385   */
1386 
1387   if ((media_type = ppdFindOption(ppd, "MediaType")) != NULL)
1388   {
1389     static const struct
1390     {
1391       const char *ppd_name;		/* PPD MediaType name or prefix to match */
1392       int        match_length;		/* Length of prefix, or -1 to match entire string */
1393       const char *pwg_name;		/* Registered PWG media-type name to use */
1394     } standard_types[] = {
1395       {"Auto", 4, "auto"},
1396       {"Any", -1, "auto"},
1397       {"Default", -1, "auto"},
1398       {"Card", 4, "cardstock"},
1399       {"Env", 3, "envelope"},
1400       {"Gloss", 5, "photographic-glossy"},
1401       {"HighGloss", -1, "photographic-high-gloss"},
1402       {"Matte", -1, "photographic-matte"},
1403       {"Plain", 5, "stationery"},
1404       {"Coated", 6, "stationery-coated"},
1405       {"Inkjet", -1, "stationery-inkjet"},
1406       {"Letterhead", -1, "stationery-letterhead"},
1407       {"Preprint", 8, "stationery-preprinted"},
1408       {"Recycled", -1, "stationery-recycled"},
1409       {"Transparen", 10, "transparency"},
1410     };
1411     const size_t num_standard_types = sizeof(standard_types) / sizeof(standard_types[0]);
1412 					/* Length of the standard_types array */
1413     int match_counts[sizeof(standard_types) / sizeof(standard_types[0])] = {0};
1414 					/* Number of matches for each standard type */
1415 
1416     if ((pc->types = calloc((size_t)media_type->num_choices, sizeof(pwg_map_t))) == NULL)
1417     {
1418       DEBUG_printf(("_ppdCacheCreateWithPPD: Unable to allocate %d "
1419                     "pwg_map_t's for MediaType.", media_type->num_choices));
1420       goto create_error;
1421     }
1422 
1423     pc->num_types = media_type->num_choices;
1424 
1425     for (i = media_type->num_choices, choice = media_type->choices,
1426              map = pc->types;
1427 	 i > 0;
1428 	 i --, choice ++, map ++)
1429     {
1430       pwg_name = NULL;
1431 
1432       for (j = 0; j < num_standard_types; j ++)
1433       {
1434         if (standard_types[j].match_length <= 0)
1435         {
1436           if (!_cups_strcasecmp(choice->choice, standard_types[j].ppd_name))
1437           {
1438             pwg_name = standard_types[j].pwg_name;
1439             match_counts[j] ++;
1440           }
1441         }
1442         else if (!_cups_strncasecmp(choice->choice, standard_types[j].ppd_name, standard_types[j].match_length))
1443         {
1444           pwg_name = standard_types[j].pwg_name;
1445           match_counts[j] ++;
1446         }
1447       }
1448 
1449       if (!pwg_name)
1450       {
1451        /*
1452         * Convert PPD name to lowercase...
1453 	*/
1454 
1455         pwg_name = pwg_keyword;
1456 	pwg_unppdize_name(choice->choice, pwg_keyword, sizeof(pwg_keyword),
1457 	                  "_");
1458       }
1459 
1460       map->pwg = strdup(pwg_name);
1461       map->ppd = strdup(choice->choice);
1462     }
1463 
1464    /*
1465     * Since three PPD name patterns can map to "auto", their match counts
1466     * should each be the count of all three combined.
1467     */
1468 
1469     i = match_counts[0] + match_counts[1] + match_counts[2];
1470     match_counts[0] = match_counts[1] = match_counts[2] = i;
1471 
1472     for (i = 0, choice = media_type->choices, map = pc->types;
1473       i < media_type->num_choices;
1474       i ++, choice ++, map ++)
1475     {
1476      /*
1477       * If there are two matches for any standard PWG media type, don't give
1478       * the PWG name to either one.
1479       */
1480 
1481       for (j = 0; j < num_standard_types; j ++)
1482       {
1483         if (match_counts[j] > 1 && !strcmp(map->pwg, standard_types[j].pwg_name))
1484         {
1485           free(map->pwg);
1486           pwg_unppdize_name(choice->choice, pwg_keyword, sizeof(pwg_keyword), "_");
1487           map->pwg = strdup(pwg_keyword);
1488         }
1489       }
1490 
1491      /*
1492       * Add localized text for PWG keyword to message catalog...
1493       */
1494 
1495       snprintf(msg_id, sizeof(msg_id), "media-type.%s", map->pwg);
1496       pwg_add_message(pc->strings, msg_id, choice->text);
1497     }
1498   }
1499 
1500  /*
1501   * Copy and convert OutputBin data...
1502   */
1503 
1504   if ((output_bin = ppdFindOption(ppd, "OutputBin")) != NULL)
1505   {
1506     if ((pc->bins = calloc((size_t)output_bin->num_choices, sizeof(pwg_map_t))) == NULL)
1507     {
1508       DEBUG_printf(("_ppdCacheCreateWithPPD: Unable to allocate %d "
1509                     "pwg_map_t's for OutputBin.", output_bin->num_choices));
1510       goto create_error;
1511     }
1512 
1513     pc->num_bins = output_bin->num_choices;
1514 
1515     for (i = output_bin->num_choices, choice = output_bin->choices,
1516              map = pc->bins;
1517 	 i > 0;
1518 	 i --, choice ++, map ++)
1519     {
1520       pwg_unppdize_name(choice->choice, pwg_keyword, sizeof(pwg_keyword), "_");
1521 
1522       map->pwg = strdup(pwg_keyword);
1523       map->ppd = strdup(choice->choice);
1524 
1525      /*
1526       * Add localized text for PWG keyword to message catalog...
1527       */
1528 
1529       snprintf(msg_id, sizeof(msg_id), "output-bin.%s", pwg_keyword);
1530       pwg_add_message(pc->strings, msg_id, choice->text);
1531     }
1532   }
1533 
1534   if ((ppd_attr = ppdFindAttr(ppd, "APPrinterPreset", NULL)) != NULL)
1535   {
1536    /*
1537     * Copy and convert APPrinterPreset (output-mode + print-quality) data...
1538     */
1539 
1540     const char	*quality,		/* com.apple.print.preset.quality value */
1541 		*output_mode,		/* com.apple.print.preset.output-mode value */
1542 		*color_model_val,	/* ColorModel choice */
1543 		*graphicsType,		/* com.apple.print.preset.graphicsType value */
1544 		*media_front_coating;	/* com.apple.print.preset.media-front-coating value */
1545 
1546     do
1547     {
1548      /*
1549       * Add localized text for PWG keyword to message catalog...
1550       */
1551 
1552       snprintf(msg_id, sizeof(msg_id), "preset-name.%s", ppd_attr->spec);
1553       pwg_add_message(pc->strings, msg_id, ppd_attr->text);
1554 
1555      /*
1556       * Get the options for this preset...
1557       */
1558 
1559       num_options = _ppdParseOptions(ppd_attr->value, 0, &options,
1560                                      _PPD_PARSE_ALL);
1561 
1562       if ((quality = cupsGetOption("com.apple.print.preset.quality",
1563                                    num_options, options)) != NULL)
1564       {
1565        /*
1566         * Get the print-quality for this preset...
1567 	*/
1568 
1569 	if (!strcmp(quality, "low"))
1570 	  pwg_print_quality = _PWG_PRINT_QUALITY_DRAFT;
1571 	else if (!strcmp(quality, "high"))
1572 	  pwg_print_quality = _PWG_PRINT_QUALITY_HIGH;
1573 	else
1574 	  pwg_print_quality = _PWG_PRINT_QUALITY_NORMAL;
1575 
1576        /*
1577 	* Ignore graphicsType "Photo" presets that are not high quality.
1578 	*/
1579 
1580 	graphicsType = cupsGetOption("com.apple.print.preset.graphicsType",
1581 				      num_options, options);
1582 
1583 	if (pwg_print_quality != _PWG_PRINT_QUALITY_HIGH && graphicsType &&
1584 	    !strcmp(graphicsType, "Photo"))
1585 	  continue;
1586 
1587        /*
1588 	* Ignore presets for normal and draft quality where the coating
1589 	* isn't "none" or "autodetect".
1590 	*/
1591 
1592 	media_front_coating = cupsGetOption(
1593 	                          "com.apple.print.preset.media-front-coating",
1594 			          num_options, options);
1595 
1596         if (pwg_print_quality != _PWG_PRINT_QUALITY_HIGH &&
1597 	    media_front_coating &&
1598 	    strcmp(media_front_coating, "none") &&
1599 	    strcmp(media_front_coating, "autodetect"))
1600 	  continue;
1601 
1602        /*
1603         * Get the output mode for this preset...
1604 	*/
1605 
1606         output_mode     = cupsGetOption("com.apple.print.preset.output-mode",
1607 	                                num_options, options);
1608         color_model_val = cupsGetOption("ColorModel", num_options, options);
1609 
1610         if (output_mode)
1611 	{
1612 	  if (!strcmp(output_mode, "monochrome"))
1613 	    pwg_print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME;
1614 	  else
1615 	    pwg_print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
1616 	}
1617 	else if (color_model_val)
1618 	{
1619 	  if (!_cups_strcasecmp(color_model_val, "Gray"))
1620 	    pwg_print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME;
1621 	  else
1622 	    pwg_print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
1623 	}
1624 	else
1625 	  pwg_print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
1626 
1627        /*
1628         * Save the options for this combination as needed...
1629 	*/
1630 
1631         if (!pc->num_presets[pwg_print_color_mode][pwg_print_quality])
1632 	  pc->num_presets[pwg_print_color_mode][pwg_print_quality] =
1633 	      _ppdParseOptions(ppd_attr->value, 0,
1634 	                       pc->presets[pwg_print_color_mode] +
1635 			           pwg_print_quality, _PPD_PARSE_OPTIONS);
1636       }
1637 
1638       cupsFreeOptions(num_options, options);
1639     }
1640     while ((ppd_attr = ppdFindNextAttr(ppd, "APPrinterPreset", NULL)) != NULL);
1641   }
1642 
1643   if (!pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][_PWG_PRINT_QUALITY_DRAFT] &&
1644       !pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][_PWG_PRINT_QUALITY_NORMAL] &&
1645       !pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][_PWG_PRINT_QUALITY_HIGH])
1646   {
1647    /*
1648     * Try adding some common color options to create grayscale presets.  These
1649     * are listed in order of popularity...
1650     */
1651 
1652     const char	*color_option = NULL,	/* Color control option */
1653 		*gray_choice = NULL;	/* Choice to select grayscale */
1654 
1655     if ((color_model = ppdFindOption(ppd, "ColorModel")) != NULL &&
1656         ppdFindChoice(color_model, "Gray"))
1657     {
1658       color_option = "ColorModel";
1659       gray_choice  = "Gray";
1660     }
1661     else if ((color_model = ppdFindOption(ppd, "HPColorMode")) != NULL &&
1662              ppdFindChoice(color_model, "grayscale"))
1663     {
1664       color_option = "HPColorMode";
1665       gray_choice  = "grayscale";
1666     }
1667     else if ((color_model = ppdFindOption(ppd, "BRMonoColor")) != NULL &&
1668              ppdFindChoice(color_model, "Mono"))
1669     {
1670       color_option = "BRMonoColor";
1671       gray_choice  = "Mono";
1672     }
1673     else if ((color_model = ppdFindOption(ppd, "CNIJSGrayScale")) != NULL &&
1674              ppdFindChoice(color_model, "1"))
1675     {
1676       color_option = "CNIJSGrayScale";
1677       gray_choice  = "1";
1678     }
1679     else if ((color_model = ppdFindOption(ppd, "HPColorAsGray")) != NULL &&
1680              ppdFindChoice(color_model, "True"))
1681     {
1682       color_option = "HPColorAsGray";
1683       gray_choice  = "True";
1684     }
1685 
1686     if (color_option && gray_choice)
1687     {
1688      /*
1689       * Copy and convert ColorModel (output-mode) data...
1690       */
1691 
1692       cups_option_t	*coption,	/* Color option */
1693 			  *moption;	/* Monochrome option */
1694 
1695       for (pwg_print_quality = _PWG_PRINT_QUALITY_DRAFT;
1696 	   pwg_print_quality < _PWG_PRINT_QUALITY_MAX;
1697 	   pwg_print_quality ++)
1698       {
1699 	if (pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][pwg_print_quality])
1700 	{
1701 	 /*
1702 	  * Copy the color options...
1703 	  */
1704 
1705 	  num_options = pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR]
1706 					[pwg_print_quality];
1707 	  options     = calloc((size_t)num_options, sizeof(cups_option_t));
1708 
1709 	  if (options)
1710 	  {
1711 	    for (i = num_options, moption = options,
1712 		     coption = pc->presets[_PWG_PRINT_COLOR_MODE_COLOR]
1713 					   [pwg_print_quality];
1714 		 i > 0;
1715 		 i --, moption ++, coption ++)
1716 	    {
1717 	      moption->name  = _cupsStrRetain(coption->name);
1718 	      moption->value = _cupsStrRetain(coption->value);
1719 	    }
1720 
1721 	    pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][pwg_print_quality] =
1722 		num_options;
1723 	    pc->presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][pwg_print_quality] =
1724 		options;
1725 	  }
1726 	}
1727 	else if (pwg_print_quality != _PWG_PRINT_QUALITY_NORMAL)
1728 	  continue;
1729 
1730        /*
1731 	* Add the grayscale option to the preset...
1732 	*/
1733 
1734 	pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME][pwg_print_quality] =
1735 	    cupsAddOption(color_option, gray_choice,
1736 			  pc->num_presets[_PWG_PRINT_COLOR_MODE_MONOCHROME]
1737 					  [pwg_print_quality],
1738 			  pc->presets[_PWG_PRINT_COLOR_MODE_MONOCHROME] +
1739 			      pwg_print_quality);
1740       }
1741     }
1742   }
1743 
1744  /*
1745   * Copy and convert Duplex (sides) data...
1746   */
1747 
1748   if ((duplex = ppdFindOption(ppd, "Duplex")) == NULL)
1749     if ((duplex = ppdFindOption(ppd, "JCLDuplex")) == NULL)
1750       if ((duplex = ppdFindOption(ppd, "EFDuplex")) == NULL)
1751         if ((duplex = ppdFindOption(ppd, "EFDuplexing")) == NULL)
1752 	  duplex = ppdFindOption(ppd, "KD03Duplex");
1753 
1754   if (duplex)
1755   {
1756     pc->sides_option = strdup(duplex->keyword);
1757 
1758     for (i = duplex->num_choices, choice = duplex->choices;
1759          i > 0;
1760 	 i --, choice ++)
1761     {
1762       if ((!_cups_strcasecmp(choice->choice, "None") ||
1763 	   !_cups_strcasecmp(choice->choice, "False")) && !pc->sides_1sided)
1764         pc->sides_1sided = strdup(choice->choice);
1765       else if ((!_cups_strcasecmp(choice->choice, "DuplexNoTumble") ||
1766 	        !_cups_strcasecmp(choice->choice, "LongEdge") ||
1767 	        !_cups_strcasecmp(choice->choice, "Top")) && !pc->sides_2sided_long)
1768         pc->sides_2sided_long = strdup(choice->choice);
1769       else if ((!_cups_strcasecmp(choice->choice, "DuplexTumble") ||
1770 	        !_cups_strcasecmp(choice->choice, "ShortEdge") ||
1771 	        !_cups_strcasecmp(choice->choice, "Bottom")) &&
1772 	       !pc->sides_2sided_short)
1773         pc->sides_2sided_short = strdup(choice->choice);
1774     }
1775   }
1776 
1777  /*
1778   * Copy filters and pre-filters...
1779   */
1780 
1781   pc->filters = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
1782 
1783   cupsArrayAdd(pc->filters,
1784                "application/vnd.cups-raw application/octet-stream 0 -");
1785 
1786   if ((ppd_attr = ppdFindAttr(ppd, "cupsFilter2", NULL)) != NULL)
1787   {
1788     do
1789     {
1790       cupsArrayAdd(pc->filters, ppd_attr->value);
1791     }
1792     while ((ppd_attr = ppdFindNextAttr(ppd, "cupsFilter2", NULL)) != NULL);
1793   }
1794   else if (ppd->num_filters > 0)
1795   {
1796     for (i = 0; i < ppd->num_filters; i ++)
1797       cupsArrayAdd(pc->filters, ppd->filters[i]);
1798   }
1799   else
1800     cupsArrayAdd(pc->filters, "application/vnd.cups-postscript 0 -");
1801 
1802  /*
1803   * See if we have a command filter...
1804   */
1805 
1806   for (filter = (const char *)cupsArrayFirst(pc->filters);
1807        filter;
1808        filter = (const char *)cupsArrayNext(pc->filters))
1809     if (!_cups_strncasecmp(filter, "application/vnd.cups-command", 28) &&
1810         _cups_isspace(filter[28]))
1811       break;
1812 
1813   if (!filter &&
1814       ((ppd_attr = ppdFindAttr(ppd, "cupsCommands", NULL)) == NULL ||
1815        _cups_strcasecmp(ppd_attr->value, "none")))
1816   {
1817    /*
1818     * No command filter and no cupsCommands keyword telling us not to use one.
1819     * See if this is a PostScript printer, and if so add a PostScript command
1820     * filter...
1821     */
1822 
1823     for (filter = (const char *)cupsArrayFirst(pc->filters);
1824 	 filter;
1825 	 filter = (const char *)cupsArrayNext(pc->filters))
1826       if (!_cups_strncasecmp(filter, "application/vnd.cups-postscript", 31) &&
1827 	  _cups_isspace(filter[31]))
1828 	break;
1829 
1830     if (filter)
1831       cupsArrayAdd(pc->filters,
1832                    "application/vnd.cups-command application/postscript 100 "
1833                    "commandtops");
1834   }
1835 
1836   if ((ppd_attr = ppdFindAttr(ppd, "cupsPreFilter", NULL)) != NULL)
1837   {
1838     pc->prefilters = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
1839 
1840     do
1841     {
1842       cupsArrayAdd(pc->prefilters, ppd_attr->value);
1843     }
1844     while ((ppd_attr = ppdFindNextAttr(ppd, "cupsPreFilter", NULL)) != NULL);
1845   }
1846 
1847   if ((ppd_attr = ppdFindAttr(ppd, "cupsSingleFile", NULL)) != NULL)
1848     pc->single_file = !_cups_strcasecmp(ppd_attr->value, "true");
1849 
1850  /*
1851   * Copy the product string, if any...
1852   */
1853 
1854   if (ppd->product)
1855     pc->product = strdup(ppd->product);
1856 
1857  /*
1858   * Copy finishings mapping data...
1859   */
1860 
1861   if ((ppd_attr = ppdFindAttr(ppd, "cupsIPPFinishings", NULL)) != NULL)
1862   {
1863    /*
1864     * Have proper vendor mapping of IPP finishings values to PPD options...
1865     */
1866 
1867     pc->finishings = cupsArrayNew3((cups_array_func_t)pwg_compare_finishings,
1868                                    NULL, NULL, 0, NULL,
1869                                    (cups_afree_func_t)pwg_free_finishings);
1870 
1871     do
1872     {
1873       if ((finishings = calloc(1, sizeof(_pwg_finishings_t))) == NULL)
1874         goto create_error;
1875 
1876       finishings->value       = (ipp_finishings_t)atoi(ppd_attr->spec);
1877       finishings->num_options = _ppdParseOptions(ppd_attr->value, 0,
1878                                                  &(finishings->options),
1879                                                  _PPD_PARSE_OPTIONS);
1880 
1881       cupsArrayAdd(pc->finishings, finishings);
1882     }
1883     while ((ppd_attr = ppdFindNextAttr(ppd, "cupsIPPFinishings",
1884                                        NULL)) != NULL);
1885   }
1886   else
1887   {
1888    /*
1889     * No IPP mapping data, try to map common/standard PPD keywords...
1890     */
1891 
1892     pc->finishings = cupsArrayNew3((cups_array_func_t)pwg_compare_finishings, NULL, NULL, 0, NULL, (cups_afree_func_t)pwg_free_finishings);
1893 
1894     if ((ppd_option = ppdFindOption(ppd, "StapleLocation")) != NULL)
1895     {
1896      /*
1897       * Add staple finishings...
1898       */
1899 
1900       if (ppdFindChoice(ppd_option, "SinglePortrait"))
1901         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_STAPLE_TOP_LEFT, "StapleLocation", "SinglePortrait");
1902       if (ppdFindChoice(ppd_option, "UpperLeft")) /* Ricoh extension */
1903         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_STAPLE_TOP_LEFT, "StapleLocation", "UpperLeft");
1904       if (ppdFindChoice(ppd_option, "UpperRight")) /* Ricoh extension */
1905         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_STAPLE_TOP_RIGHT, "StapleLocation", "UpperRight");
1906       if (ppdFindChoice(ppd_option, "SingleLandscape"))
1907         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_STAPLE_BOTTOM_LEFT, "StapleLocation", "SingleLandscape");
1908       if (ppdFindChoice(ppd_option, "DualLandscape"))
1909         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_STAPLE_DUAL_LEFT, "StapleLocation", "DualLandscape");
1910     }
1911 
1912     if ((ppd_option = ppdFindOption(ppd, "RIPunch")) != NULL)
1913     {
1914      /*
1915       * Add (Ricoh) punch finishings...
1916       */
1917 
1918       if (ppdFindChoice(ppd_option, "Left2"))
1919         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_DUAL_LEFT, "RIPunch", "Left2");
1920       if (ppdFindChoice(ppd_option, "Left3"))
1921         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_TRIPLE_LEFT, "RIPunch", "Left3");
1922       if (ppdFindChoice(ppd_option, "Left4"))
1923         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_QUAD_LEFT, "RIPunch", "Left4");
1924       if (ppdFindChoice(ppd_option, "Right2"))
1925         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_DUAL_RIGHT, "RIPunch", "Right2");
1926       if (ppdFindChoice(ppd_option, "Right3"))
1927         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_TRIPLE_RIGHT, "RIPunch", "Right3");
1928       if (ppdFindChoice(ppd_option, "Right4"))
1929         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_QUAD_RIGHT, "RIPunch", "Right4");
1930       if (ppdFindChoice(ppd_option, "Upper2"))
1931         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_DUAL_TOP, "RIPunch", "Upper2");
1932       if (ppdFindChoice(ppd_option, "Upper3"))
1933         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_TRIPLE_TOP, "RIPunch", "Upper3");
1934       if (ppdFindChoice(ppd_option, "Upper4"))
1935         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_PUNCH_QUAD_TOP, "RIPunch", "Upper4");
1936     }
1937 
1938     if ((ppd_option = ppdFindOption(ppd, "BindEdge")) != NULL)
1939     {
1940      /*
1941       * Add bind finishings...
1942       */
1943 
1944       if (ppdFindChoice(ppd_option, "Left"))
1945         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_BIND_LEFT, "BindEdge", "Left");
1946       if (ppdFindChoice(ppd_option, "Right"))
1947         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_BIND_RIGHT, "BindEdge", "Right");
1948       if (ppdFindChoice(ppd_option, "Top"))
1949         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_BIND_TOP, "BindEdge", "Top");
1950       if (ppdFindChoice(ppd_option, "Bottom"))
1951         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_BIND_BOTTOM, "BindEdge", "Bottom");
1952     }
1953 
1954     if ((ppd_option = ppdFindOption(ppd, "FoldType")) != NULL)
1955     {
1956      /*
1957       * Add (Adobe) fold finishings...
1958       */
1959 
1960       if (ppdFindChoice(ppd_option, "ZFold"))
1961         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_Z, "FoldType", "ZFold");
1962       if (ppdFindChoice(ppd_option, "Saddle"))
1963         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_HALF, "FoldType", "Saddle");
1964       if (ppdFindChoice(ppd_option, "DoubleGate"))
1965         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_DOUBLE_GATE, "FoldType", "DoubleGate");
1966       if (ppdFindChoice(ppd_option, "LeftGate"))
1967         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_LEFT_GATE, "FoldType", "LeftGate");
1968       if (ppdFindChoice(ppd_option, "RightGate"))
1969         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_RIGHT_GATE, "FoldType", "RightGate");
1970       if (ppdFindChoice(ppd_option, "Letter"))
1971         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_LETTER, "FoldType", "Letter");
1972       if (ppdFindChoice(ppd_option, "XFold"))
1973         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_POSTER, "FoldType", "XFold");
1974     }
1975 
1976     if ((ppd_option = ppdFindOption(ppd, "RIFoldType")) != NULL)
1977     {
1978      /*
1979       * Add (Ricoh) fold finishings...
1980       */
1981 
1982       if (ppdFindChoice(ppd_option, "OutsideTwoFold"))
1983         pwg_add_finishing(pc->finishings, IPP_FINISHINGS_FOLD_LETTER, "RIFoldType", "OutsideTwoFold");
1984     }
1985 
1986     if (cupsArrayCount(pc->finishings) == 0)
1987     {
1988       cupsArrayDelete(pc->finishings);
1989       pc->finishings = NULL;
1990     }
1991   }
1992 
1993   if ((ppd_option = ppdFindOption(ppd, "cupsFinishingTemplate")) != NULL)
1994   {
1995     pc->templates = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
1996 
1997     for (choice = ppd_option->choices, i = ppd_option->num_choices; i > 0; choice ++, i --)
1998     {
1999       cupsArrayAdd(pc->templates, (void *)choice->choice);
2000 
2001      /*
2002       * Add localized text for PWG keyword to message catalog...
2003       */
2004 
2005       snprintf(msg_id, sizeof(msg_id), "finishing-template.%s", choice->choice);
2006       pwg_add_message(pc->strings, msg_id, choice->text);
2007     }
2008   }
2009 
2010  /*
2011   * Max copies...
2012   */
2013 
2014   if ((ppd_attr = ppdFindAttr(ppd, "cupsMaxCopies", NULL)) != NULL)
2015     pc->max_copies = atoi(ppd_attr->value);
2016   else if (ppd->manual_copies)
2017     pc->max_copies = 1;
2018   else
2019     pc->max_copies = 9999;
2020 
2021  /*
2022   * cupsChargeInfoURI, cupsJobAccountId, cupsJobAccountingUserId,
2023   * cupsJobPassword, and cupsMandatory.
2024   */
2025 
2026   if ((ppd_attr = ppdFindAttr(ppd, "cupsChargeInfoURI", NULL)) != NULL)
2027     pc->charge_info_uri = strdup(ppd_attr->value);
2028 
2029   if ((ppd_attr = ppdFindAttr(ppd, "cupsJobAccountId", NULL)) != NULL)
2030     pc->account_id = !_cups_strcasecmp(ppd_attr->value, "true");
2031 
2032   if ((ppd_attr = ppdFindAttr(ppd, "cupsJobAccountingUserId", NULL)) != NULL)
2033     pc->accounting_user_id = !_cups_strcasecmp(ppd_attr->value, "true");
2034 
2035   if ((ppd_attr = ppdFindAttr(ppd, "cupsJobPassword", NULL)) != NULL)
2036     pc->password = strdup(ppd_attr->value);
2037 
2038   if ((ppd_attr = ppdFindAttr(ppd, "cupsMandatory", NULL)) != NULL)
2039     pc->mandatory = _cupsArrayNewStrings(ppd_attr->value, ' ');
2040 
2041  /*
2042   * Support files...
2043   */
2044 
2045   pc->support_files = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
2046 
2047   for (ppd_attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
2048        ppd_attr;
2049        ppd_attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
2050     cupsArrayAdd(pc->support_files, ppd_attr->value);
2051 
2052 #ifdef HAVE_APPLICATIONSERVICES_H
2053   if ((ppd_attr = ppdFindAttr(ppd, "APPrinterIconPath", NULL)) != NULL)
2054     cupsArrayAdd(pc->support_files, ppd_attr->value);
2055 #endif
2056 
2057  /*
2058   * Return the cache data...
2059   */
2060 
2061   return (pc);
2062 
2063  /*
2064   * If we get here we need to destroy the PWG mapping data and return NULL...
2065   */
2066 
2067   create_error:
2068 
2069   _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Out of memory."), 1);
2070   _ppdCacheDestroy(pc);
2071 
2072   return (NULL);
2073 }
2074 
2075 
2076 /*
2077  * '_ppdCacheDestroy()' - Free all memory used for PWG mapping data.
2078  */
2079 
2080 void
_ppdCacheDestroy(_ppd_cache_t * pc)2081 _ppdCacheDestroy(_ppd_cache_t *pc)	/* I - PPD cache and mapping data */
2082 {
2083   int		i;			/* Looping var */
2084   pwg_map_t	*map;			/* Current map */
2085   pwg_size_t	*size;			/* Current size */
2086 
2087 
2088  /*
2089   * Range check input...
2090   */
2091 
2092   if (!pc)
2093     return;
2094 
2095  /*
2096   * Free memory as needed...
2097   */
2098 
2099   if (pc->bins)
2100   {
2101     for (i = pc->num_bins, map = pc->bins; i > 0; i --, map ++)
2102     {
2103       free(map->pwg);
2104       free(map->ppd);
2105     }
2106 
2107     free(pc->bins);
2108   }
2109 
2110   if (pc->sizes)
2111   {
2112     for (i = pc->num_sizes, size = pc->sizes; i > 0; i --, size ++)
2113     {
2114       free(size->map.pwg);
2115       free(size->map.ppd);
2116     }
2117 
2118     free(pc->sizes);
2119   }
2120 
2121   free(pc->source_option);
2122 
2123   if (pc->sources)
2124   {
2125     for (i = pc->num_sources, map = pc->sources; i > 0; i --, map ++)
2126     {
2127       free(map->pwg);
2128       free(map->ppd);
2129     }
2130 
2131     free(pc->sources);
2132   }
2133 
2134   if (pc->types)
2135   {
2136     for (i = pc->num_types, map = pc->types; i > 0; i --, map ++)
2137     {
2138       free(map->pwg);
2139       free(map->ppd);
2140     }
2141 
2142     free(pc->types);
2143   }
2144 
2145   free(pc->custom_max_keyword);
2146   free(pc->custom_min_keyword);
2147 
2148   free(pc->product);
2149   cupsArrayDelete(pc->filters);
2150   cupsArrayDelete(pc->prefilters);
2151   cupsArrayDelete(pc->finishings);
2152 
2153   free(pc->charge_info_uri);
2154   free(pc->password);
2155 
2156   cupsArrayDelete(pc->mandatory);
2157 
2158   cupsArrayDelete(pc->support_files);
2159 
2160   cupsArrayDelete(pc->strings);
2161 
2162   free(pc);
2163 }
2164 
2165 
2166 /*
2167  * '_ppdCacheGetBin()' - Get the PWG output-bin keyword associated with a PPD
2168  *                  OutputBin.
2169  */
2170 
2171 const char *				/* O - output-bin or NULL */
_ppdCacheGetBin(_ppd_cache_t * pc,const char * output_bin)2172 _ppdCacheGetBin(
2173     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2174     const char   *output_bin)		/* I - PPD OutputBin string */
2175 {
2176   int	i;				/* Looping var */
2177 
2178 
2179  /*
2180   * Range check input...
2181   */
2182 
2183   if (!pc || !output_bin)
2184     return (NULL);
2185 
2186  /*
2187   * Look up the OutputBin string...
2188   */
2189 
2190 
2191   for (i = 0; i < pc->num_bins; i ++)
2192     if (!_cups_strcasecmp(output_bin, pc->bins[i].ppd) || !_cups_strcasecmp(output_bin, pc->bins[i].pwg))
2193       return (pc->bins[i].pwg);
2194 
2195   return (NULL);
2196 }
2197 
2198 
2199 /*
2200  * '_ppdCacheGetFinishingOptions()' - Get PPD finishing options for the given
2201  *                                    IPP finishings value(s).
2202  */
2203 
2204 int					/* O  - New number of options */
_ppdCacheGetFinishingOptions(_ppd_cache_t * pc,ipp_t * job,ipp_finishings_t value,int num_options,cups_option_t ** options)2205 _ppdCacheGetFinishingOptions(
2206     _ppd_cache_t     *pc,		/* I  - PPD cache and mapping data */
2207     ipp_t            *job,		/* I  - Job attributes or NULL */
2208     ipp_finishings_t value,		/* I  - IPP finishings value of IPP_FINISHINGS_NONE */
2209     int              num_options,	/* I  - Number of options */
2210     cups_option_t    **options)		/* IO - Options */
2211 {
2212   int			i;		/* Looping var */
2213   _pwg_finishings_t	*f,		/* PWG finishings options */
2214 			key;		/* Search key */
2215   ipp_attribute_t	*attr;		/* Finishings attribute */
2216   cups_option_t		*option;	/* Current finishings option */
2217 
2218 
2219  /*
2220   * Range check input...
2221   */
2222 
2223   if (!pc || cupsArrayCount(pc->finishings) == 0 || !options ||
2224       (!job && value == IPP_FINISHINGS_NONE))
2225     return (num_options);
2226 
2227  /*
2228   * Apply finishing options...
2229   */
2230 
2231   if (job && (attr = ippFindAttribute(job, "finishings", IPP_TAG_ENUM)) != NULL)
2232   {
2233     int	num_values = ippGetCount(attr);	/* Number of values */
2234 
2235     for (i = 0; i < num_values; i ++)
2236     {
2237       key.value = (ipp_finishings_t)ippGetInteger(attr, i);
2238 
2239       if ((f = cupsArrayFind(pc->finishings, &key)) != NULL)
2240       {
2241         int	j;			/* Another looping var */
2242 
2243         for (j = f->num_options, option = f->options; j > 0; j --, option ++)
2244           num_options = cupsAddOption(option->name, option->value,
2245                                       num_options, options);
2246       }
2247     }
2248   }
2249   else if (value != IPP_FINISHINGS_NONE)
2250   {
2251     key.value = value;
2252 
2253     if ((f = cupsArrayFind(pc->finishings, &key)) != NULL)
2254     {
2255       int	j;			/* Another looping var */
2256 
2257       for (j = f->num_options, option = f->options; j > 0; j --, option ++)
2258 	num_options = cupsAddOption(option->name, option->value,
2259 				    num_options, options);
2260     }
2261   }
2262 
2263   return (num_options);
2264 }
2265 
2266 
2267 /*
2268  * '_ppdCacheGetFinishingValues()' - Get IPP finishings value(s) from the given
2269  *                                   PPD options.
2270  */
2271 
2272 int					/* O - Number of finishings values */
_ppdCacheGetFinishingValues(ppd_file_t * ppd,_ppd_cache_t * pc,int max_values,int * values)2273 _ppdCacheGetFinishingValues(
2274     ppd_file_t    *ppd,			/* I - Marked PPD file */
2275     _ppd_cache_t  *pc,			/* I - PPD cache and mapping data */
2276     int           max_values,		/* I - Maximum number of finishings values */
2277     int           *values)		/* O - Finishings values */
2278 {
2279   int			i,		/* Looping var */
2280 			num_values = 0;	/* Number of values */
2281   _pwg_finishings_t	*f;		/* Current finishings option */
2282   cups_option_t		*option;	/* Current option */
2283   ppd_choice_t		*choice;	/* Marked PPD choice */
2284 
2285 
2286  /*
2287   * Range check input...
2288   */
2289 
2290   DEBUG_printf(("_ppdCacheGetFinishingValues(ppd=%p, pc=%p, max_values=%d, values=%p)", ppd, pc, max_values, values));
2291 
2292   if (!ppd || !pc || max_values < 1 || !values)
2293   {
2294     DEBUG_puts("_ppdCacheGetFinishingValues: Bad arguments, returning 0.");
2295     return (0);
2296   }
2297   else if (!pc->finishings)
2298   {
2299     DEBUG_puts("_ppdCacheGetFinishingValues: No finishings support, returning 0.");
2300     return (0);
2301   }
2302 
2303  /*
2304   * Go through the finishings options and see what is set...
2305   */
2306 
2307   for (f = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings);
2308        f;
2309        f = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
2310   {
2311     DEBUG_printf(("_ppdCacheGetFinishingValues: Checking %d (%s)", (int)f->value, ippEnumString("finishings", (int)f->value)));
2312 
2313     for (i = f->num_options, option = f->options; i > 0; i --, option ++)
2314     {
2315       DEBUG_printf(("_ppdCacheGetFinishingValues: %s=%s?", option->name, option->value));
2316 
2317       if ((choice = ppdFindMarkedChoice(ppd, option->name)) == NULL || _cups_strcasecmp(option->value, choice->choice))
2318       {
2319         DEBUG_puts("_ppdCacheGetFinishingValues: NO");
2320         break;
2321       }
2322     }
2323 
2324     if (i == 0)
2325     {
2326       DEBUG_printf(("_ppdCacheGetFinishingValues: Adding %d (%s)", (int)f->value, ippEnumString("finishings", (int)f->value)));
2327 
2328       values[num_values ++] = (int)f->value;
2329 
2330       if (num_values >= max_values)
2331         break;
2332     }
2333   }
2334 
2335   if (num_values == 0)
2336   {
2337    /*
2338     * Always have at least "finishings" = 'none'...
2339     */
2340 
2341     DEBUG_puts("_ppdCacheGetFinishingValues: Adding 3 (none).");
2342     values[0] = IPP_FINISHINGS_NONE;
2343     num_values ++;
2344   }
2345 
2346   DEBUG_printf(("_ppdCacheGetFinishingValues: Returning %d.", num_values));
2347 
2348   return (num_values);
2349 }
2350 
2351 
2352 /*
2353  * 'ppd_inputslot_for_keyword()' - Return the PPD InputSlot associated
2354  *                                a keyword string, or NULL if no mapping
2355  *                                exists.
2356  */
2357 static const char *			/* O - PPD InputSlot or NULL */
ppd_inputslot_for_keyword(_ppd_cache_t * pc,const char * keyword)2358 ppd_inputslot_for_keyword(
2359     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2360     const char   *keyword)		/* I - Keyword string */
2361 {
2362   int	i;				/* Looping var */
2363 
2364   if (!pc || !keyword)
2365     return (NULL);
2366 
2367   for (i = 0; i < pc->num_sources; i ++)
2368     if (!_cups_strcasecmp(keyword, pc->sources[i].pwg) || !_cups_strcasecmp(keyword, pc->sources[i].ppd))
2369       return (pc->sources[i].ppd);
2370 
2371   return (NULL);
2372 }
2373 
2374 /*
2375  * '_ppdCacheGetInputSlot()' - Get the PPD InputSlot associated with the job
2376  *                        attributes or a keyword string.
2377  */
2378 
2379 const char *				/* O - PPD InputSlot or NULL */
_ppdCacheGetInputSlot(_ppd_cache_t * pc,ipp_t * job,const char * keyword)2380 _ppdCacheGetInputSlot(
2381     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2382     ipp_t        *job,			/* I - Job attributes or NULL */
2383     const char   *keyword)		/* I - Keyword string or NULL */
2384 {
2385  /*
2386   * Range check input...
2387   */
2388 
2389   if (!pc || pc->num_sources == 0 || (!job && !keyword))
2390     return (NULL);
2391 
2392   if (job && !keyword)
2393   {
2394    /*
2395     * Lookup the media-col attribute and any media-source found there...
2396     */
2397 
2398     ipp_attribute_t	*media_col,	/* media-col attribute */
2399 			*media_source;	/* media-source attribute */
2400     pwg_size_t		size;		/* Dimensional size */
2401     int			margins_set;	/* Were the margins set? */
2402 
2403     media_col = ippFindAttribute(job, "media-col", IPP_TAG_BEGIN_COLLECTION);
2404     if (media_col &&
2405         (media_source = ippFindAttribute(ippGetCollection(media_col, 0),
2406                                          "media-source",
2407 	                                 IPP_TAG_KEYWORD)) != NULL)
2408     {
2409      /*
2410       * Use the media-source value from media-col...
2411       */
2412 
2413       keyword = ippGetString(media_source, 0, NULL);
2414     }
2415     else if (pwgInitSize(&size, job, &margins_set))
2416     {
2417      /*
2418       * For media <= 5x7, try to ask for automatic selection so the printer can
2419       * pick the photo tray.  If auto isn't available, fall back to explicitly
2420       * asking for the photo tray.
2421       */
2422 
2423       if (size.width <= (5 * 2540) && size.length <= (7 * 2540)) {
2424         const char* match;
2425         if ((match = ppd_inputslot_for_keyword(pc, "auto")) != NULL)
2426           return (match);
2427         keyword = "photo";
2428       }
2429     }
2430   }
2431 
2432   return (ppd_inputslot_for_keyword(pc, keyword));
2433 }
2434 
2435 
2436 /*
2437  * '_ppdCacheGetMediaType()' - Get the PPD MediaType associated with the job
2438  *                        attributes or a keyword string.
2439  */
2440 
2441 const char *				/* O - PPD MediaType or NULL */
_ppdCacheGetMediaType(_ppd_cache_t * pc,ipp_t * job,const char * keyword)2442 _ppdCacheGetMediaType(
2443     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2444     ipp_t        *job,			/* I - Job attributes or NULL */
2445     const char   *keyword)		/* I - Keyword string or NULL */
2446 {
2447  /*
2448   * Range check input...
2449   */
2450 
2451   if (!pc || pc->num_types == 0 || (!job && !keyword))
2452     return (NULL);
2453 
2454   if (job && !keyword)
2455   {
2456    /*
2457     * Lookup the media-col attribute and any media-source found there...
2458     */
2459 
2460     ipp_attribute_t	*media_col,	/* media-col attribute */
2461 			*media_type;	/* media-type attribute */
2462 
2463     media_col = ippFindAttribute(job, "media-col", IPP_TAG_BEGIN_COLLECTION);
2464     if (media_col)
2465     {
2466       if ((media_type = ippFindAttribute(media_col->values[0].collection,
2467                                          "media-type",
2468 	                                 IPP_TAG_KEYWORD)) == NULL)
2469 	media_type = ippFindAttribute(media_col->values[0].collection,
2470 				      "media-type", IPP_TAG_NAME);
2471 
2472       if (media_type)
2473 	keyword = media_type->values[0].string.text;
2474     }
2475   }
2476 
2477   if (keyword)
2478   {
2479     int	i;				/* Looping var */
2480 
2481     for (i = 0; i < pc->num_types; i ++)
2482       if (!_cups_strcasecmp(keyword, pc->types[i].pwg) || !_cups_strcasecmp(keyword, pc->types[i].ppd))
2483         return (pc->types[i].ppd);
2484   }
2485 
2486   return (NULL);
2487 }
2488 
2489 
2490 /*
2491  * '_ppdCacheGetOutputBin()' - Get the PPD OutputBin associated with the keyword
2492  *                        string.
2493  */
2494 
2495 const char *				/* O - PPD OutputBin or NULL */
_ppdCacheGetOutputBin(_ppd_cache_t * pc,const char * output_bin)2496 _ppdCacheGetOutputBin(
2497     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2498     const char   *output_bin)		/* I - Keyword string */
2499 {
2500   int	i;				/* Looping var */
2501 
2502 
2503  /*
2504   * Range check input...
2505   */
2506 
2507   if (!pc || !output_bin)
2508     return (NULL);
2509 
2510  /*
2511   * Look up the OutputBin string...
2512   */
2513 
2514 
2515   for (i = 0; i < pc->num_bins; i ++)
2516     if (!_cups_strcasecmp(output_bin, pc->bins[i].pwg) || !_cups_strcasecmp(output_bin, pc->bins[i].ppd))
2517       return (pc->bins[i].ppd);
2518 
2519   return (NULL);
2520 }
2521 
2522 
2523 /*
2524  * '_ppdCacheGetPageSize()' - Get the PPD PageSize associated with the job
2525  *                       attributes or a keyword string.
2526  */
2527 
2528 const char *				/* O - PPD PageSize or NULL */
_ppdCacheGetPageSize(_ppd_cache_t * pc,ipp_t * job,const char * keyword,int * exact)2529 _ppdCacheGetPageSize(
2530     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2531     ipp_t        *job,			/* I - Job attributes or NULL */
2532     const char   *keyword,		/* I - Keyword string or NULL */
2533     int          *exact)		/* O - 1 if exact match, 0 otherwise */
2534 {
2535   int		i;			/* Looping var */
2536   pwg_size_t	*size,			/* Current size */
2537 		*closest,		/* Closest size */
2538 		jobsize;		/* Size data from job */
2539   int		margins_set,		/* Were the margins set? */
2540 		dwidth,			/* Difference in width */
2541 		dlength,		/* Difference in length */
2542 		dleft,			/* Difference in left margins */
2543 		dright,			/* Difference in right margins */
2544 		dbottom,		/* Difference in bottom margins */
2545 		dtop,			/* Difference in top margins */
2546 		dmin,			/* Minimum difference */
2547 		dclosest;		/* Closest difference */
2548   const char	*ppd_name;		/* PPD media name */
2549 
2550 
2551   DEBUG_printf(("_ppdCacheGetPageSize(pc=%p, job=%p, keyword=\"%s\", exact=%p)",
2552 	        pc, job, keyword, exact));
2553 
2554  /*
2555   * Range check input...
2556   */
2557 
2558   if (!pc || (!job && !keyword))
2559     return (NULL);
2560 
2561   if (exact)
2562     *exact = 0;
2563 
2564   ppd_name = keyword;
2565 
2566   if (job)
2567   {
2568    /*
2569     * Try getting the PPD media name from the job attributes...
2570     */
2571 
2572     ipp_attribute_t	*attr;		/* Job attribute */
2573 
2574     if ((attr = ippFindAttribute(job, "PageSize", IPP_TAG_ZERO)) == NULL)
2575       if ((attr = ippFindAttribute(job, "PageRegion", IPP_TAG_ZERO)) == NULL)
2576         attr = ippFindAttribute(job, "media", IPP_TAG_ZERO);
2577 
2578 #ifdef DEBUG
2579     if (attr)
2580       DEBUG_printf(("1_ppdCacheGetPageSize: Found attribute %s (%s)",
2581                     attr->name, ippTagString(attr->value_tag)));
2582     else
2583       DEBUG_puts("1_ppdCacheGetPageSize: Did not find media attribute.");
2584 #endif /* DEBUG */
2585 
2586     if (attr && (attr->value_tag == IPP_TAG_NAME ||
2587                  attr->value_tag == IPP_TAG_KEYWORD))
2588       ppd_name = attr->values[0].string.text;
2589   }
2590 
2591   DEBUG_printf(("1_ppdCacheGetPageSize: ppd_name=\"%s\"", ppd_name));
2592 
2593   if (ppd_name)
2594   {
2595    /*
2596     * Try looking up the named PPD size first...
2597     */
2598 
2599     for (i = pc->num_sizes, size = pc->sizes; i > 0; i --, size ++)
2600     {
2601       DEBUG_printf(("2_ppdCacheGetPageSize: size[%d]=[\"%s\" \"%s\"]",
2602                     (int)(size - pc->sizes), size->map.pwg, size->map.ppd));
2603 
2604       if (!_cups_strcasecmp(ppd_name, size->map.ppd) ||
2605           !_cups_strcasecmp(ppd_name, size->map.pwg))
2606       {
2607 	if (exact)
2608 	  *exact = 1;
2609 
2610         DEBUG_printf(("1_ppdCacheGetPageSize: Returning \"%s\"", ppd_name));
2611 
2612         return (size->map.ppd);
2613       }
2614     }
2615   }
2616 
2617   if (job && !keyword)
2618   {
2619    /*
2620     * Get the size using media-col or media, with the preference being
2621     * media-col.
2622     */
2623 
2624     if (!pwgInitSize(&jobsize, job, &margins_set))
2625       return (NULL);
2626   }
2627   else
2628   {
2629    /*
2630     * Get the size using a media keyword...
2631     */
2632 
2633     pwg_media_t	*media;		/* Media definition */
2634 
2635 
2636     if ((media = pwgMediaForPWG(keyword)) == NULL)
2637       if ((media = pwgMediaForLegacy(keyword)) == NULL)
2638         if ((media = pwgMediaForPPD(keyword)) == NULL)
2639 	  return (NULL);
2640 
2641     jobsize.width  = media->width;
2642     jobsize.length = media->length;
2643     margins_set    = 0;
2644   }
2645 
2646  /*
2647   * Now that we have the dimensions and possibly the margins, look at the
2648   * available sizes and find the match...
2649   */
2650 
2651   closest  = NULL;
2652   dclosest = 999999999;
2653 
2654   if (!ppd_name || _cups_strncasecmp(ppd_name, "Custom.", 7) ||
2655       _cups_strncasecmp(ppd_name, "custom_", 7))
2656   {
2657     for (i = pc->num_sizes, size = pc->sizes; i > 0; i --, size ++)
2658     {
2659      /*
2660       * Adobe uses a size matching algorithm with an epsilon of 5 points, which
2661       * is just about 176/2540ths...
2662       */
2663 
2664       dwidth  = size->width - jobsize.width;
2665       dlength = size->length - jobsize.length;
2666 
2667       if (dwidth <= -176 || dwidth >= 176 || dlength <= -176 || dlength >= 176)
2668 	continue;
2669 
2670       if (margins_set)
2671       {
2672        /*
2673 	* Use a tighter epsilon of 1 point (35/2540ths) for margins...
2674 	*/
2675 
2676 	dleft   = size->left - jobsize.left;
2677 	dright  = size->right - jobsize.right;
2678 	dtop    = size->top - jobsize.top;
2679 	dbottom = size->bottom - jobsize.bottom;
2680 
2681 	if (dleft <= -35 || dleft >= 35 || dright <= -35 || dright >= 35 ||
2682 	    dtop <= -35 || dtop >= 35 || dbottom <= -35 || dbottom >= 35)
2683 	{
2684 	  dleft   = dleft < 0 ? -dleft : dleft;
2685 	  dright  = dright < 0 ? -dright : dright;
2686 	  dbottom = dbottom < 0 ? -dbottom : dbottom;
2687 	  dtop    = dtop < 0 ? -dtop : dtop;
2688 	  dmin    = dleft + dright + dbottom + dtop;
2689 
2690 	  if (dmin < dclosest)
2691 	  {
2692 	    dclosest = dmin;
2693 	    closest  = size;
2694 	  }
2695 
2696 	  continue;
2697 	}
2698       }
2699 
2700       if (exact)
2701 	*exact = 1;
2702 
2703       DEBUG_printf(("1_ppdCacheGetPageSize: Returning \"%s\"", size->map.ppd));
2704 
2705       return (size->map.ppd);
2706     }
2707   }
2708 
2709   if (closest)
2710   {
2711     DEBUG_printf(("1_ppdCacheGetPageSize: Returning \"%s\" (closest)",
2712                   closest->map.ppd));
2713 
2714     return (closest->map.ppd);
2715   }
2716 
2717  /*
2718   * If we get here we need to check for custom page size support...
2719   */
2720 
2721   if (jobsize.width >= pc->custom_min_width &&
2722       jobsize.width <= pc->custom_max_width &&
2723       jobsize.length >= pc->custom_min_length &&
2724       jobsize.length <= pc->custom_max_length)
2725   {
2726    /*
2727     * In range, format as Custom.WWWWxLLLL (points).
2728     */
2729 
2730     snprintf(pc->custom_ppd_size, sizeof(pc->custom_ppd_size), "Custom.%dx%d",
2731              (int)PWG_TO_POINTS(jobsize.width), (int)PWG_TO_POINTS(jobsize.length));
2732 
2733     if (margins_set && exact)
2734     {
2735       dleft   = pc->custom_size.left - jobsize.left;
2736       dright  = pc->custom_size.right - jobsize.right;
2737       dtop    = pc->custom_size.top - jobsize.top;
2738       dbottom = pc->custom_size.bottom - jobsize.bottom;
2739 
2740       if (dleft > -35 && dleft < 35 && dright > -35 && dright < 35 &&
2741           dtop > -35 && dtop < 35 && dbottom > -35 && dbottom < 35)
2742 	*exact = 1;
2743     }
2744     else if (exact)
2745       *exact = 1;
2746 
2747     DEBUG_printf(("1_ppdCacheGetPageSize: Returning \"%s\" (custom)",
2748                   pc->custom_ppd_size));
2749 
2750     return (pc->custom_ppd_size);
2751   }
2752 
2753  /*
2754   * No custom page size support or the size is out of range - return NULL.
2755   */
2756 
2757   DEBUG_puts("1_ppdCacheGetPageSize: Returning NULL");
2758 
2759   return (NULL);
2760 }
2761 
2762 
2763 /*
2764  * '_ppdCacheGetSize()' - Get the PWG size associated with a PPD PageSize.
2765  */
2766 
2767 pwg_size_t *				/* O - PWG size or NULL */
_ppdCacheGetSize(_ppd_cache_t * pc,const char * page_size)2768 _ppdCacheGetSize(
2769     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2770     const char   *page_size)		/* I - PPD PageSize */
2771 {
2772   int		i;			/* Looping var */
2773   pwg_media_t	*media;			/* Media */
2774   pwg_size_t	*size;			/* Current size */
2775 
2776 
2777  /*
2778   * Range check input...
2779   */
2780 
2781   if (!pc || !page_size)
2782     return (NULL);
2783 
2784   if (!_cups_strncasecmp(page_size, "Custom.", 7))
2785   {
2786    /*
2787     * Custom size; size name can be one of the following:
2788     *
2789     *    Custom.WIDTHxLENGTHin    - Size in inches
2790     *    Custom.WIDTHxLENGTHft    - Size in feet
2791     *    Custom.WIDTHxLENGTHcm    - Size in centimeters
2792     *    Custom.WIDTHxLENGTHmm    - Size in millimeters
2793     *    Custom.WIDTHxLENGTHm     - Size in meters
2794     *    Custom.WIDTHxLENGTH[pt]  - Size in points
2795     */
2796 
2797     double		w, l;		/* Width and length of page */
2798     char		*ptr;		/* Pointer into PageSize */
2799     struct lconv	*loc;		/* Locale data */
2800 
2801     loc = localeconv();
2802     w   = (float)_cupsStrScand(page_size + 7, &ptr, loc);
2803     if (!ptr || *ptr != 'x')
2804       return (NULL);
2805 
2806     l = (float)_cupsStrScand(ptr + 1, &ptr, loc);
2807     if (!ptr)
2808       return (NULL);
2809 
2810     if (!_cups_strcasecmp(ptr, "in"))
2811     {
2812       w *= 2540.0;
2813       l *= 2540.0;
2814     }
2815     else if (!_cups_strcasecmp(ptr, "ft"))
2816     {
2817       w *= 12.0 * 2540.0;
2818       l *= 12.0 * 2540.0;
2819     }
2820     else if (!_cups_strcasecmp(ptr, "mm"))
2821     {
2822       w *= 100.0;
2823       l *= 100.0;
2824     }
2825     else if (!_cups_strcasecmp(ptr, "cm"))
2826     {
2827       w *= 1000.0;
2828       l *= 1000.0;
2829     }
2830     else if (!_cups_strcasecmp(ptr, "m"))
2831     {
2832       w *= 100000.0;
2833       l *= 100000.0;
2834     }
2835     else
2836     {
2837       w *= 2540.0 / 72.0;
2838       l *= 2540.0 / 72.0;
2839     }
2840 
2841     pc->custom_size.width  = (int)w;
2842     pc->custom_size.length = (int)l;
2843 
2844     return (&(pc->custom_size));
2845   }
2846 
2847  /*
2848   * Not a custom size - look it up...
2849   */
2850 
2851   for (i = pc->num_sizes, size = pc->sizes; i > 0; i --, size ++)
2852     if (!_cups_strcasecmp(page_size, size->map.ppd) ||
2853         !_cups_strcasecmp(page_size, size->map.pwg))
2854       return (size);
2855 
2856  /*
2857   * Look up standard sizes...
2858   */
2859 
2860   if ((media = pwgMediaForPPD(page_size)) == NULL)
2861     if ((media = pwgMediaForLegacy(page_size)) == NULL)
2862       media = pwgMediaForPWG(page_size);
2863 
2864   if (media)
2865   {
2866     pc->custom_size.width  = media->width;
2867     pc->custom_size.length = media->length;
2868 
2869     return (&(pc->custom_size));
2870   }
2871 
2872   return (NULL);
2873 }
2874 
2875 
2876 /*
2877  * '_ppdCacheGetSource()' - Get the PWG media-source associated with a PPD
2878  *                          InputSlot.
2879  */
2880 
2881 const char *				/* O - PWG media-source keyword */
_ppdCacheGetSource(_ppd_cache_t * pc,const char * input_slot)2882 _ppdCacheGetSource(
2883     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2884     const char   *input_slot)		/* I - PPD InputSlot */
2885 {
2886   int		i;			/* Looping var */
2887   pwg_map_t	*source;		/* Current source */
2888 
2889 
2890  /*
2891   * Range check input...
2892   */
2893 
2894   if (!pc || !input_slot)
2895     return (NULL);
2896 
2897   for (i = pc->num_sources, source = pc->sources; i > 0; i --, source ++)
2898     if (!_cups_strcasecmp(input_slot, source->ppd) || !_cups_strcasecmp(input_slot, source->pwg))
2899       return (source->pwg);
2900 
2901   return (NULL);
2902 }
2903 
2904 
2905 /*
2906  * '_ppdCacheGetType()' - Get the PWG media-type associated with a PPD
2907  *                        MediaType.
2908  */
2909 
2910 const char *				/* O - PWG media-type keyword */
_ppdCacheGetType(_ppd_cache_t * pc,const char * media_type)2911 _ppdCacheGetType(
2912     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2913     const char   *media_type)		/* I - PPD MediaType */
2914 {
2915   int		i;			/* Looping var */
2916   pwg_map_t	*type;			/* Current type */
2917 
2918 
2919  /*
2920   * Range check input...
2921   */
2922 
2923   if (!pc || !media_type)
2924     return (NULL);
2925 
2926   for (i = pc->num_types, type = pc->types; i > 0; i --, type ++)
2927     if (!_cups_strcasecmp(media_type, type->ppd) || !_cups_strcasecmp(media_type, type->pwg))
2928       return (type->pwg);
2929 
2930   return (NULL);
2931 }
2932 
2933 
2934 /*
2935  * '_ppdCacheWriteFile()' - Write PWG mapping data to a file.
2936  */
2937 
2938 int					/* O - 1 on success, 0 on failure */
_ppdCacheWriteFile(_ppd_cache_t * pc,const char * filename,ipp_t * attrs)2939 _ppdCacheWriteFile(
2940     _ppd_cache_t *pc,			/* I - PPD cache and mapping data */
2941     const char   *filename,		/* I - File to write */
2942     ipp_t        *attrs)		/* I - Attributes to write, if any */
2943 {
2944   int			i, j, k;	/* Looping vars */
2945   cups_file_t		*fp;		/* Output file */
2946   pwg_size_t		*size;		/* Current size */
2947   pwg_map_t		*map;		/* Current map */
2948   _pwg_finishings_t	*f;		/* Current finishing option */
2949   cups_option_t		*option;	/* Current option */
2950   const char		*value;		/* String value */
2951   char			newfile[1024];	/* New filename */
2952 
2953 
2954  /*
2955   * Range check input...
2956   */
2957 
2958   if (!pc || !filename)
2959   {
2960     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
2961     return (0);
2962   }
2963 
2964  /*
2965   * Open the file and write with compression...
2966   */
2967 
2968   snprintf(newfile, sizeof(newfile), "%s.N", filename);
2969   if ((fp = cupsFileOpen(newfile, "w9")) == NULL)
2970   {
2971     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
2972     return (0);
2973   }
2974 
2975  /*
2976   * Standard header...
2977   */
2978 
2979   cupsFilePrintf(fp, "#CUPS-PPD-CACHE-%d\n", _PPD_CACHE_VERSION);
2980 
2981  /*
2982   * Output bins...
2983   */
2984 
2985   if (pc->num_bins > 0)
2986   {
2987     cupsFilePrintf(fp, "NumBins %d\n", pc->num_bins);
2988     for (i = pc->num_bins, map = pc->bins; i > 0; i --, map ++)
2989       cupsFilePrintf(fp, "Bin %s %s\n", map->pwg, map->ppd);
2990   }
2991 
2992  /*
2993   * Media sizes...
2994   */
2995 
2996   cupsFilePrintf(fp, "NumSizes %d\n", pc->num_sizes);
2997   for (i = pc->num_sizes, size = pc->sizes; i > 0; i --, size ++)
2998     cupsFilePrintf(fp, "Size %s %s %d %d %d %d %d %d\n", size->map.pwg,
2999 		   size->map.ppd, size->width, size->length, size->left,
3000 		   size->bottom, size->right, size->top);
3001   if (pc->custom_max_width > 0)
3002     cupsFilePrintf(fp, "CustomSize %d %d %d %d %d %d %d %d\n",
3003                    pc->custom_max_width, pc->custom_max_length,
3004 		   pc->custom_min_width, pc->custom_min_length,
3005 		   pc->custom_size.left, pc->custom_size.bottom,
3006 		   pc->custom_size.right, pc->custom_size.top);
3007 
3008  /*
3009   * Media sources...
3010   */
3011 
3012   if (pc->source_option)
3013     cupsFilePrintf(fp, "SourceOption %s\n", pc->source_option);
3014 
3015   if (pc->num_sources > 0)
3016   {
3017     cupsFilePrintf(fp, "NumSources %d\n", pc->num_sources);
3018     for (i = pc->num_sources, map = pc->sources; i > 0; i --, map ++)
3019       cupsFilePrintf(fp, "Source %s %s\n", map->pwg, map->ppd);
3020   }
3021 
3022  /*
3023   * Media types...
3024   */
3025 
3026   if (pc->num_types > 0)
3027   {
3028     cupsFilePrintf(fp, "NumTypes %d\n", pc->num_types);
3029     for (i = pc->num_types, map = pc->types; i > 0; i --, map ++)
3030       cupsFilePrintf(fp, "Type %s %s\n", map->pwg, map->ppd);
3031   }
3032 
3033  /*
3034   * Presets...
3035   */
3036 
3037   for (i = _PWG_PRINT_COLOR_MODE_MONOCHROME; i < _PWG_PRINT_COLOR_MODE_MAX; i ++)
3038     for (j = _PWG_PRINT_QUALITY_DRAFT; j < _PWG_PRINT_QUALITY_MAX; j ++)
3039       if (pc->num_presets[i][j])
3040       {
3041 	cupsFilePrintf(fp, "Preset %d %d", i, j);
3042 	for (k = pc->num_presets[i][j], option = pc->presets[i][j];
3043 	     k > 0;
3044 	     k --, option ++)
3045 	  cupsFilePrintf(fp, " %s=%s", option->name, option->value);
3046 	cupsFilePutChar(fp, '\n');
3047       }
3048 
3049  /*
3050   * Duplex/sides...
3051   */
3052 
3053   if (pc->sides_option)
3054     cupsFilePrintf(fp, "SidesOption %s\n", pc->sides_option);
3055 
3056   if (pc->sides_1sided)
3057     cupsFilePrintf(fp, "Sides1Sided %s\n", pc->sides_1sided);
3058 
3059   if (pc->sides_2sided_long)
3060     cupsFilePrintf(fp, "Sides2SidedLong %s\n", pc->sides_2sided_long);
3061 
3062   if (pc->sides_2sided_short)
3063     cupsFilePrintf(fp, "Sides2SidedShort %s\n", pc->sides_2sided_short);
3064 
3065  /*
3066   * Product, cupsFilter, cupsFilter2, and cupsPreFilter...
3067   */
3068 
3069   if (pc->product)
3070     cupsFilePutConf(fp, "Product", pc->product);
3071 
3072   for (value = (const char *)cupsArrayFirst(pc->filters);
3073        value;
3074        value = (const char *)cupsArrayNext(pc->filters))
3075     cupsFilePutConf(fp, "Filter", value);
3076 
3077   for (value = (const char *)cupsArrayFirst(pc->prefilters);
3078        value;
3079        value = (const char *)cupsArrayNext(pc->prefilters))
3080     cupsFilePutConf(fp, "PreFilter", value);
3081 
3082   cupsFilePrintf(fp, "SingleFile %s\n", pc->single_file ? "true" : "false");
3083 
3084  /*
3085   * Finishing options...
3086   */
3087 
3088   for (f = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings);
3089        f;
3090        f = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
3091   {
3092     cupsFilePrintf(fp, "Finishings %d", f->value);
3093     for (i = f->num_options, option = f->options; i > 0; i --, option ++)
3094       cupsFilePrintf(fp, " %s=%s", option->name, option->value);
3095     cupsFilePutChar(fp, '\n');
3096   }
3097 
3098   for (value = (const char *)cupsArrayFirst(pc->templates); value; value = (const char *)cupsArrayNext(pc->templates))
3099     cupsFilePutConf(fp, "FinishingTemplate", value);
3100 
3101  /*
3102   * Max copies...
3103   */
3104 
3105   cupsFilePrintf(fp, "MaxCopies %d\n", pc->max_copies);
3106 
3107  /*
3108   * Accounting/quota/PIN/managed printing values...
3109   */
3110 
3111   if (pc->charge_info_uri)
3112     cupsFilePutConf(fp, "ChargeInfoURI", pc->charge_info_uri);
3113 
3114   cupsFilePrintf(fp, "JobAccountId %s\n", pc->account_id ? "true" : "false");
3115   cupsFilePrintf(fp, "JobAccountingUserId %s\n",
3116                  pc->accounting_user_id ? "true" : "false");
3117 
3118   if (pc->password)
3119     cupsFilePutConf(fp, "JobPassword", pc->password);
3120 
3121   for (value = (char *)cupsArrayFirst(pc->mandatory);
3122        value;
3123        value = (char *)cupsArrayNext(pc->mandatory))
3124     cupsFilePutConf(fp, "Mandatory", value);
3125 
3126  /*
3127   * Support files...
3128   */
3129 
3130   for (value = (char *)cupsArrayFirst(pc->support_files);
3131        value;
3132        value = (char *)cupsArrayNext(pc->support_files))
3133     cupsFilePutConf(fp, "SupportFile", value);
3134 
3135  /*
3136   * IPP attributes, if any...
3137   */
3138 
3139   if (attrs)
3140   {
3141     cupsFilePrintf(fp, "IPP " CUPS_LLFMT "\n", CUPS_LLCAST ippLength(attrs));
3142 
3143     attrs->state = IPP_STATE_IDLE;
3144     ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL, attrs);
3145   }
3146 
3147  /*
3148   * Close and return...
3149   */
3150 
3151   if (cupsFileClose(fp))
3152   {
3153     unlink(newfile);
3154     return (0);
3155   }
3156 
3157   unlink(filename);
3158   return (!rename(newfile, filename));
3159 }
3160 
3161 
3162 /*
3163  * '_ppdCreateFromIPP()' - Create a PPD file describing the capabilities
3164  *                         of an IPP printer.
3165  */
3166 
3167 char *					/* O - PPD filename or @code NULL@ on error */
_ppdCreateFromIPP(char * buffer,size_t bufsize,ipp_t * supported)3168 _ppdCreateFromIPP(char   *buffer,	/* I - Filename buffer */
3169                   size_t bufsize,	/* I - Size of filename buffer */
3170 		  ipp_t  *supported)	/* I - Get-Printer-Attributes response */
3171 {
3172   return (_ppdCreateFromIPP2(buffer, bufsize, supported, cupsLangDefault()));
3173 }
3174 
3175 
3176 /*
3177  * '_ppdCreateFromIPP()' - Create a PPD file describing the capabilities
3178  *                         of an IPP printer.
3179  */
3180 
3181 
3182 char *
_ppdCreateFromIPP2(char * buffer,size_t bufsize,ipp_t * supported,cups_lang_t * lang)3183 _ppdCreateFromIPP2(
3184     char        *buffer,		/* I - Filename buffer */
3185     size_t      bufsize,		/* I - Size of filename buffer */
3186     ipp_t       *supported,		/* I - Get-Printer-Attributes response */
3187     cups_lang_t *lang)			/* I - Language */
3188 {
3189   cups_file_t		*fp;		/* PPD file */
3190   cups_array_t		*sizes;		/* Media sizes supported by printer */
3191   cups_size_t		*size;		/* Current media size */
3192   ipp_attribute_t	*attr,		/* xxx-supported */
3193 			*lang_supp,	/* printer-strings-languages-supported */
3194 			*defattr,	/* xxx-default */
3195                         *quality,	/* print-quality-supported */
3196 			*x_dim, *y_dim;	/* Media dimensions */
3197   ipp_t			*media_col,	/* Media collection */
3198 			*media_size;	/* Media size collection */
3199   char			make[256],	/* Make and model */
3200 			*mptr,		/* Pointer into make and model */
3201 			ppdname[PPD_MAX_NAME];
3202 		    			/* PPD keyword */
3203   const char		*model;		/* Model name */
3204   int			i, j,		/* Looping vars */
3205 			count,		/* Number of values */
3206 			bottom,		/* Largest bottom margin */
3207 			left,		/* Largest left margin */
3208 			right,		/* Largest right margin */
3209 			top,		/* Largest top margin */
3210 			max_length = 0,	/* Maximum custom size */
3211 			max_width = 0,
3212 			min_length = INT_MAX,
3213 					/* Minimum custom size */
3214 			min_width = INT_MAX,
3215 			is_apple = 0,	/* Does the printer support Apple raster? */
3216 			is_pdf = 0,	/* Does the printer support PDF? */
3217 			is_pwg = 0;	/* Does the printer support PWG Raster? */
3218   pwg_media_t		*pwg;		/* PWG media size */
3219   int			xres, yres;	/* Resolution values */
3220   int                   resolutions[1000];
3221                                         /* Array of resolution indices */
3222   int			have_qdraft = 0,/* Have draft quality? */
3223 			have_qhigh = 0;	/* Have high quality? */
3224   char			msgid[256];	/* Message identifier (attr.value) */
3225   const char		*keyword;	/* Keyword value */
3226   cups_array_t		*strings = NULL;/* Printer strings file */
3227   struct lconv		*loc = localeconv();
3228 					/* Locale data */
3229   cups_array_t		*fin_options = NULL;
3230 					/* Finishing options */
3231 
3232 
3233  /*
3234   * Range check input...
3235   */
3236 
3237   if (buffer)
3238     *buffer = '\0';
3239 
3240   if (!buffer || bufsize < 1)
3241   {
3242     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
3243     return (NULL);
3244   }
3245 
3246   if (!supported)
3247   {
3248     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No IPP attributes."), 1);
3249     return (NULL);
3250   }
3251 
3252  /*
3253   * Open a temporary file for the PPD...
3254   */
3255 
3256   if ((fp = cupsTempFile2(buffer, (int)bufsize)) == NULL)
3257   {
3258     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
3259     return (NULL);
3260   }
3261 
3262  /*
3263   * Get a sanitized make and model...
3264   */
3265 
3266   if ((attr = ippFindAttribute(supported, "printer-make-and-model", IPP_TAG_TEXT)) != NULL && ippValidateAttribute(attr))
3267   {
3268    /*
3269     * Sanitize the model name to only contain PPD-safe characters.
3270     */
3271 
3272     strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make));
3273 
3274     for (mptr = make; *mptr; mptr ++)
3275     {
3276       if (*mptr < ' ' || *mptr >= 127 || *mptr == '\"')
3277       {
3278        /*
3279 	* Truncate the make and model on the first bad character...
3280 	*/
3281 
3282 	*mptr = '\0';
3283 	break;
3284       }
3285     }
3286 
3287     while (mptr > make)
3288     {
3289      /*
3290       * Strip trailing whitespace...
3291       */
3292 
3293       mptr --;
3294       if (*mptr == ' ')
3295 	*mptr = '\0';
3296       else
3297         break;
3298     }
3299 
3300     if (!make[0])
3301     {
3302      /*
3303       * Use a default make and model if nothing remains...
3304       */
3305 
3306       strlcpy(make, "Unknown", sizeof(make));
3307     }
3308   }
3309   else
3310   {
3311    /*
3312     * Use a default make and model...
3313     */
3314 
3315     strlcpy(make, "Unknown", sizeof(make));
3316   }
3317 
3318   if (!_cups_strncasecmp(make, "Hewlett Packard ", 16) || !_cups_strncasecmp(make, "Hewlett-Packard ", 16))
3319   {
3320    /*
3321     * Normalize HP printer make and model...
3322     */
3323 
3324     model = make + 16;
3325     strlcpy(make, "HP", sizeof(make));
3326 
3327     if (!_cups_strncasecmp(model, "HP ", 3))
3328       model += 3;
3329   }
3330   else if ((mptr = strchr(make, ' ')) != NULL)
3331   {
3332    /*
3333     * Separate "MAKE MODEL"...
3334     */
3335 
3336     while (*mptr && *mptr == ' ')
3337       *mptr++ = '\0';
3338 
3339     model = mptr;
3340   }
3341   else
3342   {
3343    /*
3344     * No separate model name...
3345     */
3346 
3347     model = "Printer";
3348   }
3349 
3350  /*
3351   * Standard stuff for PPD file...
3352   */
3353 
3354   cupsFilePuts(fp, "*PPD-Adobe: \"4.3\"\n");
3355   cupsFilePuts(fp, "*FormatVersion: \"4.3\"\n");
3356   cupsFilePrintf(fp, "*FileVersion: \"%d.%d\"\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR);
3357   cupsFilePuts(fp, "*LanguageVersion: English\n");
3358   cupsFilePuts(fp, "*LanguageEncoding: ISOLatin1\n");
3359   cupsFilePuts(fp, "*PSVersion: \"(3010.000) 0\"\n");
3360   cupsFilePuts(fp, "*LanguageLevel: \"3\"\n");
3361   cupsFilePuts(fp, "*FileSystem: False\n");
3362   cupsFilePuts(fp, "*PCFileName: \"ippeve.ppd\"\n");
3363   cupsFilePrintf(fp, "*Manufacturer: \"%s\"\n", make);
3364   cupsFilePrintf(fp, "*ModelName: \"%s\"\n", model);
3365   cupsFilePrintf(fp, "*Product: \"(%s)\"\n", model);
3366   cupsFilePrintf(fp, "*NickName: \"%s - IPP Everywhere\"\n", model);
3367   cupsFilePrintf(fp, "*ShortNickName: \"%s - IPP Everywhere\"\n", model);
3368 
3369   if ((attr = ippFindAttribute(supported, "color-supported", IPP_TAG_BOOLEAN)) != NULL && ippGetBoolean(attr, 0))
3370     cupsFilePuts(fp, "*ColorDevice: True\n");
3371   else
3372     cupsFilePuts(fp, "*ColorDevice: False\n");
3373 
3374   cupsFilePrintf(fp, "*cupsVersion: %d.%d\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR);
3375 #ifdef __APPLE__
3376   cupsFilePrintf(fp, "*APAirPrint: True\n");
3377 #endif // __APPLE__
3378   cupsFilePuts(fp, "*cupsSNMPSupplies: False\n");
3379   cupsFilePrintf(fp, "*cupsLanguages: \"%s", lang->language);
3380   if ((lang_supp = ippFindAttribute(supported, "printer-strings-languages-supported", IPP_TAG_LANGUAGE)) != NULL)
3381   {
3382     for (i = 0, count = ippGetCount(lang_supp); i < count; i ++)
3383     {
3384       keyword = ippGetString(lang_supp, i, NULL);
3385 
3386       if (strcmp(keyword, lang->language))
3387         cupsFilePrintf(fp, " %s", keyword);
3388     }
3389   }
3390   cupsFilePuts(fp, "\"\n");
3391 
3392   if ((attr = ippFindAttribute(supported, "printer-more-info", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
3393     cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL));
3394 
3395   if ((attr = ippFindAttribute(supported, "printer-charge-info-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
3396     cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0, NULL));
3397 
3398   if ((attr = ippFindAttribute(supported, "printer-strings-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
3399   {
3400     http_t	*http = NULL;		/* Connection to printer */
3401     char	stringsfile[1024];	/* Temporary strings file */
3402 
3403     if (cups_get_url(&http, ippGetString(attr, 0, NULL), stringsfile, sizeof(stringsfile)))
3404     {
3405       const char	*printer_uri = ippGetString(ippFindAttribute(supported, "printer-uri-supported", IPP_TAG_URI), 0, NULL);
3406 					// Printer URI
3407       char		resource[256];	// Resource path
3408       ipp_t		*request,	// Get-Printer-Attributes request
3409 			*response;	// Response to request
3410 
3411      /*
3412       * Load strings and save the URL for clients using the destination API
3413       * instead of this PPD file...
3414       */
3415 
3416       cupsFilePrintf(fp, "*cupsStringsURI: \"%s\"\n", ippGetString(attr, 0, NULL));
3417 
3418       strings = _cupsMessageLoad(stringsfile, _CUPS_MESSAGE_STRINGS | _CUPS_MESSAGE_UNQUOTE);
3419 
3420       unlink(stringsfile);
3421 
3422       if (lang_supp && printer_uri && cups_connect(&http, printer_uri, resource, sizeof(resource)))
3423       {
3424        /*
3425 	* Loop through all of the languages and save their URIs...
3426 	*/
3427 
3428 	for (i = 0, count = ippGetCount(lang_supp); i < count; i ++)
3429 	{
3430 	  keyword = ippGetString(lang_supp, i, NULL);
3431 
3432 	  request = ippNew();
3433 	  ippSetOperation(request, IPP_OP_GET_PRINTER_ATTRIBUTES);
3434 	  ippSetRequestId(request, i + 1);
3435 	  ippAddString(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_CHARSET), "attributes-charset", NULL, "utf-8");
3436 	  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, keyword);
3437 	  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printer_uri);
3438 	  ippAddString(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", NULL, "printer-strings-uri");
3439 
3440 	  response = cupsDoRequest(http, request, resource);
3441 
3442 	  if ((attr = ippFindAttribute(response, "printer-strings-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
3443 	    cupsFilePrintf(fp, "*cupsStringsURI %s: \"%s\"\n", keyword, ippGetString(attr, 0, NULL));
3444 
3445 	  ippDelete(response);
3446 	}
3447       }
3448     }
3449 
3450     if (http)
3451       httpClose(http);
3452   }
3453 
3454  /*
3455   * Accounting...
3456   */
3457 
3458   if (ippGetBoolean(ippFindAttribute(supported, "job-account-id-supported", IPP_TAG_BOOLEAN), 0))
3459     cupsFilePuts(fp, "*cupsJobAccountId: True\n");
3460 
3461   if (ippGetBoolean(ippFindAttribute(supported, "job-accounting-user-id-supported", IPP_TAG_BOOLEAN), 0))
3462     cupsFilePuts(fp, "*cupsJobAccountingUserId: True\n");
3463 
3464   if ((attr = ippFindAttribute(supported, "printer-privacy-policy-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
3465     cupsFilePrintf(fp, "*cupsPrivacyURI: \"%s\"\n", ippGetString(attr, 0, NULL));
3466 
3467   if ((attr = ippFindAttribute(supported, "printer-mandatory-job-attributes", IPP_TAG_KEYWORD)) != NULL && ippValidateAttribute(attr))
3468   {
3469     char	prefix = '\"';		// Prefix for string
3470 
3471     cupsFilePuts(fp, "*cupsMandatory: \"");
3472     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3473     {
3474       keyword = ippGetString(attr, i, NULL);
3475 
3476       if (strcmp(keyword, "attributes-charset") && strcmp(keyword, "attributes-natural-language") && strcmp(keyword, "printer-uri"))
3477       {
3478         cupsFilePrintf(fp, "%c%s", prefix, keyword);
3479         prefix = ',';
3480       }
3481     }
3482     cupsFilePuts(fp, "\"\n");
3483   }
3484 
3485   if ((attr = ippFindAttribute(supported, "printer-requested-job-attributes", IPP_TAG_KEYWORD)) != NULL && ippValidateAttribute(attr))
3486   {
3487     char	prefix = '\"';		// Prefix for string
3488 
3489     cupsFilePuts(fp, "*cupsRequested: \"");
3490     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3491     {
3492       keyword = ippGetString(attr, i, NULL);
3493 
3494       if (strcmp(keyword, "attributes-charset") && strcmp(keyword, "attributes-natural-language") && strcmp(keyword, "printer-uri"))
3495       {
3496         cupsFilePrintf(fp, "%c%s", prefix, keyword);
3497         prefix = ',';
3498       }
3499     }
3500     cupsFilePuts(fp, "\"\n");
3501   }
3502 
3503  /*
3504   * Password/PIN printing...
3505   */
3506 
3507   if ((attr = ippFindAttribute(supported, "job-password-supported", IPP_TAG_INTEGER)) != NULL)
3508   {
3509     char	pattern[33];		/* Password pattern */
3510     int		maxlen = ippGetInteger(attr, 0);
3511 					/* Maximum length */
3512     const char	*repertoire = ippGetString(ippFindAttribute(supported, "job-password-repertoire-configured", IPP_TAG_KEYWORD), 0, NULL);
3513 					/* Type of password */
3514 
3515     if (maxlen > (int)(sizeof(pattern) - 1))
3516       maxlen = (int)sizeof(pattern) - 1;
3517 
3518     if (!repertoire || !strcmp(repertoire, "iana_us-ascii_digits"))
3519       memset(pattern, '1', (size_t)maxlen);
3520     else if (!strcmp(repertoire, "iana_us-ascii_letters"))
3521       memset(pattern, 'A', (size_t)maxlen);
3522     else if (!strcmp(repertoire, "iana_us-ascii_complex"))
3523       memset(pattern, 'C', (size_t)maxlen);
3524     else if (!strcmp(repertoire, "iana_us-ascii_any"))
3525       memset(pattern, '.', (size_t)maxlen);
3526     else if (!strcmp(repertoire, "iana_utf-8_digits"))
3527       memset(pattern, 'N', (size_t)maxlen);
3528     else if (!strcmp(repertoire, "iana_utf-8_letters"))
3529       memset(pattern, 'U', (size_t)maxlen);
3530     else
3531       memset(pattern, '*', (size_t)maxlen);
3532 
3533     pattern[maxlen] = '\0';
3534 
3535     cupsFilePrintf(fp, "*cupsJobPassword: \"%s\"\n", pattern);
3536   }
3537 
3538  /*
3539   * Filters...
3540   */
3541 
3542   if ((attr = ippFindAttribute(supported, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL)
3543   {
3544     is_apple = ippContainsString(attr, "image/urf") && (ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD) != NULL);
3545     is_pdf   = ippContainsString(attr, "application/pdf");
3546     is_pwg   = ippContainsString(attr, "image/pwg-raster") && !is_apple &&
3547 	       (ippFindAttribute(supported, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION) != NULL) &&
3548 	       (ippFindAttribute(supported, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD) != NULL);
3549 
3550     if (ippContainsString(attr, "image/jpeg"))
3551       cupsFilePuts(fp, "*cupsFilter2: \"image/jpeg image/jpeg 0 -\"\n");
3552     if (ippContainsString(attr, "image/png"))
3553       cupsFilePuts(fp, "*cupsFilter2: \"image/png image/png 0 -\"\n");
3554     if (is_pdf)
3555     {
3556      /*
3557       * Don't locally filter PDF content when printing to a CUPS shared
3558       * printer, otherwise the options will be applied twice...
3559       */
3560 
3561       if (ippContainsString(attr, "application/vnd.cups-pdf"))
3562         cupsFilePuts(fp, "*cupsFilter2: \"application/pdf application/pdf 0 -\"\n");
3563       else
3564         cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 10 -\"\n");
3565     }
3566     else
3567       cupsFilePuts(fp, "*cupsManualCopies: True\n");
3568     if (is_apple)
3569       cupsFilePuts(fp, "*cupsFilter2: \"image/urf image/urf 100 -\"\n");
3570     if (is_pwg)
3571       cupsFilePuts(fp, "*cupsFilter2: \"image/pwg-raster image/pwg-raster 100 -\"\n");
3572   }
3573 
3574   if (!is_apple && !is_pdf && !is_pwg)
3575     goto bad_ppd;
3576 
3577  /*
3578   * cupsUrfSupported
3579   */
3580   if ((attr = ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
3581   {
3582     cupsFilePuts(fp, "*cupsUrfSupported: \"");
3583     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3584     {
3585       keyword = ippGetString(attr, i, NULL);
3586       cupsFilePrintf(fp, "%s%s", keyword, i != count - 1 ? "," : "");
3587     }
3588     cupsFilePuts(fp, "\"\n");
3589   }
3590 
3591  /*
3592   * PageSize/PageRegion/ImageableArea/PaperDimension
3593   */
3594 
3595   if ((attr = ippFindAttribute(supported, "media-bottom-margin-supported", IPP_TAG_INTEGER)) != NULL)
3596   {
3597     for (i = 1, bottom = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++)
3598       if (ippGetInteger(attr, i) > bottom)
3599         bottom = ippGetInteger(attr, i);
3600   }
3601   else
3602     bottom = 1270;
3603 
3604   if ((attr = ippFindAttribute(supported, "media-left-margin-supported", IPP_TAG_INTEGER)) != NULL)
3605   {
3606     for (i = 1, left = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++)
3607       if (ippGetInteger(attr, i) > left)
3608         left = ippGetInteger(attr, i);
3609   }
3610   else
3611     left = 635;
3612 
3613   if ((attr = ippFindAttribute(supported, "media-right-margin-supported", IPP_TAG_INTEGER)) != NULL)
3614   {
3615     for (i = 1, right = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++)
3616       if (ippGetInteger(attr, i) > right)
3617         right = ippGetInteger(attr, i);
3618   }
3619   else
3620     right = 635;
3621 
3622   if ((attr = ippFindAttribute(supported, "media-top-margin-supported", IPP_TAG_INTEGER)) != NULL)
3623   {
3624     for (i = 1, top = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++)
3625       if (ippGetInteger(attr, i) > top)
3626         top = ippGetInteger(attr, i);
3627   }
3628   else
3629     top = 1270;
3630 
3631   if ((defattr = ippFindAttribute(supported, "media-col-default", IPP_TAG_BEGIN_COLLECTION)) != NULL)
3632   {
3633     if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-size", IPP_TAG_BEGIN_COLLECTION)) != NULL)
3634     {
3635       media_size = ippGetCollection(attr, 0);
3636       x_dim      = ippFindAttribute(media_size, "x-dimension", IPP_TAG_INTEGER);
3637       y_dim      = ippFindAttribute(media_size, "y-dimension", IPP_TAG_INTEGER);
3638 
3639       if (x_dim && y_dim && (pwg = pwgMediaForSize(ippGetInteger(x_dim, 0), ippGetInteger(y_dim, 0))) != NULL)
3640 	strlcpy(ppdname, pwg->ppd, sizeof(ppdname));
3641       else
3642 	strlcpy(ppdname, "Unknown", sizeof(ppdname));
3643     }
3644     else
3645       strlcpy(ppdname, "Unknown", sizeof(ppdname));
3646   }
3647   else if ((pwg = pwgMediaForPWG(ippGetString(ippFindAttribute(supported, "media-default", IPP_TAG_ZERO), 0, NULL))) != NULL)
3648     strlcpy(ppdname, pwg->ppd, sizeof(ppdname));
3649   else
3650     strlcpy(ppdname, "Unknown", sizeof(ppdname));
3651 
3652   sizes = cupsArrayNew3((cups_array_func_t)pwg_compare_sizes, NULL, NULL, 0, (cups_acopy_func_t)pwg_copy_size, (cups_afree_func_t)free);
3653 
3654   if ((attr = ippFindAttribute(supported, "media-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL)
3655   {
3656     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3657     {
3658       cups_size_t	temp;		/* Current size */
3659       ipp_attribute_t	*margin;	/* media-xxx-margin attribute */
3660 
3661       media_col   = ippGetCollection(attr, i);
3662       media_size  = ippGetCollection(ippFindAttribute(media_col, "media-size", IPP_TAG_BEGIN_COLLECTION), 0);
3663       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
3664       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
3665       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0), ippGetInteger(y_dim, 0));
3666 
3667       if (pwg)
3668       {
3669 	temp.width  = pwg->width;
3670 	temp.length = pwg->length;
3671 
3672 	if ((margin = ippFindAttribute(media_col, "media-bottom-margin", IPP_TAG_INTEGER)) != NULL)
3673 	  temp.bottom = ippGetInteger(margin, 0);
3674 	else
3675 	  temp.bottom = bottom;
3676 
3677 	if ((margin = ippFindAttribute(media_col, "media-left-margin", IPP_TAG_INTEGER)) != NULL)
3678 	  temp.left = ippGetInteger(margin, 0);
3679 	else
3680 	  temp.left = left;
3681 
3682 	if ((margin = ippFindAttribute(media_col, "media-right-margin", IPP_TAG_INTEGER)) != NULL)
3683 	  temp.right = ippGetInteger(margin, 0);
3684 	else
3685 	  temp.right = right;
3686 
3687 	if ((margin = ippFindAttribute(media_col, "media-top-margin", IPP_TAG_INTEGER)) != NULL)
3688 	  temp.top = ippGetInteger(margin, 0);
3689 	else
3690 	  temp.top = top;
3691 
3692 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 && temp.top == 0)
3693 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", pwg->ppd);
3694 	else
3695 	  strlcpy(temp.media, pwg->ppd, sizeof(temp.media));
3696 
3697 	if (!cupsArrayFind(sizes, &temp))
3698 	  cupsArrayAdd(sizes, &temp);
3699       }
3700       else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE || ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3701       {
3702        /*
3703 	* Custom size - record the min/max values...
3704 	*/
3705 
3706 	int lower, upper;		/* Range values */
3707 
3708 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
3709 	  lower = ippGetRange(x_dim, 0, &upper);
3710 	else
3711 	  lower = upper = ippGetInteger(x_dim, 0);
3712 
3713 	if (lower < min_width)
3714 	  min_width = lower;
3715 	if (upper > max_width)
3716 	  max_width = upper;
3717 
3718 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3719 	  lower = ippGetRange(y_dim, 0, &upper);
3720 	else
3721 	  lower = upper = ippGetInteger(y_dim, 0);
3722 
3723 	if (lower < min_length)
3724 	  min_length = lower;
3725 	if (upper > max_length)
3726 	  max_length = upper;
3727       }
3728     }
3729 
3730     if ((max_width == 0 || max_length == 0) && (attr = ippFindAttribute(supported, "media-size-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL)
3731     {
3732      /*
3733       * Some printers don't list custom size support in media-col-database...
3734       */
3735 
3736       for (i = 0, count = ippGetCount(attr); i < count; i ++)
3737       {
3738 	media_size  = ippGetCollection(attr, i);
3739 	x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
3740 	y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
3741 
3742 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE || ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3743 	{
3744 	 /*
3745 	  * Custom size - record the min/max values...
3746 	  */
3747 
3748 	  int lower, upper;		/* Range values */
3749 
3750 	  if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
3751 	    lower = ippGetRange(x_dim, 0, &upper);
3752 	  else
3753 	    lower = upper = ippGetInteger(x_dim, 0);
3754 
3755 	  if (lower < min_width)
3756 	    min_width = lower;
3757 	  if (upper > max_width)
3758 	    max_width = upper;
3759 
3760 	  if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3761 	    lower = ippGetRange(y_dim, 0, &upper);
3762 	  else
3763 	    lower = upper = ippGetInteger(y_dim, 0);
3764 
3765 	  if (lower < min_length)
3766 	    min_length = lower;
3767 	  if (upper > max_length)
3768 	    max_length = upper;
3769 	}
3770       }
3771     }
3772   }
3773   else if ((attr = ippFindAttribute(supported, "media-size-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL)
3774   {
3775     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3776     {
3777       cups_size_t	temp;		/* Current size */
3778 
3779       media_size  = ippGetCollection(attr, i);
3780       x_dim       = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO);
3781       y_dim       = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO);
3782       pwg         = pwgMediaForSize(ippGetInteger(x_dim, 0), ippGetInteger(y_dim, 0));
3783 
3784       if (pwg)
3785       {
3786 	temp.width  = pwg->width;
3787 	temp.length = pwg->length;
3788 	temp.bottom = bottom;
3789 	temp.left   = left;
3790 	temp.right  = right;
3791 	temp.top    = top;
3792 
3793 	if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 && temp.top == 0)
3794 	  snprintf(temp.media, sizeof(temp.media), "%s.Borderless", pwg->ppd);
3795 	else
3796 	  strlcpy(temp.media, pwg->ppd, sizeof(temp.media));
3797 
3798 	if (!cupsArrayFind(sizes, &temp))
3799 	  cupsArrayAdd(sizes, &temp);
3800       }
3801       else if (ippGetValueTag(x_dim) == IPP_TAG_RANGE || ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3802       {
3803        /*
3804 	* Custom size - record the min/max values...
3805 	*/
3806 
3807 	int lower, upper;		/* Range values */
3808 
3809 	if (ippGetValueTag(x_dim) == IPP_TAG_RANGE)
3810 	  lower = ippGetRange(x_dim, 0, &upper);
3811 	else
3812 	  lower = upper = ippGetInteger(x_dim, 0);
3813 
3814 	if (lower < min_width)
3815 	  min_width = lower;
3816 	if (upper > max_width)
3817 	  max_width = upper;
3818 
3819 	if (ippGetValueTag(y_dim) == IPP_TAG_RANGE)
3820 	  lower = ippGetRange(y_dim, 0, &upper);
3821 	else
3822 	  lower = upper = ippGetInteger(y_dim, 0);
3823 
3824 	if (lower < min_length)
3825 	  min_length = lower;
3826 	if (upper > max_length)
3827 	  max_length = upper;
3828       }
3829     }
3830   }
3831   else if ((attr = ippFindAttribute(supported, "media-supported", IPP_TAG_ZERO)) != NULL)
3832   {
3833     for (i = 0, count = ippGetCount(attr); i < count; i ++)
3834     {
3835       const char	*pwg_size = ippGetString(attr, i, NULL);
3836     					/* PWG size name */
3837       cups_size_t	temp;		/* Current size */
3838 
3839       if ((pwg = pwgMediaForPWG(pwg_size)) != NULL)
3840       {
3841         if (strstr(pwg_size, "_max_") || strstr(pwg_size, "_max."))
3842         {
3843           if (pwg->width > max_width)
3844             max_width = pwg->width;
3845           if (pwg->length > max_length)
3846             max_length = pwg->length;
3847         }
3848         else if (strstr(pwg_size, "_min_") || strstr(pwg_size, "_min."))
3849         {
3850           if (pwg->width < min_width)
3851             min_width = pwg->width;
3852           if (pwg->length < min_length)
3853             min_length = pwg->length;
3854         }
3855         else
3856         {
3857 	  temp.width  = pwg->width;
3858 	  temp.length = pwg->length;
3859 	  temp.bottom = bottom;
3860 	  temp.left   = left;
3861 	  temp.right  = right;
3862 	  temp.top    = top;
3863 
3864 	  if (temp.bottom == 0 && temp.left == 0 && temp.right == 0 && temp.top == 0)
3865 	    snprintf(temp.media, sizeof(temp.media), "%s.Borderless", pwg->ppd);
3866 	  else
3867 	    strlcpy(temp.media, pwg->ppd, sizeof(temp.media));
3868 
3869 	  if (!cupsArrayFind(sizes, &temp))
3870 	    cupsArrayAdd(sizes, &temp);
3871 	}
3872       }
3873     }
3874   }
3875 
3876   if (cupsArrayCount(sizes) > 0)
3877   {
3878    /*
3879     * List all of the standard sizes...
3880     */
3881 
3882     char	tleft[256],		/* Left string */
3883 		tbottom[256],		/* Bottom string */
3884 		tright[256],		/* Right string */
3885 		ttop[256],		/* Top string */
3886 		twidth[256],		/* Width string */
3887 		tlength[256];		/* Length string */
3888 
3889     cupsFilePrintf(fp, "*OpenUI *PageSize: PickOne\n"
3890 		       "*OrderDependency: 10 AnySetup *PageSize\n"
3891                        "*DefaultPageSize: %s\n", ppdname);
3892     for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes))
3893     {
3894       _cupsStrFormatd(twidth, twidth + sizeof(twidth), size->width * 72.0 / 2540.0, loc);
3895       _cupsStrFormatd(tlength, tlength + sizeof(tlength), size->length * 72.0 / 2540.0, loc);
3896 
3897       cupsFilePrintf(fp, "*PageSize %s: \"<</PageSize[%s %s]>>setpagedevice\"\n", size->media, twidth, tlength);
3898     }
3899     cupsFilePuts(fp, "*CloseUI: *PageSize\n");
3900 
3901     cupsFilePrintf(fp, "*OpenUI *PageRegion: PickOne\n"
3902                        "*OrderDependency: 10 AnySetup *PageRegion\n"
3903                        "*DefaultPageRegion: %s\n", ppdname);
3904     for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes))
3905     {
3906       _cupsStrFormatd(twidth, twidth + sizeof(twidth), size->width * 72.0 / 2540.0, loc);
3907       _cupsStrFormatd(tlength, tlength + sizeof(tlength), size->length * 72.0 / 2540.0, loc);
3908 
3909       cupsFilePrintf(fp, "*PageRegion %s: \"<</PageSize[%s %s]>>setpagedevice\"\n", size->media, twidth, tlength);
3910     }
3911     cupsFilePuts(fp, "*CloseUI: *PageRegion\n");
3912 
3913     cupsFilePrintf(fp, "*DefaultImageableArea: %s\n"
3914 		       "*DefaultPaperDimension: %s\n", ppdname, ppdname);
3915 
3916     for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes))
3917     {
3918       _cupsStrFormatd(tleft, tleft + sizeof(tleft), size->left * 72.0 / 2540.0, loc);
3919       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom), size->bottom * 72.0 / 2540.0, loc);
3920       _cupsStrFormatd(tright, tright + sizeof(tright), (size->width - size->right) * 72.0 / 2540.0, loc);
3921       _cupsStrFormatd(ttop, ttop + sizeof(ttop), (size->length - size->top) * 72.0 / 2540.0, loc);
3922       _cupsStrFormatd(twidth, twidth + sizeof(twidth), size->width * 72.0 / 2540.0, loc);
3923       _cupsStrFormatd(tlength, tlength + sizeof(tlength), size->length * 72.0 / 2540.0, loc);
3924 
3925       cupsFilePrintf(fp, "*ImageableArea %s: \"%s %s %s %s\"\n", size->media, tleft, tbottom, tright, ttop);
3926       cupsFilePrintf(fp, "*PaperDimension %s: \"%s %s\"\n", size->media, twidth, tlength);
3927     }
3928 
3929     cupsArrayDelete(sizes);
3930 
3931    /*
3932     * Custom size support...
3933     */
3934 
3935     if (max_width > 0 && min_width < INT_MAX && max_length > 0 && min_length < INT_MAX)
3936     {
3937       char	tmax[256], tmin[256];	/* Min/max values */
3938 
3939       _cupsStrFormatd(tleft, tleft + sizeof(tleft), left * 72.0 / 2540.0, loc);
3940       _cupsStrFormatd(tbottom, tbottom + sizeof(tbottom), bottom * 72.0 / 2540.0, loc);
3941       _cupsStrFormatd(tright, tright + sizeof(tright), right * 72.0 / 2540.0, loc);
3942       _cupsStrFormatd(ttop, ttop + sizeof(ttop), top * 72.0 / 2540.0, loc);
3943 
3944       cupsFilePrintf(fp, "*HWMargins: \"%s %s %s %s\"\n", tleft, tbottom, tright, ttop);
3945 
3946       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_width * 72.0 / 2540.0, loc);
3947       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_width * 72.0 / 2540.0, loc);
3948       cupsFilePrintf(fp, "*ParamCustomPageSize Width: 1 points %s %s\n", tmin, tmax);
3949 
3950       _cupsStrFormatd(tmax, tmax + sizeof(tmax), max_length * 72.0 / 2540.0, loc);
3951       _cupsStrFormatd(tmin, tmin + sizeof(tmin), min_length * 72.0 / 2540.0, loc);
3952       cupsFilePrintf(fp, "*ParamCustomPageSize Height: 2 points %s %s\n", tmin, tmax);
3953 
3954       cupsFilePuts(fp, "*ParamCustomPageSize WidthOffset: 3 points 0 0\n");
3955       cupsFilePuts(fp, "*ParamCustomPageSize HeightOffset: 4 points 0 0\n");
3956       cupsFilePuts(fp, "*ParamCustomPageSize Orientation: 5 int 0 3\n");
3957       cupsFilePuts(fp, "*CustomPageSize True: \"pop pop pop <</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice\"\n");
3958     }
3959   }
3960   else
3961   {
3962     cupsArrayDelete(sizes);
3963     goto bad_ppd;
3964   }
3965 
3966  /*
3967   * InputSlot...
3968   */
3969 
3970   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-source", IPP_TAG_ZERO)) != NULL)
3971     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
3972   else
3973     ppdname[0] = '\0';
3974 
3975   if ((attr = ippFindAttribute(supported, "media-source-supported", IPP_TAG_ZERO)) != NULL && (count = ippGetCount(attr)) > 1)
3976   {
3977     int have_default = ppdname[0] != '\0';
3978 					/* Do we have a default InputSlot? */
3979     static const char * const sources[] =
3980     {					/* Standard "media-source" strings */
3981       "auto",
3982       "main",
3983       "alternate",
3984       "large-capacity",
3985       "manual",
3986       "envelope",
3987       "disc",
3988       "photo",
3989       "hagaki",
3990       "main-roll",
3991       "alternate-roll",
3992       "top",
3993       "middle",
3994       "bottom",
3995       "side",
3996       "left",
3997       "right",
3998       "center",
3999       "rear",
4000       "by-pass-tray",
4001       "tray-1",
4002       "tray-2",
4003       "tray-3",
4004       "tray-4",
4005       "tray-5",
4006       "tray-6",
4007       "tray-7",
4008       "tray-8",
4009       "tray-9",
4010       "tray-10",
4011       "tray-11",
4012       "tray-12",
4013       "tray-13",
4014       "tray-14",
4015       "tray-15",
4016       "tray-16",
4017       "tray-17",
4018       "tray-18",
4019       "tray-19",
4020       "tray-20",
4021       "roll-1",
4022       "roll-2",
4023       "roll-3",
4024       "roll-4",
4025       "roll-5",
4026       "roll-6",
4027       "roll-7",
4028       "roll-8",
4029       "roll-9",
4030       "roll-10"
4031     };
4032 
4033     cupsFilePuts(fp, "*OpenUI *InputSlot: PickOne\n"
4034                      "*OrderDependency: 10 AnySetup *InputSlot\n");
4035     if (have_default)
4036       cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
4037 
4038     for (i = 0; i < count; i ++)
4039     {
4040       keyword = ippGetString(attr, i, NULL);
4041 
4042       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4043 
4044       if (i == 0 && !have_default)
4045 	cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
4046 
4047       for (j = 0; j < (int)(sizeof(sources) / sizeof(sources[0])); j ++)
4048       {
4049         if (!strcmp(sources[j], keyword))
4050 	{
4051 	  snprintf(msgid, sizeof(msgid), "media-source.%s", keyword);
4052 
4053 	  cupsFilePrintf(fp, "*InputSlot %s: \"<</MediaPosition %d>>setpagedevice\"\n", ppdname, j);
4054 	  ppd_put_string(fp, lang, strings, "InputSlot", ppdname, msgid);
4055 	  break;
4056 	}
4057       }
4058     }
4059     cupsFilePuts(fp, "*CloseUI: *InputSlot\n");
4060   }
4061 
4062  /*
4063   * MediaType...
4064   */
4065 
4066   if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-type", IPP_TAG_ZERO)) != NULL)
4067     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
4068   else
4069     strlcpy(ppdname, "Unknown", sizeof(ppdname));
4070 
4071   if ((attr = ippFindAttribute(supported, "media-type-supported", IPP_TAG_ZERO)) != NULL && (count = ippGetCount(attr)) > 1)
4072   {
4073     cupsFilePrintf(fp, "*OpenUI *MediaType: PickOne\n"
4074                        "*OrderDependency: 10 AnySetup *MediaType\n"
4075                        "*DefaultMediaType: %s\n", ppdname);
4076     for (i = 0; i < count; i ++)
4077     {
4078       keyword = ippGetString(attr, i, NULL);
4079 
4080       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4081 
4082       snprintf(msgid, sizeof(msgid), "media-type.%s", keyword);
4083 
4084       cupsFilePrintf(fp, "*MediaType %s: \"<</MediaType(%s)>>setpagedevice\"\n", ppdname, ppdname);
4085       ppd_put_string(fp, lang, strings, "MediaType", ppdname, msgid);
4086     }
4087     cupsFilePuts(fp, "*CloseUI: *MediaType\n");
4088   }
4089 
4090  /*
4091   * cupsPrintQuality and DefaultResolution...
4092   */
4093 
4094   quality = ippFindAttribute(supported, "print-quality-supported", IPP_TAG_ENUM);
4095 
4096   if ((attr = ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
4097   {
4098     int lowdpi = 0, hidpi = 0;    /* Lower and higher resolution */
4099 
4100     for (i = 0, count = ippGetCount(attr); i < count; i ++)
4101     {
4102       const char *rs = ippGetString(attr, i, NULL);
4103           /* RS value */
4104 
4105       if (_cups_strncasecmp(rs, "RS", 2))
4106         continue;
4107 
4108       lowdpi = atoi(rs + 2);
4109       if ((rs = strrchr(rs, '-')) != NULL)
4110         hidpi = atoi(rs + 1);
4111       else
4112         hidpi = lowdpi;
4113       break;
4114     }
4115 
4116     if (lowdpi == 0)
4117     {
4118      /*
4119       * Invalid "urf-supported" value...
4120       */
4121 
4122       goto bad_ppd;
4123     }
4124     else
4125     {
4126      /*
4127       * Generate print qualities based on low and high DPIs...
4128       */
4129 
4130       cupsFilePrintf(fp, "*DefaultResolution: %ddpi\n", lowdpi);
4131 
4132       cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality: PickOne\n"
4133 			 "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
4134 			 "*%s.Translation cupsPrintQuality/%s: \"\"\n"
4135 			 "*DefaultcupsPrintQuality: Normal\n", lang->language, _cupsLangString(lang, _("Print Quality")));
4136       if ((lowdpi & 1) == 0)
4137       {
4138 	cupsFilePrintf(fp, "*cupsPrintQuality Draft: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality Draft/%s: \"\"\n", lowdpi, lowdpi / 2, lang->language, _cupsLangString(lang, _("Draft")));
4139 	have_qdraft = 1;
4140       }
4141       else if (ippContainsInteger(quality, IPP_QUALITY_DRAFT))
4142       {
4143 	cupsFilePrintf(fp, "*cupsPrintQuality Draft: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality Draft/%s: \"\"\n", lowdpi, lowdpi, lang->language, _cupsLangString(lang, _("Draft")));
4144 	have_qdraft = 1;
4145       }
4146 
4147       cupsFilePrintf(fp, "*cupsPrintQuality Normal: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality Normal/%s: \"\"\n", lowdpi, lowdpi, lang->language, _cupsLangString(lang, _("Normal")));
4148 
4149       if (hidpi > lowdpi || ippContainsInteger(quality, IPP_QUALITY_HIGH))
4150       {
4151 	cupsFilePrintf(fp, "*cupsPrintQuality High: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality High/%s: \"\"\n", hidpi, hidpi, lang->language, _cupsLangString(lang, _("High")));
4152 	have_qhigh = 1;
4153       }
4154 
4155       cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
4156     }
4157   }
4158   else if ((attr = ippFindAttribute(supported, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
4159   {
4160    /*
4161     * Make a sorted list of resolutions.
4162     */
4163 
4164     count = ippGetCount(attr);
4165     if (count > (int)(sizeof(resolutions) / sizeof(resolutions[0])))
4166       count = (int)(sizeof(resolutions) / sizeof(resolutions[0]));
4167 
4168     resolutions[0] = 0; /* Not in loop to silence Clang static analyzer... */
4169     for (i = 1; i < count; i ++)
4170       resolutions[i] = i;
4171 
4172     for (i = 0; i < (count - 1); i ++)
4173     {
4174       for (j = i + 1; j < count; j ++)
4175       {
4176         int       ix, iy,               /* First X and Y resolution */
4177                   jx, jy,               /* Second X and Y resolution */
4178                   temp;                 /* Swap variable */
4179         ipp_res_t units;                /* Resolution units */
4180 
4181         ix = ippGetResolution(attr, resolutions[i], &iy, &units);
4182         jx = ippGetResolution(attr, resolutions[j], &jy, &units);
4183 
4184         if (ix > jx || (ix == jx && iy > jy))
4185         {
4186          /*
4187           * Swap these two resolutions...
4188           */
4189 
4190           temp           = resolutions[i];
4191           resolutions[i] = resolutions[j];
4192           resolutions[j] = temp;
4193         }
4194       }
4195     }
4196 
4197    /*
4198     * Generate print quality options...
4199     */
4200 
4201     pwg_ppdize_resolution(attr, resolutions[count / 2], &xres, &yres, ppdname, sizeof(ppdname));
4202     cupsFilePrintf(fp, "*DefaultResolution: %s\n", ppdname);
4203 
4204     cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality: PickOne\n"
4205 		       "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
4206 		       "*%s.Translation cupsPrintQuality/%s: \"\"\n"
4207 		       "*DefaultcupsPrintQuality: Normal\n", lang->language, _cupsLangString(lang, _("Print Quality")));
4208     if (count > 2 || ippContainsInteger(quality, IPP_QUALITY_DRAFT))
4209     {
4210       pwg_ppdize_resolution(attr, resolutions[0], &xres, &yres, NULL, 0);
4211       cupsFilePrintf(fp, "*cupsPrintQuality Draft: \"<</HWResolution[%d %d]>>setpagedevice\"\n", xres, yres);
4212       cupsFilePrintf(fp, "*%s.cupsPrintQuality Draft/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Draft")));
4213       have_qdraft = 1;
4214     }
4215 
4216     pwg_ppdize_resolution(attr, resolutions[count / 2], &xres, &yres, NULL, 0);
4217     cupsFilePrintf(fp, "*cupsPrintQuality Normal: \"<</HWResolution[%d %d]>>setpagedevice\"\n", xres, yres);
4218     cupsFilePrintf(fp, "*%s.cupsPrintQuality Normal/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Normal")));
4219 
4220     if (count > 1 || ippContainsInteger(quality, IPP_QUALITY_HIGH))
4221     {
4222       pwg_ppdize_resolution(attr, resolutions[count - 1], &xres, &yres, NULL, 0);
4223       cupsFilePrintf(fp, "*cupsPrintQuality High: \"<</HWResolution[%d %d]>>setpagedevice\"\n", xres, yres);
4224       cupsFilePrintf(fp, "*%s.cupsPrintQuality High/%s: \"\"\n", lang->language, _cupsLangString(lang, _("High")));
4225       have_qhigh = 1;
4226     }
4227 
4228     cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
4229   }
4230   else if (is_apple || is_pwg)
4231     goto bad_ppd;
4232   else
4233   {
4234     if ((attr = ippFindAttribute(supported, "printer-resolution-default", IPP_TAG_RESOLUTION)) != NULL)
4235     {
4236       pwg_ppdize_resolution(attr, 0, &xres, &yres, ppdname, sizeof(ppdname));
4237     }
4238     else
4239     {
4240       xres = yres = 300;
4241       strlcpy(ppdname, "300dpi", sizeof(ppdname));
4242     }
4243 
4244     cupsFilePrintf(fp, "*DefaultResolution: %s\n", ppdname);
4245 
4246     cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality: PickOne\n"
4247                        "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
4248                        "*%s.Translation cupsPrintQuality/%s: \"\"\n"
4249                        "*DefaultcupsPrintQuality: Normal\n", lang->language, _cupsLangString(lang, _("Print Quality")));
4250     if (ippContainsInteger(quality, IPP_QUALITY_DRAFT))
4251     {
4252       cupsFilePrintf(fp, "*cupsPrintQuality Draft: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality Draft/%s: \"\"\n", xres, yres, lang->language, _cupsLangString(lang, _("Draft")));
4253       have_qdraft = 1;
4254     }
4255 
4256     cupsFilePrintf(fp, "*cupsPrintQuality Normal: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality Normal/%s: \"\"\n", xres, yres, lang->language, _cupsLangString(lang, _("Normal")));
4257 
4258     if (ippContainsInteger(quality, IPP_QUALITY_HIGH))
4259     {
4260       cupsFilePrintf(fp, "*cupsPrintQuality High: \"<</HWResolution[%d %d]>>setpagedevice\"\n*%s.cupsPrintQuality High/%s: \"\"\n", xres, yres, lang->language, _cupsLangString(lang, _("High")));
4261       have_qhigh = 1;
4262     }
4263     cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
4264   }
4265 
4266  /*
4267   * ColorModel...
4268   */
4269 
4270   if ((defattr = ippFindAttribute(supported, "print-color-mode-default", IPP_TAG_KEYWORD)) == NULL)
4271     defattr = ippFindAttribute(supported, "output-mode-default", IPP_TAG_KEYWORD);
4272 
4273   if ((attr = ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD)) == NULL)
4274     if ((attr = ippFindAttribute(supported, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL)
4275       if ((attr = ippFindAttribute(supported, "print-color-mode-supported", IPP_TAG_KEYWORD)) == NULL)
4276         attr = ippFindAttribute(supported, "output-mode-supported", IPP_TAG_KEYWORD);
4277 
4278   if (attr)
4279   {
4280     int wrote_color = 0;
4281     const char *default_color = NULL;	/* Default */
4282 
4283     if ((keyword = ippGetString(defattr, 0, NULL)) != NULL &&
4284 	strcmp(keyword, "auto"))
4285     {
4286       if (!strcmp(keyword, "bi-level"))
4287         default_color = "FastGray";
4288       else if (!strcmp(keyword, "monochrome") || !strcmp(keyword, "auto-monochrome"))
4289         default_color = "Gray";
4290       else
4291         default_color = "RGB";
4292     }
4293 
4294     cupsFilePrintf(fp, "*%% ColorModel from %s\n", ippGetName(attr));
4295 
4296     for (i = 0, count = ippGetCount(attr); i < count; i ++)
4297     {
4298       keyword = ippGetString(attr, i, NULL);
4299 
4300 #define PRINTF_COLORMODEL if (!wrote_color) { cupsFilePrintf(fp, "*OpenUI *ColorModel: PickOne\n*OrderDependency: 10 AnySetup *ColorModel\n*%s.Translation ColorModel/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Color Mode"))); wrote_color = 1; }
4301 #define PRINTF_COLOROPTION(name,text,cspace,bpp) { cupsFilePrintf(fp, "*ColorModel %s: \"<</cupsColorSpace %d/cupsBitsPerColor %d/cupsColorOrder 0/cupsCompression 0>>setpagedevice\"\n", name, cspace, bpp); cupsFilePrintf(fp, "*%s.ColorModel %s/%s: \"\"\n", lang->language, name, _cupsLangString(lang, text)); }
4302 
4303       if (!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") || !strcmp(keyword, "process-bi-level"))
4304       {
4305 	PRINTF_COLORMODEL
4306 
4307 	PRINTF_COLOROPTION("FastGray", _("Fast Grayscale"), CUPS_CSPACE_K, 1)
4308 
4309 	if (!default_color)
4310 	  default_color = "FastGray";
4311       }
4312       else if (!strcasecmp(keyword, "sgray_8") || !strcmp(keyword, "W8") || !strcmp(keyword, "monochrome") || !strcmp(keyword, "process-monochrome"))
4313       {
4314 	PRINTF_COLORMODEL
4315 
4316 	PRINTF_COLOROPTION("Gray", _("Grayscale"), CUPS_CSPACE_SW, 8)
4317 
4318 	if (!default_color || (!defattr && !strcmp(default_color, "FastGray")))
4319 	  default_color = "Gray";
4320       }
4321       else if (!strcasecmp(keyword, "sgray_16") || !strcmp(keyword, "W8-16"))
4322       {
4323 	PRINTF_COLORMODEL
4324 
4325 	if (!strcmp(keyword, "W8-16"))
4326 	{
4327 	  PRINTF_COLOROPTION("Gray", _("Grayscale"), CUPS_CSPACE_SW, 8)
4328 
4329 	  if (!default_color || (!defattr && !strcmp(default_color, "FastGray")))
4330 	    default_color = "Gray";
4331 	}
4332 
4333 	PRINTF_COLOROPTION("Gray16", _("Deep Gray"), CUPS_CSPACE_SW, 16)
4334       }
4335       else if (!strcasecmp(keyword, "srgb_8") || !strncmp(keyword, "SRGB24", 6) || !strcmp(keyword, "color"))
4336       {
4337 	PRINTF_COLORMODEL
4338 
4339 	PRINTF_COLOROPTION("RGB", _("Color"), CUPS_CSPACE_SRGB, 8)
4340 
4341         if (!default_color)
4342 	  default_color = "RGB";
4343 
4344         // Apparently some printers only advertise color support, so make sure
4345         // we also do grayscale for these printers...
4346 	if (!ippContainsString(attr, "sgray_8") && !ippContainsString(attr, "black_1") && !ippContainsString(attr, "black_8") && !ippContainsString(attr, "W8") && !ippContainsString(attr, "W8-16"))
4347 	  PRINTF_COLOROPTION("Gray", _("GrayScale"), CUPS_CSPACE_SW, 8)
4348       }
4349       else if (!strcasecmp(keyword, "adobe-rgb_16") || !strcmp(keyword, "ADOBERGB48") || !strcmp(keyword, "ADOBERGB24-48"))
4350       {
4351 	PRINTF_COLORMODEL
4352 
4353 	PRINTF_COLOROPTION("AdobeRGB", _("Deep Color"), CUPS_CSPACE_ADOBERGB, 16)
4354 
4355 	if (!default_color)
4356 	  default_color = "AdobeRGB";
4357       }
4358       else if ((!strcasecmp(keyword, "adobe-rgb_8") && !ippContainsString(attr, "adobe-rgb_16")) || !strcmp(keyword, "ADOBERGB24"))
4359       {
4360 	PRINTF_COLORMODEL
4361 
4362 	PRINTF_COLOROPTION("AdobeRGB", _("Deep Color"), CUPS_CSPACE_ADOBERGB, 8)
4363 
4364 	if (!default_color)
4365 	  default_color = "AdobeRGB";
4366       }
4367       else if ((!strcasecmp(keyword, "black_8") && !ippContainsString(attr, "black_16")) || !strcmp(keyword, "DEVW8"))
4368       {
4369 	PRINTF_COLORMODEL
4370 
4371 	PRINTF_COLOROPTION("DeviceGray", _("Device Gray"), CUPS_CSPACE_W, 8)
4372       }
4373       else if (!strcasecmp(keyword, "black_16") || !strcmp(keyword, "DEVW16") || !strcmp(keyword, "DEVW8-16"))
4374       {
4375 	PRINTF_COLORMODEL
4376 
4377 	PRINTF_COLOROPTION("DeviceGray", _("Device Gray"), CUPS_CSPACE_W, 16)
4378       }
4379       else if ((!strcasecmp(keyword, "cmyk_8") && !ippContainsString(attr, "cmyk_16")) || !strcmp(keyword, "DEVCMYK32"))
4380       {
4381 	PRINTF_COLORMODEL
4382 
4383 	PRINTF_COLOROPTION("CMYK", _("Device CMYK"), CUPS_CSPACE_CMYK, 8)
4384       }
4385       else if (!strcasecmp(keyword, "cmyk_16") || !strcmp(keyword, "DEVCMYK32-64") || !strcmp(keyword, "DEVCMYK64"))
4386       {
4387 	PRINTF_COLORMODEL
4388 
4389 	PRINTF_COLOROPTION("CMYK", _("Device CMYK"), CUPS_CSPACE_CMYK, 16)
4390       }
4391       else if ((!strcasecmp(keyword, "rgb_8") && ippContainsString(attr, "rgb_16")) || !strcmp(keyword, "DEVRGB24"))
4392       {
4393 	PRINTF_COLORMODEL
4394 
4395 	PRINTF_COLOROPTION("DeviceRGB", _("Device RGB"), CUPS_CSPACE_RGB, 8)
4396       }
4397       else if (!strcasecmp(keyword, "rgb_16") || !strcmp(keyword, "DEVRGB24-48") || !strcmp(keyword, "DEVRGB48"))
4398       {
4399 	PRINTF_COLORMODEL
4400 
4401 	PRINTF_COLOROPTION("DeviceRGB", _("Device RGB"), CUPS_CSPACE_RGB, 16)
4402       }
4403     }
4404 
4405     if (default_color)
4406       cupsFilePrintf(fp, "*DefaultColorModel: %s\n", default_color);
4407     if (wrote_color)
4408       cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
4409 
4410     if (default_color)
4411     {
4412       // Standard presets for color mode and quality...
4413       if (have_qdraft)
4414 	cupsFilePuts(fp,
4415 		     "*APPrinterPreset Gray_with_Paper_Auto-Detect_-_Draft/Draft B&W: \"\n"
4416 		     "  *cupsPrintQuality Draft *ColorModel Gray\n"
4417 		     "  com.apple.print.preset.graphicsType General\n"
4418 		     "  com.apple.print.preset.quality low\n"
4419 		     "  com.apple.print.preset.media-front-coating autodetect\n"
4420 		     "  com.apple.print.preset.output-mode monochrome\"\n"
4421 		     "*End\n");
4422       cupsFilePuts(fp,
4423                    "*APPrinterPreset Gray_with_Paper_Auto-Detect/Black and White: \"\n"
4424 		   "  *cupsPrintQuality Normal *ColorModel Gray\n"
4425 		   "  com.apple.print.preset.graphicsType General\n"
4426 		   "  com.apple.print.preset.quality mid\n"
4427 		   "  com.apple.print.preset.media-front-coating autodetect\n"
4428 		   "  com.apple.print.preset.output-mode monochrome\"\n"
4429 		   "*End\n");
4430       if (strcmp(default_color, "Gray"))
4431 	cupsFilePuts(fp,
4432 		     "*APPrinterPreset Color_with_Paper_Auto-Detect/Color: \"\n"
4433 		     "  *cupsPrintQuality Normal *ColorModel RGB\n"
4434 		     "  com.apple.print.preset.graphicsType General\n"
4435 		     "  com.apple.print.preset.quality mid\n"
4436 		     "  com.apple.print.preset.media-front-coating autodetect\n"
4437 		     "  com.apple.print.preset.output-mode color\"\n"
4438 		     "*End\n");
4439       if (!strcmp(default_color, "AdobeRGB") || have_qhigh)
4440 	cupsFilePrintf(fp,
4441 		       "*APPrinterPreset Photo_with_Paper_Auto-Detect/Photo: \"\n"
4442 		       "  *cupsPrintQuality %s *ColorModel %s\n"
4443 		       "  com.apple.print.preset.graphicsType Photo\n"
4444 		       "  com.apple.print.preset.quality %s\n"
4445 		       "  com.apple.print.preset.media-front-coating autodetect\n"
4446 		       "  com.apple.print.preset.output-mode color\"\n"
4447 		       "*End\n", have_qhigh ? "High" : "Normal", default_color, have_qhigh ? "high" : "mid");
4448     }
4449   }
4450 
4451  /*
4452   * Duplex...
4453   */
4454 
4455   if ((attr = ippFindAttribute(supported, "sides-supported", IPP_TAG_KEYWORD)) != NULL && ippContainsString(attr, "two-sided-long-edge"))
4456   {
4457     cupsFilePrintf(fp, "*OpenUI *Duplex: PickOne\n"
4458 		       "*OrderDependency: 10 AnySetup *Duplex\n"
4459 		       "*%s.Translation Duplex/%s: \"\"\n"
4460 		       "*DefaultDuplex: None\n"
4461 		       "*Duplex None: \"<</Duplex false>>setpagedevice\"\n"
4462 		       "*%s.Duplex None/%s: \"\"\n"
4463 		       "*Duplex DuplexNoTumble: \"<</Duplex true/Tumble false>>setpagedevice\"\n"
4464 		       "*%s.Duplex DuplexNoTumble/%s: \"\"\n"
4465 		       "*Duplex DuplexTumble: \"<</Duplex true/Tumble true>>setpagedevice\"\n"
4466 		       "*%s.Duplex DuplexTumble/%s: \"\"\n"
4467 		       "*CloseUI: *Duplex\n", lang->language, _cupsLangString(lang, _("2-Sided Printing")), lang->language, _cupsLangString(lang, _("Off (1-Sided)")), lang->language, _cupsLangString(lang, _("Long-Edge (Portrait)")), lang->language, _cupsLangString(lang, _("Short-Edge (Landscape)")));
4468 
4469     if ((attr = ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
4470     {
4471       for (i = 0, count = ippGetCount(attr); i < count; i ++)
4472       {
4473         const char *dm = ippGetString(attr, i, NULL);
4474                                         /* DM value */
4475 
4476         if (!_cups_strcasecmp(dm, "DM1"))
4477         {
4478           cupsFilePuts(fp, "*cupsBackSide: Normal\n");
4479           break;
4480         }
4481         else if (!_cups_strcasecmp(dm, "DM2"))
4482         {
4483           cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
4484           break;
4485         }
4486         else if (!_cups_strcasecmp(dm, "DM3"))
4487         {
4488           cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
4489           break;
4490         }
4491         else if (!_cups_strcasecmp(dm, "DM4"))
4492         {
4493           cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
4494           break;
4495         }
4496       }
4497     }
4498     else if ((attr = ippFindAttribute(supported, "pwg-raster-document-sheet-back", IPP_TAG_KEYWORD)) != NULL)
4499     {
4500       keyword = ippGetString(attr, 0, NULL);
4501 
4502       if (!strcmp(keyword, "flipped"))
4503         cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
4504       else if (!strcmp(keyword, "manual-tumble"))
4505         cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
4506       else if (!strcmp(keyword, "normal"))
4507         cupsFilePuts(fp, "*cupsBackSide: Normal\n");
4508       else
4509         cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
4510     }
4511   }
4512 
4513  /*
4514   * Output bin...
4515   */
4516 
4517   if ((attr = ippFindAttribute(supported, "output-bin-default", IPP_TAG_ZERO)) != NULL)
4518     pwg_ppdize_name(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
4519   else
4520     strlcpy(ppdname, "Unknown", sizeof(ppdname));
4521 
4522   if ((attr = ippFindAttribute(supported, "output-bin-supported", IPP_TAG_ZERO)) != NULL && (count = ippGetCount(attr)) > 0)
4523   {
4524     ipp_attribute_t	*trays = ippFindAttribute(supported, "printer-output-tray", IPP_TAG_STRING);
4525 					/* printer-output-tray attribute, if any */
4526     const char		*tray_ptr;	/* printer-output-tray value */
4527     int			tray_len;	/* Len of printer-output-tray value */
4528     char		tray[IPP_MAX_OCTETSTRING];
4529 					/* printer-output-tray string value */
4530 
4531     cupsFilePrintf(fp, "*OpenUI *OutputBin: PickOne\n"
4532                        "*OrderDependency: 10 AnySetup *OutputBin\n"
4533                        "*DefaultOutputBin: %s\n", ppdname);
4534     if (!strcmp(ppdname, "FaceUp"))
4535       cupsFilePuts(fp, "*DefaultOutputOrder: Reverse\n");
4536     else
4537       cupsFilePuts(fp, "*DefaultOutputOrder: Normal\n");
4538 
4539     for (i = 0; i < count; i ++)
4540     {
4541       keyword = ippGetString(attr, i, NULL);
4542 
4543       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4544 
4545       snprintf(msgid, sizeof(msgid), "output-bin.%s", keyword);
4546 
4547       cupsFilePrintf(fp, "*OutputBin %s: \"\"\n", ppdname);
4548       ppd_put_string(fp, lang, strings, "OutputBin", ppdname, msgid);
4549 
4550       if ((tray_ptr = ippGetOctetString(trays, i, &tray_len)) != NULL)
4551       {
4552         if (tray_len >= (int)sizeof(tray))
4553           tray_len = (int)sizeof(tray) - 1;
4554 
4555         memcpy(tray, tray_ptr, (size_t)tray_len);
4556         tray[tray_len] = '\0';
4557 
4558         if (strstr(tray, "stackingorder=lastToFirst;"))
4559           cupsFilePrintf(fp, "*PageStackOrder %s: Reverse\n", ppdname);
4560         else
4561           cupsFilePrintf(fp, "*PageStackOrder %s: Normal\n", ppdname);
4562       }
4563       else if (!strcmp(ppdname, "FaceUp"))
4564 	cupsFilePrintf(fp, "*PageStackOrder %s: Reverse\n", ppdname);
4565       else
4566 	cupsFilePrintf(fp, "*PageStackOrder %s: Normal\n", ppdname);
4567     }
4568     cupsFilePuts(fp, "*CloseUI: *OutputBin\n");
4569   }
4570 
4571  /*
4572   * Finishing options...
4573   */
4574 
4575   if ((attr = ippFindAttribute(supported, "finishings-supported", IPP_TAG_ENUM)) != NULL)
4576   {
4577     int			value;		/* Enum value */
4578     const char		*ppd_keyword;	/* PPD keyword for enum */
4579     cups_array_t	*names;		/* Names we've added */
4580     static const char * const base_keywords[] =
4581     {					/* Base STD 92 keywords */
4582       NULL,				/* none */
4583       "SingleAuto",			/* staple */
4584       "SingleAuto",			/* punch */
4585       NULL,				/* cover */
4586       "BindAuto",			/* bind */
4587       "SaddleStitch",			/* saddle-stitch */
4588       "EdgeStitchAuto",			/* edge-stitch */
4589       "Auto",				/* fold */
4590       NULL,				/* trim */
4591       NULL,				/* bale */
4592       NULL,				/* booklet-maker */
4593       NULL,				/* jog-offset */
4594       NULL,				/* coat */
4595       NULL				/* laminate */
4596     };
4597 
4598     count       = ippGetCount(attr);
4599     names       = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
4600     fin_options = cupsArrayNew((cups_array_func_t)strcmp, NULL);
4601 
4602    /*
4603     * Staple/Bind/Stitch
4604     */
4605 
4606     for (i = 0; i < count; i ++)
4607     {
4608       value   = ippGetInteger(attr, i);
4609       keyword = ippEnumString("finishings", value);
4610 
4611       if (!strncmp(keyword, "staple-", 7) || !strncmp(keyword, "bind-", 5) || !strncmp(keyword, "edge-stitch-", 12) || !strcmp(keyword, "saddle-stitch"))
4612         break;
4613     }
4614 
4615     if (i < count)
4616     {
4617       static const char * const staple_keywords[] =
4618       {					/* StapleLocation keywords */
4619 	"SinglePortrait",
4620 	"SingleRevLandscape",
4621 	"SingleLandscape",
4622 	"SingleRevPortrait",
4623 	"EdgeStitchPortrait",
4624 	"EdgeStitchLandscape",
4625 	"EdgeStitchRevPortrait",
4626 	"EdgeStitchRevLandscape",
4627 	"DualPortrait",
4628 	"DualLandscape",
4629 	"DualRevPortrait",
4630 	"DualRevLandscape",
4631 	"TriplePortrait",
4632 	"TripleLandscape",
4633 	"TripleRevPortrait",
4634 	"TripleRevLandscape"
4635       };
4636       static const char * const bind_keywords[] =
4637       {					/* StapleLocation binding keywords */
4638 	"BindPortrait",
4639 	"BindLandscape",
4640 	"BindRevPortrait",
4641 	"BindRevLandscape"
4642       };
4643 
4644       cupsArrayAdd(fin_options, "*StapleLocation");
4645 
4646       cupsFilePuts(fp, "*OpenUI *StapleLocation: PickOne\n");
4647       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *StapleLocation\n");
4648       cupsFilePrintf(fp, "*%s.Translation StapleLocation/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Staple")));
4649       cupsFilePuts(fp, "*DefaultStapleLocation: None\n");
4650       cupsFilePuts(fp, "*StapleLocation None: \"\"\n");
4651       cupsFilePrintf(fp, "*%s.StapleLocation None/%s: \"\"\n", lang->language, _cupsLangString(lang, _("None")));
4652 
4653       for (; i < count; i ++)
4654       {
4655         value   = ippGetInteger(attr, i);
4656         keyword = ippEnumString("finishings", value);
4657 
4658         if (strncmp(keyword, "staple-", 7) && strncmp(keyword, "bind-", 5) && strncmp(keyword, "edge-stitch-", 12) && strcmp(keyword, "saddle-stitch"))
4659           continue;
4660 
4661         if (cupsArrayFind(names, (char *)keyword))
4662           continue;			/* Already did this finishing template */
4663 
4664         cupsArrayAdd(names, (char *)keyword);
4665 
4666 	snprintf(msgid, sizeof(msgid), "finishings.%d", value);
4667 
4668         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
4669           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
4670         else if (value >= IPP_FINISHINGS_STAPLE_TOP_LEFT && value <= IPP_FINISHINGS_STAPLE_TRIPLE_BOTTOM)
4671           ppd_keyword = staple_keywords[value - IPP_FINISHINGS_STAPLE_TOP_LEFT];
4672         else if (value >= IPP_FINISHINGS_BIND_LEFT && value <= IPP_FINISHINGS_BIND_BOTTOM)
4673           ppd_keyword = bind_keywords[value - IPP_FINISHINGS_BIND_LEFT];
4674         else
4675           ppd_keyword = NULL;
4676 
4677         if (!ppd_keyword)
4678           continue;
4679 
4680 	cupsFilePrintf(fp, "*StapleLocation %s: \"\"\n", ppd_keyword);
4681 	ppd_put_string(fp, lang, strings, "StapleLocation", ppd_keyword, msgid);
4682 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n", value, keyword, ppd_keyword);
4683       }
4684 
4685       cupsFilePuts(fp, "*CloseUI: *StapleLocation\n");
4686     }
4687 
4688    /*
4689     * Fold
4690     */
4691 
4692     for (i = 0; i < count; i ++)
4693     {
4694       value   = ippGetInteger(attr, i);
4695       keyword = ippEnumString("finishings", value);
4696 
4697       if (!strncmp(keyword, "cups-fold-", 10) || !strcmp(keyword, "fold") || !strncmp(keyword, "fold-", 5))
4698         break;
4699     }
4700 
4701     if (i < count)
4702     {
4703       static const char * const fold_keywords[] =
4704       {					/* FoldType keywords */
4705 	"Accordion",
4706 	"DoubleGate",
4707 	"Gate",
4708 	"Half",
4709 	"HalfZ",
4710 	"LeftGate",
4711 	"Letter",
4712 	"Parallel",
4713 	"XFold",
4714 	"RightGate",
4715 	"ZFold",
4716 	"EngineeringZ"
4717       };
4718 
4719       cupsArrayAdd(fin_options, "*FoldType");
4720 
4721       cupsFilePuts(fp, "*OpenUI *FoldType: PickOne\n");
4722       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *FoldType\n");
4723       cupsFilePrintf(fp, "*%s.Translation FoldType/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Fold")));
4724       cupsFilePuts(fp, "*DefaultFoldType: None\n");
4725       cupsFilePuts(fp, "*FoldType None: \"\"\n");
4726       cupsFilePrintf(fp, "*%s.FoldType None/%s: \"\"\n", lang->language, _cupsLangString(lang, _("None")));
4727 
4728       for (; i < count; i ++)
4729       {
4730         value   = ippGetInteger(attr, i);
4731         keyword = ippEnumString("finishings", value);
4732 
4733         if (!strncmp(keyword, "cups-fold-", 10))
4734           keyword += 5;
4735         else if (strcmp(keyword, "fold") && strncmp(keyword, "fold-", 5))
4736           continue;
4737 
4738         if (cupsArrayFind(names, (char *)keyword))
4739           continue;			/* Already did this finishing template */
4740 
4741         cupsArrayAdd(names, (char *)keyword);
4742 
4743 	snprintf(msgid, sizeof(msgid), "finishings.%d", value);
4744 
4745         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
4746           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
4747         else if (value >= IPP_FINISHINGS_FOLD_ACCORDION && value <= IPP_FINISHINGS_FOLD_ENGINEERING_Z)
4748           ppd_keyword = fold_keywords[value - IPP_FINISHINGS_FOLD_ACCORDION];
4749         else if (value >= IPP_FINISHINGS_CUPS_FOLD_ACCORDION && value <= IPP_FINISHINGS_CUPS_FOLD_Z)
4750           ppd_keyword = fold_keywords[value - IPP_FINISHINGS_CUPS_FOLD_ACCORDION];
4751         else
4752           ppd_keyword = NULL;
4753 
4754         if (!ppd_keyword)
4755           continue;
4756 
4757 	cupsFilePrintf(fp, "*FoldType %s: \"\"\n", ppd_keyword);
4758 	ppd_put_string(fp, lang, strings, "FoldType", ppd_keyword, msgid);
4759 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n", value, keyword, ppd_keyword);
4760       }
4761 
4762       cupsFilePuts(fp, "*CloseUI: *FoldType\n");
4763     }
4764 
4765    /*
4766     * Punch
4767     */
4768 
4769     for (i = 0; i < count; i ++)
4770     {
4771       value   = ippGetInteger(attr, i);
4772       keyword = ippEnumString("finishings", value);
4773 
4774       if (!strcmp(keyword, "punch") || !strncmp(keyword, "cups-punch-", 11) || !strncmp(keyword, "punch-", 6))
4775         break;
4776     }
4777 
4778     if (i < count)
4779     {
4780       static const char * const punch_keywords[] =
4781       {					/* PunchMedia keywords */
4782 	"SinglePortrait",
4783 	"SingleRevLandscape",
4784 	"SingleLandscape",
4785 	"SingleRevPortrait",
4786 	"DualPortrait",
4787 	"DualLandscape",
4788 	"DualRevPortrait",
4789 	"DualRevLandscape",
4790 	"TriplePortrait",
4791 	"TripleLandscape",
4792 	"TripleRevPortrait",
4793 	"TripleRevLandscape",
4794 	"QuadPortrait",
4795 	"QuadLandscape",
4796 	"QuadRevPortrait",
4797 	"QuadRevLandscape",
4798 	"MultiplePortrait",
4799 	"MultipleLandscape",
4800 	"MultipleRevPortrait",
4801 	"MultipleRevLandscape"
4802       };
4803 
4804       cupsArrayAdd(fin_options, "*PunchMedia");
4805 
4806       cupsFilePuts(fp, "*OpenUI *PunchMedia: PickOne\n");
4807       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *PunchMedia\n");
4808       cupsFilePrintf(fp, "*%s.Translation PunchMedia/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Punch")));
4809       cupsFilePuts(fp, "*DefaultPunchMedia: None\n");
4810       cupsFilePuts(fp, "*PunchMedia None: \"\"\n");
4811       cupsFilePrintf(fp, "*%s.PunchMedia None/%s: \"\"\n", lang->language, _cupsLangString(lang, _("None")));
4812 
4813       for (i = 0; i < count; i ++)
4814       {
4815         value   = ippGetInteger(attr, i);
4816         keyword = ippEnumString("finishings", value);
4817 
4818         if (!strncmp(keyword, "cups-punch-", 11))
4819           keyword += 5;
4820         else if (strcmp(keyword, "punch") && strncmp(keyword, "punch-", 6))
4821           continue;
4822 
4823         if (cupsArrayFind(names, (char *)keyword))
4824           continue;			/* Already did this finishing template */
4825 
4826         cupsArrayAdd(names, (char *)keyword);
4827 
4828 	snprintf(msgid, sizeof(msgid), "finishings.%d", value);
4829 
4830         if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
4831           ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
4832         else if (value >= IPP_FINISHINGS_PUNCH_TOP_LEFT && value <= IPP_FINISHINGS_PUNCH_MULTIPLE_BOTTOM)
4833           ppd_keyword = punch_keywords[value - IPP_FINISHINGS_PUNCH_TOP_LEFT];
4834         else if (value >= IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT && value <= IPP_FINISHINGS_CUPS_PUNCH_QUAD_BOTTOM)
4835           ppd_keyword = punch_keywords[value - IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT];
4836         else
4837           ppd_keyword = NULL;
4838 
4839         if (!ppd_keyword)
4840           continue;
4841 
4842 	cupsFilePrintf(fp, "*PunchMedia %s: \"\"\n", ppd_keyword);
4843 	ppd_put_string(fp, lang, strings, "PunchMedia", ppd_keyword, msgid);
4844 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n", value, keyword, ppd_keyword);
4845       }
4846 
4847       cupsFilePuts(fp, "*CloseUI: *PunchMedia\n");
4848     }
4849 
4850    /*
4851     * Booklet
4852     */
4853 
4854     if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER))
4855     {
4856       cupsArrayAdd(fin_options, "*Booklet");
4857 
4858       cupsFilePuts(fp, "*OpenUI *Booklet: Boolean\n");
4859       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *Booklet\n");
4860       cupsFilePrintf(fp, "*%s.Translation Booklet/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Booklet")));
4861       cupsFilePuts(fp, "*DefaultBooklet: False\n");
4862       cupsFilePuts(fp, "*Booklet False: \"\"\n");
4863       cupsFilePuts(fp, "*Booklet True: \"\"\n");
4864       cupsFilePrintf(fp, "*cupsIPPFinishings %d/booklet-maker: \"*Booklet True\"\n", IPP_FINISHINGS_BOOKLET_MAKER);
4865       cupsFilePuts(fp, "*CloseUI: *Booklet\n");
4866     }
4867 
4868    /*
4869     * CutMedia
4870     */
4871 
4872     for (i = 0; i < count; i ++)
4873     {
4874       value   = ippGetInteger(attr, i);
4875       keyword = ippEnumString("finishings", value);
4876 
4877       if (!strcmp(keyword, "trim") || !strncmp(keyword, "trim-", 5))
4878         break;
4879     }
4880 
4881     if (i < count)
4882     {
4883       static const char * const trim_keywords[] =
4884       {				/* CutMedia keywords */
4885         "EndOfPage",
4886         "EndOfDoc",
4887         "EndOfSet",
4888         "EndOfJob"
4889       };
4890 
4891       cupsArrayAdd(fin_options, "*CutMedia");
4892 
4893       cupsFilePuts(fp, "*OpenUI *CutMedia: PickOne\n");
4894       cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *CutMedia\n");
4895       cupsFilePrintf(fp, "*%s.Translation CutMedia/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Cut")));
4896       cupsFilePuts(fp, "*DefaultCutMedia: None\n");
4897       cupsFilePuts(fp, "*CutMedia None: \"\"\n");
4898       cupsFilePrintf(fp, "*%s.CutMedia None/%s: \"\"\n", lang->language, _cupsLangString(lang, _("None")));
4899 
4900       for (i = 0; i < count; i ++)
4901       {
4902         value   = ippGetInteger(attr, i);
4903         keyword = ippEnumString("finishings", value);
4904 
4905 	if (strcmp(keyword, "trim") && strncmp(keyword, "trim-", 5))
4906           continue;
4907 
4908         if (cupsArrayFind(names, (char *)keyword))
4909           continue;			/* Already did this finishing template */
4910 
4911         cupsArrayAdd(names, (char *)keyword);
4912 
4913 	snprintf(msgid, sizeof(msgid), "finishings.%d", value);
4914 
4915         if (value == IPP_FINISHINGS_TRIM)
4916           ppd_keyword = "Auto";
4917 	else
4918 	  ppd_keyword = trim_keywords[value - IPP_FINISHINGS_TRIM_AFTER_PAGES];
4919 
4920 	cupsFilePrintf(fp, "*CutMedia %s: \"\"\n", ppd_keyword);
4921 	ppd_put_string(fp, lang, strings, "CutMedia", ppd_keyword, msgid);
4922 	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*CutMedia %s\"\n", value, keyword, ppd_keyword);
4923       }
4924 
4925       cupsFilePuts(fp, "*CloseUI: *CutMedia\n");
4926     }
4927 
4928     cupsArrayDelete(names);
4929   }
4930 
4931   if ((attr = ippFindAttribute(supported, "finishings-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL)
4932   {
4933     ipp_t	*finishing_col;		/* Current finishing collection */
4934     ipp_attribute_t *finishing_attr;	/* Current finishing member attribute */
4935     cups_array_t *templates;		/* Finishing templates */
4936 
4937     cupsFilePuts(fp, "*OpenUI *cupsFinishingTemplate: PickOne\n");
4938     cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n");
4939     cupsFilePrintf(fp, "*%s.Translation cupsFinishingTemplate/%s: \"\"\n", lang->language, _cupsLangString(lang, _("Finishing Preset")));
4940     cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: none\n");
4941     cupsFilePuts(fp, "*cupsFinishingTemplate none: \"\"\n");
4942     cupsFilePrintf(fp, "*%s.cupsFinishingTemplate none/%s: \"\"\n", lang->language, _cupsLangString(lang, _("None")));
4943 
4944     templates = cupsArrayNew((cups_array_func_t)strcmp, NULL);
4945     count     = ippGetCount(attr);
4946 
4947     for (i = 0; i < count; i ++)
4948     {
4949       finishing_col = ippGetCollection(attr, i);
4950       keyword       = ippGetString(ippFindAttribute(finishing_col, "finishing-template", IPP_TAG_ZERO), 0, NULL);
4951 
4952       if (!keyword || cupsArrayFind(templates, (void *)keyword))
4953         continue;
4954 
4955       if (!strcmp(keyword, "none"))
4956         continue;
4957 
4958       cupsArrayAdd(templates, (void *)keyword);
4959 
4960       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
4961 
4962       snprintf(msgid, sizeof(msgid), "finishing-template.%s", keyword);
4963 
4964       cupsFilePrintf(fp, "*cupsFinishingTemplate %s: \"\n", ppdname);
4965       for (finishing_attr = ippFirstAttribute(finishing_col); finishing_attr; finishing_attr = ippNextAttribute(finishing_col))
4966       {
4967         if (ippGetValueTag(finishing_attr) == IPP_TAG_BEGIN_COLLECTION)
4968         {
4969 	  const char *name = ippGetName(finishing_attr);
4970 					/* Member attribute name */
4971 
4972           if (strcmp(name, "media-size"))
4973             cupsFilePrintf(fp, "%% %s\n", name);
4974 	}
4975       }
4976       cupsFilePuts(fp, "\"\n");
4977       ppd_put_string(fp, lang, strings, "cupsFinishingTemplate", ppdname, msgid);
4978       cupsFilePuts(fp, "*End\n");
4979     }
4980 
4981     cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");
4982 
4983     if (cupsArrayCount(fin_options))
4984     {
4985       const char	*fin_option;	/* Current finishing option */
4986 
4987       cupsFilePuts(fp, "*cupsUIConstraint finishing-template: \"*cupsFinishingTemplate");
4988       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option; fin_option = (const char *)cupsArrayNext(fin_options))
4989         cupsFilePrintf(fp, " %s", fin_option);
4990       cupsFilePuts(fp, "\"\n");
4991 
4992       cupsFilePuts(fp, "*cupsUIResolver finishing-template: \"*cupsFinishingTemplate None");
4993       for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option; fin_option = (const char *)cupsArrayNext(fin_options))
4994         cupsFilePrintf(fp, " %s None", fin_option);
4995       cupsFilePuts(fp, "\"\n");
4996     }
4997 
4998     cupsArrayDelete(templates);
4999   }
5000 
5001   cupsArrayDelete(fin_options);
5002 
5003  /*
5004   * Presets...
5005   */
5006 
5007   if ((attr = ippFindAttribute(supported, "job-presets-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL)
5008   {
5009     for (i = 0, count = ippGetCount(attr); i < count; i ++)
5010     {
5011       ipp_t	*preset = ippGetCollection(attr, i);
5012 					/* Preset collection */
5013       const char *preset_name = ippGetString(ippFindAttribute(preset, "preset-name", IPP_TAG_ZERO), 0, NULL);
5014 					/* Preset name */
5015       ipp_attribute_t *member;		/* Member attribute in preset */
5016       const char *member_name;		/* Member attribute name */
5017       char      	member_value[256];	/* Member attribute value */
5018 
5019       if (!preset || !preset_name)
5020         continue;
5021 
5022       pwg_ppdize_name(preset_name, ppdname, sizeof(ppdname));
5023       cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", ppdname);
5024       for (member = ippFirstAttribute(preset); member; member = ippNextAttribute(preset))
5025       {
5026         member_name = ippGetName(member);
5027 
5028         if (!member_name || !strcmp(member_name, "preset-name"))
5029           continue;
5030 
5031         if (!strcmp(member_name, "finishings"))
5032         {
5033 	  for (i = 0, count = ippGetCount(member); i < count; i ++)
5034 	  {
5035 	    const char *option = NULL;	/* PPD option name */
5036 
5037 	    keyword = ippEnumString("finishings", ippGetInteger(member, i));
5038 
5039 	    if (!strcmp(keyword, "booklet-maker"))
5040 	    {
5041 	      option  = "Booklet";
5042 	      keyword = "True";
5043 	    }
5044 	    else if (!strncmp(keyword, "fold-", 5))
5045 	      option = "FoldType";
5046 	    else if (!strncmp(keyword, "punch-", 6))
5047 	      option = "PunchMedia";
5048 	    else if (!strncmp(keyword, "bind-", 5) || !strncmp(keyword, "edge-stitch-", 12) || !strcmp(keyword, "saddle-stitch") || !strncmp(keyword, "staple-", 7))
5049 	      option = "StapleLocation";
5050 
5051 	    if (option && keyword)
5052 	      cupsFilePrintf(fp, "*%s %s\n", option, keyword);
5053 	  }
5054         }
5055         else if (!strcmp(member_name, "finishings-col"))
5056         {
5057           ipp_t *fin_col;		/* finishings-col value */
5058 
5059           for (i = 0, count = ippGetCount(member); i < count; i ++)
5060           {
5061             fin_col = ippGetCollection(member, i);
5062 
5063             if ((keyword = ippGetString(ippFindAttribute(fin_col, "finishing-template", IPP_TAG_ZERO), 0, NULL)) != NULL)
5064             {
5065               pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
5066               cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", ppdname);
5067             }
5068           }
5069         }
5070         else if (!strcmp(member_name, "media"))
5071         {
5072          /*
5073           * Map media to PageSize...
5074           */
5075 
5076           if ((pwg = pwgMediaForPWG(ippGetString(member, 0, NULL))) != NULL && pwg->ppd)
5077             cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
5078         }
5079         else if (!strcmp(member_name, "media-col"))
5080         {
5081           media_col = ippGetCollection(member, 0);
5082 
5083           if ((media_size = ippGetCollection(ippFindAttribute(media_col, "media-size", IPP_TAG_BEGIN_COLLECTION), 0)) != NULL)
5084           {
5085             x_dim = ippFindAttribute(media_size, "x-dimension", IPP_TAG_INTEGER);
5086             y_dim = ippFindAttribute(media_size, "y-dimension", IPP_TAG_INTEGER);
5087             if ((pwg = pwgMediaForSize(ippGetInteger(x_dim, 0), ippGetInteger(y_dim, 0))) != NULL && pwg->ppd)
5088 	      cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
5089           }
5090 
5091           if ((keyword = ippGetString(ippFindAttribute(media_col, "media-source", IPP_TAG_ZERO), 0, NULL)) != NULL)
5092           {
5093             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
5094             cupsFilePrintf(fp, "*InputSlot %s\n", ppdname);
5095 	  }
5096 
5097           if ((keyword = ippGetString(ippFindAttribute(media_col, "media-type", IPP_TAG_ZERO), 0, NULL)) != NULL)
5098           {
5099             pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
5100             cupsFilePrintf(fp, "*MediaType %s\n", ppdname);
5101 	  }
5102         }
5103         else if (!strcmp(member_name, "print-quality"))
5104         {
5105 	 /*
5106 	  * Map print-quality to cupsPrintQuality...
5107 	  */
5108 
5109           int qval = ippGetInteger(member, 0);
5110 					/* print-quality value */
5111 	  static const char * const qualities[] = { "Draft", "Normal", "High" };
5112 					/* cupsPrintQuality values */
5113 
5114           if (qval >= IPP_QUALITY_DRAFT && qval <= IPP_QUALITY_HIGH)
5115             cupsFilePrintf(fp, "*cupsPrintQuality %s\n", qualities[qval - IPP_QUALITY_DRAFT]);
5116         }
5117         else if (!strcmp(member_name, "output-bin"))
5118         {
5119           pwg_ppdize_name(ippGetString(member, 0, NULL), ppdname, sizeof(ppdname));
5120           cupsFilePrintf(fp, "*OutputBin %s\n", ppdname);
5121         }
5122         else if (!strcmp(member_name, "sides"))
5123         {
5124           keyword = ippGetString(member, 0, NULL);
5125           if (keyword && !strcmp(keyword, "one-sided"))
5126             cupsFilePuts(fp, "*Duplex None\n");
5127 	  else if (keyword && !strcmp(keyword, "two-sided-long-edge"))
5128 	    cupsFilePuts(fp, "*Duplex DuplexNoTumble\n");
5129 	  else if (keyword && !strcmp(keyword, "two-sided-short-edge"))
5130 	    cupsFilePuts(fp, "*Duplex DuplexTumble\n");
5131         }
5132         else
5133         {
5134          /*
5135           * Add attribute name and value as-is...
5136           */
5137 
5138           ippAttributeString(member, member_value, sizeof(member_value));
5139           cupsFilePrintf(fp, "*%s %s\n", member_name, member_value);
5140 	}
5141       }
5142 
5143       cupsFilePuts(fp, "\"\n*End\n");
5144 
5145       snprintf(msgid, sizeof(msgid), "preset-name.%s", preset_name);
5146       pwg_ppdize_name(preset_name, ppdname, sizeof(ppdname));
5147       ppd_put_string(fp, lang, strings, "APPrinterPreset", ppdname, msgid);
5148     }
5149   }
5150 
5151  /*
5152   * Add cupsSingleFile to support multiple files printing on printers
5153   * which don't support multiple files in its firmware...
5154   *
5155   * Adding the keyword degrades printing performance (there is 1-2 seconds
5156   * pause between files).
5157   */
5158 
5159   cupsFilePuts(fp, "*cupsSingleFile: true\n");
5160 
5161  /*
5162   * Close up and return...
5163   */
5164 
5165   cupsFileClose(fp);
5166 
5167   _cupsMessageFree(strings);
5168 
5169   return (buffer);
5170 
5171  /*
5172   * If we get here then there was a problem creating the PPD...
5173   */
5174 
5175   bad_ppd:
5176 
5177   cupsFileClose(fp);
5178   unlink(buffer);
5179   *buffer = '\0';
5180 
5181   _cupsMessageFree(strings);
5182 
5183   _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Printer does not support required IPP attributes or document formats."), 1);
5184 
5185   return (NULL);
5186 }
5187 
5188 
5189 /*
5190  * '_pwgInputSlotForSource()' - Get the InputSlot name for the given PWG
5191  *                              media-source.
5192  */
5193 
5194 const char *				/* O - InputSlot name */
_pwgInputSlotForSource(const char * media_source,char * name,size_t namesize)5195 _pwgInputSlotForSource(
5196     const char *media_source,		/* I - PWG media-source */
5197     char       *name,			/* I - Name buffer */
5198     size_t     namesize)		/* I - Size of name buffer */
5199 {
5200  /*
5201   * Range check input...
5202   */
5203 
5204   if (!media_source || !name || namesize < PPD_MAX_NAME)
5205     return (NULL);
5206 
5207   if (_cups_strcasecmp(media_source, "main"))
5208     strlcpy(name, "Cassette", namesize);
5209   else if (_cups_strcasecmp(media_source, "alternate"))
5210     strlcpy(name, "Multipurpose", namesize);
5211   else if (_cups_strcasecmp(media_source, "large-capacity"))
5212     strlcpy(name, "LargeCapacity", namesize);
5213   else if (_cups_strcasecmp(media_source, "bottom"))
5214     strlcpy(name, "Lower", namesize);
5215   else if (_cups_strcasecmp(media_source, "middle"))
5216     strlcpy(name, "Middle", namesize);
5217   else if (_cups_strcasecmp(media_source, "top"))
5218     strlcpy(name, "Upper", namesize);
5219   else if (_cups_strcasecmp(media_source, "rear"))
5220     strlcpy(name, "Rear", namesize);
5221   else if (_cups_strcasecmp(media_source, "side"))
5222     strlcpy(name, "Side", namesize);
5223   else if (_cups_strcasecmp(media_source, "envelope"))
5224     strlcpy(name, "Envelope", namesize);
5225   else if (_cups_strcasecmp(media_source, "main-roll"))
5226     strlcpy(name, "Roll", namesize);
5227   else if (_cups_strcasecmp(media_source, "alternate-roll"))
5228     strlcpy(name, "Roll2", namesize);
5229   else
5230     pwg_ppdize_name(media_source, name, namesize);
5231 
5232   return (name);
5233 }
5234 
5235 
5236 /*
5237  * '_pwgMediaTypeForType()' - Get the MediaType name for the given PWG
5238  *                            media-type.
5239  */
5240 
5241 const char *				/* O - MediaType name */
_pwgMediaTypeForType(const char * media_type,char * name,size_t namesize)5242 _pwgMediaTypeForType(
5243     const char *media_type,		/* I - PWG media-type */
5244     char       *name,			/* I - Name buffer */
5245     size_t     namesize)		/* I - Size of name buffer */
5246 {
5247  /*
5248   * Range check input...
5249   */
5250 
5251   if (!media_type || !name || namesize < PPD_MAX_NAME)
5252     return (NULL);
5253 
5254   if (_cups_strcasecmp(media_type, "auto"))
5255     strlcpy(name, "Auto", namesize);
5256   else if (_cups_strcasecmp(media_type, "cardstock"))
5257     strlcpy(name, "Cardstock", namesize);
5258   else if (_cups_strcasecmp(media_type, "envelope"))
5259     strlcpy(name, "Envelope", namesize);
5260   else if (_cups_strcasecmp(media_type, "photographic-glossy"))
5261     strlcpy(name, "Glossy", namesize);
5262   else if (_cups_strcasecmp(media_type, "photographic-high-gloss"))
5263     strlcpy(name, "HighGloss", namesize);
5264   else if (_cups_strcasecmp(media_type, "photographic-matte"))
5265     strlcpy(name, "Matte", namesize);
5266   else if (_cups_strcasecmp(media_type, "stationery"))
5267     strlcpy(name, "Plain", namesize);
5268   else if (_cups_strcasecmp(media_type, "stationery-coated"))
5269     strlcpy(name, "Coated", namesize);
5270   else if (_cups_strcasecmp(media_type, "stationery-inkjet"))
5271     strlcpy(name, "Inkjet", namesize);
5272   else if (_cups_strcasecmp(media_type, "stationery-letterhead"))
5273     strlcpy(name, "Letterhead", namesize);
5274   else if (_cups_strcasecmp(media_type, "stationery-preprinted"))
5275     strlcpy(name, "Preprinted", namesize);
5276   else if (_cups_strcasecmp(media_type, "transparency"))
5277     strlcpy(name, "Transparency", namesize);
5278   else
5279     pwg_ppdize_name(media_type, name, namesize);
5280 
5281   return (name);
5282 }
5283 
5284 
5285 /*
5286  * '_pwgPageSizeForMedia()' - Get the PageSize name for the given media.
5287  */
5288 
5289 const char *				/* O - PageSize name */
_pwgPageSizeForMedia(pwg_media_t * media,char * name,size_t namesize)5290 _pwgPageSizeForMedia(
5291     pwg_media_t *media,			/* I - Media */
5292     char        *name,			/* I - PageSize name buffer */
5293     size_t      namesize)		/* I - Size of name buffer */
5294 {
5295   const char	*sizeptr,		/* Pointer to size in PWG name */
5296 		*dimptr;		/* Pointer to dimensions in PWG name */
5297 
5298 
5299  /*
5300   * Range check input...
5301   */
5302 
5303   if (!media || !name || namesize < PPD_MAX_NAME)
5304     return (NULL);
5305 
5306  /*
5307   * Copy or generate a PageSize name...
5308   */
5309 
5310   if (media->ppd)
5311   {
5312    /*
5313     * Use a standard Adobe name...
5314     */
5315 
5316     strlcpy(name, media->ppd, namesize);
5317   }
5318   else if (!media->pwg || !strncmp(media->pwg, "custom_", 7) ||
5319            (sizeptr = strchr(media->pwg, '_')) == NULL ||
5320 	   (dimptr = strchr(sizeptr + 1, '_')) == NULL ||
5321 	   (size_t)(dimptr - sizeptr) > namesize)
5322   {
5323    /*
5324     * Use a name of the form "wNNNhNNN"...
5325     */
5326 
5327     snprintf(name, namesize, "w%dh%d", (int)PWG_TO_POINTS(media->width),
5328              (int)PWG_TO_POINTS(media->length));
5329   }
5330   else
5331   {
5332    /*
5333     * Copy the size name from class_sizename_dimensions...
5334     */
5335 
5336     memcpy(name, sizeptr + 1, (size_t)(dimptr - sizeptr - 1));
5337     name[dimptr - sizeptr - 1] = '\0';
5338   }
5339 
5340   return (name);
5341 }
5342 
5343 
5344 /*
5345  * 'cups_connect()' - Connect to a URL and get the resource path.
5346  */
5347 
5348 static int				/* O  - 1 on success, 0 on failure */
cups_connect(http_t ** http,const char * url,char * resource,size_t ressize)5349 cups_connect(http_t     **http,		/* IO - Current HTTP connection */
5350              const char *url,		/* I  - URL to connect */
5351              char       *resource,	/* I  - Resource path buffer */
5352              size_t     ressize)	/* I  - Size of resource path buffer */
5353 {
5354   char			scheme[32],	/* URL scheme */
5355 			userpass[256],	/* URL username:password */
5356 			host[256],	/* URL host */
5357 			curhost[256];	/* Current host */
5358   int			port;		/* URL port */
5359   http_encryption_t	encryption;	/* Type of encryption to use */
5360 
5361 
5362   // Separate the URI...
5363   if (httpSeparateURI(HTTP_URI_CODING_ALL, url, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, ressize) < HTTP_URI_STATUS_OK)
5364     return (0);
5365 
5366   // Use encryption as needed..
5367   if (port == 443 || !strcmp(scheme, "https") || !strcmp(scheme, "ipps"))
5368     encryption = HTTP_ENCRYPTION_ALWAYS;
5369   else
5370     encryption = HTTP_ENCRYPTION_IF_REQUESTED;
5371 
5372   if (!*http || strcasecmp(host, httpGetHostname(*http, curhost, sizeof(curhost))) || httpAddrPort(httpGetAddress(*http)) != port || httpIsEncrypted(*http) != (encryption == HTTP_ENCRYPTION_ALWAYS))
5373   {
5374     httpClose(*http);
5375     *http = httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 5000, NULL);
5376   }
5377 
5378   return (*http != NULL);
5379 }
5380 
5381 
5382 /*
5383  * 'cups_get_url()' - Get a copy of the file at the given URL.
5384  */
5385 
5386 static int				/* O  - 1 on success, 0 on failure */
cups_get_url(http_t ** http,const char * url,char * name,size_t namesize)5387 cups_get_url(http_t     **http,		/* IO - Current HTTP connection */
5388              const char *url,		/* I  - URL to get */
5389              char       *name,		/* I  - Temporary filename */
5390              size_t     namesize)	/* I  - Size of temporary filename buffer */
5391 {
5392   char			resource[256];	/* URL resource */
5393   http_status_t		status;		/* Status of GET request */
5394   int			fd;		/* Temporary file */
5395 
5396 
5397   if (!cups_connect(http, url, resource, sizeof(resource)))
5398     return (0);
5399 
5400   if ((fd = cupsTempFd(name, (int)namesize)) < 0)
5401     return (0);
5402 
5403   status = cupsGetFd(*http, resource, fd);
5404 
5405   close(fd);
5406 
5407   if (status != HTTP_STATUS_OK)
5408   {
5409     unlink(name);
5410     *name = '\0';
5411     return (0);
5412   }
5413 
5414   return (1);
5415 }
5416 
5417 
5418 /*
5419  * 'ppd_put_strings()' - Write localization attributes to a PPD file.
5420  */
5421 
5422 static void
ppd_put_string(cups_file_t * fp,cups_lang_t * lang,cups_array_t * strings,const char * ppd_option,const char * ppd_choice,const char * pwg_msgid)5423 ppd_put_string(cups_file_t  *fp,	/* I - PPD file */
5424                cups_lang_t  *lang,	/* I - Language */
5425                cups_array_t *strings,	/* I - Strings */
5426 	       const char   *ppd_option,/* I - PPD option */
5427 	       const char   *ppd_choice,/* I - PPD choice */
5428 	       const char   *pwg_msgid)	/* I - PWG message ID */
5429 {
5430   const char	*text;			/* Localized text */
5431 
5432 
5433   if ((text = _cupsLangString(lang, pwg_msgid)) == pwg_msgid || !strcmp(pwg_msgid, text))
5434   {
5435     if ((text = _cupsMessageLookup(strings, pwg_msgid)) == pwg_msgid)
5436       return;
5437   }
5438 
5439   // Add the first line of localized text...
5440   cupsFilePrintf(fp, "*%s.%s %s/", lang->language, ppd_option, ppd_choice);
5441   while (*text && *text != '\n')
5442   {
5443     // Escape ":" and "<"...
5444     if (*text == ':' || *text == '<')
5445       cupsFilePrintf(fp, "<%02X>", *text);
5446     else
5447       cupsFilePutChar(fp, *text);
5448 
5449     text ++;
5450   }
5451   cupsFilePuts(fp, ": \"\"\n");
5452 }
5453 
5454 
5455 /*
5456  * 'pwg_add_finishing()' - Add a finishings value.
5457  */
5458 
5459 static void
pwg_add_finishing(cups_array_t * finishings,ipp_finishings_t template,const char * name,const char * value)5460 pwg_add_finishing(
5461     cups_array_t     *finishings,	/* I - Finishings array */
5462     ipp_finishings_t template,		/* I - Finishing template */
5463     const char       *name,		/* I - PPD option */
5464     const char       *value)		/* I - PPD choice */
5465 {
5466   _pwg_finishings_t	*f;		/* New finishings value */
5467 
5468 
5469   if ((f = (_pwg_finishings_t *)calloc(1, sizeof(_pwg_finishings_t))) != NULL)
5470   {
5471     f->value       = template;
5472     f->num_options = cupsAddOption(name, value, 0, &f->options);
5473 
5474     cupsArrayAdd(finishings, f);
5475   }
5476 }
5477 
5478 
5479 /*
5480  * 'pwg_add_message()' - Add a message to the PPD cached strings.
5481  */
5482 
5483 static void
pwg_add_message(cups_array_t * a,const char * msg,const char * str)5484 pwg_add_message(cups_array_t *a,	/* I - Message catalog */
5485                 const char   *msg,	/* I - Message identifier */
5486                 const char   *str)	/* I - Localized string */
5487 {
5488   _cups_message_t	*m;		/* New message */
5489 
5490 
5491   if ((m = calloc(1, sizeof(_cups_message_t))) != NULL)
5492   {
5493     m->msg = strdup(msg);
5494     m->str = strdup(str);
5495     cupsArrayAdd(a, m);
5496   }
5497 }
5498 
5499 
5500 /*
5501  * 'pwg_compare_finishings()' - Compare two finishings values.
5502  */
5503 
5504 static int				/* O - Result of comparison */
pwg_compare_finishings(_pwg_finishings_t * a,_pwg_finishings_t * b)5505 pwg_compare_finishings(
5506     _pwg_finishings_t *a,		/* I - First finishings value */
5507     _pwg_finishings_t *b)		/* I - Second finishings value */
5508 {
5509   return ((int)b->value - (int)a->value);
5510 }
5511 
5512 
5513 /*
5514  * 'pwg_compare_sizes()' - Compare two media sizes...
5515  */
5516 
5517 static int				/* O - Result of comparison */
pwg_compare_sizes(cups_size_t * a,cups_size_t * b)5518 pwg_compare_sizes(cups_size_t *a,	/* I - First media size */
5519                   cups_size_t *b)	/* I - Second media size */
5520 {
5521   return (strcmp(a->media, b->media));
5522 }
5523 
5524 
5525 /*
5526  * 'pwg_copy_size()' - Copy a media size.
5527  */
5528 
5529 static cups_size_t *			/* O - New media size */
pwg_copy_size(cups_size_t * size)5530 pwg_copy_size(cups_size_t *size)	/* I - Media size to copy */
5531 {
5532   cups_size_t	*newsize = (cups_size_t *)calloc(1, sizeof(cups_size_t));
5533 					/* New media size */
5534 
5535   if (newsize)
5536     memcpy(newsize, size, sizeof(cups_size_t));
5537 
5538   return (newsize);
5539 }
5540 
5541 
5542 /*
5543  * 'pwg_free_finishings()' - Free a finishings value.
5544  */
5545 
5546 static void
pwg_free_finishings(_pwg_finishings_t * f)5547 pwg_free_finishings(
5548     _pwg_finishings_t *f)		/* I - Finishings value */
5549 {
5550   cupsFreeOptions(f->num_options, f->options);
5551   free(f);
5552 }
5553 
5554 
5555 /*
5556  * 'pwg_ppdize_name()' - Convert an IPP keyword to a PPD keyword.
5557  */
5558 
5559 static void
pwg_ppdize_name(const char * ipp,char * name,size_t namesize)5560 pwg_ppdize_name(const char *ipp,	/* I - IPP keyword */
5561                 char       *name,	/* I - Name buffer */
5562 		size_t     namesize)	/* I - Size of name buffer */
5563 {
5564   char	*ptr,				/* Pointer into name buffer */
5565 	*end;				/* End of name buffer */
5566 
5567 
5568   if (!ipp || !_cups_isalnum(*ipp))
5569   {
5570     *name = '\0';
5571     return;
5572   }
5573 
5574   *name = (char)toupper(*ipp++);
5575 
5576   for (ptr = name + 1, end = name + namesize - 1; *ipp && ptr < end;)
5577   {
5578     if (*ipp == '-' && _cups_isalnum(ipp[1]))
5579     {
5580       ipp ++;
5581       *ptr++ = (char)toupper(*ipp++ & 255);
5582     }
5583     else if (*ipp == '_' || *ipp == '.' || *ipp == '-' || _cups_isalnum(*ipp))
5584     {
5585       *ptr++ = *ipp++;
5586     }
5587     else
5588     {
5589       ipp ++;
5590     }
5591   }
5592 
5593   *ptr = '\0';
5594 }
5595 
5596 
5597 /*
5598  * 'pwg_ppdize_resolution()' - Convert PWG resolution values to PPD values.
5599  */
5600 
5601 static void
pwg_ppdize_resolution(ipp_attribute_t * attr,int element,int * xres,int * yres,char * name,size_t namesize)5602 pwg_ppdize_resolution(
5603     ipp_attribute_t *attr,		/* I - Attribute to convert */
5604     int             element,		/* I - Element to convert */
5605     int             *xres,		/* O - X resolution in DPI */
5606     int             *yres,		/* O - Y resolution in DPI */
5607     char            *name,		/* I - Name buffer */
5608     size_t          namesize)		/* I - Size of name buffer */
5609 {
5610   ipp_res_t units;			/* Units for resolution */
5611 
5612 
5613   *xres = ippGetResolution(attr, element, yres, &units);
5614 
5615   if (units == IPP_RES_PER_CM)
5616   {
5617     *xres = (int)(*xres * 2.54);
5618     *yres = (int)(*yres * 2.54);
5619   }
5620 
5621   if (name && namesize > 4)
5622   {
5623     if (*xres == *yres)
5624       snprintf(name, namesize, "%ddpi", *xres);
5625     else
5626       snprintf(name, namesize, "%dx%ddpi", *xres, *yres);
5627   }
5628 }
5629 
5630 
5631 /*
5632  * 'pwg_unppdize_name()' - Convert a PPD keyword to a lowercase IPP keyword.
5633  */
5634 
5635 static void
pwg_unppdize_name(const char * ppd,char * name,size_t namesize,const char * dashchars)5636 pwg_unppdize_name(const char *ppd,	/* I - PPD keyword */
5637 		  char       *name,	/* I - Name buffer */
5638                   size_t     namesize,	/* I - Size of name buffer */
5639                   const char *dashchars)/* I - Characters to be replaced by dashes */
5640 {
5641   char	*ptr,				/* Pointer into name buffer */
5642 	*end;				/* End of name buffer */
5643   int   nodash = 1;                     /* Next char in IPP name cannot be a
5644                                            dash (first char or after a dash) */
5645 
5646 
5647   if (_cups_islower(*ppd))
5648   {
5649    /*
5650     * Already lowercase name, use as-is?
5651     */
5652 
5653     const char *ppdptr;			/* Pointer into PPD keyword */
5654 
5655     for (ppdptr = ppd + 1; *ppdptr; ppdptr ++)
5656       if (_cups_isupper(*ppdptr) || strchr(dashchars, *ppdptr) ||
5657 	  (*ppdptr == '-' && *(ppdptr - 1) == '-') ||
5658 	  (*ppdptr == '-' && *(ppdptr + 1) == '\0'))
5659         break;
5660 
5661     if (!*ppdptr)
5662     {
5663       strlcpy(name, ppd, namesize);
5664       return;
5665     }
5666   }
5667 
5668   for (ptr = name, end = name + namesize - 1; *ppd && ptr < end; ppd ++)
5669   {
5670     if (_cups_isalnum(*ppd))
5671     {
5672       *ptr++ = (char)tolower(*ppd & 255);
5673       nodash = 0;
5674     }
5675     else if (*ppd == '-' || strchr(dashchars, *ppd))
5676     {
5677       if (nodash == 0)
5678       {
5679 	*ptr++ = '-';
5680 	nodash = 1;
5681       }
5682     }
5683 
5684     if (nodash == 0)
5685     {
5686       if (!_cups_isupper(*ppd) && _cups_isalnum(*ppd) &&
5687 	  _cups_isupper(ppd[1]) && ptr < end)
5688       {
5689 	*ptr++ = '-';
5690 	nodash = 1;
5691       }
5692       else if (!isdigit(*ppd & 255) && isdigit(ppd[1] & 255))
5693       {
5694 	*ptr++ = '-';
5695 	nodash = 1;
5696       }
5697     }
5698   }
5699 
5700   /* Remove trailing dashes */
5701   while (ptr > name && *(ptr - 1) == '-')
5702     ptr --;
5703 
5704   *ptr = '\0';
5705 }
5706