//
// PPD options <-> IPP attributes 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 "ppd.h"
#include "debug-internal.h"
#include <ctype.h>
#include <errno.h>


//
// Macro to test for two almost-equal PWG measurements.
//

#define _PPD_PWG_EQUIVALENT(x, y)	(abs((x)-(y)) < 2)


//
// Local functions...
//

static ipp_t		*create_media_col(const char *media, const char *source,
					  const char *type, int width,
					  int length, int bottom, int left,
					  int right, int top);
static ipp_t		*create_media_size(int width, int length);
static ipp_t		*create_media_size_ranges(int min_width, int min_length,
						  int max_width,
						  int max_length);


//
// 'ppdGetOptions()' - Get the PPD options corresponding to the IPP Job Template
//                     attributes for a printer with given PPD and given
//                     printer attributes (generated by ppdLoadAttributes()).
//

int					// O - Number of options
ppdGetOptions(cups_option_t **options,	// O - Options
	      ipp_t *printer_attrs,	// I - Printer attributes, from
					//     ppdLoadAttributes()
	      ipp_t *job_attrs,		// I - Job attributes
	      ppd_file_t *ppd)		// I - PPD file data
{
  ppd_cache_t	*ppd_cache;		// IPP to PPD cache data
  int		num_options = 0;	// Number of options
  ipp_attribute_t	*attr;		// Job attribute
  char		valstr[8192];		// Attribute value string
  const char	*value;			// Option value
  pwg_media_t	*media = NULL;		// Media mapping
  int		num_media_col = 0;	// Number of media-col values
  cups_option_t	*media_col = NULL;	// media-col values
  const char	*choice;		// PPD choice


  //
  // No options to start...
  //

  *options = NULL;

  //
  // Media...
  //

  if ((attr = ippFindAttribute(job_attrs, "media", IPP_TAG_ZERO)) == NULL)
    if ((attr = ippFindAttribute(job_attrs, "media-col", IPP_TAG_ZERO)) == NULL)
      if ((attr = ippFindAttribute(printer_attrs, "media-default",
				   IPP_TAG_ZERO)) == NULL)
	attr = ippFindAttribute(printer_attrs, "media-col-default",
				IPP_TAG_ZERO);

  if (attr)
  {
    ippAttributeString(attr, valstr, sizeof(valstr));
    value = valstr;

    if (*value == '{')
    {
      //
      // media-col value...
      //

      num_media_col = cupsParseOptions(value, 0, &media_col);
    }
    else
    {
      //
      // media value - map to media-col.media-size-name...
      //

      num_media_col = cupsAddOption("media-size-name", value, 0, &media_col);
    }
  }

  if ((value = cupsGetOption("media-size-name", num_media_col, media_col))
      != NULL)
  {
    media = pwgMediaForPWG(value);
  }
  else if ((value = cupsGetOption("media-size", num_media_col, media_col))
	   != NULL)
  {
    int		num_media_size;		// Number of media-size values
    cups_option_t *media_size;		// media-size values
    const char	*x_dimension,		// x-dimension value
		*y_dimension;		// y-dimension value

    num_media_size = cupsParseOptions(value, 0, &media_size);

    if ((x_dimension = cupsGetOption("x-dimension",
				     num_media_size, media_size)) != NULL &&
	(y_dimension = cupsGetOption("y-dimension",
				     num_media_size, media_size)) != NULL)
      media = pwgMediaForSize(atoi(x_dimension), atoi(y_dimension));

    cupsFreeOptions(num_media_size, media_size);
  }

  if (media)
    num_options = cupsAddOption("PageSize", media->ppd, num_options, options);

  //
  // Generate PPD's corresponding IPP <-> PPD cache data...
  //

  if (ppd)
  {
    if (ppd->cache == NULL)
    {
      if ((ppd_cache = ppdCacheCreateWithPPD(ppd)) != NULL)
	ppd->cache = ppd_cache;
    }
    else
      ppd_cache = ppd->cache;

    // TODO: Fix me - values are names, not numbers... Also need to support
    // finishings-col
    if ((attr = ippFindAttribute(job_attrs, "finishings", IPP_TAG_ZERO))
	== NULL)
      attr = ippFindAttribute(printer_attrs, "finishings-default",
			      IPP_TAG_ZERO);

    if (attr)
    {
      char	*ptr;			// Pointer into value
      int	fin;			// Current value

      ippAttributeString(attr, valstr, sizeof(valstr));
      value = valstr;

      for (fin = strtol(value, &ptr, 10); fin > 0; fin = strtol(ptr + 1, &ptr, 10))
      {
	num_options =
	  ppdCacheGetFinishingOptions(ppd_cache, NULL,
				      (ipp_finishings_t)fin,
				      num_options, options);

	if (*ptr != ',')
	  break;
      }
    }

    if ((value = cupsGetOption("media-source",
			       num_media_col, media_col)) != NULL)
    {
      if ((choice = ppdCacheGetInputSlot(ppd_cache, NULL, value)) != NULL)
	num_options = cupsAddOption("InputSlot", choice, num_options, options);
    }

    if ((value = cupsGetOption("media-type", num_media_col, media_col)) != NULL)
    {
      if ((choice = ppdCacheGetMediaType(ppd_cache, NULL, value)) != NULL)
	num_options = cupsAddOption("MediaType", choice, num_options, options);
    }

    if ((attr = ippFindAttribute(job_attrs, "output-bin", IPP_TAG_ZERO))
	== NULL)
      attr = ippFindAttribute(printer_attrs, "output-bin-default",
			      IPP_TAG_ZERO);

    if (attr)
    {
      ippAttributeString(attr, valstr, sizeof(valstr));
      value = valstr;
      if ((choice = ppdCacheGetOutputBin(ppd_cache, value)) != NULL)
	num_options = cupsAddOption("OutputBin", choice, num_options, options);
    }

    if ((attr = ippFindAttribute(job_attrs, "sides", IPP_TAG_ZERO)) == NULL)
      attr = ippFindAttribute(printer_attrs, "sides-default",
			      IPP_TAG_ZERO);

    if (attr && ppd_cache->sides_option)
    {
      ippAttributeString(attr, valstr, sizeof(valstr));
      value = valstr;
      if (!strcmp(value, "one-sided") && ppd_cache->sides_1sided)
	num_options = cupsAddOption(ppd_cache->sides_option,
				    ppd_cache->sides_1sided,
				    num_options, options);
      else if (!strcmp(value, "two-sided-long-edge") &&
	       ppd_cache->sides_2sided_long)
	num_options = cupsAddOption(ppd_cache->sides_option,
				    ppd_cache->sides_2sided_long,
				    num_options, options);
      else if (!strcmp(value, "two-sided-short-edge") &&
	       ppd_cache->sides_2sided_short)
	num_options = cupsAddOption(ppd_cache->sides_option,
				    ppd_cache->sides_2sided_short,
				    num_options, options);
    }

    if ((attr = ippFindAttribute(job_attrs, "print-quality", IPP_TAG_ZERO))
	== NULL)
      attr = ippFindAttribute(printer_attrs, "print-quality-default",
			      IPP_TAG_ZERO);

    if (attr)
    {
      int		i;		// Looping var
      int		pq;		// Print quality (0-2)
      int		pcm = 1;	// Print color model
                                        // (0 = mono, 1 = color)
      int		num_presets;	// Number of presets
      cups_option_t	*presets;	// Presets

      ippAttributeString(attr, valstr, sizeof(valstr));
      value = valstr;
      if (!strcmp(value, "draft"))
        pq = 0;
      else if (!strcmp(value, "high"))
        pq = 2;
      else
        pq = 1;

      if ((attr = ippFindAttribute(job_attrs, "print-color-mode", IPP_TAG_ZERO))
	  == NULL)
	attr = ippFindAttribute(printer_attrs, "print-color-mode-default",
				IPP_TAG_ZERO);

      if (attr)
      {
	ippAttributeString(attr, valstr, sizeof(valstr));
	value = valstr;
	if (value && !strcmp(value, "monochrome"))
	  pcm = 0;
      }

      num_presets = ppd_cache->num_presets[pcm][pq];
      presets     = ppd_cache->presets[pcm][pq];

      for (i = 0; i < num_presets; i ++)
	num_options = cupsAddOption(presets[i].name, presets[i].value,
				    num_options, options);
    }

    //
    // Mark the PPD with the options...
    //

    ppdMarkDefaults(ppd);
    ppdMarkOptions(ppd, num_options, *options);
  }

  cupsFreeOptions(num_media_col, media_col);

  return (num_options);
}


