/*
 * Option marking routines for CUPS.
 *
 * Copyright © 2007-2019 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 *
 * PostScript is a trademark of Adobe Systems, Inc.
 */

/*
 * Include necessary headers...
 */

#include "cups-private.h"
#include "ppd-private.h"
#include "debug-internal.h"


/*
 * Local functions...
 */

#ifdef DEBUG
static void	ppd_debug_marked(ppd_file_t *ppd, const char *title);
#else
#  define	ppd_debug_marked(ppd,title)
#endif /* DEBUG */
static void	ppd_defaults(ppd_file_t *ppd, ppd_group_t *g);
static void	ppd_mark_choices(ppd_file_t *ppd, const char *s);
static void	ppd_mark_option(ppd_file_t *ppd, const char *option,
		                const char *choice);


/*
 * 'cupsMarkOptions()' - Mark command-line options in a PPD file.
 *
 * This function maps the IPP "finishings", "media", "mirror",
 * "multiple-document-handling", "output-bin", "print-color-mode",
 * "print-quality", "printer-resolution", and "sides" attributes to their
 * corresponding PPD options and choices.
 */

int					/* O - 1 if conflicts exist, 0 otherwise */
cupsMarkOptions(
    ppd_file_t    *ppd,			/* I - PPD file */
    int           num_options,		/* I - Number of options */
    cups_option_t *options)		/* I - Options */
{
  int		i, j;			/* Looping vars */
  char		*ptr,			/* Pointer into string */
		s[255];			/* Temporary string */
  const char	*val,			/* Pointer into value */
		*media,			/* media option */
		*output_bin,		/* output-bin option */
		*page_size,		/* PageSize option */
		*ppd_keyword,		/* PPD keyword */
		*print_color_mode,	/* print-color-mode option */
		*print_quality,		/* print-quality option */
		*sides;			/* sides option */
  cups_option_t	*optptr;		/* Current option */
  ppd_attr_t	*attr;			/* PPD attribute */
  _ppd_cache_t	*cache;			/* PPD cache and mapping data */


 /*
  * Check arguments...
  */

  if (!ppd || num_options <= 0 || !options)
    return (0);

  ppd_debug_marked(ppd, "Before...");

 /*
  * Do special handling for finishings, media, output-bin, output-mode,
  * print-color-mode, print-quality, and PageSize...
  */

  media         = cupsGetOption("media", num_options, options);
  output_bin    = cupsGetOption("output-bin", num_options, options);
  page_size     = cupsGetOption("PageSize", num_options, options);
  print_quality = cupsGetOption("print-quality", num_options, options);
  sides         = cupsGetOption("sides", num_options, options);

  if ((print_color_mode = cupsGetOption("print-color-mode", num_options,
                                        options)) == NULL)
    print_color_mode = cupsGetOption("output-mode", num_options, options);

  if ((media || output_bin || print_color_mode || print_quality || sides) &&
      !ppd->cache)
  {
   /*
    * Load PPD cache and mapping data as needed...
    */

    ppd->cache = _ppdCacheCreateWithPPD(ppd);
  }

  cache = ppd->cache;

  if (media)
  {
   /*
    * Loop through the option string, separating it at commas and marking each
    * individual option as long as the corresponding PPD option (PageSize,
    * InputSlot, etc.) is not also set.
    *
    * For PageSize, we also check for an empty option value since some versions
    * of macOS use it to specify auto-selection of the media based solely on
    * the size.
    */

    for (val = media; *val;)
    {
     /*
      * Extract the sub-option from the string...
      */

      for (ptr = s; *val && *val != ',' && (size_t)(ptr - s) < (sizeof(s) - 1);)
	*ptr++ = *val++;
      *ptr++ = '\0';

      if (*val == ',')
	val ++;

     /*
      * Mark it...
      */

      if (!page_size || !page_size[0])
      {
        if (!_cups_strncasecmp(s, "Custom.", 7) || ppdPageSize(ppd, s))
          ppd_mark_option(ppd, "PageSize", s);
        else if ((ppd_keyword = _ppdCacheGetPageSize(cache, NULL, s, NULL)) != NULL)
	  ppd_mark_option(ppd, "PageSize", ppd_keyword);
      }

      if (cache && cache->source_option &&
          !cupsGetOption(cache->source_option, num_options, options) &&
	  (ppd_keyword = _ppdCacheGetInputSlot(cache, NULL, s)) != NULL)
	ppd_mark_option(ppd, cache->source_option, ppd_keyword);

      if (!cupsGetOption("MediaType", num_options, options) &&
	  (ppd_keyword = _ppdCacheGetMediaType(cache, NULL, s)) != NULL)
	ppd_mark_option(ppd, "MediaType", ppd_keyword);
    }
  }

  if (cache)
  {
    if (!cupsGetOption("com.apple.print.DocumentTicket.PMSpoolFormat",
                       num_options, options) &&
        !cupsGetOption("APPrinterPreset", num_options, options) &&
        (print_color_mode || print_quality))
    {
     /*
      * Map output-mode and print-quality to a preset...
      */

      _pwg_print_color_mode_t	pwg_pcm;/* print-color-mode index */
      _pwg_print_quality_t	pwg_pq;	/* print-quality index */
      cups_option_t		*preset;/* Current preset option */

      if (print_color_mode && !strcmp(print_color_mode, "monochrome"))
	pwg_pcm = _PWG_PRINT_COLOR_MODE_MONOCHROME;
      else
	pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;

      if (print_quality)
      {
	pwg_pq = (_pwg_print_quality_t)(atoi(print_quality) - IPP_QUALITY_DRAFT);
	if (pwg_pq < _PWG_PRINT_QUALITY_DRAFT)
	  pwg_pq = _PWG_PRINT_QUALITY_DRAFT;
	else if (pwg_pq > _PWG_PRINT_QUALITY_HIGH)
	  pwg_pq = _PWG_PRINT_QUALITY_HIGH;
      }
      else
	pwg_pq = _PWG_PRINT_QUALITY_NORMAL;

      if (cache->num_presets[pwg_pcm][pwg_pq] == 0)
      {
       /*
	* Try to find a preset that works so that we maximize the chances of us
	* getting a good print using IPP attributes.
	*/

	if (cache->num_presets[pwg_pcm][_PWG_PRINT_QUALITY_NORMAL] > 0)
	  pwg_pq = _PWG_PRINT_QUALITY_NORMAL;
	else if (cache->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][pwg_pq] > 0)
	  pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;
	else
	{
	  pwg_pq  = _PWG_PRINT_QUALITY_NORMAL;
	  pwg_pcm = _PWG_PRINT_COLOR_MODE_COLOR;
	}
      }

      if (cache->num_presets[pwg_pcm][pwg_pq] > 0)
      {
       /*
	* Copy the preset options as long as the corresponding names are not
	* already defined in the IPP request...
	*/

	for (i = cache->num_presets[pwg_pcm][pwg_pq],
		 preset = cache->presets[pwg_pcm][pwg_pq];
	     i > 0;
	     i --, preset ++)
	{
	  if (!cupsGetOption(preset->name, num_options, options))
	    ppd_mark_option(ppd, preset->name, preset->value);
	}
      }
    }

    if (output_bin && !cupsGetOption("OutputBin", num_options, options) &&
	(ppd_keyword = _ppdCacheGetOutputBin(cache, output_bin)) != NULL)
    {
     /*
      * Map output-bin to OutputBin...
      */

      ppd_mark_option(ppd, "OutputBin", ppd_keyword);
    }

    if (sides && cache->sides_option &&
        !cupsGetOption(cache->sides_option, num_options, options))
    {
     /*
      * Map sides to duplex option...
      */

      if (!strcmp(sides, "one-sided") && cache->sides_1sided)
        ppd_mark_option(ppd, cache->sides_option, cache->sides_1sided);
      else if (!strcmp(sides, "two-sided-long-edge") &&
               cache->sides_2sided_long)
        ppd_mark_option(ppd, cache->sides_option, cache->sides_2sided_long);
      else if (!strcmp(sides, "two-sided-short-edge") &&
               cache->sides_2sided_short)
        ppd_mark_option(ppd, cache->sides_option, cache->sides_2sided_short);
    }
  }

 /*
  * Mark other options...
  */

  for (i = num_options, optptr = options; i > 0; i --, optptr ++)
  {
    if (!_cups_strcasecmp(optptr->name, "media") ||
        !_cups_strcasecmp(optptr->name, "output-bin") ||
	!_cups_strcasecmp(optptr->name, "output-mode") ||
	!_cups_strcasecmp(optptr->name, "print-quality") ||
	!_cups_strcasecmp(optptr->name, "sides"))
      continue;
    else if (!_cups_strcasecmp(optptr->name, "resolution") ||
             !_cups_strcasecmp(optptr->name, "printer-resolution"))
    {
      ppd_mark_option(ppd, "Resolution", optptr->value);
      ppd_mark_option(ppd, "SetResolution", optptr->value);
      	/* Calcomp, Linotype, QMS, Summagraphics, Tektronix, Varityper */
      ppd_mark_option(ppd, "JCLResolution", optptr->value);
      	/* HP */
      ppd_mark_option(ppd, "CNRes_PGP", optptr->value);
      	/* Canon */
    }
    else if (!_cups_strcasecmp(optptr->name, "multiple-document-handling"))
    {
      if (!cupsGetOption("Collate", num_options, options) &&
          ppdFindOption(ppd, "Collate"))
      {
        if (_cups_strcasecmp(optptr->value, "separate-documents-uncollated-copies"))
	  ppd_mark_option(ppd, "Collate", "True");
	else
	  ppd_mark_option(ppd, "Collate", "False");
      }
    }
    else if (!_cups_strcasecmp(optptr->name, "finishings"))
    {
     /*
      * Lookup cupsIPPFinishings attributes for each value...
      */

      for (ptr = optptr->value; *ptr;)
      {
       /*
        * Get the next finishings number...
	*/

        if (!isdigit(*ptr & 255))
	  break;

        if ((j = (int)strtol(ptr, &ptr, 10)) < 3)
	  break;

       /*
        * Skip separator as needed...
	*/

        if (*ptr == ',')
	  ptr ++;

       /*
        * Look it up in the PPD file...
	*/

	snprintf(s, sizeof(s), "%d", j);

        if ((attr = ppdFindAttr(ppd, "cupsIPPFinishings", s)) == NULL)
	  continue;

       /*
        * Apply "*Option Choice" settings from the attribute value...
	*/

        ppd_mark_choices(ppd, attr->value);
      }
    }
    else if (!_cups_strcasecmp(optptr->name, "APPrinterPreset"))
    {
     /*
      * Lookup APPrinterPreset value...
      */

      if ((attr = ppdFindAttr(ppd, "APPrinterPreset", optptr->value)) != NULL)
      {
       /*
        * Apply "*Option Choice" settings from the attribute value...
	*/

        ppd_mark_choices(ppd, attr->value);
      }
    }
    else if (!_cups_strcasecmp(optptr->name, "mirror"))
      ppd_mark_option(ppd, "MirrorPrint", optptr->value);
    else
      ppd_mark_option(ppd, optptr->name, optptr->value);
  }

  if (print_quality)
  {
    int pq = atoi(print_quality);       /* print-quaity value */

    if (pq == IPP_QUALITY_DRAFT)
      ppd_mark_option(ppd, "cupsPrintQuality", "Draft");
    else if (pq == IPP_QUALITY_HIGH)
      ppd_mark_option(ppd, "cupsPrintQuality", "High");
    else
      ppd_mark_option(ppd, "cupsPrintQuality", "Normal");
  }

  ppd_debug_marked(ppd, "After...");

  return (ppdConflicts(ppd) > 0);
}


