Bump to cups 2.3.3
[platform/upstream/cups.git] / cups / ppd-mark.c
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 */
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@ */
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@ */
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@ */
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 */
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
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 */
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@ */
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@ */
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 */
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
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
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
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
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 }