//
// 'ppdLoadAttributes()' - Load IPP attributes from a PPD file to
//                         create printer IPP attriutes which describe
//                         the capabilities of the printer. Note that
//                         this is for telling filters and drivers how
//                         to print a job on the printer and NOT for a
//                         PPD retro-fitting Printer Application (or
//                         any other PPD-supporting IPP print server)
//                         to answer a get-printer-attributes IPP
//                         request.
//

ipp_t *					// O - IPP attributes or `NULL`
					//     on error
ppdLoadAttributes(
    ppd_file_t   *ppd)			// I - PPD file data
{
  int		i, j;			// Looping vars
  char          *ptr;
  cups_array_t  *docformats;		// document-format-supported
					// values
  ipp_t		*attrs;			// Attributes
  ipp_attribute_t *attr;		// Current attribute
  ipp_attribute_t *aux_attr;		// Auxiliary attribute
  ipp_t		*col;			// Current collection value
  ppd_attr_t	*ppd_attr;		// PPD attribute
  ppd_option_t	*ppd_option;		// PPD option
  ppd_choice_t	*ppd_choice;		// PPD choice
  ppd_size_t	*ppd_size;		// Default PPD size
  pwg_size_t	*pwg_size,		// Current PWG size
		*default_size = NULL;	// Default PWG size
  const char	*default_source = NULL,	// Default media source
		*default_type = NULL,	// Default media type
		*default_output_bin = NULL,	// Default output bin
		*default_output_order = NULL;	// Default output order
  pwg_map_t	*pwg_map;		// Mapping from PWG to PPD keywords
  ppd_cache_t	*pc;			// PPD cache
  ppd_pwg_finishings_t *finishings;	// Current finishings value
  const char	*template;		// Current finishings-template value
  int		num_margins;		// Number of media-xxx-margin-supported
					// values
  int		margins[10];		// media-xxx-margin-supported values
  int		xres = 0,		// Default horizontal resolution
		yres = 0;		// Default vertical resolution
  int           is_texttotext;
  int		num_items;		// Number of IPP attribute values
  const char	*items[20];		// IPP attribute values
  char		item_buf[64];		// RS value
  int           res_x[20], res_y[20], int_item[20];
  char          *val,
                *def_output_bin = NULL,
                buf[1024],
                cmd[256],               // Device ID CMD: string
                *cmdptr;
  int           def_found,
                order,
                face_up,
                have_custom_size = 0;
  cups_page_header2_t header;
  static const char * const pdls[][2] =
  {                                     // MIME media type to command set
					// mapping
    { "application/postscript", "POSTSCRIPT,PS" },
    { "application/vnd.cups-postscript", "POSTSCRIPT,PS" },
    { "application/pdf", "PDF" },
    { "application/vnd.cups-pdf", "PDF" },
    { "application/vnd.canon-cpdl", "CPDL" },
    { "application/vnd.canon-lips", "LIPS" },
    { "application/vnd.hp-PCL", "PCL" },
    { "application/vnd.hp-PCLXL", "PCLXL" },
    { "application/vnd.ms-xpsdocument", "XPS" },
    { "image/jpeg", "JPEG" },
    { "image/pwg-raster", "PWGRaster" },
    { "image/urf", "URF,AppleRaster" },
    { "application/PCLm", "PCLM" },
    { "image/tiff", "TIFF" }
  };
  static const int	orientation_requested_supported[4] =
  {					// orientation-requested-supported
					// values
    IPP_ORIENT_PORTRAIT,
    IPP_ORIENT_LANDSCAPE,
    IPP_ORIENT_REVERSE_LANDSCAPE,
    IPP_ORIENT_REVERSE_PORTRAIT
  };
  static const char * const overrides_supported[] =
  {					// overrides-supported
    "document-numbers",
    "media",
    "media-col",
    "orientation-requested",
    "pages"
  };
  static const char * const print_color_mode_supported[] =
  {					// print-color-mode-supported values
    "monochrome"
  };
  static const char * const print_color_mode_supported_color[] =
  {					// print-color-mode-supported values
    "auto",
    "color",
    "monochrome"
  };
  static const int	print_quality_supported[] =
  {					// print-quality-supported values
    IPP_QUALITY_DRAFT,
    IPP_QUALITY_NORMAL,
    IPP_QUALITY_HIGH
  };
  static const char * const print_content_optimize_supported[] =
  {					// print-content-optimize-supported
					// values
    "auto",
    "text",
    "graphic",
    "text-and-graphic",
    "photo"
  };
  static const char * const sides_supported[] =
  {					// sides-supported values
    "one-sided",
    "two-sided-long-edge",
    "two-sided-short-edge"
  };


  //
  // Open the PPD file...
  //

  if (ppd == NULL)
    return (NULL);

  if (ppd->cache == NULL)
  {
    if ((pc = ppdCacheCreateWithPPD(ppd)) != NULL)
      ppd->cache = pc;
    else
      return (NULL);
  }
  else
    pc = ppd->cache;

  if ((ppd_size = ppdPageSize(ppd, NULL)) != NULL)
  {
    //
    // Look up default size...
    //

    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
    {
      if (!strcmp(pwg_size->map.ppd, ppd_size->name))
      {
        default_size = pwg_size;
        break;
      }
    }

    //
    // Try to find one with the same size...
    //

    if (!default_size)
    {
      for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
      {
	if (_PPD_PWG_EQUIVALENT(PWG_FROM_POINTS(ppd_size->width),
				pwg_size->width) &&
	    _PPD_PWG_EQUIVALENT(PWG_FROM_POINTS(ppd_size->length),
				pwg_size->length))
	{
	  default_size = pwg_size;
	  break;
	}
      }
    }
 }

  //
  // Default to A4 or Letter...
  //

  if (!default_size)
    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
    {
      if (!strcmp(pwg_size->map.ppd, "Letter") ||
	  !strcmp(pwg_size->map.ppd, "A4"))
      {
        default_size = pwg_size;
        break;
      }
    }

  //
  // Last resort: First size in the list
  //

  if (!default_size)
    default_size = pc->sizes;

  if ((ppd_choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL)
    default_source = ppdCacheGetSource(pc, ppd_choice->choice);

  if ((ppd_choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL)
    default_type = ppdCacheGetType(pc, ppd_choice->choice);

  if ((ppd_choice = ppdFindMarkedChoice(ppd, "OutputOrder")) != NULL)
    default_output_order = ppd_choice->choice;
  if ((ppd_choice = ppdFindMarkedChoice(ppd, "OutputBin")) != NULL)
  {
    default_output_bin = ppd_choice->choice;
    if (default_output_order == NULL &&
	(ppd_attr = ppdFindAttr(ppd, "PageStackOrder",
				ppd_choice->choice)) != NULL)
      default_output_order = ppd_attr->value;
  }
  if (default_output_order == NULL &&
      (ppd_attr = ppdFindAttr(ppd, "DefaultOutputOrder", 0)) != NULL)
    default_output_order = ppd_attr->value;
  if (default_output_order == NULL)
    default_output_order = "Normal";

  //
  // Data formats which the printer understands, if the PPD specifies
  // a driver in a "*cupsFilter(2): ..." line, we add the input format
  // of the driver, as this is what our filter need to produce.
  //

  docformats = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, NULL,
			     (cups_afree_func_t)free);
  is_texttotext = 0;
  strcpy(cmd, "CMD:");
  cmdptr = cmd + 4;
  if (ppd->num_filters)
  {
    char *filter;
    for (filter = (char *)cupsArrayFirst(pc->filters);
	 filter;
	 filter = (char *)cupsArrayNext(pc->filters))
    {
      // String of the "*cupsfilter:" or "*cupsfilter2:" line
      strncpy(buf, filter, sizeof(buf) - 1);

      // Do not count in "comandto..." filters
      if (strstr(buf, " commandto"))
	continue;

      // Is the PPD file using the "texttotext" filter?
      if (strcmp(buf + strlen(buf) - 10, "texttotext") == 0)
	is_texttotext = 1;

      // Separate the first word
      ptr = buf;
      while (*ptr && !isspace(*ptr)) ptr ++;
      if (*ptr)
	*ptr = '\0';

      // Check whether the second word is not the cost value, then we have
      // a "*cupsFilter2:* line and the second word is the printer's input
      // format
      ptr ++;
      while (*ptr && isspace(*ptr)) ptr ++;
      if (!isdigit(*ptr))
      {
	strcpy(buf, ptr);
	ptr = buf;
	while (*ptr && !isspace(*ptr)) ptr ++;
	if (*ptr)
	  *ptr = '\0';
      }

      // Add it to the list of output formats
      cupsArrayAdd(docformats, strdup(buf));

      // Add it to the CMD: string for the device ID
      if (!ppdFindAttr(ppd, "1284DeviceId", NULL))
      {
	// See if it is a known MIME media type and map to the corresponding
	// 1284 command-set name...
	for (i = 0; i < (sizeof(pdls) / sizeof(pdls[0])); i ++)
	{
	  if (!strcasecmp(buf, pdls[i][0]) &&
	      strlen(pdls[i][1]) < sizeof(cmd) - (size_t)(cmdptr - cmd) - 3)
	  {
	    // MIME media type matches, append this CMD value...
	    if (cmdptr > cmd + 4)
	      *cmdptr++ = ',';
	    strcpy(cmdptr, pdls[i][1]);
	    cmdptr += strlen(cmdptr);
	  }
	}
      }
    }
    if (strlen(cmd) > 4)
    {
      cmdptr[0] = ';';
      cmdptr[1] = '\0';
    }
    else
      cmd[0] = '\0';
  }
  else
  {
    cupsArrayAdd(docformats, strdup("application/vnd.cups-postscript"));
    if (!ppdFindAttr(ppd, "1284DeviceId", NULL))
      strcpy(cmd, "CMD:POSTSCRIPT,PS;");
  }

  //
  // Create the attributes...
  //

  attrs = ippNew();

  //
  // Properties of supported Raster formats: Convert from strings
  // in the PPD files (from driverless PPD auto-generator) into
  // printer IPP attributes
  //

  for (i = 0; i < ppd->num_attrs; i ++)
  {
    ppd_attr = ppd->attrs[i];
    if ((cupsArrayFind(docformats, (void *)"image/urf") &&
	 strcasecmp(ppd_attr->name, "cupsUrfSupported") == 0) ||
	(cupsArrayFind(docformats, (void *)"image/pwg-raster") &&
	 strncasecmp(ppd_attr->name, "cupsPwgRaster", 13) == 0) ||
	(cupsArrayFind(docformats, (void *)"application/PCLm") &&
	 strncasecmp(ppd_attr->name, "cupsPclm", 8) == 0))
    {
      // Convert PPD-style names into IPP-style names
      ppdPwgUnppdizeName(ppd_attr->name + 4, item_buf, sizeof(item_buf), NULL);
      // Make array from comma-separated list
      strncpy(buf, ppd_attr->value, sizeof(buf) - 1);
      num_items = 0;
      char *p = buf;
      do
      {
	items[num_items ++] = p;
	if ((p = strchr(p, ',')) != NULL)
	{
	  *p = '\0';
	  p ++;
	}
	// Default resolution via urf-supported
	if (!strcmp(item_buf, "urf-supported") &&
	    (xres == 0 && yres == 0) &&// Default resolution not found in
				       // other raster-format-related PPD
				       // attribute yet, use first resolution in
				       // urf-supported
	    items[num_items - 1][0] == 'R' && items[num_items - 1][1] == 'S')
	  xres = yres = atoi(items[num_items - 1] + 2);
      }
      while (p);
      if (strlen(items[0]) > 3 &&
	  strncasecmp(items[0] + strlen(items[0]) - 3, "dpi", 3) == 0 &&
	  isdigit(*(items[0] + strlen(items[0]) - 4)))
      {
	// Resolutions
	for (j = 0; j < num_items; j ++)
	  if (sscanf(items[j], "%dx%d", &res_x[j], &res_y[j]) == 1)
	    res_y[j] = res_x[j];
	ippAddResolutions(attrs, IPP_TAG_PRINTER, item_buf, num_items,
			  IPP_RES_PER_INCH, res_x, res_y);
	// Default resolution?
	if ((xres == 0 && yres == 0) ||  // Take first in list if there is no
					 // separate PPD attribute providing a
					 // default resolution
	    strstr(item_buf, "default")) // PPD attribute with default
					 // resolution, take it, even if we
					 // already had the list from which we
					 // have taken the first item.
	{
	  xres = res_x[0];
	  yres = res_y[0];
	}
      }
      else if (strlen(items[0]) > 0 &&
	       ((int)(strtol(items[0], &p, 10) * 0) ||
		(errno == 0 && *p == '\0')))
      {
	// Integer number(s)
	for (j = 0; j < num_items; j ++)
	  int_item[j] = (int)strtol(items[j], NULL, 10);
	ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, item_buf,
		       num_items, int_item);
      }
      else
	// General
	ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, item_buf,
		      num_items, NULL, items);
    }
  }

  //
  // Resolution
  //

  // Ignore error exits of ppdRasterInterpretPPD(), if it found a resolution
  // setting before erroring it is OK for us
  ppdRasterInterpretPPD(&header, ppd, 0, NULL, NULL);
  // 100 dpi is default, this means that if we have 100 dpi here this
  // method failed to find the printing resolution
  buf[0] = '\0';
  if (header.HWResolution[0] != 100 || header.HWResolution[1] != 100)
  {
    xres = header.HWResolution[0];
    yres = header.HWResolution[1];
  }
  else if ((ppd_choice = ppdFindMarkedChoice(ppd, "Resolution")) != NULL)
    strncpy(buf, ppd_choice->choice, sizeof(buf) - 1);
  else if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL)
    strncpy(buf, ppd_attr->value, sizeof(buf) - 1);
  buf[sizeof(buf) - 1] = '\0';
  if (buf[0])
  {
    int x, y;
    // Use the marked resolution or the default resolution in the PPD...
    if ((i = sscanf(buf, "%dx%d", &x, &y)) == 1)
      y = x;
    if (i > 0)
    {
      xres = x;
      yres = y;
    }
  }
  if (xres == 0 || yres == 0)
  {
    // Use default of 300dpi...
    xres = yres = 300;
  }

  // Fax out PPD?
  if (((ppd_attr = ppdFindAttr(ppd, "cupsFax", NULL)) != NULL &&
       strcasecmp(ppd_attr->value, "True") == 0) ||
      ((ppd_attr = ppdFindAttr(ppd, "cupsIPPFaxOut", NULL)) != NULL &&
       strcasecmp(ppd_attr->value, "True") == 0))
  {
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "ipp-features-supported", NULL, "faxout");
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_URI),
		 "printer-uri-supported", NULL, "ipp://localhost/ipp/faxout");
  }
  
  // color-supported
  ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported",
		(char)ppd->color_device);

  // copies-default
  if (((ppd_choice = ppdFindMarkedChoice(ppd, "Copies")) == NULL ||
       (i = atoi(ppd_choice->choice)) <= 0) &&
      ((ppd_attr = ppdFindAttr(ppd, "DefaultCopies", NULL)) == NULL ||
       (i = atoi(ppd_attr->value)) <= 0))
    i = 1;
  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", i);

  // copies-supported
  ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1,
	      pc->max_copies > 0 ? pc->max_copies : 999);

  // document-format-supported
  attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		       "document-format-supported",
		       cupsArrayCount(docformats), NULL, NULL);
  for (ptr = (char *)cupsArrayFirst(docformats), i = 0; ptr;
       ptr = (char *)cupsArrayNext(docformats), i ++)
    ippSetString(attrs, &attr, i, ptr);

  // finishing-template-supported
  attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		       "finishing-template-supported",
		       cupsArrayCount(pc->templates) + 1, NULL, NULL);
  ippSetString(attrs, &attr, 0, "none");
  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates);
       template; i ++, template = (const char *)cupsArrayNext(pc->templates))
    ippSetString(attrs, &attr, i, template);

  // finishings-col-database
  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-database",
			   cupsArrayCount(pc->templates) + 1, NULL);

  col = ippNew();
  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template",
	       NULL, "none");
  ippSetCollection(attrs, &attr, 0, col);
  ippDelete(col);

  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates);
       template; i ++, template = (const char *)cupsArrayNext(pc->templates))
  {
    col = ippNew();
    ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template",
		 NULL, template);
    ippSetCollection(attrs, &attr, i, col);
    ippDelete(col);
  }

  // finishings-col-default
  col = ippNew();
  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template",
	       NULL, "none");
  ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col);
  ippDelete(col);

  // finishings-col-ready
  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-ready",
			   cupsArrayCount(pc->templates) + 1, NULL);

  col = ippNew();
  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template",
	       NULL, "none");
  ippSetCollection(attrs, &attr, 0, col);
  ippDelete(col);

  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template;
       i ++, template = (const char *)cupsArrayNext(pc->templates))
  {
    col = ippNew();
    ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template",
		 NULL, template);
    ippSetCollection(attrs, &attr, i, col);
    ippDelete(col);
  }

  // finishings-col-supported
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
	       "finishings-col-supported", NULL, "finishing-template");

  // finishings-default
  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default",
		IPP_FINISHINGS_NONE);

  // finishings-ready
  attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
			"finishings-ready",
			cupsArrayCount(pc->finishings) + 1, NULL);
  ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
  for (i = 1,
	 finishings = (ppd_pwg_finishings_t *)cupsArrayFirst(pc->finishings);
       finishings;
       i ++,
	 finishings = (ppd_pwg_finishings_t *)cupsArrayNext(pc->finishings))
    ippSetInteger(attrs, &attr, i, (int)finishings->value);

  // finishings-supported
  attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
			"finishings-supported",
			cupsArrayCount(pc->finishings) + 1, NULL);
  ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
  for (i = 1,
	 finishings = (ppd_pwg_finishings_t *)cupsArrayFirst(pc->finishings);
       finishings;
       i ++,
	 finishings = (ppd_pwg_finishings_t *)cupsArrayNext(pc->finishings))
    ippSetInteger(attrs, &attr, i, (int)finishings->value);

  // media-bottom-margin-supported
  for (i = 0, num_margins = 0, pwg_size = pc->sizes;
       i < pc->num_sizes &&
	 num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
       i ++, pwg_size ++)
  {
    for (j = 0; j < num_margins; j ++)
    {
      if (margins[j] == pwg_size->bottom)
        break;
    }

    if (j >= num_margins)
      margins[num_margins ++] = pwg_size->bottom;
  }

  for (i = 0; i < (num_margins - 1); i ++)
  {
    for (j = i + 1; j < num_margins; j ++)
    {
      if (margins[i] > margins[j])
      {
        int mtemp = margins[i];

        margins[i] = margins[j];
        margins[j] = mtemp;
      }
    }
  }

  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		 "media-bottom-margin-supported", num_margins, margins);

  // landscape-orientation-requested-preferred
  if (ppd->landscape < 0)      // Direction the printer rotates landscape
			       // (-90)
  {
    // Rotate clockwise (reverse landscape)
    ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		  "landscape-orientation-requested-preferred", 5);
  }
  else if (ppd->landscape > 0) // (+90)
  {
    // Rotate counter-clockwise (landscape)
    ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		  "landscape-orientation-requested-preferred", 4);
  }

  // media-col-database, media-col-default, media-col-ready, media-default,
  // media-ready
  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database",
			   pc->num_sizes, NULL);
  if (ppd_size && strncasecmp(ppd_size->name, "Custom", 6) == 0)
  {
    // media-col-default - Custom size
    int w = (int)(ppd_size->width / 72.0 * 2540.0);
    int l = (int)(ppd_size->length / 72.0 * 2540.0);
    if (w >= pc->custom_min_width && w <= pc->custom_max_width &&
	l >= pc->custom_min_length && l <= pc->custom_max_length)
    {
      pwgFormatSizeName(buf, sizeof(buf), NULL, "custom", w, l, NULL);
      col = create_media_col(buf, default_source, default_type, w, l,
			     (int)(ppd_size->bottom / 72.0 * 2540.0),
			     (int)(ppd_size->left / 72.0 * 2540.0),
			     w - (int)(ppd_size->right / 72.0 * 2540.0),
			     l - (int)(ppd_size->top / 72.0 * 2540.0));
      ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col);
      ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col);
      ippDelete(col);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default",
		   NULL, buf);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready",
		   NULL, buf);
      have_custom_size = 1;
    }
  }

  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
  {
    if ((ptr = strchr(pwg_size->map.ppd, '.')) != NULL)
    {
      // Page size variant, use original size's PWG name but PPD's
      // dimensions and margins.
      //
      // This way we can associate extra size for overspraying when
      // printing borderless with the original page size, for example
      // for cfFilterPWGToRaster() to correct original-sized PWG
      // Raster to overspray-sized CUPS Raster (for HPLIP for
      // example). Filters can also switch to overspray size if the
      // original size plus zero margins is requested for a job
      pwg_size_t *size;
      memmove(buf, pwg_size->map.ppd, ptr - pwg_size->map.ppd);
      buf[ptr - pwg_size->map.ppd] = '\0';
      for (j = 0, size = pc->sizes; j < pc->num_sizes; j ++, size ++)
	// Find entry of original size
	if (strcasecmp(buf, size->map.ppd) == 0)
	{
	  ptr = size->map.pwg;
	  break;
	}
      if (j == pc->num_sizes)
	ptr = pwg_size->map.pwg;
    }
    else
      ptr = pwg_size->map.pwg;
    col = create_media_col(ptr, NULL, NULL, pwg_size->width, pwg_size->length,
			   pwg_size->bottom, pwg_size->left, pwg_size->right,
			   pwg_size->top);
    if (is_texttotext)
    {
      // Add info about the number of lines and number of columns defined in
      // the PPD for each page size to the appropriate media-col-database
      // entries
      snprintf(buf, sizeof(buf), "%sNumColumns", pwg_size->map.ppd);
      if ((ppd_choice = ppdFindMarkedChoice(ppd, buf)) != NULL &&
	  (j = atoi(ppd_choice->choice)) > 0)
	ippAddInteger(col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "num-columns", j);
      snprintf(buf, sizeof(buf), "%sNumLines", pwg_size->map.ppd);
      if ((ppd_choice = ppdFindMarkedChoice(ppd, buf)) != NULL &&
	  (j = atoi(ppd_choice->choice)) > 0)
	ippAddInteger(col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "num-lines", j);
    }
    ippSetCollection(attrs, &attr, i, col);
    ippDelete(col);
    if (!have_custom_size && pwg_size == default_size)
    {
      // media-col-default - Standard size
      col = create_media_col(ptr, default_source, default_type,
			     pwg_size->width, pwg_size->length,
			     pwg_size->bottom, pwg_size->left,
			     pwg_size->right, pwg_size->top);
      ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col);
      ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col);
      ippDelete(col);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, ptr);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, ptr);
    }
  }

  // media-left-margin-supported
  for (i = 0, num_margins = 0, pwg_size = pc->sizes;
       i < pc->num_sizes &&
	 num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
       i ++, pwg_size ++)
  {
    for (j = 0; j < num_margins; j ++)
    {
      if (margins[j] == pwg_size->left)
        break;
    }

    if (j >= num_margins)
      margins[num_margins ++] = pwg_size->left;
  }

  for (i = 0; i < (num_margins - 1); i ++)
  {
    for (j = i + 1; j < num_margins; j ++)
    {
      if (margins[i] > margins[j])
      {
        int mtemp = margins[i];

        margins[i] = margins[j];
        margins[j] = mtemp;
      }
    }
  }

  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		 "media-left-margin-supported", num_margins, margins);

  // media-right-margin-supported
  for (i = 0, num_margins = 0, pwg_size = pc->sizes;
       i < pc->num_sizes &&
	 num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
       i ++, pwg_size ++)
  {
    for (j = 0; j < num_margins; j ++)
    {
      if (margins[j] == pwg_size->right)
        break;
    }

    if (j >= num_margins)
      margins[num_margins ++] = pwg_size->right;
  }

  for (i = 0; i < (num_margins - 1); i ++)
  {
    for (j = i + 1; j < num_margins; j ++)
    {
      if (margins[i] > margins[j])
      {
        int mtemp = margins[i];

        margins[i] = margins[j];
        margins[j] = mtemp;
      }
    }
  }

  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		 "media-right-margin-supported", num_margins, margins);

  // media-supported
  attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		       "media-supported",
		       pc->num_sizes + (ppd->variable_sizes ? 2 : 0),
		       NULL, NULL);
  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
    ippSetString(attrs, &attr, i, pwg_size->map.pwg);
  if (ppd->variable_sizes)
  {
    ippSetString(attrs, &attr, pc->num_sizes, pc->custom_min_keyword);
    ippSetString(attrs, &attr, pc->num_sizes + 1, pc->custom_max_keyword);
  }

  // media-size-supported
  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported",
			   pc->num_sizes + (ppd->variable_sizes ? 1 : 0), NULL);
  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
  {
    col = create_media_size(pwg_size->width, pwg_size->length);
    ippSetCollection(attrs, &attr, i, col);
    ippDelete(col);
  }
  if (ppd->variable_sizes)
  {
    col = create_media_size_ranges(pc->custom_min_width, pc->custom_min_length,
				   pc->custom_max_width, pc->custom_max_length);
    ippSetCollection(attrs, &attr, pc->num_sizes, col);
    ippDelete(col);
  }

  // media-source-supported
  if (pc->num_sources > 0)
  {
    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			 "media-source-supported", pc->num_sources,
			 NULL,  NULL);
    for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i ++, pwg_map ++)
      ippSetString(attrs, &attr, i, pwg_map->pwg);
  }
  else
  {
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "media-source-supported", NULL, "auto");
  }

  // media-top-margin-supported
  for (i = 0, num_margins = 0, pwg_size = pc->sizes;
       i < pc->num_sizes &&
	 num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
       i ++, pwg_size ++)
  {
    for (j = 0; j < num_margins; j ++)
    {
      if (margins[j] == pwg_size->top)
        break;
    }

    if (j >= num_margins)
      margins[num_margins ++] = pwg_size->top;
  }

  for (i = 0; i < (num_margins - 1); i ++)
  {
    for (j = i + 1; j < num_margins; j ++)
    {
      if (margins[i] > margins[j])
      {
        int mtemp = margins[i];

        margins[i] = margins[j];
        margins[j] = mtemp;
      }
    }
  }

  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		 "media-top-margin-supported", num_margins, margins);

  // media-type-supported
  if (pc->num_types > 0)
  {
    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			 "media-type-supported", pc->num_types, NULL,  NULL);
    for (i = 0, pwg_map = pc->types; i < pc->num_types; i ++, pwg_map ++)
      ippSetString(attrs, &attr, i, pwg_map->pwg);
  }
  else
  {
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "media-type-supported", NULL, "auto");
  }

  // orientation-requested-default
  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		"orientation-requested-default", IPP_ORIENT_PORTRAIT);

  // orientation-requested-supported
  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		 "orientation-requested-supported",
		 (int)(sizeof(orientation_requested_supported) /
		       sizeof(orientation_requested_supported[0])),
		 orientation_requested_supported);

  // output-bin-supported and output-bin-default
  def_found = 0;
  if (pc->num_bins > 0)
  {
    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			 "output-bin-supported", pc->num_bins, NULL,  NULL);
    for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i ++, pwg_map ++)
    {
      ippSetString(attrs, &attr, i, pwg_map->pwg);
      if (default_output_bin && !strcmp(pwg_map->ppd, default_output_bin))
      {
	def_output_bin = pwg_map->pwg;
	def_found = 1;
      }
      if ((ppd_attr = ppdFindAttr(ppd, "PageStackOrder",
				  pwg_map->ppd)) != NULL)
	val = ppd_attr->value;
      else
	val = NULL;
      order = (val && strcasecmp(val, "Normal") == 0 ? 1 :
	       (val && strcasecmp(val, "Reverse") == 0 ? -1 : 0));
      if (i == 0 ||
	  (!def_found && ((order == 1 &&
			   strcasecmp(default_output_order, "Normal")) ||
			  (order == -1 &&
			   strcasecmp(default_output_order, "Reverse")))))
	def_output_bin = pwg_map->pwg;
      face_up = (((strcasestr(pwg_map->pwg, "face") &&
		   strcasestr(pwg_map->pwg, "up")) ||
		  (strcasestr(pwg_map->ppd, "face") &&
		   strcasestr(pwg_map->ppd, "up"))) ? 1 :
		 (((strcasestr(pwg_map->pwg, "face") &&
		    strcasestr(pwg_map->pwg, "down")) ||
		   (strcasestr(pwg_map->ppd, "face") &&
		    strcasestr(pwg_map->ppd, "down"))) ? -1 : 0));
      if (order == 0)
      {
	if (face_up != 0)
	  order = -face_up;
	else
	  order = (strcasecmp(default_output_order, "Normal") == 0 ? 1 :
		   (strcasecmp(default_output_order, "Reverse") == 0 ? -1 : 0));
      }
      if (face_up == 0 && order != 0)
	face_up = -order;
      snprintf(buf, sizeof(buf),
	       "type=unknown;maxcapacity=-2;remaining=-2;status=5;stackingorder=%s;pagedelivery=%s;name=%s",
	      (order == -1 ? "lastToFirst" :
	       (order == 1 ? "firstToLast" :
		"unknown")),
	      (face_up == -1 ? "faceDown" :
	       (face_up == -1 ? "faceUp" :
		"unknown")),
	      pwg_map->ppd);
      if (i == 0)
	aux_attr = ippAddOctetString(attrs, IPP_TAG_PRINTER,
				     "printer-output-tray", buf, strlen(buf));
      else
	ippSetOctetString(attrs, &aux_attr, i, buf, strlen(buf)); 
    }
  }
  else
  {
    order = (strcasecmp(default_output_order, "Normal") == 0 ? 1 :
	     (strcasecmp(default_output_order, "Reverse") == 0 ? -1 : 1));
    def_output_bin = (order == 1 ? "face-down" : "face-up");
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		 "output-bin-supported", NULL, def_output_bin);
    snprintf(buf, sizeof(buf),
	     "type=unknown;maxcapacity=-2;remaining=-2;status=5;stackingorder=%s;pagedelivery=%s;name=%s",
	     (order == -1 ? "lastToFirst" : "firstToLast"),
	     (order == -1 ? "faceUp" : "faceDown"),
	     def_output_bin);
    ippAddOctetString(attrs, IPP_TAG_PRINTER,
		      "printer-output-tray", buf, strlen(buf));
  }
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	       "output-bin-default", NULL, def_output_bin);

  // overrides-supported
  ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		"overrides-supported",
		(int)(sizeof(overrides_supported) /
		      sizeof(overrides_supported[0])),
		NULL, overrides_supported);

  // page-ranges-supported
  ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);

  // pages-per-minute
  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute",
		ppd->throughput);

  // pages-per-minute-color
  if (ppd->color_device)
    ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		  "pages-per-minute-color", ppd->throughput);

  // print-color-mode-default
  bool mono =
    (ppd->color_device &&
     (((ppd_choice = ppdFindMarkedChoice(ppd, "ColorModel")) != NULL &&
       (strcasestr(ppd_choice->choice, "mono") ||
	strcasestr(ppd_choice->choice, "gray") ||
	strcasestr(ppd_choice->choice, "bw") ||
	strcasestr(ppd_choice->choice, "bi-level") ||
	strcasestr(ppd_choice->choice, "bi_level"))) ||
      ((ppd_choice = ppdFindMarkedChoice(ppd, "OutputMode")) != NULL &&
       (strcasestr(ppd_choice->choice, "mono") ||
	strcasestr(ppd_choice->choice, "gray") ||
	strcasestr(ppd_choice->choice, "bw") ||
	strcasestr(ppd_choice->choice, "bi-level") ||
	strcasestr(ppd_choice->choice, "bi_level")))));
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	       "print-color-mode-default", NULL,
	       ppd->color_device && !mono ? "auto" : "monochrome");

  // print-color-mode-supported
  if (ppd->color_device)
    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		  "print-color-mode-supported",
		  (int)(sizeof(print_color_mode_supported_color) /
			sizeof(print_color_mode_supported_color[0])),
		  NULL, print_color_mode_supported_color);
  else
    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		  "print-color-mode-supported",
		  (int)(sizeof(print_color_mode_supported) /
			sizeof(print_color_mode_supported[0])),
		  NULL, print_color_mode_supported);

  // print-content-optimize-default
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
	       "print-content-optimize-default", NULL, "auto");

  // print-content-optimize-supported
  ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		"print-content-optimize-supported",
		(int)(sizeof(print_content_optimize_supported) /
		      sizeof(print_content_optimize_supported[0])),
		NULL, print_content_optimize_supported);

  // print-quality-default
  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default",
		IPP_QUALITY_NORMAL);

  // print-quality-supported
  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		 "print-quality-supported",
		 (int)(sizeof(print_quality_supported) /
		       sizeof(print_quality_supported[0])),
		 print_quality_supported);

  // print-rendering-intent-default
  if ((ppd_choice =
       ppdFindMarkedChoice(ppd, "cupsRenderingIntent")) != NULL ||
      (ppd_choice =
       ppdFindMarkedChoice(ppd, "print-rendering-intent")) != NULL)
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		 "print-rendering-intent-default", NULL, ppd_choice->choice);
  else
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "print-rendering-intent-default", NULL, "auto");

  // print-rendering-intent-supported
  if (((ppd_option = ppdFindOption(ppd, "cupsRenderingIntent")) != NULL ||
       (ppd_option = ppdFindOption(ppd, "print-rendering-intent")) != NULL) &&
      ppd_option->num_choices > 0)
  {
    for (i = 0; i < ppd_option->num_choices && i < sizeof(items); i ++)
      items[i] = ppd_option->choices[i].choice;
    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		  "print-rendering-intent-supported", i, NULL, items);
  }
  else
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "print-rendering-intent-supported", NULL, "auto");

  // printer-device-id
  if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL)
  {
    //
    // Use the device ID string from the PPD...
    //

    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id",
		 NULL, ppd_attr->value);
  }
  else
  {
    //
    // Synthesize a device ID string...
    //

    char	device_id[1024];		// Device ID string

    snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;%s",
	     ppd->manufacturer, ppd->modelname, cmd);

    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id",
		 NULL, device_id);
  }

  // printer-info
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL,
	       ppd->nickname);

  // printer-input-tray
  if (pc->num_sources > 0)
  {
    for (i = 0, attr = NULL; i < pc->num_sources; i ++)
    {
      char	input_tray[1024];	// printer-input-tray value

      if (!strcmp(pc->sources[i].pwg, "manual") ||
	  strstr(pc->sources[i].pwg, "-man") != NULL)
        snprintf(input_tray, sizeof(input_tray),
		 "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s",
		 pc->sources[i].pwg);
      else
        snprintf(input_tray, sizeof(input_tray),
		 "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s",
		 pc->sources[i].pwg);

      if (attr)
        ippSetOctetString(attrs, &attr, i, input_tray, (int)strlen(input_tray));
      else
        attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray",
				 input_tray, (int)strlen(input_tray));
    }
  }
  else
  {
    static const char *printer_input_tray =
      "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto";

    ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray",
		      printer_input_tray, (int)strlen(printer_input_tray));
  }

  // printer-make-and-model
  char	make_model[256];		// Manufacturer and Model value
  if (cfIEEE1284NormalizeMakeModel(ppd->nickname, ppd->manufacturer,
				   CF_IEEE1284_NORMALIZE_HUMAN,
				   NULL, make_model, sizeof(make_model),
				   NULL, NULL, NULL) == NULL)
    snprintf(make_model, sizeof(make_model), "%s", ppd->nickname);
  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model",
	       NULL, make_model);

  // printer-resolution-default
  ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default",
		   IPP_RES_PER_INCH, xres, yres);

  // printer-resolution-supported
  ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported",
		   IPP_RES_PER_INCH, xres, yres);

  // sides-default
  if (pc->sides_option && pc->sides_2sided_long &&
      ppdIsMarked(ppd, pc->sides_option, pc->sides_2sided_long))
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "sides-default", NULL, "two-sided-long-edge");
  else if (pc->sides_option && pc->sides_2sided_short &&
	   ppdIsMarked(ppd, pc->sides_option, pc->sides_2sided_short))
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "sides-default", NULL, "two-sided-long-short");
  else
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "sides-default", NULL, "one-sided");

  // sides-supported
  if (pc->sides_option && pc->sides_2sided_long)
    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		  "sides-supported",
		  (int)(sizeof(sides_supported) / sizeof(sides_supported[0])),
		  NULL, sides_supported);
  else
    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD),
		 "sides-supported", NULL, "one-sided");

  // Extra attributes for "texttotext" filter 
  if (is_texttotext)
  {
    if ((ppd_choice = ppdFindMarkedChoice(ppd, "OverLongLines")) != NULL &&
	ppd_choice->choice[0])
    {
      ppdPwgUnppdizeName(ppd_choice->choice, buf, sizeof(buf), NULL);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "over-long-lines-default", NULL, buf);
    }

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "TabWidth")) != NULL &&
	(i = atoi(ppd_choice->choice)) > 0)
      ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "tab-width-default", i);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "Pagination")) != NULL &&
	ppd_choice->choice[0])
    {
      ppdPwgUnppdizeName(ppd_choice->choice, buf, sizeof(buf), NULL);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "pagination-default", NULL, buf);
    }

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "page-left")) != NULL &&
	(i = atoi(ppd_choice->choice)) > 0)
      ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "page-left-default", i);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "page-right")) != NULL &&
	(i = atoi(ppd_choice->choice)) > 0)
      ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "page-right-default", i);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "page-top")) != NULL &&
	(i = atoi(ppd_choice->choice)) > 0)
      ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "page-top-default", i);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "page-bottom")) != NULL &&
	(i = atoi(ppd_choice->choice)) > 0)
      ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "page-bottom-default", i);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "PrinterEncoding")) != NULL &&
	ppd_choice->choice[0])
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
		   "printer-encoding-default", NULL, ppd_choice->choice);

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "NewlineCharacters")) != NULL &&
	ppd_choice->choice[0])
    {
      ppdPwgUnppdizeName(ppd_choice->choice, buf, sizeof(buf), NULL);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "newline-characters-default", NULL, buf);
    }

    if ((ppd_choice = ppdFindMarkedChoice(ppd, "SendFF")) != NULL &&
	ppd_choice->choice[0])
    {
      ppdPwgUnppdizeName(ppd_choice->choice, buf, sizeof(buf), NULL);
      ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "send-ff-default", NULL, buf);
    }

  }

  // Clean up
  cupsArrayDelete(docformats);
  return (attrs);
}

