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