1 /*
2  * Option marking routines for CUPS.
3  *
4  * Copyright © 2007-2019 by Apple Inc.
5  * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
8  * information.
9  *
10  * PostScript is a trademark of Adobe Systems, Inc.
11  */
12 
13 /*
14  * Include necessary headers...
15  */
16 
17 #include "cups-private.h"
18 #include "ppd-private.h"
19 #include "debug-internal.h"
20 
21 
22 /*
23  * Local functions...
24  */
25 
26 #ifdef DEBUG
27 static void	ppd_debug_marked(ppd_file_t *ppd, const char *title);
28 #else
29 #  define	ppd_debug_marked(ppd,title)
30 #endif /* DEBUG */
31 static void	ppd_defaults(ppd_file_t *ppd, ppd_group_t *g);
32 static void	ppd_mark_choices(ppd_file_t *ppd, const char *s);
33 static void	ppd_mark_option(ppd_file_t *ppd, const char *option,
34 		                const char *choice);
35 
36 
37 /*
38  * 'cupsMarkOptions()' - Mark command-line options in a PPD file.
39  *
40  * This function maps the IPP "finishings", "media", "mirror",
41  * "multiple-document-handling", "output-bin", "print-color-mode",
42  * "print-quality", "printer-resolution", and "sides" attributes to their
43  * corresponding PPD options and choices.
44  */
45 
46 int					/* O - 1 if conflicts exist, 0 otherwise */
cupsMarkOptions(ppd_file_t * ppd,int num_options,cups_option_t * options)47 cupsMarkOptions(
48     ppd_file_t    *ppd,			/* I - PPD file */
49     int           num_options,		/* I - Number of options */
50     cups_option_t *options)		/* I - Options */
51 {
52   int		i, j;			/* Looping vars */
53   char		*ptr,			/* Pointer into string */
54 		s[255];			/* Temporary string */
55   const char	*val,			/* Pointer into value */
56 		*media,			/* media option */
57 		*output_bin,		/* output-bin option */
58 		*page_size,		/* PageSize option */
59 		*ppd_keyword,		/* PPD keyword */
60 		*print_color_mode,	/* print-color-mode option */
61 		*print_quality,		/* print-quality option */
62 		*sides;			/* sides option */
63   cups_option_t	*optptr;		/* Current option */
64   ppd_attr_t	*attr;			/* PPD attribute */
65   _ppd_cache_t	*cache;			/* PPD cache and mapping data */
66 
67 
68  /*
69   * Check arguments...
70   */
71 
72   if (!ppd || num_options <= 0 || !options)
73     return (0);
74 
75   ppd_debug_marked(ppd, "Before...");
76 
77  /*
78   * Do special handling for finishings, media, output-bin, output-mode,
79   * print-color-mode, print-quality, and PageSize...
80   */
81 
82   media         = cupsGetOption("media", num_options, options);
83   output_bin    = cupsGetOption("output-bin", num_options, options);
84   page_size     = cupsGetOption("PageSize", num_options, options);
85   print_quality = cupsGetOption("print-quality", num_options, options);
86   sides         = cupsGetOption("sides", num_options, options);
87 
88   if ((print_color_mode = cupsGetOption("print-color-mode", num_options,
89                                         options)) == NULL)
90     print_color_mode = cupsGetOption("output-mode", num_options, options);
91 
92   if ((media || output_bin || print_color_mode || print_quality || sides) &&
93       !ppd->cache)
94   {
95    /*
96     * Load PPD cache and mapping data as needed...
97     */
98 
99     ppd->cache = _ppdCacheCreateWithPPD(ppd);
100   }
101 
102   cache = ppd->cache;
103 
104   if (media)
105   {
106    /*
107     * Loop through the option string, separating it at commas and marking each
108     * individual option as long as the corresponding PPD option (PageSize,
109     * InputSlot, etc.) is not also set.
110     *
111     * For PageSize, we also check for an empty option value since some versions
112     * of macOS use it to specify auto-selection of the media based solely on
113     * the size.
114     */
115 
116     for (val = media; *val;)
117     {
118      /*
119       * Extract the sub-option from the string...
120       */
121 
122       for (ptr = s; *val && *val != ',' && (size_t)(ptr - s) < (sizeof(s) - 1);)
123 	*ptr++ = *val++;
124       *ptr++ = '\0';
125 
126       if (*val == ',')
127 	val ++;
128 
129      /*
130       * Mark it...
131       */
132 
133       if (!page_size || !page_size[0])
134       {
135         if (!_cups_strncasecmp(s, "Custom.", 7) || ppdPageSize(ppd, s))
136           ppd_mark_option(ppd, "PageSize", s);
137         else if ((ppd_keyword = _ppdCacheGetPageSize(cache, NULL, s, NULL)) != NULL)
138 	  ppd_mark_option(ppd, "PageSize", ppd_keyword);
139       }
140 
141       if (cache && cache->source_option &&
142           !cupsGetOption(cache->source_option, num_options, options) &&
143 	  (ppd_keyword = _ppdCacheGetInputSlot(cache, NULL, s)) != NULL)
144 	ppd_mark_option(ppd, cache->source_option, ppd_keyword);
145 
146       if (!cupsGetOption("MediaType", num_options, options) &&
147 	  (ppd_keyword = _ppdCacheGetMediaType(cache, NULL, s)) != NULL)
148 	ppd_mark_option(ppd, "MediaType", ppd_keyword);
149     }
150   }
151 
152   if (cache)
153   {
154     if (!cupsGetOption("com.apple.print.DocumentTicket.PMSpoolFormat",
155                        num_options, options) &&
156         !cupsGetOption("APPrinterPreset", num_options, options) &&
157         (print_color_mode || print_quality))
158     {
159      /*
160       * Map output-mode and print-quality to a preset...
161       */
162 
163       _pwg_print_color_mode_t	pwg_pcm;/* print-color-mode index */
164       _pwg_print_quality_t	pwg_pq;	/* print-quality index */
165       cups_option_t		*preset;/* Current preset option */
166 
167       if (print_color_mode && !strcmp(print_color_mode, "monochrome"))
168 	pwg_pcm = _PWG_PRINT_COLOR_MODE_MONOCHROME;
169       else
170 	pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;
171 
172       if (print_quality)
173       {
174 	pwg_pq = (_pwg_print_quality_t)(atoi(print_quality) - IPP_QUALITY_DRAFT);
175 	if (pwg_pq < _PWG_PRINT_QUALITY_DRAFT)
176 	  pwg_pq = _PWG_PRINT_QUALITY_DRAFT;
177 	else if (pwg_pq > _PWG_PRINT_QUALITY_HIGH)
178 	  pwg_pq = _PWG_PRINT_QUALITY_HIGH;
179       }
180       else
181 	pwg_pq = _PWG_PRINT_QUALITY_NORMAL;
182 
183       if (cache->num_presets[pwg_pcm][pwg_pq] == 0)
184       {
185        /*
186 	* Try to find a preset that works so that we maximize the chances of us
187 	* getting a good print using IPP attributes.
188 	*/
189 
190 	if (cache->num_presets[pwg_pcm][_PWG_PRINT_QUALITY_NORMAL] > 0)
191 	  pwg_pq = _PWG_PRINT_QUALITY_NORMAL;
192 	else if (cache->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][pwg_pq] > 0)
193 	  pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;
194 	else
195 	{
196 	  pwg_pq  = _PWG_PRINT_QUALITY_NORMAL;
197 	  pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;
198 	}
199       }
200 
201       if (cache->num_presets[pwg_pcm][pwg_pq] > 0)
202       {
203        /*
204 	* Copy the preset options as long as the corresponding names are not
205 	* already defined in the IPP request...
206 	*/
207 
208 	for (i = cache->num_presets[pwg_pcm][pwg_pq],
209 		 preset = cache->presets[pwg_pcm][pwg_pq];
210 	     i > 0;
211 	     i --, preset ++)
212 	{
213 	  if (!cupsGetOption(preset->name, num_options, options))
214 	    ppd_mark_option(ppd, preset->name, preset->value);
215 	}
216       }
217     }
218 
219     if (output_bin && !cupsGetOption("OutputBin", num_options, options) &&
220 	(ppd_keyword = _ppdCacheGetOutputBin(cache, output_bin)) != NULL)
221     {
222      /*
223       * Map output-bin to OutputBin...
224       */
225 
226       ppd_mark_option(ppd, "OutputBin", ppd_keyword);
227     }
228 
229     if (sides && cache->sides_option &&
230         !cupsGetOption(cache->sides_option, num_options, options))
231     {
232      /*
233       * Map sides to duplex option...
234       */
235 
236       if (!strcmp(sides, "one-sided") && cache->sides_1sided)
237         ppd_mark_option(ppd, cache->sides_option, cache->sides_1sided);
238       else if (!strcmp(sides, "two-sided-long-edge") &&
239                cache->sides_2sided_long)
240         ppd_mark_option(ppd, cache->sides_option, cache->sides_2sided_long);
241       else if (!strcmp(sides, "two-sided-short-edge") &&
242                cache->sides_2sided_short)
243         ppd_mark_option(ppd, cache->sides_option, cache->sides_2sided_short);
244     }
245   }
246 
247  /*
248   * Mark other options...
249   */
250 
251   for (i = num_options, optptr = options; i > 0; i --, optptr ++)
252   {
253     if (!_cups_strcasecmp(optptr->name, "media") ||
254         !_cups_strcasecmp(optptr->name, "output-bin") ||
255 	!_cups_strcasecmp(optptr->name, "output-mode") ||
256 	!_cups_strcasecmp(optptr->name, "print-quality") ||
257 	!_cups_strcasecmp(optptr->name, "sides"))
258       continue;
259     else if (!_cups_strcasecmp(optptr->name, "resolution") ||
260              !_cups_strcasecmp(optptr->name, "printer-resolution"))
261     {
262       ppd_mark_option(ppd, "Resolution", optptr->value);
263       ppd_mark_option(ppd, "SetResolution", optptr->value);
264       	/* Calcomp, Linotype, QMS, Summagraphics, Tektronix, Varityper */
265       ppd_mark_option(ppd, "JCLResolution", optptr->value);
266       	/* HP */
267       ppd_mark_option(ppd, "CNRes_PGP", optptr->value);
268       	/* Canon */
269     }
270     else if (!_cups_strcasecmp(optptr->name, "multiple-document-handling"))
271     {
272       if (!cupsGetOption("Collate", num_options, options) &&
273           ppdFindOption(ppd, "Collate"))
274       {
275         if (_cups_strcasecmp(optptr->value, "separate-documents-uncollated-copies"))
276 	  ppd_mark_option(ppd, "Collate", "True");
277 	else
278 	  ppd_mark_option(ppd, "Collate", "False");
279       }
280     }
281     else if (!_cups_strcasecmp(optptr->name, "finishings"))
282     {
283      /*
284       * Lookup cupsIPPFinishings attributes for each value...
285       */
286 
287       for (ptr = optptr->value; *ptr;)
288       {
289        /*
290         * Get the next finishings number...
291 	*/
292 
293         if (!isdigit(*ptr & 255))
294 	  break;
295 
296         if ((j = (int)strtol(ptr, &ptr, 10)) < 3)
297 	  break;
298 
299        /*
300         * Skip separator as needed...
301 	*/
302 
303         if (*ptr == ',')
304 	  ptr ++;
305 
306        /*
307         * Look it up in the PPD file...
308 	*/
309 
310 	sprintf(s, "%d", j);
311 
312         if ((attr = ppdFindAttr(ppd, "cupsIPPFinishings", s)) == NULL)
313 	  continue;
314 
315        /*
316         * Apply "*Option Choice" settings from the attribute value...
317 	*/
318 
319         ppd_mark_choices(ppd, attr->value);
320       }
321     }
322     else if (!_cups_strcasecmp(optptr->name, "APPrinterPreset"))
323     {
324      /*
325       * Lookup APPrinterPreset value...
326       */
327 
328       if ((attr = ppdFindAttr(ppd, "APPrinterPreset", optptr->value)) != NULL)
329       {
330        /*
331         * Apply "*Option Choice" settings from the attribute value...
332 	*/
333 
334         ppd_mark_choices(ppd, attr->value);
335       }
336     }
337     else if (!_cups_strcasecmp(optptr->name, "mirror"))
338       ppd_mark_option(ppd, "MirrorPrint", optptr->value);
339     else
340       ppd_mark_option(ppd, optptr->name, optptr->value);
341   }
342 
343   if (print_quality)
344   {
345     int pq = atoi(print_quality);       /* print-quaity value */
346 
347     if (pq == IPP_QUALITY_DRAFT)
348       ppd_mark_option(ppd, "cupsPrintQuality", "Draft");
349     else if (pq == IPP_QUALITY_HIGH)
350       ppd_mark_option(ppd, "cupsPrintQuality", "High");
351     else
352       ppd_mark_option(ppd, "cupsPrintQuality", "Normal");
353   }
354 
355   ppd_debug_marked(ppd, "After...");
356 
357   return (ppdConflicts(ppd) > 0);
358 }
359 
360 
361 /*
362  * 'ppdFindChoice()' - Return a pointer to an option choice.
363  */
364 
365 ppd_choice_t *				/* O - Choice pointer or @code NULL@ */
ppdFindChoice(ppd_option_t * o,const char * choice)366 ppdFindChoice(ppd_option_t *o,		/* I - Pointer to option */
367               const char   *choice)	/* I - Name of choice */
368 {
369   int		i;			/* Looping var */
370   ppd_choice_t	*c;			/* Current choice */
371 
372 
373   if (!o || !choice)
374     return (NULL);
375 
376   if (choice[0] == '{' || !_cups_strncasecmp(choice, "Custom.", 7))
377     choice = "Custom";
378 
379   for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
380     if (!_cups_strcasecmp(c->choice, choice))
381       return (c);
382 
383   return (NULL);
384 }
385 
386 
387 /*
388  * 'ppdFindMarkedChoice()' - Return the marked choice for the specified option.
389  */
390 
391 ppd_choice_t *				/* O - Pointer to choice or @code NULL@ */
ppdFindMarkedChoice(ppd_file_t * ppd,const char * option)392 ppdFindMarkedChoice(ppd_file_t *ppd,	/* I - PPD file */
393                     const char *option)	/* I - Keyword/option name */
394 {
395   ppd_choice_t	key,			/* Search key for choice */
396 		*marked;		/* Marked choice */
397 
398 
399   DEBUG_printf(("2ppdFindMarkedChoice(ppd=%p, option=\"%s\")", ppd, option));
400 
401   if ((key.option = ppdFindOption(ppd, option)) == NULL)
402   {
403     DEBUG_puts("3ppdFindMarkedChoice: Option not found, returning NULL");
404     return (NULL);
405   }
406 
407   marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key);
408 
409   DEBUG_printf(("3ppdFindMarkedChoice: Returning %p(%s)...", marked,
410                 marked ? marked->choice : "NULL"));
411 
412   return (marked);
413 }
414 
415 
416 /*
417  * 'ppdFindOption()' - Return a pointer to the specified option.
418  */
419 
420 ppd_option_t *				/* O - Pointer to option or @code NULL@ */
ppdFindOption(ppd_file_t * ppd,const char * option)421 ppdFindOption(ppd_file_t *ppd,		/* I - PPD file data */
422               const char *option)	/* I - Option/Keyword name */
423 {
424  /*
425   * Range check input...
426   */
427 
428   if (!ppd || !option)
429     return (NULL);
430 
431   if (ppd->options)
432   {
433    /*
434     * Search in the array...
435     */
436 
437     ppd_option_t	key;		/* Option search key */
438 
439 
440     strlcpy(key.keyword, option, sizeof(key.keyword));
441 
442     return ((ppd_option_t *)cupsArrayFind(ppd->options, &key));
443   }
444   else
445   {
446    /*
447     * Search in each group...
448     */
449 
450     int			i, j;		/* Looping vars */
451     ppd_group_t		*group;		/* Current group */
452     ppd_option_t	*optptr;	/* Current option */
453 
454 
455     for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
456       for (j = group->num_options, optptr = group->options;
457            j > 0;
458 	   j --, optptr ++)
459         if (!_cups_strcasecmp(optptr->keyword, option))
460 	  return (optptr);
461 
462     return (NULL);
463   }
464 }
465 
466 
467 /*
468  * 'ppdIsMarked()' - Check to see if an option is marked.
469  */
470 
471 int					/* O - Non-zero if option is marked */
ppdIsMarked(ppd_file_t * ppd,const char * option,const char * choice)472 ppdIsMarked(ppd_file_t *ppd,		/* I - PPD file data */
473             const char *option,		/* I - Option/Keyword name */
474             const char *choice)		/* I - Choice name */
475 {
476   ppd_choice_t	key,			/* Search key */
477 		*c;			/* Choice pointer */
478 
479 
480   if (!ppd)
481     return (0);
482 
483   if ((key.option = ppdFindOption(ppd, option)) == NULL)
484     return (0);
485 
486   if ((c = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) == NULL)
487     return (0);
488 
489   return (!strcmp(c->choice, choice));
490 }
491 
492 
493 /*
494  * 'ppdMarkDefaults()' - Mark all default options in the PPD file.
495  */
496 
497 void
ppdMarkDefaults(ppd_file_t * ppd)498 ppdMarkDefaults(ppd_file_t *ppd)	/* I - PPD file record */
499 {
500   int		i;			/* Looping variables */
501   ppd_group_t	*g;			/* Current group */
502   ppd_choice_t	*c;			/* Current choice */
503 
504 
505   if (!ppd)
506     return;
507 
508  /*
509   * Clean out the marked array...
510   */
511 
512   for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
513        c;
514        c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
515   {
516     cupsArrayRemove(ppd->marked, c);
517     c->marked = 0;
518   }
519 
520  /*
521   * Then repopulate it with the defaults...
522   */
523 
524   for (i = ppd->num_groups, g = ppd->groups; i > 0; i --, g ++)
525     ppd_defaults(ppd, g);
526 
527  /*
528   * Finally, tag any conflicts (API compatibility) once at the end.
529   */
530 
531   ppdConflicts(ppd);
532 }
533 
534 
535 /*
536  * 'ppdMarkOption()' - Mark an option in a PPD file and return the number of
537  *                     conflicts.
538  */
539 
540 int					/* O - Number of conflicts */
ppdMarkOption(ppd_file_t * ppd,const char * option,const char * choice)541 ppdMarkOption(ppd_file_t *ppd,		/* I - PPD file record */
542               const char *option,	/* I - Keyword */
543               const char *choice)	/* I - Option name */
544 {
545   DEBUG_printf(("ppdMarkOption(ppd=%p, option=\"%s\", choice=\"%s\")",
546         	ppd, option, choice));
547 
548  /*
549   * Range check input...
550   */
551 
552   if (!ppd || !option || !choice)
553     return (0);
554 
555  /*
556   * Mark the option...
557   */
558 
559   ppd_mark_option(ppd, option, choice);
560 
561  /*
562   * Return the number of conflicts...
563   */
564 
565   return (ppdConflicts(ppd));
566 }
567 
568 
569 /*
570  * 'ppdFirstOption()' - Return the first option in the PPD file.
571  *
572  * Options are returned from all groups in ascending alphanumeric order.
573  *
574  * @since CUPS 1.2/macOS 10.5@
575  */
576 
577 ppd_option_t *				/* O - First option or @code NULL@ */
ppdFirstOption(ppd_file_t * ppd)578 ppdFirstOption(ppd_file_t *ppd)		/* I - PPD file */
579 {
580   if (!ppd)
581     return (NULL);
582   else
583     return ((ppd_option_t *)cupsArrayFirst(ppd->options));
584 }
585 
586 
587 /*
588  * 'ppdNextOption()' - Return the next option in the PPD file.
589  *
590  * Options are returned from all groups in ascending alphanumeric order.
591  *
592  * @since CUPS 1.2/macOS 10.5@
593  */
594 
595 ppd_option_t *				/* O - Next option or @code NULL@ */
ppdNextOption(ppd_file_t * ppd)596 ppdNextOption(ppd_file_t *ppd)		/* I - PPD file */
597 {
598   if (!ppd)
599     return (NULL);
600   else
601     return ((ppd_option_t *)cupsArrayNext(ppd->options));
602 }
603 
604 
605 /*
606  * '_ppdParseOptions()' - Parse options from a PPD file.
607  *
608  * This function looks for strings of the form:
609  *
610  *     *option choice ... *optionN choiceN
611  *     property value ... propertyN valueN
612  *
613  * It stops when it finds a string that doesn't match this format.
614  */
615 
616 int					/* O  - Number of options */
_ppdParseOptions(const char * s,int num_options,cups_option_t ** options,_ppd_parse_t which)617 _ppdParseOptions(
618     const char    *s,			/* I  - String to parse */
619     int           num_options,		/* I  - Number of options */
620     cups_option_t **options,		/* IO - Options */
621     _ppd_parse_t  which)		/* I  - What to parse */
622 {
623   char	option[PPD_MAX_NAME * 2 + 1],	/* Current option/property */
624 	choice[PPD_MAX_NAME],		/* Current choice/value */
625 	*ptr;				/* Pointer into option or choice */
626 
627 
628   if (!s)
629     return (num_options);
630 
631  /*
632   * Read all of the "*Option Choice" and "property value" pairs from the
633   * string, add them to an options array as we go...
634   */
635 
636   while (*s)
637   {
638    /*
639     * Skip leading whitespace...
640     */
641 
642     while (_cups_isspace(*s))
643       s ++;
644 
645    /*
646     * Get the option/property name...
647     */
648 
649     ptr = option;
650     while (*s && !_cups_isspace(*s) && ptr < (option + sizeof(option) - 1))
651       *ptr++ = *s++;
652 
653     if (ptr == s || !_cups_isspace(*s))
654       break;
655 
656     *ptr = '\0';
657 
658    /*
659     * Get the choice...
660     */
661 
662     while (_cups_isspace(*s))
663       s ++;
664 
665     if (!*s)
666       break;
667 
668     ptr = choice;
669     while (*s && !_cups_isspace(*s) && ptr < (choice + sizeof(choice) - 1))
670       *ptr++ = *s++;
671 
672     if (*s && !_cups_isspace(*s))
673       break;
674 
675     *ptr = '\0';
676 
677    /*
678     * Add it to the options array...
679     */
680 
681     if (option[0] == '*' && which != _PPD_PARSE_PROPERTIES)
682       num_options = cupsAddOption(option + 1, choice, num_options, options);
683     else if (option[0] != '*' && which != _PPD_PARSE_OPTIONS)
684       num_options = cupsAddOption(option, choice, num_options, options);
685   }
686 
687   return (num_options);
688 }
689 
690 
691 #ifdef DEBUG
692 /*
693  * 'ppd_debug_marked()' - Output the marked array to stdout...
694  */
695 
696 static void
ppd_debug_marked(ppd_file_t * ppd,const char * title)697 ppd_debug_marked(ppd_file_t *ppd,		/* I - PPD file data */
698              const char *title)		/* I - Title for list */
699 {
700   ppd_choice_t	*c;			/* Current choice */
701 
702 
703   DEBUG_printf(("2cupsMarkOptions: %s", title));
704 
705   for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
706        c;
707        c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
708     DEBUG_printf(("2cupsMarkOptions: %s=%s", c->option->keyword, c->choice));
709 }
710 #endif /* DEBUG */
711 
712 
713 /*
714  * 'ppd_defaults()' - Set the defaults for this group and all sub-groups.
715  */
716 
717 static void
ppd_defaults(ppd_file_t * ppd,ppd_group_t * g)718 ppd_defaults(ppd_file_t  *ppd,		/* I - PPD file */
719              ppd_group_t *g)		/* I - Group to default */
720 {
721   int		i;			/* Looping var */
722   ppd_option_t	*o;			/* Current option */
723   ppd_group_t	*sg;			/* Current sub-group */
724 
725 
726   for (i = g->num_options, o = g->options; i > 0; i --, o ++)
727     if (_cups_strcasecmp(o->keyword, "PageRegion") != 0)
728       ppd_mark_option(ppd, o->keyword, o->defchoice);
729 
730   for (i = g->num_subgroups, sg = g->subgroups; i > 0; i --, sg ++)
731     ppd_defaults(ppd, sg);
732 }
733 
734 
735 /*
736  * 'ppd_mark_choices()' - Mark one or more option choices from a string.
737  */
738 
739 static void
ppd_mark_choices(ppd_file_t * ppd,const char * s)740 ppd_mark_choices(ppd_file_t *ppd,	/* I - PPD file */
741                  const char *s)		/* I - "*Option Choice ..." string */
742 {
743   int		i,			/* Looping var */
744 		num_options;		/* Number of options */
745   cups_option_t	*options,		/* Options */
746 		*option;		/* Current option */
747 
748 
749   if (!s)
750     return;
751 
752   options     = NULL;
753   num_options = _ppdParseOptions(s, 0, &options, 0);
754 
755   for (i = num_options, option = options; i > 0; i --, option ++)
756     ppd_mark_option(ppd, option->name, option->value);
757 
758   cupsFreeOptions(num_options, options);
759 }
760 
761 
762 /*
763  * 'ppd_mark_option()' - Quick mark an option without checking for conflicts.
764  */
765 
766 static void
ppd_mark_option(ppd_file_t * ppd,const char * option,const char * choice)767 ppd_mark_option(ppd_file_t *ppd,	/* I - PPD file */
768                 const char *option,	/* I - Option name */
769                 const char *choice)	/* I - Choice name */
770 {
771   int		i, j;			/* Looping vars */
772   ppd_option_t	*o;			/* Option pointer */
773   ppd_choice_t	*c,			/* Choice pointer */
774 		*oldc,			/* Old choice pointer */
775 		key;			/* Search key for choice */
776   struct lconv	*loc;			/* Locale data */
777 
778 
779   DEBUG_printf(("7ppd_mark_option(ppd=%p, option=\"%s\", choice=\"%s\")",
780         	ppd, option, choice));
781 
782  /*
783   * AP_D_InputSlot is the "default input slot" on macOS, and setting
784   * it clears the regular InputSlot choices...
785   */
786 
787   if (!_cups_strcasecmp(option, "AP_D_InputSlot"))
788   {
789     cupsArraySave(ppd->options);
790 
791     if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
792     {
793       key.option = o;
794       if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
795       {
796         oldc->marked = 0;
797         cupsArrayRemove(ppd->marked, oldc);
798       }
799     }
800 
801     cupsArrayRestore(ppd->options);
802   }
803 
804  /*
805   * Check for custom options...
806   */
807 
808   cupsArraySave(ppd->options);
809 
810   o = ppdFindOption(ppd, option);
811 
812   cupsArrayRestore(ppd->options);
813 
814   if (!o)
815     return;
816 
817   loc = localeconv();
818 
819   if (!_cups_strncasecmp(choice, "Custom.", 7))
820   {
821    /*
822     * Handle a custom option...
823     */
824 
825     if ((c = ppdFindChoice(o, "Custom")) == NULL)
826       return;
827 
828     if (!_cups_strcasecmp(option, "PageSize"))
829     {
830      /*
831       * Handle custom page sizes...
832       */
833 
834       ppdPageSize(ppd, choice);
835     }
836     else
837     {
838      /*
839       * Handle other custom options...
840       */
841 
842       ppd_coption_t	*coption;	/* Custom option */
843       ppd_cparam_t	*cparam;	/* Custom parameter */
844       char		*units;		/* Custom points units */
845 
846 
847       if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
848       {
849         if ((cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params)) == NULL)
850 	  return;
851 
852         switch (cparam->type)
853 	{
854 	  case PPD_CUSTOM_UNKNOWN :
855 	      break;
856 
857 	  case PPD_CUSTOM_CURVE :
858 	  case PPD_CUSTOM_INVCURVE :
859 	  case PPD_CUSTOM_REAL :
860 	      cparam->current.custom_real = (float)_cupsStrScand(choice + 7,
861 	                                                         NULL, loc);
862 	      break;
863 
864 	  case PPD_CUSTOM_POINTS :
865 	      cparam->current.custom_points = (float)_cupsStrScand(choice + 7,
866 	                                                           &units,
867 	                                                           loc);
868 
869               if (units)
870 	      {
871         	if (!_cups_strcasecmp(units, "cm"))
872 	          cparam->current.custom_points *= 72.0f / 2.54f;
873         	else if (!_cups_strcasecmp(units, "mm"))
874 	          cparam->current.custom_points *= 72.0f / 25.4f;
875         	else if (!_cups_strcasecmp(units, "m"))
876 	          cparam->current.custom_points *= 72.0f / 0.0254f;
877         	else if (!_cups_strcasecmp(units, "in"))
878 	          cparam->current.custom_points *= 72.0f;
879         	else if (!_cups_strcasecmp(units, "ft"))
880 	          cparam->current.custom_points *= 12.0f * 72.0f;
881               }
882 	      break;
883 
884 	  case PPD_CUSTOM_INT :
885 	      cparam->current.custom_int = atoi(choice + 7);
886 	      break;
887 
888 	  case PPD_CUSTOM_PASSCODE :
889 	  case PPD_CUSTOM_PASSWORD :
890 	  case PPD_CUSTOM_STRING :
891 	      if (cparam->current.custom_string)
892 	        free(cparam->current.custom_string);
893 
894 	      cparam->current.custom_string = strdup(choice + 7);
895 	      break;
896 	}
897       }
898     }
899 
900    /*
901     * Make sure that we keep the option marked below...
902     */
903 
904     choice = "Custom";
905   }
906   else if (choice[0] == '{')
907   {
908    /*
909     * Handle multi-value custom options...
910     */
911 
912     ppd_coption_t	*coption;	/* Custom option */
913     ppd_cparam_t	*cparam;	/* Custom parameter */
914     char		*units;		/* Custom points units */
915     int			num_vals;	/* Number of values */
916     cups_option_t	*vals,		/* Values */
917 			*val;		/* Value */
918 
919 
920     if ((c = ppdFindChoice(o, "Custom")) == NULL)
921       return;
922 
923     if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
924     {
925       num_vals = cupsParseOptions(choice, 0, &vals);
926 
927       for (i = 0, val = vals; i < num_vals; i ++, val ++)
928       {
929         if ((cparam = ppdFindCustomParam(coption, val->name)) == NULL)
930 	  continue;
931 
932 	switch (cparam->type)
933 	{
934 	  case PPD_CUSTOM_UNKNOWN :
935 	      break;
936 
937 	  case PPD_CUSTOM_CURVE :
938 	  case PPD_CUSTOM_INVCURVE :
939 	  case PPD_CUSTOM_REAL :
940 	      cparam->current.custom_real = (float)_cupsStrScand(val->value,
941 	                                                         NULL, loc);
942 	      break;
943 
944 	  case PPD_CUSTOM_POINTS :
945 	      cparam->current.custom_points = (float)_cupsStrScand(val->value,
946 	                                                           &units,
947 	                                                           loc);
948 
949 	      if (units)
950 	      {
951         	if (!_cups_strcasecmp(units, "cm"))
952 		  cparam->current.custom_points *= 72.0f / 2.54f;
953         	else if (!_cups_strcasecmp(units, "mm"))
954 		  cparam->current.custom_points *= 72.0f / 25.4f;
955         	else if (!_cups_strcasecmp(units, "m"))
956 		  cparam->current.custom_points *= 72.0f / 0.0254f;
957         	else if (!_cups_strcasecmp(units, "in"))
958 		  cparam->current.custom_points *= 72.0f;
959         	else if (!_cups_strcasecmp(units, "ft"))
960 		  cparam->current.custom_points *= 12.0f * 72.0f;
961 	      }
962 	      break;
963 
964 	  case PPD_CUSTOM_INT :
965 	      cparam->current.custom_int = atoi(val->value);
966 	      break;
967 
968 	  case PPD_CUSTOM_PASSCODE :
969 	  case PPD_CUSTOM_PASSWORD :
970 	  case PPD_CUSTOM_STRING :
971 	      if (cparam->current.custom_string)
972 		free(cparam->current.custom_string);
973 
974 	      cparam->current.custom_string = strdup(val->value);
975 	      break;
976 	}
977       }
978 
979       cupsFreeOptions(num_vals, vals);
980     }
981   }
982   else
983   {
984     for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
985       if (!_cups_strcasecmp(c->choice, choice))
986         break;
987 
988     if (!i)
989       return;
990   }
991 
992  /*
993   * Option found; mark it and then handle unmarking any other options.
994   */
995 
996   if (o->ui != PPD_UI_PICKMANY)
997   {
998    /*
999     * Unmark all other choices...
1000     */
1001 
1002     if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, c)) != NULL)
1003     {
1004       oldc->marked = 0;
1005       cupsArrayRemove(ppd->marked, oldc);
1006     }
1007 
1008     if (!_cups_strcasecmp(option, "PageSize") || !_cups_strcasecmp(option, "PageRegion"))
1009     {
1010      /*
1011       * Mark current page size...
1012       */
1013 
1014       for (j = 0; j < ppd->num_sizes; j ++)
1015 	ppd->sizes[j].marked = !_cups_strcasecmp(ppd->sizes[j].name,
1016 		                           choice);
1017 
1018      /*
1019       * Unmark the current PageSize or PageRegion setting, as
1020       * appropriate...
1021       */
1022 
1023       cupsArraySave(ppd->options);
1024 
1025       if (!_cups_strcasecmp(option, "PageSize"))
1026       {
1027 	if ((o = ppdFindOption(ppd, "PageRegion")) != NULL)
1028         {
1029           key.option = o;
1030           if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
1031           {
1032             oldc->marked = 0;
1033             cupsArrayRemove(ppd->marked, oldc);
1034           }
1035         }
1036       }
1037       else
1038       {
1039 	if ((o = ppdFindOption(ppd, "PageSize")) != NULL)
1040         {
1041           key.option = o;
1042           if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
1043           {
1044             oldc->marked = 0;
1045             cupsArrayRemove(ppd->marked, oldc);
1046           }
1047         }
1048       }
1049 
1050       cupsArrayRestore(ppd->options);
1051     }
1052     else if (!_cups_strcasecmp(option, "InputSlot"))
1053     {
1054      /*
1055       * Unmark ManualFeed option...
1056       */
1057 
1058       cupsArraySave(ppd->options);
1059 
1060       if ((o = ppdFindOption(ppd, "ManualFeed")) != NULL)
1061       {
1062         key.option = o;
1063         if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
1064         {
1065           oldc->marked = 0;
1066           cupsArrayRemove(ppd->marked, oldc);
1067         }
1068       }
1069 
1070       cupsArrayRestore(ppd->options);
1071     }
1072     else if (!_cups_strcasecmp(option, "ManualFeed") &&
1073 	     !_cups_strcasecmp(choice, "True"))
1074     {
1075      /*
1076       * Unmark InputSlot option...
1077       */
1078 
1079       cupsArraySave(ppd->options);
1080 
1081       if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
1082       {
1083         key.option = o;
1084         if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
1085         {
1086           oldc->marked = 0;
1087           cupsArrayRemove(ppd->marked, oldc);
1088         }
1089       }
1090 
1091       cupsArrayRestore(ppd->options);
1092     }
1093   }
1094 
1095   c->marked = 1;
1096 
1097   cupsArrayAdd(ppd->marked, c);
1098 }
1099