//
// 'create_media_col()' - Create a media-col value.
//

static ipp_t *				// O - media-col collection
create_media_col(const char *media,	// I - Media name
		 const char *source,	// I - Media source, if any
		 const char *type,	// I - Media type, if any
		 int        width,	// I - x-dimension in 2540ths
		 int        length,	// I - y-dimension in 2540ths
		 int        bottom,	// I - Bottom margin in 2540ths
		 int        left,	// I - Left margin in 2540ths
		 int        right,	// I - Right margin in 2540ths
		 int        top)	// I - Top margin in 2540ths
{
  ipp_t		*media_col = ippNew(),	// media-col value
		*media_size = create_media_size(width, length);
					// media-size value
  char		media_key[256];		// media-key value
  const char	*media_key_suffix = "";	// media-key suffix


  if (bottom == 0 && left == 0 && right == 0 && top == 0)
    media_key_suffix = "_borderless";

  if (type && source)
    snprintf(media_key, sizeof(media_key), "%s_%s_%s%s", media, source,
	     type, media_key_suffix);
  else if (type)
    snprintf(media_key, sizeof(media_key), "%s__%s%s", media, type,
	     media_key_suffix);
  else if (source)
    snprintf(media_key, sizeof(media_key), "%s_%s%s", media, source,
	     media_key_suffix);
  else
    snprintf(media_key, sizeof(media_key), "%s%s", media, media_key_suffix);

  ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key",
	       NULL, media_key);
  ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
  ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	       "media-size-name", NULL, media);
  if (bottom >= 0)
    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		  "media-bottom-margin", bottom);
  if (left >= 0)
    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		  "media-left-margin", left);
  if (right >= 0)
    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		  "media-right-margin", right);
  if (top >= 0)
    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		  "media-top-margin", top);
  if (source)
    ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		 "media-source", NULL, source);
  if (type)
    ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		 "media-type", NULL, type);

  ippDelete(media_size);

  return (media_col);
}


//
// 'create_media_size()' - Create a media-size value.
//

static ipp_t *				// O - media-col collection
create_media_size(int width,		// I - x-dimension in 2540ths
		  int length)		// I - y-dimension in 2540ths
{
  ipp_t	*media_size = ippNew();		// media-size value


  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension",
		width);
  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension",
		length);

  return (media_size);
}


//
// 'create_media_size_range()' - Create a media-size range for custom sizes.
//

static ipp_t *					// O - media-col collection
create_media_size_ranges(int min_width,		// I - min x dim in 2540ths
			 int min_length,	// I - nin y dim in 2540ths
			 int max_width,		// I - max x dim in 2540ths
			 int max_length)	// I - max y dim in 2540ths
{
  ipp_t	*media_size_ranges = ippNew();		// media-size value

  ippAddRange(media_size_ranges, IPP_TAG_PRINTER, "x-dimension", min_width,
	      max_width);
  ippAddRange(media_size_ranges, IPP_TAG_PRINTER, "y-dimension", min_length,
	      max_length);

  return (media_size_ranges);
}