/*
 * 'ppdFindChoice()' - Return a pointer to an option choice.
 */

ppd_choice_t *				/* O - Choice pointer or @code NULL@ */
ppdFindChoice(ppd_option_t *o,		/* I - Pointer to option */
              const char   *choice)	/* I - Name of choice */
{
  int		i;			/* Looping var */
  ppd_choice_t	*c;			/* Current choice */


  if (!o || !choice)
    return (NULL);

  if (choice[0] == '{' || !_cups_strncasecmp(choice, "Custom.", 7))
    choice = "Custom";

  for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
    if (!_cups_strcasecmp(c->choice, choice))
      return (c);

  return (NULL);
}


/*
 * 'ppdFindMarkedChoice()' - Return the marked choice for the specified option.
 */

ppd_choice_t *				/* O - Pointer to choice or @code NULL@ */
ppdFindMarkedChoice(ppd_file_t *ppd,	/* I - PPD file */
                    const char *option)	/* I - Keyword/option name */
{
  ppd_choice_t	key,			/* Search key for choice */
		*marked;		/* Marked choice */


  DEBUG_printf(("2ppdFindMarkedChoice(ppd=%p, option=\"%s\")", ppd, option));

  if ((key.option = ppdFindOption(ppd, option)) == NULL)
  {
    DEBUG_puts("3ppdFindMarkedChoice: Option not found, returning NULL");
    return (NULL);
  }

  marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key);

  DEBUG_printf(("3ppdFindMarkedChoice: Returning %p(%s)...", marked,
                marked ? marked->choice : "NULL"));

  return (marked);
}


