Home | History | Annotate | Download | only in cups
      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 }
   1083