//
// Option marking routines for libppd.
//
// 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 "string-private.h"
#include "ppd.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);


//
// 'ppdMarkOptions()' - 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
ppdMarkOptions(
    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 (!_ppd_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...
      //

      ppd_pwg_print_color_mode_t	pwg_pcm;// print-color-mode index
      ppd_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 = PPD_PWG_PRINT_COLOR_MODE_MONOCHROME;
      else
	pwg_pcm = PPD_PWG_PRINT_COLOR_MODE_COLOR;

      if (print_quality)
      {
	pwg_pq = (ppd_pwg_print_quality_t)(atoi(print_quality) -
					   IPP_QUALITY_DRAFT);
	if (pwg_pq < PPD_PWG_PRINT_QUALITY_DRAFT)
	  pwg_pq = PPD_PWG_PRINT_QUALITY_DRAFT;
	else if (pwg_pq > PPD_PWG_PRINT_QUALITY_HIGH)
	  pwg_pq = PPD_PWG_PRINT_QUALITY_HIGH;
      }
      else
	pwg_pq = PPD_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][PPD_PWG_PRINT_QUALITY_NORMAL] > 0)
	  pwg_pq = PPD_PWG_PRINT_QUALITY_NORMAL;
	else if (cache->num_presets[PPD_PWG_PRINT_COLOR_MODE_COLOR][pwg_pq] > 0)
	  pwg_pcm = PPD_PWG_PRINT_COLOR_MODE_COLOR;
	else
	{
	  pwg_pq  = PPD_PWG_PRINT_QUALITY_NORMAL;
	  pwg_pcm = PPD_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 (!_ppd_strcasecmp(optptr->name, "media") ||
        !_ppd_strcasecmp(optptr->name, "output-bin") ||
	!_ppd_strcasecmp(optptr->name, "output-mode") ||
	!_ppd_strcasecmp(optptr->name, "print-quality") ||
	!_ppd_strcasecmp(optptr->name, "sides"))
      continue;
    else if (!_ppd_strcasecmp(optptr->name, "resolution") ||
             !_ppd_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 (!_ppd_strcasecmp(optptr->name, "multiple-document-handling"))
    {
      if (!cupsGetOption("Collate", num_options, options) &&
          ppdFindOption(ppd, "Collate"))
      {
        if (_ppd_strcasecmp(optptr->value,
			    "separate-documents-uncollated-copies"))
	  ppd_mark_option(ppd, "Collate", "True");
	else
	  ppd_mark_option(ppd, "Collate", "False");
      }
    }
    else if (!_ppd_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...
	//

	sprintf(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 (!_ppd_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 (!_ppd_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] == '{' || !_ppd_strncasecmp(choice, "Custom.", 7))
    choice = "Custom";

  for (i = o->num_choices, c = o->choices; i > 0; i --, c ++)
    if (!_ppd_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 (!_ppd_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 (_ppd_isspace(*s))
      s ++;

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

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

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

    *ptr = '\0';

    //
    // Get the choice...
    //

    while (_ppd_isspace(*s))
      s ++;

    if (!*s)
      break;

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

    if (*s && !_ppd_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(("2ppdMarkOptions: %s", title));

  for (c = (ppd_choice_t *)cupsArrayFirst(ppd->marked);
       c;
       c = (ppd_choice_t *)cupsArrayNext(ppd->marked))
    DEBUG_printf(("2ppdMarkOptions: %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 (_ppd_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 (!_ppd_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 (!_ppd_strncasecmp(choice, "Custom.", 7))
  {
    //
    // Handle a custom option...
    //

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

    if (!_ppd_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)_ppdStrScand(choice + 7,
	                                                         NULL, loc);
	      break;

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

              if (units)
	      {
        	if (!_ppd_strcasecmp(units, "cm"))
	          cparam->current.custom_points *= 72.0f / 2.54f;
        	else if (!_ppd_strcasecmp(units, "mm"))
	          cparam->current.custom_points *= 72.0f / 25.4f;
        	else if (!_ppd_strcasecmp(units, "m"))
	          cparam->current.custom_points *= 72.0f / 0.0254f;
        	else if (!_ppd_strcasecmp(units, "in"))
	          cparam->current.custom_points *= 72.0f;
        	else if (!_ppd_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)_ppdStrScand(val->value,
	                                                         NULL, loc);
	      break;

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

	      if (units)
	      {
        	if (!_ppd_strcasecmp(units, "cm"))
		  cparam->current.custom_points *= 72.0f / 2.54f;
        	else if (!_ppd_strcasecmp(units, "mm"))
		  cparam->current.custom_points *= 72.0f / 25.4f;
        	else if (!_ppd_strcasecmp(units, "m"))
		  cparam->current.custom_points *= 72.0f / 0.0254f;
        	else if (!_ppd_strcasecmp(units, "in"))
		  cparam->current.custom_points *= 72.0f;
        	else if (!_ppd_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 (!_ppd_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 (!_ppd_strcasecmp(option, "PageSize") ||
	!_ppd_strcasecmp(option, "PageRegion"))
    {
      //
      // Mark current page size...
      //

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

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

      cupsArraySave(ppd->options);

      if (!_ppd_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 (!_ppd_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 (!_ppd_strcasecmp(option, "ManualFeed") &&
	     !_ppd_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);
}