/*
 * 'ppdFindOption()' - Return a pointer to the specified option.
 */

ppd_option_t *				/* O - Pointer to option or @code NULL@ */
ppdFindOption(ppd_file_t *ppd,		/* I - PPD file data */
              const char *option)	/* I - Option/Keyword name */
{
 /*
  * Range check input...
  */

  if (!ppd || !option)
    return (NULL);

  if (ppd->options)
  {
   /*
    * Search in the array...
    */

    ppd_option_t	key;		/* Option search key */


    strlcpy(key.keyword, option, sizeof(key.keyword));

    return ((ppd_option_t *)cupsArrayFind(ppd->options, &key));
  }
  else
  {
   /*
    * Search in each group...
    */

    int			i, j;		/* Looping vars */
    ppd_group_t		*group;		/* Current group */
    ppd_option_t	*optptr;	/* Current option */


    for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
      for (j = group->num_options, optptr = group->options;
           j > 0;
	   j --, optptr ++)
        if (!_cups_strcasecmp(optptr->keyword, option))
	  return (optptr);

    return (NULL);
  }
}


/*
 * 'ppdIsMarked()' - Check to see if an option is marked.
 */

int					/* O - Non-zero if option is marked */
ppdIsMarked(ppd_file_t *ppd,		/* I - PPD file data */
            const char *option,		/* I - Option/Keyword name */
            const char *choice)		/* I - Choice name */
{
  ppd_choice_t	key,			/* Search key */
		*c;			/* Choice pointer */


  if (!ppd)
    return (0);

  if ((key.option = ppdFindOption(ppd, option)) == NULL)
    return (0);

  if ((c = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) == NULL)
    return (0);

  return (!strcmp(c->choice, choice));
}


