Home | History | Annotate | Download | only in cups
      1 /*
      2  * Option marking routines for CUPS.
      3  *
      4  * Copyright 2007-2017 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   {
    257     if (!_cups_strcasecmp(optptr->name, "media") ||
    258         !_cups_strcasecmp(optptr->name, "output-bin") ||
    259 	!_cups_strcasecmp(optptr->name, "output-mode") ||
    260 	!_cups_strcasecmp(optptr->name, "print-quality") ||
    261 	!_cups_strcasecmp(optptr->name, "sides"))
    262       continue;
    263     else if (!_cups_strcasecmp(optptr->name, "resolution") ||
    264              !_cups_strcasecmp(optptr->name, "printer-resolution"))
    265     {
    266       ppd_mark_option(ppd, "Resolution", optptr->value);
    267       ppd_mark_option(ppd, "SetResolution", optptr->value);
    268       	/* Calcomp, Linotype, QMS, Summagraphics, Tektronix, Varityper */
    269       ppd_mark_option(ppd, "JCLResolution", optptr->value);
    270       	/* HP */
    271       ppd_mark_option(ppd, "CNRes_PGP", optptr->value);
    272       	/* Canon */
    273     }
    274     else if (!_cups_strcasecmp(optptr->name, "multiple-document-handling"))
    275     {
    276       if (!cupsGetOption("Collate", num_options, options) &&
    277           ppdFindOption(ppd, "Collate"))
    278       {
    279         if (_cups_strcasecmp(optptr->value, "separate-documents-uncollated-copies"))
    280 	  ppd_mark_option(ppd, "Collate", "True");
    281 	else
    282 	  ppd_mark_option(ppd, "Collate", "False");
    283       }
    284     }
    285     else if (!_cups_strcasecmp(optptr->name, "finishings"))
    286     {
    287      /*
    288       * Lookup cupsIPPFinishings attributes for each value...
    289       */
    290 
    291       for (ptr = optptr->value; *ptr;)
    292       {
    293        /*
    294         * Get the next finishings number...
    295 	*/
    296 
    297         if (!isdigit(*ptr & 255))
    298 	  break;
    299 
    300         if ((j = (int)strtol(ptr, &ptr, 10)) < 3)
    301 	  break;
    302 
    303        /*
    304         * Skip separator as needed...
    305 	*/
    306 
    307         if (*ptr == ',')
    308 	  ptr ++;
    309 
    310        /*
    311         * Look it up in the PPD file...
    312 	*/
    313 
    314 	sprintf(s, "%d", j);
    315 
    316         if ((attr = ppdFindAttr(ppd, "cupsIPPFinishings", s)) == NULL)
    317 	  continue;
    318 
    319        /*
    320         * Apply "*Option Choice" settings from the attribute value...
    321 	*/
    322 
    323         ppd_mark_choices(ppd, attr->value);
    324       }
    325     }
    326     else if (!_cups_strcasecmp(optptr->name, "APPrinterPreset"))
    327     {
    328      /*
    329       * Lookup APPrinterPreset value...
    330       */
    331 
    332       if ((attr = ppdFindAttr(ppd, "APPrinterPreset", optptr->value)) != NULL)
    333       {
    334        /*
    335         * Apply "*Option Choice" settings from the attribute value...
    336 	*/
    337 
    338         ppd_mark_choices(ppd, attr->value);
    339       }
    340     }
    341     else if (!_cups_strcasecmp(optptr->name, "mirror"))
    342       ppd_mark_option(ppd, "MirrorPrint", optptr->value);
    343     else
    344       ppd_mark_option(ppd, optptr->name, optptr->value);
    345   }
    346 
    347   if (print_quality)
    348   {
    349     int pq = atoi(print_quality);       /* print-quaity value */
    350 
    351     if (pq == IPP_QUALITY_DRAFT)
    352       ppd_mark_option(ppd, "cupsPrintQuality", "Draft");
    353     else if (pq == IPP_QUALITY_HIGH)
    354       ppd_mark_option(ppd, "cupsPrintQuality", "High");
    355     else
    356       ppd_mark_option(ppd, "cupsPrintQuality", "Normal");
    357   }
    358 
    359   ppd_debug_marked(ppd, "After...");
    360 
    361   return (ppdConflicts(ppd) > 0);
    362 }
    363 
    364 
    365 /*
    366  * 'ppdFindChoice()' - Return a pointer to an option choice.
    367  */
    368 
    369 ppd_choice_t *				/* O - Choice pointer or @code NULL@ */
    370 ppdFindChoice(ppd_option_t *o,		/* I - Pointer to option */
    371               const char   *choice)	/* I - Name of choice */
    372 {
    373   int		i;			/* Looping var */
    374   ppd_choice_t	*c;			/* Current choice */
    375 
    376 
    377   if (!o || !choice)
    378     return (NULL);
    379 
    380   if (choice[0] == '{' || !_cups_strncasecmp(choice, "Custom.", 7))
    381     choice = "Custom";
    382 
    383   for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
    384     if (!_cups_strcasecmp(c->choice, choice))
    385       return (c);
    386 
    387   return (NULL);
    388 }
    389 
    390 
    391 /*
    392  * 'ppdFindMarkedChoice()' - Return the marked choice for the specified option.
    393  */
    394 
    395 ppd_choice_t *				/* O - Pointer to choice or @code NULL@ */
    396 ppdFindMarkedChoice(ppd_file_t *ppd,	/* I - PPD file */
    397                     const char *option)	/* I - Keyword/option name */
    398 {
    399   ppd_choice_t	key,			/* Search key for choice */
    400 		*marked;		/* Marked choice */
    401 
    402 
    403   DEBUG_printf(("2ppdFindMarkedChoice(ppd=%p, option=\"%s\")", ppd, option));
    404 
    405   if ((key.option = ppdFindOption(ppd, option)) == NULL)
    406   {
    407     DEBUG_puts("3ppdFindMarkedChoice: Option not found, returning NULL");
    408     return (NULL);
    409   }
    410 
    411   marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key);
    412 
    413   DEBUG_printf(("3ppdFindMarkedChoice: Returning %p(%s)...", marked,
    414                 marked ? marked->choice : "NULL"));
    415 
    416   return (marked);
    417 }
    418 
    419 
    420 /*
    421  * 'ppdFindOption()' - Return a pointer to the specified option.
    422  */
    423 
    424 ppd_option_t *				/* O - Pointer to option or @code NULL@ */
    425 ppdFindOption(ppd_file_t *ppd,		/* I - PPD file data */
    426               const char *option)	/* I - Option/Keyword name */
    427 {
    428  /*
    429   * Range check input...
    430   */
    431 
    432   if (!ppd || !option)
    433     return (NULL);
    434 
    435   if (ppd->options)
    436   {
    437    /*
    438     * Search in the array...
    439     */
    440 
    441     ppd_option_t	key;		/* Option search key */
    442 
    443 
    444     strlcpy(key.keyword, option, sizeof(key.keyword));
    445 
    446     return ((ppd_option_t *)cupsArrayFind(ppd->options, &key));
    447   }
    448   else
    449   {
    450    /*
    451     * Search in each group...
    452     */
    453 
    454     int			i, j;		/* Looping vars */
    455     ppd_group_t		*group;		/* Current group */
    456     ppd_option_t	*optptr;	/* Current option */
    457 
    458 
    459     for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
    460       for (j = group->num_options, optptr = group->options;
    461            j > 0;
    462 	   j --, optptr ++)
    463         if (!_cups_strcasecmp(optptr->keyword, option))
    464 	  return (optptr);
    465 
    466     return (NULL);
    467   }
    468 }
    469 
    470 
    471 /*
    472  * 'ppdIsMarked()' - Check to see if an option is marked.
    473  */
    474 
    475 int					/* O - Non-zero if option is marked */
    476 ppdIsMarked(ppd_file_t *ppd,		/* I - PPD file data */
    477             const char *option,		/* I - Option/Keyword name */
    478             const char *choice)		/* I - Choice name */
    479 {
    480   ppd_choice_t	key,			/* Search key */
    481 		*c;			/* Choice pointer */
    482 
    483 
    484   if (!ppd)
    485     return (0);
    486 
    487   if ((key.option = ppdFindOption(ppd, option)) == NULL)
    488     return (0);
    489 
    490   if ((c = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) == NULL)
    491     return (0);
    492 
    493   return (!strcmp(c->choice, choice));
    494 }
    495 
    496 
    497 /*
    498  * 'ppdMarkDefaults()' - Mark all default options in the PPD file.
    499  */
    500 
    501 void
    502 ppdMarkDefaults(ppd_file_t *ppd)	/* I - PPD file record */
    503 {
    504   int		i;			/* Looping variables */
    505   ppd_group_t	*g;			/* Current group */
    506   ppd_choice_t	*c;			/* Current choice */
    507 
    508 
    509   if (!ppd)
    510     return;
    511 
    512  /*
    513   * Clean out the marked array...
    514   */
    515 
    516   for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
    517        c;
    518        c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
    519   {
    520     cupsArrayRemove(ppd->marked, c);
    521     c->marked = 0;
    522   }
    523 
    524  /*
    525   * Then repopulate it with the defaults...
    526   */
    527 
    528   for (i = ppd->num_groups, g = ppd->groups; i > 0; i --, g ++)
    529     ppd_defaults(ppd, g);
    530 
    531  /*
    532   * Finally, tag any conflicts (API compatibility) once at the end.
    533   */
    534 
    535   ppdConflicts(ppd);
    536 }
    537 
    538 
    539 /*
    540  * 'ppdMarkOption()' - Mark an option in a PPD file and return the number of
    541  *                     conflicts.
    542  */
    543 
    544 int					/* O - Number of conflicts */
    545 ppdMarkOption(ppd_file_t *ppd,		/* I - PPD file record */
    546               const char *option,	/* I - Keyword */
    547               const char *choice)	/* I - Option name */
    548 {
    549   DEBUG_printf(("ppdMarkOption(ppd=%p, option=\"%s\", choice=\"%s\")",
    550         	ppd, option, choice));
    551 
    552  /*
    553   * Range check input...
    554   */
    555 
    556   if (!ppd || !option || !choice)
    557     return (0);
    558 
    559  /*
    560   * Mark the option...
    561   */
    562 
    563   ppd_mark_option(ppd, option, choice);
    564 
    565  /*
    566   * Return the number of conflicts...
    567   */
    568 
    569   return (ppdConflicts(ppd));
    570 }
    571 
    572 
    573 /*
    574  * 'ppdFirstOption()' - Return the first option in the PPD file.
    575  *
    576  * Options are returned from all groups in ascending alphanumeric order.
    577  *
    578  * @since CUPS 1.2/macOS 10.5@
    579  */
    580 
    581 ppd_option_t *				/* O - First option or @code NULL@ */
    582 ppdFirstOption(ppd_file_t *ppd)		/* I - PPD file */
    583 {
    584   if (!ppd)
    585     return (NULL);
    586   else
    587     return ((ppd_option_t *)cupsArrayFirst(ppd->options));
    588 }
    589 
    590 
    591 /*
    592  * 'ppdNextOption()' - Return the next option in the PPD file.
    593  *
    594  * Options are returned from all groups in ascending alphanumeric order.
    595  *
    596  * @since CUPS 1.2/macOS 10.5@
    597  */
    598 
    599 ppd_option_t *				/* O - Next option or @code NULL@ */
    600 ppdNextOption(ppd_file_t *ppd)		/* I - PPD file */
    601 {
    602   if (!ppd)
    603     return (NULL);
    604   else
    605     return ((ppd_option_t *)cupsArrayNext(ppd->options));
    606 }
    607 
    608 
    609 /*
    610  * '_ppdParseOptions()' - Parse options from a PPD file.
    611  *
    612  * This function looks for strings of the form:
    613  *
    614  *     *option choice ... *optionN choiceN
    615  *     property value ... propertyN valueN
    616  *
    617  * It stops when it finds a string that doesn't match this format.
    618  */
    619 
    620 int					/* O  - Number of options */
    621 _ppdParseOptions(
    622     const char    *s,			/* I  - String to parse */
    623     int           num_options,		/* I  - Number of options */
    624     cups_option_t **options,		/* IO - Options */
    625     _ppd_parse_t  which)		/* I  - What to parse */
    626 {
    627   char	option[PPD_MAX_NAME * 2 + 1],	/* Current option/property */
    628 	choice[PPD_MAX_NAME],		/* Current choice/value */
    629 	*ptr;				/* Pointer into option or choice */
    630 
    631 
    632   if (!s)
    633     return (num_options);
    634 
    635  /*
    636   * Read all of the "*Option Choice" and "property value" pairs from the
    637   * string, add them to an options array as we go...
    638   */
    639 
    640   while (*s)
    641   {
    642    /*
    643     * Skip leading whitespace...
    644     */
    645 
    646     while (_cups_isspace(*s))
    647       s ++;
    648 
    649    /*
    650     * Get the option/property name...
    651     */
    652 
    653     ptr = option;
    654     while (*s && !_cups_isspace(*s) && ptr < (option + sizeof(option) - 1))
    655       *ptr++ = *s++;
    656 
    657     if (ptr == s || !_cups_isspace(*s))
    658       break;
    659 
    660     *ptr = '\0';
    661 
    662    /*
    663     * Get the choice...
    664     */
    665 
    666     while (_cups_isspace(*s))
    667       s ++;
    668 
    669     if (!*s)
    670       break;
    671 
    672     ptr = choice;
    673     while (*s && !_cups_isspace(*s) && ptr < (choice + sizeof(choice) - 1))
    674       *ptr++ = *s++;
    675 
    676     if (*s && !_cups_isspace(*s))
    677       break;
    678 
    679     *ptr = '\0';
    680 
    681    /*
    682     * Add it to the options array...
    683     */
    684 
    685     if (option[0] == '*' && which != _PPD_PARSE_PROPERTIES)
    686       num_options = cupsAddOption(option + 1, choice, num_options, options);
    687     else if (option[0] != '*' && which != _PPD_PARSE_OPTIONS)
    688       num_options = cupsAddOption(option, choice, num_options, options);
    689   }
    690 
    691   return (num_options);
    692 }
    693 
    694 
    695 #ifdef DEBUG
    696 /*
    697  * 'ppd_debug_marked()' - Output the marked array to stdout...
    698  */
    699 
    700 static void
    701 ppd_debug_marked(ppd_file_t *ppd,		/* I - PPD file data */
    702              const char *title)		/* I - Title for list */
    703 {
    704   ppd_choice_t	*c;			/* Current choice */
    705 
    706 
    707   DEBUG_printf(("2cupsMarkOptions: %s", title));
    708 
    709   for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
    710        c;
    711        c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
    712     DEBUG_printf(("2cupsMarkOptions: %s=%s", c->option->keyword, c->choice));
    713 }
    714 #endif /* DEBUG */
    715 
    716 
    717 /*
    718  * 'ppd_defaults()' - Set the defaults for this group and all sub-groups.
    719  */
    720 
    721 static void
    722 ppd_defaults(ppd_file_t  *ppd,		/* I - PPD file */
    723              ppd_group_t *g)		/* I - Group to default */
    724 {
    725   int		i;			/* Looping var */
    726   ppd_option_t	*o;			/* Current option */
    727   ppd_group_t	*sg;			/* Current sub-group */
    728 
    729 
    730   for (i = g->num_options, o = g->options; i > 0; i --, o ++)
    731     if (_cups_strcasecmp(o->keyword, "PageRegion") != 0)
    732       ppd_mark_option(ppd, o->keyword, o->defchoice);
    733 
    734   for (i = g->num_subgroups, sg = g->subgroups; i > 0; i --, sg ++)
    735     ppd_defaults(ppd, sg);
    736 }
    737 
    738 
    739 /*
    740  * 'ppd_mark_choices()' - Mark one or more option choices from a string.
    741  */
    742 
    743 static void
    744 ppd_mark_choices(ppd_file_t *ppd,	/* I - PPD file */
    745                  const char *s)		/* I - "*Option Choice ..." string */
    746 {
    747   int		i,			/* Looping var */
    748 		num_options;		/* Number of options */
    749   cups_option_t	*options,		/* Options */
    750 		*option;		/* Current option */
    751 
    752 
    753   if (!s)
    754     return;
    755 
    756   options     = NULL;
    757   num_options = _ppdParseOptions(s, 0, &options, 0);
    758 
    759   for (i = num_options, option = options; i > 0; i --, option ++)
    760     ppd_mark_option(ppd, option->name, option->value);
    761 
    762   cupsFreeOptions(num_options, options);
    763 }
    764 
    765 
    766 /*
    767  * 'ppd_mark_option()' - Quick mark an option without checking for conflicts.
    768  */
    769 
    770 static void
    771 ppd_mark_option(ppd_file_t *ppd,	/* I - PPD file */
    772                 const char *option,	/* I - Option name */
    773                 const char *choice)	/* I - Choice name */
    774 {
    775   int		i, j;			/* Looping vars */
    776   ppd_option_t	*o;			/* Option pointer */
    777   ppd_choice_t	*c,			/* Choice pointer */
    778 		*oldc,			/* Old choice pointer */
    779 		key;			/* Search key for choice */
    780   struct lconv	*loc;			/* Locale data */
    781 
    782 
    783   DEBUG_printf(("7ppd_mark_option(ppd=%p, option=\"%s\", choice=\"%s\")",
    784         	ppd, option, choice));
    785 
    786  /*
    787   * AP_D_InputSlot is the "default input slot" on macOS, and setting
    788   * it clears the regular InputSlot choices...
    789   */
    790 
    791   if (!_cups_strcasecmp(option, "AP_D_InputSlot"))
    792   {
    793     cupsArraySave(ppd->options);
    794 
    795     if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
    796     {
    797       key.option = o;
    798       if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
    799       {
    800         oldc->marked = 0;
    801         cupsArrayRemove(ppd->marked, oldc);
    802       }
    803     }
    804 
    805     cupsArrayRestore(ppd->options);
    806   }
    807 
    808  /*
    809   * Check for custom options...
    810   */
    811 
    812   cupsArraySave(ppd->options);
    813 
    814   o = ppdFindOption(ppd, option);
    815 
    816   cupsArrayRestore(ppd->options);
    817 
    818   if (!o)
    819     return;
    820 
    821   loc = localeconv();
    822 
    823   if (!_cups_strncasecmp(choice, "Custom.", 7))
    824   {
    825    /*
    826     * Handle a custom option...
    827     */
    828 
    829     if ((c = ppdFindChoice(o, "Custom")) == NULL)
    830       return;
    831 
    832     if (!_cups_strcasecmp(option, "PageSize"))
    833     {
    834      /*
    835       * Handle custom page sizes...
    836       */
    837 
    838       ppdPageSize(ppd, choice);
    839     }
    840     else
    841     {
    842      /*
    843       * Handle other custom options...
    844       */
    845 
    846       ppd_coption_t	*coption;	/* Custom option */
    847       ppd_cparam_t	*cparam;	/* Custom parameter */
    848       char		*units;		/* Custom points units */
    849 
    850 
    851       if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
    852       {
    853         if ((cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params)) == NULL)
    854 	  return;
    855 
    856         switch (cparam->type)
    857 	{
    858 	  case PPD_CUSTOM_CURVE :
    859 	  case PPD_CUSTOM_INVCURVE :
    860 	  case PPD_CUSTOM_REAL :
    861 	      cparam->current.custom_real = (float)_cupsStrScand(choice + 7,
    862 	                                                         NULL, loc);
    863 	      break;
    864 
    865 	  case PPD_CUSTOM_POINTS :
    866 	      cparam->current.custom_points = (float)_cupsStrScand(choice + 7,
    867 	                                                           &units,
    868 	                                                           loc);
    869 
    870               if (units)
    871 	      {
    872         	if (!_cups_strcasecmp(units, "cm"))
    873 	          cparam->current.custom_points *= 72.0f / 2.54f;
    874         	else if (!_cups_strcasecmp(units, "mm"))
    875 	          cparam->current.custom_points *= 72.0f / 25.4f;
    876         	else if (!_cups_strcasecmp(units, "m"))
    877 	          cparam->current.custom_points *= 72.0f / 0.0254f;
    878         	else if (!_cups_strcasecmp(units, "in"))
    879 	          cparam->current.custom_points *= 72.0f;
    880         	else if (!_cups_strcasecmp(units, "ft"))
    881 	          cparam->current.custom_points *= 12.0f * 72.0f;
    882               }
    883 	      break;
    884 
    885 	  case PPD_CUSTOM_INT :
    886 	      cparam->current.custom_int = atoi(choice + 7);
    887 	      break;
    888 
    889 	  case PPD_CUSTOM_PASSCODE :
    890 	  case PPD_CUSTOM_PASSWORD :
    891 	  case PPD_CUSTOM_STRING :
    892 	      if (cparam->current.custom_string)
    893 	        _cupsStrFree(cparam->current.custom_string);
    894 
    895 	      cparam->current.custom_string = _cupsStrAlloc(choice + 7);
    896 	      break;
    897 	}
    898       }
    899     }
    900 
    901    /*
    902     * Make sure that we keep the option marked below...
    903     */
    904 
    905     choice = "Custom";
    906   }
    907   else if (choice[0] == '{')
    908   {
    909    /*
    910     * Handle multi-value custom options...
    911     */
    912 
    913     ppd_coption_t	*coption;	/* Custom option */
    914     ppd_cparam_t	*cparam;	/* Custom parameter */
    915     char		*units;		/* Custom points units */
    916     int			num_vals;	/* Number of values */
    917     cups_option_t	*vals,		/* Values */
    918 			*val;		/* Value */
    919 
    920 
    921     if ((c = ppdFindChoice(o, "Custom")) == NULL)
    922       return;
    923 
    924     if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
    925     {
    926       num_vals = cupsParseOptions(choice, 0, &vals);
    927 
    928       for (i = 0, val = vals; i < num_vals; i ++, val ++)
    929       {
    930         if ((cparam = ppdFindCustomParam(coption, val->name)) == NULL)
    931 	  continue;
    932 
    933 	switch (cparam->type)
    934 	{
    935 	  case PPD_CUSTOM_CURVE :
    936 	  case PPD_CUSTOM_INVCURVE :
    937 	  case PPD_CUSTOM_REAL :
    938 	      cparam->current.custom_real = (float)_cupsStrScand(val->value,
    939 	                                                         NULL, loc);
    940 	      break;
    941 
    942 	  case PPD_CUSTOM_POINTS :
    943 	      cparam->current.custom_points = (float)_cupsStrScand(val->value,
    944 	                                                           &units,
    945 	                                                           loc);
    946 
    947 	      if (units)
    948 	      {
    949         	if (!_cups_strcasecmp(units, "cm"))
    950 		  cparam->current.custom_points *= 72.0f / 2.54f;
    951         	else if (!_cups_strcasecmp(units, "mm"))
    952 		  cparam->current.custom_points *= 72.0f / 25.4f;
    953         	else if (!_cups_strcasecmp(units, "m"))
    954 		  cparam->current.custom_points *= 72.0f / 0.0254f;
    955         	else if (!_cups_strcasecmp(units, "in"))
    956 		  cparam->current.custom_points *= 72.0f;
    957         	else if (!_cups_strcasecmp(units, "ft"))
    958 		  cparam->current.custom_points *= 12.0f * 72.0f;
    959 	      }
    960 	      break;
    961 
    962 	  case PPD_CUSTOM_INT :
    963 	      cparam->current.custom_int = atoi(val->value);
    964 	      break;
    965 
    966 	  case PPD_CUSTOM_PASSCODE :
    967 	  case PPD_CUSTOM_PASSWORD :
    968 	  case PPD_CUSTOM_STRING :
    969 	      if (cparam->current.custom_string)
    970 		_cupsStrFree(cparam->current.custom_string);
    971 
    972 	      cparam->current.custom_string = _cupsStrRetain(val->value);
    973 	      break;
    974 	}
    975       }
    976 
    977       cupsFreeOptions(num_vals, vals);
    978     }
    979   }
    980   else
    981   {
    982     for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
    983       if (!_cups_strcasecmp(c->choice, choice))
    984         break;
    985 
    986     if (!i)
    987       return;
    988   }
    989 
    990  /*
    991   * Option found; mark it and then handle unmarking any other options.
    992   */
    993 
    994   if (o->ui != PPD_UI_PICKMANY)
    995   {
    996    /*
    997     * Unmark all other choices...
    998     */
    999 
   1000     if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, c)) != NULL)
   1001     {
   1002       oldc->marked = 0;
   1003       cupsArrayRemove(ppd->marked, oldc);
   1004     }
   1005 
   1006     if (!_cups_strcasecmp(option, "PageSize") || !_cups_strcasecmp(option, "PageRegion"))
   1007     {
   1008      /*
   1009       * Mark current page size...
   1010       */
   1011 
   1012       for (j = 0; j < ppd->num_sizes; j ++)
   1013 	ppd->sizes[j].marked = !_cups_strcasecmp(ppd->sizes[j].name,
   1014 		                           choice);
   1015 
   1016      /*
   1017       * Unmark the current PageSize or PageRegion setting, as
   1018       * appropriate...
   1019       */
   1020 
   1021       cupsArraySave(ppd->options);
   1022 
   1023       if (!_cups_strcasecmp(option, "PageSize"))
   1024       {
   1025 	if ((o = ppdFindOption(ppd, "PageRegion")) != NULL)
   1026         {
   1027           key.option = o;
   1028           if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
   1029           {
   1030             oldc->marked = 0;
   1031             cupsArrayRemove(ppd->marked, oldc);
   1032           }
   1033         }
   1034       }
   1035       else
   1036       {
   1037 	if ((o = ppdFindOption(ppd, "PageSize")) != NULL)
   1038         {
   1039           key.option = o;
   1040           if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
   1041           {
   1042             oldc->marked = 0;
   1043             cupsArrayRemove(ppd->marked, oldc);
   1044           }
   1045         }
   1046       }
   1047 
   1048       cupsArrayRestore(ppd->options);
   1049     }
   1050     else if (!_cups_strcasecmp(option, "InputSlot"))
   1051     {
   1052      /*
   1053       * Unmark ManualFeed option...
   1054       */
   1055 
   1056       cupsArraySave(ppd->options);
   1057 
   1058       if ((o = ppdFindOption(ppd, "ManualFeed")) != NULL)
   1059       {
   1060         key.option = o;
   1061         if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
   1062         {
   1063           oldc->marked = 0;
   1064           cupsArrayRemove(ppd->marked, oldc);
   1065         }
   1066       }
   1067 
   1068       cupsArrayRestore(ppd->options);
   1069     }
   1070     else if (!_cups_strcasecmp(option, "ManualFeed") &&
   1071 	     !_cups_strcasecmp(choice, "True"))
   1072     {
   1073      /*
   1074       * Unmark InputSlot option...
   1075       */
   1076 
   1077       cupsArraySave(ppd->options);
   1078 
   1079       if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
   1080       {
   1081         key.option = o;
   1082         if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
   1083         {
   1084           oldc->marked = 0;
   1085           cupsArrayRemove(ppd->marked, oldc);
   1086         }
   1087       }
   1088 
   1089       cupsArrayRestore(ppd->options);
   1090     }
   1091   }
   1092 
   1093   c->marked = 1;
   1094 
   1095   cupsArrayAdd(ppd->marked, c);
   1096 }
   1097