/*
 * 'ppdMarkDefaults()' - Mark all default options in the PPD file.
 */

void
ppdMarkDefaults(ppd_file_t *ppd)	/* I - PPD file record */
{
  int		i;			/* Looping variables */
  ppd_group_t	*g;			/* Current group */
  ppd_choice_t	*c;			/* Current choice */


  if (!ppd)
    return;

 /*
  * Clean out the marked array...
  */

  for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
       c;
       c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
  {
    cupsArrayRemove(ppd->marked, c);
    c->marked = 0;
  }

 /*
  * Then repopulate it with the defaults...
  */

  for (i = ppd->num_groups, g = ppd->groups; i > 0; i --, g ++)
    ppd_defaults(ppd, g);

 /*
  * Finally, tag any conflicts (API compatibility) once at the end.
  */

  ppdConflicts(ppd);
}


/*
 * 'ppdMarkOption()' - Mark an option in a PPD file and return the number of
 *                     conflicts.
 */

int					/* O - Number of conflicts */
ppdMarkOption(ppd_file_t *ppd,		/* I - PPD file record */
              const char *option,	/* I - Keyword */
              const char *choice)	/* I - Option name */
{
  DEBUG_printf(("ppdMarkOption(ppd=%p, option=\"%s\", choice=\"%s\")",
        	ppd, option, choice));

 /*
  * Range check input...
  */

  if (!ppd || !option || !choice)
    return (0);

 /*
  * Mark the option...
  */

  ppd_mark_option(ppd, option, choice);

 /*
  * Return the number of conflicts...
  */

  return (ppdConflicts(ppd));
}


/*
 * 'ppdFirstOption()' - Return the first option in the PPD file.
 *
 * Options are returned from all groups in ascending alphanumeric order.
 *
 * @since CUPS 1.2/macOS 10.5@
 */

ppd_option_t *				/* O - First option or @code NULL@ */
ppdFirstOption(ppd_file_t *ppd)		/* I - PPD file */
{
  if (!ppd)
    return (NULL);
  else
    return ((ppd_option_t *)cupsArrayFirst(ppd->options));
}


/*
 * 'ppdNextOption()' - Return the next option in the PPD file.
 *
 * Options are returned from all groups in ascending alphanumeric order.
 *
 * @since CUPS 1.2/macOS 10.5@
 */

ppd_option_t *				/* O - Next option or @code NULL@ */
ppdNextOption(ppd_file_t *ppd)		/* I - PPD file */
{
  if (!ppd)
    return (NULL);
  else
    return ((ppd_option_t *)cupsArrayNext(ppd->options));
}


/*
 * '_ppdParseOptions()' - Parse options from a PPD file.
 *
 * This function looks for strings of the form:
 *
 *     *option choice ... *optionN choiceN
 *     property value ... propertyN valueN
 *
 * It stops when it finds a string that doesn't match this format.
 */

int					/* O  - Number of options */
_ppdParseOptions(
    const char    *s,			/* I  - String to parse */
    int           num_options,		/* I  - Number of options */
    cups_option_t **options,		/* IO - Options */
    _ppd_parse_t  which)		/* I  - What to parse */
{
  char	option[PPD_MAX_NAME * 2 + 1],	/* Current option/property */
	choice[PPD_MAX_NAME],		/* Current choice/value */
	*ptr;				/* Pointer into option or choice */


  if (!s)
    return (num_options);

 /*
  * Read all of the "*Option Choice" and "property value" pairs from the
  * string, add them to an options array as we go...
  */

  while (*s)
  {
   /*
    * Skip leading whitespace...
    */

    while (_cups_isspace(*s))
      s ++;

   /*
    * Get the option/property name...
    */

    ptr = option;
    while (*s && !_cups_isspace(*s) && ptr < (option + sizeof(option) - 1))
      *ptr++ = *s++;

    if (ptr == s || !_cups_isspace(*s))
      break;

    *ptr = '\0';

   /*
    * Get the choice...
    */

    while (_cups_isspace(*s))
      s ++;

    if (!*s)
      break;

    ptr = choice;
    while (*s && !_cups_isspace(*s) && ptr < (choice + sizeof(choice) - 1))
      *ptr++ = *s++;

    if (*s && !_cups_isspace(*s))
      break;

    *ptr = '\0';

   /*
    * Add it to the options array...
    */

    if (option[0] == '*' && which != _PPD_PARSE_PROPERTIES)
      num_options = cupsAddOption(option + 1, choice, num_options, options);
    else if (option[0] != '*' && which != _PPD_PARSE_OPTIONS)
      num_options = cupsAddOption(option, choice, num_options, options);
  }

  return (num_options);
}


#ifdef DEBUG
/*
 * 'ppd_debug_marked()' - Output the marked array to stdout...
 */

static void
ppd_debug_marked(ppd_file_t *ppd,		/* I - PPD file data */
             const char *title)		/* I - Title for list */
{
  ppd_choice_t	*c;			/* Current choice */


  DEBUG_printf(("2cupsMarkOptions: %s", title));

  for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
       c;
       c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
    DEBUG_printf(("2cupsMarkOptions: %s=%s", c->option->keyword, c->choice));
}
#endif /* DEBUG */


/*
 * 'ppd_defaults()' - Set the defaults for this group and all sub-groups.
 */

static void
ppd_defaults(ppd_file_t  *ppd,		/* I - PPD file */
             ppd_group_t *g)		/* I - Group to default */
{
  int		i;			/* Looping var */
  ppd_option_t	*o;			/* Current option */
  ppd_group_t	*sg;			/* Current sub-group */


  for (i = g->num_options, o = g->options; i > 0; i --, o ++)
    if (_cups_strcasecmp(o->keyword, "PageRegion") != 0)
      ppd_mark_option(ppd, o->keyword, o->defchoice);

  for (i = g->num_subgroups, sg = g->subgroups; i > 0; i --, sg ++)
    ppd_defaults(ppd, sg);
}


/*
 * 'ppd_mark_choices()' - Mark one or more option choices from a string.
 */

static void
ppd_mark_choices(ppd_file_t *ppd,	/* I - PPD file */
                 const char *s)		/* I - "*Option Choice ..." string */
{
  int		i,			/* Looping var */
		num_options;		/* Number of options */
  cups_option_t	*options,		/* Options */
		*option;		/* Current option */


  if (!s)
    return;

  options     = NULL;
  num_options = _ppdParseOptions(s, 0, &options, 0);

  for (i = num_options, option = options; i > 0; i --, option ++)
    ppd_mark_option(ppd, option->name, option->value);

  cupsFreeOptions(num_options, options);
}


/*
 * 'ppd_mark_option()' - Quick mark an option without checking for conflicts.
 */

static void
ppd_mark_option(ppd_file_t *ppd,	/* I - PPD file */
                const char *option,	/* I - Option name */
                const char *choice)	/* I - Choice name */
{
  int		i, j;			/* Looping vars */
  ppd_option_t	*o;			/* Option pointer */
  ppd_choice_t	*c,			/* Choice pointer */
		*oldc,			/* Old choice pointer */
		key;			/* Search key for choice */
  struct lconv	*loc;			/* Locale data */


  DEBUG_printf(("7ppd_mark_option(ppd=%p, option=\"%s\", choice=\"%s\")",
        	ppd, option, choice));

 /*
  * AP_D_InputSlot is the "default input slot" on macOS, and setting
  * it clears the regular InputSlot choices...
  */

  if (!_cups_strcasecmp(option, "AP_D_InputSlot"))
  {
    cupsArraySave(ppd->options);

    if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
    {
      key.option = o;
      if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
      {
        oldc->marked = 0;
        cupsArrayRemove(ppd->marked, oldc);
      }
    }

    cupsArrayRestore(ppd->options);
  }

 /*
  * Check for custom options...
  */

  cupsArraySave(ppd->options);

  o = ppdFindOption(ppd, option);

  cupsArrayRestore(ppd->options);

  if (!o)
    return;

  loc = localeconv();

  if (!_cups_strncasecmp(choice, "Custom.", 7))
  {
   /*
    * Handle a custom option...
    */

    if ((c = ppdFindChoice(o, "Custom")) == NULL)
      return;

    if (!_cups_strcasecmp(option, "PageSize"))
    {
     /*
      * Handle custom page sizes...
      */

      ppdPageSize(ppd, choice);
    }
    else
    {
     /*
      * Handle other custom options...
      */

      ppd_coption_t	*coption;	/* Custom option */
      ppd_cparam_t	*cparam;	/* Custom parameter */
      char		*units;		/* Custom points units */


      if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
      {
        if ((cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params)) == NULL)
	  return;

        switch (cparam->type)
	{
	  case PPD_CUSTOM_UNKNOWN :
	      break;

	  case PPD_CUSTOM_CURVE :
	  case PPD_CUSTOM_INVCURVE :
	  case PPD_CUSTOM_REAL :
	      cparam->current.custom_real = (float)_cupsStrScand(choice + 7,
	                                                         NULL, loc);
	      break;

	  case PPD_CUSTOM_POINTS :
	      cparam->current.custom_points = (float)_cupsStrScand(choice + 7,
	                                                           &units,
	                                                           loc);

              if (units)
	      {
        	if (!_cups_strcasecmp(units, "cm"))
	          cparam->current.custom_points *= 72.0f / 2.54f;
        	else if (!_cups_strcasecmp(units, "mm"))
	          cparam->current.custom_points *= 72.0f / 25.4f;
        	else if (!_cups_strcasecmp(units, "m"))
	          cparam->current.custom_points *= 72.0f / 0.0254f;
        	else if (!_cups_strcasecmp(units, "in"))
	          cparam->current.custom_points *= 72.0f;
        	else if (!_cups_strcasecmp(units, "ft"))
	          cparam->current.custom_points *= 12.0f * 72.0f;
              }
	      break;

	  case PPD_CUSTOM_INT :
	      cparam->current.custom_int = atoi(choice + 7);
	      break;

	  case PPD_CUSTOM_PASSCODE :
	  case PPD_CUSTOM_PASSWORD :
	  case PPD_CUSTOM_STRING :
	      if (cparam->current.custom_string)
	        free(cparam->current.custom_string);

	      cparam->current.custom_string = strdup(choice + 7);
	      break;
	}
      }
    }

   /*
    * Make sure that we keep the option marked below...
    */

    choice = "Custom";
  }
  else if (choice[0] == '{')
  {
   /*
    * Handle multi-value custom options...
    */

    ppd_coption_t	*coption;	/* Custom option */
    ppd_cparam_t	*cparam;	/* Custom parameter */
    char		*units;		/* Custom points units */
    int			num_vals;	/* Number of values */
    cups_option_t	*vals,		/* Values */
			*val;		/* Value */


    if ((c = ppdFindChoice(o, "Custom")) == NULL)
      return;

    if ((coption = ppdFindCustomOption(ppd, option)) != NULL)
    {
      num_vals = cupsParseOptions(choice, 0, &vals);

      for (i = 0, val = vals; i < num_vals; i ++, val ++)
      {
        if ((cparam = ppdFindCustomParam(coption, val->name)) == NULL)
	  continue;

	switch (cparam->type)
	{
	  case PPD_CUSTOM_UNKNOWN :
	      break;

	  case PPD_CUSTOM_CURVE :
	  case PPD_CUSTOM_INVCURVE :
	  case PPD_CUSTOM_REAL :
	      cparam->current.custom_real = (float)_cupsStrScand(val->value,
	                                                         NULL, loc);
	      break;

	  case PPD_CUSTOM_POINTS :
	      cparam->current.custom_points = (float)_cupsStrScand(val->value,
	                                                           &units,
	                                                           loc);

	      if (units)
	      {
        	if (!_cups_strcasecmp(units, "cm"))
		  cparam->current.custom_points *= 72.0f / 2.54f;
        	else if (!_cups_strcasecmp(units, "mm"))
		  cparam->current.custom_points *= 72.0f / 25.4f;
        	else if (!_cups_strcasecmp(units, "m"))
		  cparam->current.custom_points *= 72.0f / 0.0254f;
        	else if (!_cups_strcasecmp(units, "in"))
		  cparam->current.custom_points *= 72.0f;
        	else if (!_cups_strcasecmp(units, "ft"))
		  cparam->current.custom_points *= 12.0f * 72.0f;
	      }
	      break;

	  case PPD_CUSTOM_INT :
	      cparam->current.custom_int = atoi(val->value);
	      break;

	  case PPD_CUSTOM_PASSCODE :
	  case PPD_CUSTOM_PASSWORD :
	  case PPD_CUSTOM_STRING :
	      if (cparam->current.custom_string)
		free(cparam->current.custom_string);

	      cparam->current.custom_string = strdup(val->value);
	      break;
	}
      }

      cupsFreeOptions(num_vals, vals);
    }
  }
  else
  {
    for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
      if (!_cups_strcasecmp(c->choice, choice))
        break;

    if (!i)
      return;
  }

 /*
  * Option found; mark it and then handle unmarking any other options.
  */

  if (o->ui != PPD_UI_PICKMANY)
  {
   /*
    * Unmark all other choices...
    */

    if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, c)) != NULL)
    {
      oldc->marked = 0;
      cupsArrayRemove(ppd->marked, oldc);
    }

    if (!_cups_strcasecmp(option, "PageSize") || !_cups_strcasecmp(option, "PageRegion"))
    {
     /*
      * Mark current page size...
      */

      for (j = 0; j < ppd->num_sizes; j ++)
	ppd->sizes[j].marked = !_cups_strcasecmp(ppd->sizes[j].name,
		                           choice);

     /*
      * Unmark the current PageSize or PageRegion setting, as
      * appropriate...
      */

      cupsArraySave(ppd->options);

      if (!_cups_strcasecmp(option, "PageSize"))
      {
	if ((o = ppdFindOption(ppd, "PageRegion")) != NULL)
        {
          key.option = o;
          if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
          {
            oldc->marked = 0;
            cupsArrayRemove(ppd->marked, oldc);
          }
        }
      }
      else
      {
	if ((o = ppdFindOption(ppd, "PageSize")) != NULL)
        {
          key.option = o;
          if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
          {
            oldc->marked = 0;
            cupsArrayRemove(ppd->marked, oldc);
          }
        }
      }

      cupsArrayRestore(ppd->options);
    }
    else if (!_cups_strcasecmp(option, "InputSlot"))
    {
     /*
      * Unmark ManualFeed option...
      */

      cupsArraySave(ppd->options);

      if ((o = ppdFindOption(ppd, "ManualFeed")) != NULL)
      {
        key.option = o;
        if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
        {
          oldc->marked = 0;
          cupsArrayRemove(ppd->marked, oldc);
        }
      }

      cupsArrayRestore(ppd->options);
    }
    else if (!_cups_strcasecmp(option, "ManualFeed") &&
	     !_cups_strcasecmp(choice, "True"))
    {
     /*
      * Unmark InputSlot option...
      */

      cupsArraySave(ppd->options);

      if ((o = ppdFindOption(ppd, "InputSlot")) != NULL)
      {
        key.option = o;
        if ((oldc = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key)) != NULL)
        {
          oldc->marked = 0;
          cupsArrayRemove(ppd->marked, oldc);
        }
      }

      cupsArrayRestore(ppd->options);
    }
  }

  c->marked = 1;

  cupsArrayAdd(ppd->marked, c);
}
