//
//   PWG Raster/Apple Raster/PCLm/PDF/IPP legacy PPD generator for libppd.
//
//   Copyright © 2024 by OpenPrinting
//   Copyright © 2016-2019 by Till Kamppeter.
//   Copyright © 2017-2019 by Sahil Arora.
//   Copyright © 2018-2019 by Deepak Patankar.
//
//   The PPD generator is based on the PPD generator for the CUPS
//   "lpadmin -m everywhere" functionality in the cups/ppd-cache.c
//   file. The copyright of this file is:
//
//   Copyright © 2010-2016 by Apple Inc.
//
//   Licensed under Apache License v2.0.  See the file "LICENSE" for more
//   information.
//

#include <config.h>
#include <limits.h>
#include <cups/cups.h>
#include <cups/dir.h>
#include <ppd/ppd.h>
#include <ppd/string-private.h>
#include <cupsfilters/ipp.h>
#include <cupsfilters/catalog.h>


//
// Include necessary headers.
//

#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <cups/pwg.h>


//
// Macros to work around typos in older libcups version
//

#if (CUPS_VERSION_MAJOR < 2) || ((CUPS_VERSION_MAJOR == 2) && ((CUPS_VERSION_MINOR < 3) || ((CUPS_VERSION_MINOR == 3) && (CUPS_VERSION_PATCH < 1))))
#define IPP_FINISHINGS_CUPS_FOLD_ACCORDION IPP_FINISHINGS_CUPS_FOLD_ACCORDIAN
#define IPP_FINISHINGS_FOLD_ACCORDION IPP_FINISHINGS_FOLD_ACCORDIAN
#endif


//
// Local functions...
//

static int	http_connect(http_t **http, const char *url, char *resource,
			     size_t ressize);
static void	ppd_put_string(cups_file_t *fp, cups_lang_t *lang, const char *ppd_option, const char *ppd_choice, const char *pwg_msgid);


//
// The code below is borrowed from the CUPS 2.2.x upstream repository
// (via patches attached to https://www.cups.org/str.php?L4258). This
// allows for automatic PPD generation already with CUPS versions older
// than CUPS 2.2.x. We have also an additional test and development
// platform for this code. Taken from cups/ppd-cache.c,
// cups/string-private.h, cups/string.c.
//
// The advantage of PPD generation instead of working with System V
// interface scripts is that the print dialogs of the clients do not
// need to ask the printer for its options via IPP. So we have access
// to the options with the current PPD-based dialogs and can even share
// the automatically created print queues to other CUPS-based machines
// without problems.
//


char ppdgenerator_msg[1024];


//
// Human-readable strings in the PPDs generated by the PPD generator for
// using driverless IPP printers with CUPS
//
// To allow for translated/localized PPDs we use the standard set
// of human-readable strings from the PWG:
//
// https://ftp.pwg.org/pub/pwg/ipp/examples/ipp.pot
// https://ftp.pwg.org/pub/pwg/ipp/examples/ipp.strings
//
// Creating those with translated human-readable strings would allow us to
// easily produce PPDs in other languges.
//
// Translations are supposed to go here (but none are available yet):
//
// https://github.com/istopwg/ippregistry/tree/master/localizations
//
// These standard strings are also part of CUPS' translation files:
//
// https://github.com/OpenPrinting/cups/tree/master/locale
//
// Here translations take actually place as part of the translations
// of CUPS itself.
//
// We take the files from the CUPS installed into the system (as the PPD
// generator actually does not make sense without CUPS) but currently
// only use the English version. It is not complicated to check the user
// language in the printer's IPP response via the
// attributes-natural-language attribute and then request an appropriate
// language version of the files if available. The printer-specific
// strings are downloaded from the printer following the URI in the
// printer-strings-uri attribute and are in the selected language.
//
// It is not clear whether PPD translation will get fixed in the PPD
// generator as the need of PPDs in CUPS will go away with version
// 3.x.
//
// See also:
//
// https://lists.linuxfoundation.org/pipermail/printing-architecture/2021/003992.html
// https://lists.linuxfoundation.org/pipermail/printing-architecture/2021/003995.html
//


//
// 'ppdCreatePPDFromIPP()' - Create a PPD file describing the capabilities
//                           of an IPP printer, using info from DNS-SD record
//                           as fallback (for poor IPP responses, especially
//                           IPP 1.x legacy)
//

char *                                             // O - PPD filename or NULL
                                                   //     on error
ppdCreatePPDFromIPP(char         *buffer,          // I - Filename buffer
		    size_t       bufsize,          // I - Size of filename
						   //     buffer
		    ipp_t        *supported,       // I - Get-Printer-Attributes
						   //     response
		    const char   *make_model,      // I - Make and model from
						   //     DNS-SD
		    const char   *pdl,             // I - List of PDLs from
						   //     DNS-SD
		    int          color,            // I - Color printer? (from
						   //     DNS-SD)
		    int          duplex,           // I - Duplex printer? (from
						   //     DNS-SD)
		    char         *status_msg,      // I - Status message buffer,
						   //     NULL to ignore
						   //     message
		    size_t       status_msg_size)  // I - Size of status message
						   //     buffer
{
  return ppdCreatePPDFromIPP2(buffer, bufsize, supported, make_model, pdl,
			      color, duplex, NULL, NULL, NULL, NULL,
			      status_msg, status_msg_size);
}

//
// 'ppdCreatePPDFromIPP2()' - Create a PPD file describing the
//                            capabilities of an IPP printer, with
//                            extra parameters for PPDs from a merged
//                            IPP record for printer clusters
//

char *                                              // O - PPD filename or NULL
						    //     on error
ppdCreatePPDFromIPP2(char         *buffer,          // I - Filename buffer
		     size_t       bufsize,          // I - Size of filename
						    //     buffer
		     ipp_t        *supported,       // I - Get-Printer-
						    //     Attributes response
		     const char   *make_model,      // I - Make and model from
						    //     DNS-SD
		     const char   *pdl,             // I - List of PDLs from
						    //     DNS-SD
		     int          color,            // I - Color printer? (from
						    //     DNS-SD)
		     int          duplex,           // I - Duplex printer? (from
						    //     DNS-SD)
		     cups_array_t *conflicts,       // I - Array of
						    //     constraints
		     cups_array_t *sizes,           // I - Media sizes we've
						    //     added
		     char*        default_pagesize, // I - Default page size
		     const char   *default_cluster_color, // I - cluster def
						    //     color (if cluster's
						    //     attributes are
						    //     returned)
		     char         *status_msg,      // I - Status message
						    //     buffer, NULL to
						    //     ignore message
		     size_t       status_msg_size)  // I - Size of status
						    //     message buffer
{
  cups_lang_t		*lang;		// Localization language
  cups_file_t		*fp;		// PPD file
  cups_array_t		*printer_sizes;	// Media sizes we've added
  cups_size_t		*size;		// Current media size
  ipp_attribute_t	*attr,		// xxx-supported
                        *attr2,
			*lang_supp,	// printer-strings-languages-supported
			*defattr,	// xxx-default
                        *quality,	// print-quality-supported
			*x_dim, *y_dim;	// Media dimensions
  ipp_t			*media_col,	// Media collection
			*media_size;	// Media size collection
  char			make[256],	// Make and model
			*mptr,		// Pointer into make and model
			ppdname[PPD_MAX_NAME];
		    			// PPD keyword
  const char		*model;		// Model name
  int			i, j,		// Looping vars
			count = 0,	// Number of values
			bottom,		// Largest bottom margin
			left,		// Largest left margin
			right,		// Largest right margin
			top,		// Largest top margin
			max_length = 0,	// Maximum custom size
			max_width = 0,
			min_length = INT_MAX,
					// Minimum custom size
			min_width = INT_MAX,
			is_apple = 0,	// Does the printer support Apple
					// Raster?
                        is_pwg = 0,	// Does the printer support PWG
					// Raster?
                        is_pclm = 0,    // Does the printer support PCLm?
                        is_pdf = 0;     // Does the printer support PDF?
  pwg_media_t		*pwg;		// PWG media size
  int			xres, yres;	// Resolution values
  cups_array_t          *common_res,    // Common resolutions of all PDLs
                        *current_res,   // Resolutions of current PDL
                        *pdl_list;      // List of PDLs
  cf_res_t              *common_def,    // Common default resolution
                        *current_def,   // Default resolution of current PDL
                        *min_res,       // Minimum common resolution
                        *max_res;       // Maximum common resolution
  struct lconv		*loc = localeconv();
					// Locale data
  cups_array_t          *opt_strings_catalog = NULL;
                                        // Standard option UI strings
  cups_array_t          *printer_opt_strings_catalog = NULL;
                                        // Printer-specific option UI strings
  char                  *human_readable,
                        *human_readable2;
  const char		*keyword;	// Keyword value
  cups_array_t		*fin_options = NULL;
					// Finishing options
  char			buf[256];
  char			*defaultoutbin = NULL;
  const char		*outbin;
  char			outbin_properties[1024];
  int			octet_str_len;
  void			*outbin_properties_octet;
  int			outputorderinfofound = 0,
			faceupdown = 1,
			firsttolast = 1;
  int			manual_copies = -1,
			is_fax = 0;

  //
  // Range check input...
  //

  if (buffer)
    *buffer = '\0';

  if (!buffer || bufsize < 1)
  {
    if (status_msg && status_msg_size)
      snprintf(status_msg, status_msg_size, "%s", strerror(EINVAL));
    return (NULL);
  }

  if (!supported)
  {
    if (status_msg && status_msg_size)
      snprintf(status_msg, status_msg_size, "No IPP attributes.");
    return (NULL);
  }

  //
  // Open a temporary file for the PPD...
  //

  if ((fp = cupsTempFile2(buffer, (int)bufsize)) == NULL)
  {
    if (status_msg && status_msg_size)
      snprintf(status_msg, status_msg_size, "%s", strerror(errno));
    return (NULL);
  }

  //
  // Get a sanitized make and model...
  //

  if ((attr = ippFindAttribute(supported, "printer-make-and-model", IPP_TAG_TEXT)) != NULL && ippValidateAttribute(attr))
  {
    // Sanitize the model name to only contain PPD-safe characters.
    strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make));

    for (mptr = make; *mptr; mptr ++)
    {
      if (*mptr < ' ' || *mptr >= 127 || *mptr == '\"')
      {
        // Truncate the make and model on the first bad character...
	*mptr = '\0';
	break;
      }
    }

    while (mptr > make)
    {
      // Strip trailing whitespace...
      mptr --;
      if (*mptr == ' ')
	*mptr = '\0';
    }

    if (!make[0])
    {
      // Use a default make and model if nothing remains...
      strlcpy(make, "Unknown", sizeof(make));
    }
  }
  else
  {
    // Use a default make and model...
    strlcpy(make, "Unknown", sizeof(make));
  }

  if (!strncasecmp(make, "Hewlett Packard ", 16) || !strncasecmp(make, "Hewlett-Packard ", 16))
  {
    // Normalize HP printer make and model...
    model = make + 16;
    strlcpy(make, "HP", sizeof(make));

    if (!strncasecmp(model, "HP ", 3))
      model += 3;
  }
  else if ((mptr = strchr(make, ' ')) != NULL)
  {
    // Separate "MAKE MODEL"...
    while (*mptr && *mptr == ' ')
      *mptr++ = '\0';

    model = mptr;
  }
  else
  {
    // No separate model name...
    model = "Printer";
  }

  //
  // Standard stuff for PPD file...
  //

  cupsFilePuts(fp, "*PPD-Adobe: \"4.3\"\n");
  cupsFilePuts(fp, "*FormatVersion: \"4.3\"\n");
  cupsFilePrintf(fp, "*FileVersion: \"%s\"\n", VERSION);
  cupsFilePuts(fp, "*LanguageVersion: English\n");
  cupsFilePuts(fp, "*LanguageEncoding: ISOLatin1\n");
  cupsFilePuts(fp, "*PSVersion: \"(3010.000) 0\"\n");
  cupsFilePuts(fp, "*LanguageLevel: \"3\"\n");
  cupsFilePuts(fp, "*FileSystem: False\n");
  cupsFilePuts(fp, "*PCFileName: \"drvless.ppd\"\n");

  if ((attr = ippFindAttribute(supported, "ipp-features-supported",
			       IPP_TAG_KEYWORD)) != NULL &&
      ippContainsString(attr, "faxout"))
  {
    attr = ippFindAttribute(supported, "printer-uri-supported",
			    IPP_TAG_URI);
    if (attr)
    {
      ippAttributeString(attr, buf, sizeof(buf));
      if (strcasestr(buf, "faxout"))
	is_fax = 1;
    }
  }

  cupsFilePrintf(fp, "*Manufacturer: \"%s\"\n", make);
  cupsFilePrintf(fp, "*ModelName: \"%s %s\"\n", make, model);
  cupsFilePrintf(fp, "*Product: \"(%s %s)\"\n", make, model);
  cupsFilePrintf(fp, "*NickName: \"%s %s, %sdriverless, %s\"\n",
		 make, model, (is_fax ? "Fax, " : ""), VERSION);
  cupsFilePrintf(fp, "*ShortNickName: \"%s %s\"\n", make, model);

  // Which is the default output bin?
  if ((attr = ippFindAttribute(supported, "output-bin-default", IPP_TAG_ZERO))
      != NULL)
    defaultoutbin = strdup(ippGetString(attr, 0, NULL));
  // Find out on which position of the list of output bins the default one is,
  // if there is no default bin, take the first of this list
  i = 0;
  if ((attr = ippFindAttribute(supported, "output-bin-supported",
			       IPP_TAG_ZERO)) != NULL)
  {
    count = ippGetCount(attr);
    for (i = 0; i < count; i ++)
    {
      outbin = ippGetString(attr, i, NULL);
      if (outbin == NULL)
	continue;
      if (defaultoutbin == NULL)
      {
	defaultoutbin = strdup(outbin);
	break;
      } else if (strcasecmp(outbin, defaultoutbin) == 0)
	break;
    }
  }
  if ((attr = ippFindAttribute(supported, "printer-output-tray",
			       IPP_TAG_STRING)) != NULL &&
      i < ippGetCount(attr))
  {
    outbin_properties_octet = ippGetOctetString(attr, i, &octet_str_len);
    memset(outbin_properties, 0, sizeof(outbin_properties));
    memcpy(outbin_properties, outbin_properties_octet,
	   ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
	    (size_t)octet_str_len : sizeof(outbin_properties) - 1));
    if (strcasestr(outbin_properties, "pagedelivery=faceUp"))
    {
      outputorderinfofound = 1;
      faceupdown = -1;
    }
    if (strcasestr(outbin_properties, "stackingorder=lastToFirst"))
      firsttolast = -1;
  }
  if (outputorderinfofound == 0 && defaultoutbin &&
      strcasestr(defaultoutbin, "face-up"))
    faceupdown = -1;
  if (defaultoutbin)
    free (defaultoutbin);
  if (firsttolast * faceupdown < 0)
    cupsFilePuts(fp, "*DefaultOutputOrder: Reverse\n");
  else
    cupsFilePuts(fp, "*DefaultOutputOrder: Normal\n");

  // Do we have a color printer?
  if (((attr = ippFindAttribute(supported,
			       "color-supported", IPP_TAG_BOOLEAN)) != NULL &&
       ippGetBoolean(attr, 0)) ||
      color)
    cupsFilePuts(fp, "*ColorDevice: True\n");
  else
    cupsFilePuts(fp, "*ColorDevice: False\n");

  if ((attr = ippFindAttribute(supported,
			       "landscape-orientation-requested-preferred",
			       IPP_TAG_ZERO)) != NULL)
  {
    if (ippGetInteger(attr, 0) == 4)
      cupsFilePuts(fp, "*LandscapeOrientation: Plus90\n");
    else if (ippGetInteger(attr, 0) == 5)
      cupsFilePuts(fp, "*LandscapeOrientation: Minus90\n");
  }

  cupsFilePrintf(fp, "*cupsVersion: %d.%d\n", CUPS_VERSION_MAJOR,
		 CUPS_VERSION_MINOR);
  cupsFilePuts(fp, "*cupsSNMPSupplies: False\n");
  cupsFilePuts(fp, "*cupsLanguages: \"en");
  if ((lang_supp = ippFindAttribute(supported,
				    "printer-strings-languages-supported",
				    IPP_TAG_LANGUAGE)) != NULL)
  {
    for (i = 0, count = ippGetCount(lang_supp); i < count; i ++)
    {
      keyword = ippGetString(lang_supp, i, NULL);

      if (strcmp(keyword, "en"))
        cupsFilePrintf(fp, " %s", keyword);
    }
  }
  cupsFilePuts(fp, "\"\n");

  if ((attr = ippFindAttribute(supported, "printer-more-info", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
    cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL));

  if ((attr = ippFindAttribute(supported, "printer-charge-info-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
    cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0, NULL));

  // Message catalogs for UI strings
  lang = cupsLangDefault();
  opt_strings_catalog = cfCatalogOptionArrayNew();
  cfCatalogLoad(NULL, NULL, opt_strings_catalog);

  if ((attr = ippFindAttribute(supported, "printer-strings-uri",
			       IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
  {
    printer_opt_strings_catalog = cfCatalogOptionArrayNew();
    cfCatalogLoad(ippGetString(attr, 0, NULL), NULL,
		  printer_opt_strings_catalog);
    if (cupsArrayCount(printer_opt_strings_catalog) > 0)
    {
      http_t		*http = NULL;	// Connection to printer
      const char	*printer_uri =
	ippGetString(ippFindAttribute(supported, "printer-uri-supported",
				      IPP_TAG_URI), 0, NULL);
					// Printer URI
      char		resource[256];	// Resource path
      ipp_t		*request,	// Get-Printer-Attributes request
			*response;	// Response to request

      //
      // Load strings and save the URL for clients using the destination API
      // instead of this PPD file...
      //

      cupsFilePrintf(fp, "*cupsStringsURI: \"%s\"\n", ippGetString(attr, 0,
								   NULL));

      if (lang_supp && printer_uri && http_connect(&http, printer_uri,
						   resource, sizeof(resource)))
      {
        //
	// Loop through all of the languages and save their URIs...
	//

	for (i = 0, count = ippGetCount(lang_supp); i < count; i ++)
	{
	  keyword = ippGetString(lang_supp, i, NULL);

	  request = ippNew();
	  ippSetOperation(request, IPP_OP_GET_PRINTER_ATTRIBUTES);
	  ippSetRequestId(request, i + 1);
	  ippAddString(request, IPP_TAG_OPERATION,
		       IPP_CONST_TAG(IPP_TAG_CHARSET),
		       "attributes-charset", NULL, "utf-8");
	  ippAddString(request, IPP_TAG_OPERATION,
		       IPP_TAG_LANGUAGE,
		       "attributes-natural-language", NULL, keyword);
	  ippAddString(request, IPP_TAG_OPERATION,
		       IPP_TAG_URI, "printer-uri", NULL, printer_uri);
	  ippAddString(request, IPP_TAG_OPERATION,
		       IPP_CONST_TAG(IPP_TAG_KEYWORD),
		       "requested-attributes", NULL, "printer-strings-uri");

	  response = cupsDoRequest(http, request, resource);

	  if ((attr = ippFindAttribute(response, "printer-strings-uri",
				       IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
	    cupsFilePrintf(fp, "*cupsStringsURI %s: \"%s\"\n", keyword,
			   ippGetString(attr, 0, NULL));

	  ippDelete(response);
	}
      }

      if (http)
	httpClose(http);
    }
  }

  //
  // Accounting...
  //

  if (ippGetBoolean(ippFindAttribute(supported, "job-account-id-supported",
				     IPP_TAG_BOOLEAN), 0))
    cupsFilePuts(fp, "*cupsJobAccountId: True\n");

  if (ippGetBoolean(ippFindAttribute(supported,
				     "job-accounting-user-id-supported",
				     IPP_TAG_BOOLEAN), 0))
    cupsFilePuts(fp, "*cupsJobAccountingUserId: True\n");

  if ((attr = ippFindAttribute(supported, "printer-privacy-policy-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr))
    cupsFilePrintf(fp, "*cupsPrivacyURI: \"%s\"\n", ippGetString(attr, 0, NULL));

  if ((attr = ippFindAttribute(supported, "printer-mandatory-job-attributes", IPP_TAG_KEYWORD)) != NULL && ippValidateAttribute(attr))
  {
    char	prefix = '\"';		// Prefix for string

    cupsFilePuts(fp, "*cupsMandatory: \"");
    for (i = 0, count = ippGetCount(attr); i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL);

      if (strcmp(keyword, "attributes-charset") &&
	  strcmp(keyword, "attributes-natural-language") &&
	  strcmp(keyword, "printer-uri"))
      {
        cupsFilePrintf(fp, "%c%s", prefix, keyword);
        prefix = ',';
      }
    }
    cupsFilePuts(fp, "\"\n");
  }

  if ((attr = ippFindAttribute(supported, "printer-requested-job-attributes", IPP_TAG_KEYWORD)) != NULL && ippValidateAttribute(attr))
  {
    char	prefix = '\"';		// Prefix for string

    cupsFilePuts(fp, "*cupsRequested: \"");
    for (i = 0, count = ippGetCount(attr); i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL);

      if (strcmp(keyword, "attributes-charset") &&
	  strcmp(keyword, "attributes-natural-language") &&
	  strcmp(keyword, "printer-uri"))
      {
        cupsFilePrintf(fp, "%c%s", prefix, keyword);
        prefix = ',';
      }
    }
    cupsFilePuts(fp, "\"\n");
  }

  //
  // Password/PIN printing...
  //

  if ((attr = ippFindAttribute(supported, "job-password-supported",
			       IPP_TAG_INTEGER)) != NULL)
  {
    char	pattern[33];		// Password pattern
    int		maxlen = ippGetInteger(attr, 0);
					// Maximum length
    const char	*repertoire =
      ippGetString(ippFindAttribute(supported,
				    "job-password-repertoire-configured",
				    IPP_TAG_KEYWORD), 0, NULL);
					// Type of password

    if (maxlen > (int)(sizeof(pattern) - 1))
      maxlen = (int)sizeof(pattern) - 1;

    if (!repertoire || !strcmp(repertoire, "iana_us-ascii_digits"))
      memset(pattern, '1', (size_t)maxlen);
    else if (!strcmp(repertoire, "iana_us-ascii_letters"))
      memset(pattern, 'A', (size_t)maxlen);
    else if (!strcmp(repertoire, "iana_us-ascii_complex"))
      memset(pattern, 'C', (size_t)maxlen);
    else if (!strcmp(repertoire, "iana_us-ascii_any"))
      memset(pattern, '.', (size_t)maxlen);
    else if (!strcmp(repertoire, "iana_utf-8_digits"))
      memset(pattern, 'N', (size_t)maxlen);
    else if (!strcmp(repertoire, "iana_utf-8_letters"))
      memset(pattern, 'U', (size_t)maxlen);
    else
      memset(pattern, '*', (size_t)maxlen);

    pattern[maxlen] = '\0';

    cupsFilePrintf(fp, "*cupsJobPassword: \"%s\"\n", pattern);
  }

  //
  // Add cupsSingleFile to support multiple files printing on printers
  // which don't support multiple files in its firmware...
  //
  // Adding the keyword degrades printing performance (there is 1-2 seconds
  // pause between files).
  //

  cupsFilePuts(fp, "*cupsSingleFile: True\n");

  //
  // PDLs and common resolutions ...
  //

  common_res = NULL;
  current_res = NULL;
  common_def = NULL;
  current_def = NULL;
  min_res = NULL;
  max_res = NULL;
  // Put all available PDls into a simple case-insensitevely searchable
  // sorted string list
  if ((pdl_list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0,
				(cups_acopy_func_t)strdup,
				(cups_afree_func_t)free)) == NULL)
    goto bad_ppd;
  int formatfound = 0;

  if (((attr = ippFindAttribute(supported, "document-format-supported",
				IPP_TAG_MIMETYPE)) != NULL) ||
      (pdl && pdl[0] != '\0'))
  {
    const char *format = pdl;
    i = 0;
    count = ippGetCount(attr);
    while ((attr && i < count) || // Go through formats in attribute
	   (!attr && pdl && pdl[0] != '\0' && format[0] != '\0'))
    {
      // Go through formats in pdl string (from DNS-SD record)

      // Pick next format from attribute
      if (attr) format = ippGetString(attr, i, NULL);
      // Add format to list of supported PDLs, skip duplicates
      if (!cupsArrayFind(pdl_list, (void *)format))
	cupsArrayAdd(pdl_list, (void *)format);
      if (attr)
	// Next format in attribute
	i ++;
      else {
	// Find the next format in the string pdl, if there is none left,
	// go to the terminating zero
	while (!isspace(*format) && *format != ',' && *format != '\0')
	  format ++;
	while ((isspace(*format) || *format == ',') && *format != '\0')
	  format ++;
      }
    }
  }

  //
  // Fax
  //

  if (is_fax)
  {
    cupsFilePuts(fp, "*cupsFax: True\n");
    cupsFilePuts(fp, "*cupsIPPFaxOut: True\n");
  }

  //
  // Check for each CUPS/cups-filters-supported PDL, starting with the
  // most desirable going to the least desirable. If a PDL requires a
  // certain set of resolutions (the raster-based PDLs), find the
  // resolutions and find out which are the common resolutions of all
  // supported PDLs. Choose the default resolution from the most
  // desirable of all resolution-requiring PDLs if it is common in all
  // of them. Skip a resolution-requiring PDL if its resolution list
  // attribute is missing or contains only broken entries. Use the
  // general resolution list and default resolution of the printer
  // only if it does not support any resolution-requiring PDL. Use 300
  // dpi if there is no resolution info at all in the attributes.
  // In case of PDF as PDL check whether also the
  // "application/vnd.cups-pdf" MIME type is accepted. In this case
  // our printer is a remote CUPS queue which already runs the
  // pdftopdf filter on the server, so let the PPD take
  // "application/pdf" as input format so that pdftopdf does not also
  // get executed on the client, applying option settings twice. See
  // https://github.com/apple/cups/issues/5361
  //

  if (cupsArrayFind(pdl_list, "application/vnd.cups-pdf"))
  {
    // Remote CUPS queue
    cupsFilePuts(fp, "*cupsFilter2: \"application/pdf application/pdf 0 -\"\n");
    manual_copies = 0;
    formatfound = 1;
    is_pdf = 1;
  }
#ifdef CUPS_RASTER_HAVE_APPLERASTER
  else if (cupsArrayFind(pdl_list, "image/urf"))
  {
    int resStore = 0; // Variable for storing the no. of resolutions in the resolution array
    int resArray[__INT16_MAX__]; // Creating a resolution array supporting a maximum of 32767 resolutions.
    int lowdpi = 0, middpi = 0, hidpi = 0; // Lower , middle and higher resolution
    if ((attr = ippFindAttribute(supported, "urf-supported",
			IPP_TAG_KEYWORD)) != NULL)
    {
      for (int i = 0, count = ippGetCount(attr); i < count; i ++)
      {
       	const char *rs = ippGetString(attr, i, NULL); // RS values
        const char *rsCopy = ippGetString(attr, i, NULL); // RS values(copy)
	if (strncasecmp(rs, "RS", 2)) // Comparing attributes to have RS in
	                              // the beginning to indicate the
	                              // resolution feature
	  continue;
        int resCount = 0; // Using a count variable which can be reset
        while (*rsCopy != '\0') // Parsing through the copy pointer to
	                        // determine the no. of resolutions
        {
          if (*rsCopy == '-')
          {
            resCount ++;
          }
          rsCopy ++;
        }
        resCount ++;
        resStore = resCount;
        resCount = 0;
        resArray[resCount] = atoi(rs + 2);
        resCount ++;
        while (*rs != '\0') // Parsing through the entire pointer and
	                    // appending each resolution to an array
        {
          if (*rs == '-')
          {
            resArray[resCount] = atoi(rs + 1);
            resCount ++;
          }
          rs ++;
        }
	// Finding and storing the important dpi.
	// Lowdpi the lowest resolution, hidpi the highest resolution and
	// middpi finding the middle resolution
	// The middpi takes the rounded down middle value
        lowdpi = resArray[0];
        middpi = resArray[(resStore - 1) / 2];
        hidpi = resArray[resStore - 1];
        break;
      }
      if (lowdpi == 0)
      {
        // Invalid "urf-supported" value...
        goto bad_ppd;
      }
      else
      {
        if ((current_res = cfNewResolutionArray()) != NULL)
        {
          // Adding to the resolution list
          if ((current_def = cfNewResolution(lowdpi, lowdpi)) != NULL)
          {
	    cupsArrayAdd(current_res, current_def);
            cfFreeResolution(current_def, NULL);
          }
          if (hidpi != lowdpi &&
	        (current_def = cfNewResolution(hidpi, hidpi)) != NULL)
          {
	    cupsArrayAdd(current_res, current_def);
            cfFreeResolution(current_def, NULL);
          }
          if (middpi != hidpi && middpi != lowdpi &&
	        (current_def = cfNewResolution(middpi, middpi)) != NULL)
          {
	    cupsArrayAdd(current_res, current_def);
            cfFreeResolution(current_def, NULL);
          }
          current_def = NULL;
	  // Checking if there is printer-default-resolution and this
	  // resolution is in the list, use it. If not, use the
	  // middpi, rounding down if the number of available
	  // resolutions is even.
          if ((attr = ippFindAttribute(supported,
				       "printer-resolution-supported",
				       IPP_TAG_RESOLUTION)) != NULL)
          {
            if ((defattr = ippFindAttribute(supported,
					    "printer-resolution-default",
					    IPP_TAG_RESOLUTION)) != NULL)
            {
              current_def = cfIPPResToResolution(defattr, 0);
              for (int j = 0; j < resStore; j ++)
              {
                if (current_def == cfNewResolution(resArray[i], resArray[i]))
                {
                  current_def = cfIPPResToResolution(defattr, 0);
                  break;
                }
                else
                {
                  current_def = cfNewResolution(middpi, middpi);
                }
              }
            }
          }
          if (cupsArrayCount(current_res) > 0 &&
	      cfJoinResolutionArrays(&common_res, &current_res, &common_def,
				     &current_def))
          {
	    cupsFilePuts(fp, "*cupsFilter2: \"image/urf image/urf 0 -\"\n");
	    manual_copies = 1;
	    formatfound = 1;
	    is_apple = 1;
	  }
        }
      }
    }
  }
#endif
  else if (cupsArrayFind(pdl_list, "application/pdf"))
  {
    // PDF printer
    cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 0 -\"\n");
    manual_copies = 0;
    formatfound = 1;
    is_pdf = 1;
  }
  else if (cupsArrayFind(pdl_list, "image/pwg-raster"))
  {
    if ((attr = ippFindAttribute(supported,
				 "pwg-raster-document-resolution-supported",
				 IPP_TAG_RESOLUTION)) != NULL)
    {
      current_def = NULL;
      if ((current_res = cfIPPAttrToResolutionArray(attr)) != NULL &&
	  cfJoinResolutionArrays(&common_res, &current_res, &common_def,
				 &current_def))
      {
	cupsFilePuts(fp, "*cupsFilter2: \"image/pwg-raster image/pwg-raster 0 -\"\n");
	if (formatfound == 0) manual_copies = 1;
	formatfound = 1;
	is_pwg = 1;
      }
    }
  }
  else if (cupsArrayFind(pdl_list, "application/PCLm"))
  {
    if ((attr = ippFindAttribute(supported, "pclm-source-resolution-supported",
				 IPP_TAG_RESOLUTION)) != NULL)
    {
      if ((defattr = ippFindAttribute(supported,
				      "pclm-source-resolution-default",
				      IPP_TAG_RESOLUTION)) != NULL)
	current_def = cfIPPResToResolution(defattr, 0);
      else
	current_def = NULL;
      if ((current_res = cfIPPAttrToResolutionArray(attr)) != NULL &&
	  cfJoinResolutionArrays(&common_res, &current_res, &common_def,
				 &current_def))
      {
	cupsFilePuts(fp, "*cupsFilter2: \"application/PCLm application/PCLm 0 -\"\n");
	if (formatfound == 0) manual_copies = 1;
	formatfound = 1;
	is_pclm = 1;
      }
    }
  }
  // Legacy formats only if we have no driverless format
  else if (cupsArrayFind(pdl_list, "application/vnd.hp-pclxl"))
  {
    cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-pdf application/vnd.hp-pclxl 100 gstopxl\"\n");
    if (formatfound == 0)
      manual_copies = 1;
    formatfound = 1;
  }
  else if (cupsArrayFind(pdl_list, "application/postscript"))
  {
    // Higher cost value as PostScript interpreters are often buggy
    cupsFilePuts(fp, "*cupsFilter2: \"application/vnd.cups-postscript application/postscript 0 -\"\n");
    if (formatfound == 0)
      manual_copies = 0;
    formatfound = 1;
  }
  else if (cupsArrayFind(pdl_list, "application/vnd.hp-pcl"))
  {
    cupsFilePrintf(fp, "*cupsFilter2: \"application/vnd.cups-raster application/vnd.hp-pcl 100 rastertopclx\"\n");
    if (formatfound == 0)
      manual_copies = 1;
    formatfound = 1;
  }
  if (cupsArrayFind(pdl_list, "image/jpeg"))
    cupsFilePuts(fp, "*cupsFilter2: \"image/jpeg image/jpeg 0 -\"\n");
  if (cupsArrayFind(pdl_list, "image/png"))
    cupsFilePuts(fp, "*cupsFilter2: \"image/png image/png 0 -\"\n");
  cupsArrayDelete(pdl_list);
  if (manual_copies < 0)
    manual_copies = 1;
  if (formatfound == 0)
    goto bad_ppd;

  // For the case that we will print in a raster format and not in a high-level
  // format, we need to create multiple copies on the client. We add a line to
  // the PPD which tells the pdftopdf filter to generate the copies
  if (manual_copies == 1)
    cupsFilePuts(fp, "*cupsManualCopies: True\n");

  // No resolution requirements by any of the supported PDLs?
  // Use "printer-resolution-supported" attribute
  if (common_res == NULL)
  {
    if ((attr = ippFindAttribute(supported, "printer-resolution-supported",
				 IPP_TAG_RESOLUTION)) != NULL)
    {
      if ((defattr = ippFindAttribute(supported, "printer-resolution-default",
				      IPP_TAG_RESOLUTION)) != NULL)
	current_def = cfIPPResToResolution(defattr, 0);
      else
	current_def = NULL;
      if ((current_res = cfIPPAttrToResolutionArray(attr)) != NULL)
	cfJoinResolutionArrays(&common_res, &current_res, &common_def,
			       &current_def);
    }
  }
  // Still no resolution found? Default to 300 dpi
  if (common_res == NULL)
  {
    if ((common_res = cfNewResolutionArray()) != NULL)
    {
      if ((current_def = cfNewResolution(300, 300)) != NULL)
      {
	cupsArrayAdd(common_res, current_def);
        cfFreeResolution(current_def, NULL);
      }
      current_def = NULL;
    } else
      goto bad_ppd;
  }
  // No default resolution determined yet
  if (common_def == NULL)
  {
    if ((defattr = ippFindAttribute(supported, "printer-resolution-default",
				    IPP_TAG_RESOLUTION)) != NULL)
    {
      common_def = cfIPPResToResolution(defattr, 0);
      if (!cupsArrayFind(common_res, common_def))
      {
	free(common_def);
	common_def = NULL;
      }
    }
    if (common_def == NULL) {
      count = cupsArrayCount(common_res);
      common_def =
	cfCopyResolution(cupsArrayIndex(common_res, count / 2), NULL);
    }
  }
  // Get minimum and maximum resolution
  min_res = cfCopyResolution(cupsArrayFirst(common_res), NULL);
  max_res = cfCopyResolution(cupsArrayLast(common_res), NULL);
  cupsArrayDelete(common_res);

  //
  // Generically check for Raster-format-related attributes in IPP
  // supported and ppdize them one by one
  //

  attr = ippFirstAttribute(supported); // first attribute
  while (attr)                        // loop through all the attributes
  {
    if ((is_apple && strncasecmp(ippGetName(attr), "urf-", 4) == 0) ||
	(is_pwg && strncasecmp(ippGetName(attr), "pwg-raster-", 11) == 0) ||
	(is_pclm && strncasecmp(ippGetName(attr), "pclm-", 5) == 0))
    {
      ppdPwgPpdizeName(ippGetName(attr), ppdname, sizeof(ppdname));
      cupsFilePrintf(fp, "*cups%s: ", ppdname);
      ipp_tag_t tag = ippGetValueTag(attr);
      count = ippGetCount(attr);

      if (tag == IPP_TAG_RESOLUTION) // ppdize values of type resolution
      {
	if ((current_res = cfIPPAttrToResolutionArray(attr)) != NULL)
	{
	  count = cupsArrayCount(current_res);
	  if (count > 1)
	    cupsFilePuts(fp, "\"");
	  for (i = 0, current_def = cupsArrayFirst(current_res);
	       current_def;
	       i ++, current_def = cupsArrayNext(current_res))
	  {
	    int x = current_def->x;
	    int y = current_def->y;
	    if (x == y)
	      cupsFilePrintf(fp, "%ddpi", x);
	    else
	      cupsFilePrintf(fp, "%dx%ddpi", x, y);
	    if (i < count - 1)
	      cupsFilePuts(fp, ",");
	  }
	  if (count > 1)
	    cupsFilePuts(fp, "\"");
	  cupsFilePuts(fp, "\n");
	} else
	  cupsFilePuts(fp, "\"\"\n");
	cupsArrayDelete(current_res);
      }
      else
      {
	ippAttributeString(attr, buf, sizeof(buf));
	if (count > 1 || // quotes around multi-valued and string
			 // attributes
	    tag == IPP_TAG_STRING ||
	    tag == IPP_TAG_TEXT ||
	    tag == IPP_TAG_TEXTLANG)
	  cupsFilePrintf(fp, "\"%s\"\n", buf);
	else
	  cupsFilePrintf(fp, "%s\n", buf);
      }
    }
    attr = ippNextAttribute(supported);
  }

  //
  // PageSize/PageRegion/ImageableArea/PaperDimension
  //

  cfGenerateSizes(supported, CF_GEN_SIZES_DEFAULT, &printer_sizes, &defattr,
		  NULL, NULL, NULL, NULL, NULL, NULL,
		  &min_width, &min_length,
		  &max_width, &max_length,
		  &left, &bottom, &right, &top, ppdname, NULL);
  if (sizes == NULL)
    sizes = printer_sizes;
  else
    strcpy(ppdname, default_pagesize);

  if (cupsArrayCount(sizes) > 0)
  {
    //
    // List all of the standard sizes...
    //

    char	tleft[256],		// Left string
		tbottom[256],		// Bottom string
		tright[256],		// Right string
		ttop[256],		// Top string
		twidth[256],		// Width string
		tlength[256],		// Length string
		ppdsizename[128];
    char        *ippsizename,
                *suffix;
    int         all_borderless = 1;

    // Find a page size without ".Borderless" suffix
    // (if all are ".Borderless" we drop the suffix in the PPD)
    for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
	 size = (cups_size_t *)cupsArrayNext(sizes))
      if (strcasestr(size->media, ".Borderless") == NULL)
	break;
    if (size)
      all_borderless = 0;

    if (all_borderless)
    {
      suffix = strcasestr(ppdname, ".Borderless");
      if (suffix)
	*suffix = '\0';
    }

    cupsFilePrintf(fp, "*OpenUI *PageSize/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *PageSize\n"
		   "*DefaultPageSize: %s\n", "Media Size", ppdname);
    for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
	 size = (cups_size_t *)cupsArrayNext(sizes))
    {
      cfStrFormatd(twidth, twidth + sizeof(twidth),
		      size->width * 72.0 / 2540.0, loc);
      cfStrFormatd(tlength, tlength + sizeof(tlength),
		      size->length * 72.0 / 2540.0, loc);
      strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
      if ((ippsizename = strchr(ppdsizename, ' ')) != NULL)
      {
	*ippsizename = '\0';
	ippsizename ++;
      }

      if (ippsizename)
	human_readable = cfCatalogLookUpChoice(ippsizename, "media",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
      else
	human_readable = NULL;
      if (!human_readable)
      {
	pwg = pwgMediaForSize(size->width, size->length);
	if (pwg)
	  human_readable = cfCatalogLookUpChoice((char *)pwg->pwg, "media",
						 opt_strings_catalog,
						 printer_opt_strings_catalog);
      }

      if (all_borderless)
      {
	suffix = strcasestr(ppdsizename, ".Borderless");
	if (suffix)
	  *suffix = '\0';
      }

      cupsFilePrintf(fp, "*PageSize %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
		     ppdsizename,
		     (human_readable ? "/" : ""),
		     (human_readable ? human_readable : ""),
		     (human_readable && strstr(ppdsizename, ".Borderless") ?
		      " (Borderless)" : ""),
		     twidth, tlength);
    }
    cupsFilePuts(fp, "*CloseUI: *PageSize\n");

    cupsFilePrintf(fp, "*OpenUI *PageRegion/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *PageRegion\n"
		   "*DefaultPageRegion: %s\n", "Media Size", ppdname);
    for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
	 size = (cups_size_t *)cupsArrayNext(sizes))
    {
      cfStrFormatd(twidth, twidth + sizeof(twidth),
		      size->width * 72.0 / 2540.0, loc);
      cfStrFormatd(tlength, tlength + sizeof(tlength),
		      size->length * 72.0 / 2540.0, loc);
      strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
      if ((ippsizename = strchr(ppdsizename, ' ')) != NULL)
      {
	*ippsizename = '\0';
	ippsizename ++;
      }

      if (ippsizename)
	human_readable = cfCatalogLookUpChoice(ippsizename, "media",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
      else
	human_readable = NULL;
      if (!human_readable)
      {
	pwg = pwgMediaForSize(size->width, size->length);
	if (pwg)
	  human_readable = cfCatalogLookUpChoice((char *)pwg->pwg, "media",
						 opt_strings_catalog,
						 printer_opt_strings_catalog);
      }

      if (all_borderless)
      {
	suffix = strcasestr(ppdsizename, ".Borderless");
	if (suffix)
	  *suffix = '\0';
      }

      cupsFilePrintf(fp, "*PageRegion %s%s%s%s: \"<</PageSize[%s %s]>>setpagedevice\"\n",
		     ppdsizename,
		     (human_readable ? "/" : ""),
		     (human_readable ? human_readable : ""),
		     (human_readable && strstr(ppdsizename, ".Borderless") ?
		      " (Borderless)" : ""),
		     twidth, tlength);
    }
    cupsFilePuts(fp, "*CloseUI: *PageRegion\n");

    cupsFilePrintf(fp, "*DefaultImageableArea: %s\n"
		   "*DefaultPaperDimension: %s\n", ppdname, ppdname);

    for (size = (cups_size_t *)cupsArrayFirst(sizes); size;
	 size = (cups_size_t *)cupsArrayNext(sizes))
    {
      cfStrFormatd(tleft, tleft + sizeof(tleft),
		      size->left * 72.0 / 2540.0, loc);
      cfStrFormatd(tbottom, tbottom + sizeof(tbottom),
		      size->bottom * 72.0 / 2540.0, loc);
      cfStrFormatd(tright, tright + sizeof(tright),
		      (size->width - size->right) * 72.0 / 2540.0, loc);
      cfStrFormatd(ttop, ttop + sizeof(ttop),
		      (size->length - size->top) * 72.0 / 2540.0, loc);
      cfStrFormatd(twidth, twidth + sizeof(twidth),
		      size->width * 72.0 / 2540.0, loc);
      cfStrFormatd(tlength, tlength + sizeof(tlength),
		      size->length * 72.0 / 2540.0, loc);
      strlcpy(ppdsizename, size->media, sizeof(ppdsizename));
      if ((ippsizename = strchr(ppdsizename, ' ')) != NULL)
	*ippsizename = '\0';

      if (all_borderless)
      {
	suffix = strcasestr(ppdsizename, ".Borderless");
	if (suffix)
	  *suffix = '\0';
      }

      cupsFilePrintf(fp, "*ImageableArea %s: \"%s %s %s %s\"\n", ppdsizename,
		     tleft, tbottom, tright, ttop);
      cupsFilePrintf(fp, "*PaperDimension %s: \"%s %s\"\n", ppdsizename,
		     twidth, tlength);
    }

    //
    // Custom size support...
    //

    if (max_width > 0 && min_width < INT_MAX && max_length > 0 &&
	min_length < INT_MAX)
    {
      char	tmax[256], tmin[256];	// Min/max values

      cfStrFormatd(tleft, tleft + sizeof(tleft), left * 72.0 / 2540.0, loc);
      cfStrFormatd(tbottom, tbottom + sizeof(tbottom),
		   bottom * 72.0 / 2540.0, loc);
      cfStrFormatd(tright, tright + sizeof(tright), right * 72.0 / 2540.0,
		   loc);
      cfStrFormatd(ttop, ttop + sizeof(ttop), top * 72.0 / 2540.0, loc);

      cupsFilePrintf(fp, "*HWMargins: \"%s %s %s %s\"\n", tleft, tbottom,
		     tright, ttop);

      cfStrFormatd(tmax, tmax + sizeof(tmax), max_width * 72.0 / 2540.0,
		   loc);
      cfStrFormatd(tmin, tmin + sizeof(tmin), min_width * 72.0 / 2540.0,
		   loc);
      cupsFilePrintf(fp, "*ParamCustomPageSize Width: 1 points %s %s\n", tmin,
		     tmax);

      cfStrFormatd(tmax, tmax + sizeof(tmax), max_length * 72.0 / 2540.0,
		   loc);
      cfStrFormatd(tmin, tmin + sizeof(tmin), min_length * 72.0 / 2540.0,
		   loc);
      cupsFilePrintf(fp, "*ParamCustomPageSize Height: 2 points %s %s\n", tmin,
		     tmax);

      cupsFilePuts(fp, "*ParamCustomPageSize WidthOffset: 3 points 0 0\n");
      cupsFilePuts(fp, "*ParamCustomPageSize HeightOffset: 4 points 0 0\n");
      cupsFilePuts(fp, "*ParamCustomPageSize Orientation: 5 int 0 3\n");
      cupsFilePuts(fp, "*CustomPageSize True: \"pop pop pop <</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice\"\n");
    }
  }
  else
  {
    cupsFilePrintf(fp,
		   "*%% Printer did not supply page size info via IPP, using defaults\n"
		   "*OpenUI *PageSize/Media Size: PickOne\n"
		   "*OrderDependency: 10 AnySetup *PageSize\n"
		   "*DefaultPageSize: Letter\n"
		   "*PageSize Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
		   "*PageSize Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
		   "*PageSize Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
		   "*PageSize Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
		   "*PageSize A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
		   "*PageSize A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
		   "*PageSize A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
		   "*PageSize B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
		   "*PageSize EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
		   "*PageSize Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
		   "*PageSize EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
		   "*PageSize EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
		   "*PageSize EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
		   "*CloseUI: *PageSize\n"
		   "*OpenUI *PageRegion/Media Size: PickOne\n"
		   "*OrderDependency: 10 AnySetup *PageRegion\n"
		   "*DefaultPageRegion: Letter\n"
		   "*PageRegion Letter/US Letter: \"<</PageSize[612 792]>>setpagedevice\"\n"
		   "*PageRegion Legal/US Legal: \"<</PageSize[612 1008]>>setpagedevice\"\n"
		   "*PageRegion Executive/Executive: \"<</PageSize[522 756]>>setpagedevice\"\n"
		   "*PageRegion Tabloid/Tabloid: \"<</PageSize[792 1224]>>setpagedevice\"\n"
		   "*PageRegion A3/A3: \"<</PageSize[842 1191]>>setpagedevice\"\n"
		   "*PageRegion A4/A4: \"<</PageSize[595 842]>>setpagedevice\"\n"
		   "*PageRegion A5/A5: \"<</PageSize[420 595]>>setpagedevice\"\n"
		   "*PageRegion B5/JIS B5: \"<</PageSize[516 729]>>setpagedevice\"\n"
		   "*PageRegion EnvISOB5/Envelope B5: \"<</PageSize[499 709]>>setpagedevice\"\n"
		   "*PageRegion Env10/Envelope #10 : \"<</PageSize[297 684]>>setpagedevice\"\n"
		   "*PageRegion EnvC5/Envelope C5: \"<</PageSize[459 649]>>setpagedevice\"\n"
		   "*PageRegion EnvDL/Envelope DL: \"<</PageSize[312 624]>>setpagedevice\"\n"
		   "*PageRegion EnvMonarch/Envelope Monarch: \"<</PageSize[279 540]>>setpagedevice\"\n"
		   "*CloseUI: *PageSize\n"
		   "*DefaultImageableArea: Letter\n"
		   "*ImageableArea Letter/US Letter: \"18 12 594 780\"\n"
		   "*ImageableArea Legal/US Legal: \"18 12 594 996\"\n"
		   "*ImageableArea Executive/Executive: \"18 12 504 744\"\n"
		   "*ImageableArea Tabloid/Tabloid: \"18 12 774 1212\"\n"
		   "*ImageableArea A3/A3: \"18 12 824 1179\"\n"
		   "*ImageableArea A4/A4: \"18 12 577 830\"\n"
		   "*ImageableArea A5/A5: \"18 12 402 583\"\n"
		   "*ImageableArea B5/JIS B5: \"18 12 498 717\"\n"
		   "*ImageableArea EnvISOB5/Envelope B5: \"18 12 481 697\"\n"
		   "*ImageableArea Env10/Envelope #10 : \"18 12 279 672\"\n"
		   "*ImageableArea EnvC5/Envelope C5: \"18 12 441 637\"\n"
		   "*ImageableArea EnvDL/Envelope DL: \"18 12 294 612\"\n"
		   "*ImageableArea EnvMonarch/Envelope Monarch: \"18 12 261 528\"\n"
		   "*DefaultPaperDimension: Letter\n"
		   "*PaperDimension Letter/US Letter: \"612 792\"\n"
		   "*PaperDimension Legal/US Legal: \"612 1008\"\n"
		   "*PaperDimension Executive/Executive: \"522 756\"\n"
		   "*PaperDimension Tabloid/Tabloid: \"792 1224\"\n"
		   "*PaperDimension A3/A3: \"842 1191\"\n"
		   "*PaperDimension A4/A4: \"595 842\"\n"
		   "*PaperDimension A5/A5: \"420 595\"\n"
		   "*PaperDimension B5/JIS B5: \"516 729\"\n"
		   "*PaperDimension EnvISOB5/Envelope B5: \"499 709\"\n"
		   "*PaperDimension Env10/Envelope #10 : \"297 684\"\n"
		   "*PaperDimension EnvC5/Envelope C5: \"459 649\"\n"
		   "*PaperDimension EnvDL/Envelope DL: \"312 624\"\n"
		   "*PaperDimension EnvMonarch/Envelope Monarch: \"279 540\"\n");
  }

  cupsArrayDelete(printer_sizes);

  //
  // InputSlot...
  //

  if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-source",
			       IPP_TAG_KEYWORD)) != NULL)
    ppdPwgPpdizeName(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
  else
    ppdname[0] = '\0';

  if ((attr = ippFindAttribute(supported, "media-source-supported",
			       IPP_TAG_KEYWORD)) != NULL &&
      (count = ippGetCount(attr)) > 1)
  {
    int have_default = ppdname[0] != '\0';
					// Do we have a default InputSlot?
    static const char * const sources[] =
    {					// Standard "media-source" strings
      "auto",
      "main",
      "alternate",
      "large-capacity",
      "manual",
      "envelope",
      "disc",
      "photo",
      "hagaki",
      "main-roll",
      "alternate-roll",
      "top",
      "middle",
      "bottom",
      "side",
      "left",
      "right",
      "center",
      "rear",
      "by-pass-tray",
      "tray-1",
      "tray-2",
      "tray-3",
      "tray-4",
      "tray-5",
      "tray-6",
      "tray-7",
      "tray-8",
      "tray-9",
      "tray-10",
      "tray-11",
      "tray-12",
      "tray-13",
      "tray-14",
      "tray-15",
      "tray-16",
      "tray-17",
      "tray-18",
      "tray-19",
      "tray-20",
      "roll-1",
      "roll-2",
      "roll-3",
      "roll-4",
      "roll-5",
      "roll-6",
      "roll-7",
      "roll-8",
      "roll-9",
      "roll-10"
    };

    human_readable = cfCatalogLookUpOption("media-source", opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *InputSlot/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *InputSlot\n",
		   (human_readable ? human_readable : "Media Source"));
    if (have_default)
      cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);
    for (i = 0; i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL);

      ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));

      if (i == 0 && !have_default)
	cupsFilePrintf(fp, "*DefaultInputSlot: %s\n", ppdname);

      human_readable = cfCatalogLookUpChoice((char *)keyword, "media-source",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      for (j = (int)(sizeof(sources) / sizeof(sources[0])) - 1; j >= 0; j --)
        if (!strcmp(sources[j], keyword))
	  break;
      if (j >= 0)
      {
	cupsFilePrintf(fp, "*InputSlot %s: \"<</MediaPosition %d>>setpagedevice\"\n", ppdname, j);
	ppd_put_string(fp, lang, "InputSlot", ppdname, human_readable);
      }
      else
      {
	cupsFilePrintf(fp, "*InputSlot %s%s%s:\"\"\n", ppdname, human_readable ? "/" : "", human_readable ? human_readable : "");
      }
    }
    cupsFilePuts(fp, "*CloseUI: *InputSlot\n");
  }

  //
  // MediaType...
  //

  if ((attr = ippFindAttribute(ippGetCollection(defattr, 0), "media-type",
			       IPP_TAG_ZERO)) != NULL)
    ppdPwgPpdizeName(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
  else
    strlcpy(ppdname, "Unknown", sizeof(ppdname));

  if ((attr = ippFindAttribute(supported, "media-type-supported",
			       IPP_TAG_ZERO)) != NULL &&
      (count = ippGetCount(attr)) > 1)
  {
    human_readable = cfCatalogLookUpOption("media-type", opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *MediaType/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *MediaType\n"
		   "*DefaultMediaType: %s\n",
		   (human_readable ? human_readable : "Media Type"),
		   ppdname);
    for (i = 0; i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL);

      ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));

      human_readable = cfCatalogLookUpChoice((char *)keyword, "media-type",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*MediaType %s: \"<</MediaType(%s)>>setpagedevice\"\n", ppdname, ppdname);
      ppd_put_string(fp, lang, "MediaType", ppdname, human_readable);
    }
    cupsFilePuts(fp, "*CloseUI: *MediaType\n");
  }

  //
  // ColorModel...
  //

  if ((defattr = ippFindAttribute(supported, "print-color-mode-default",
				  IPP_TAG_KEYWORD)) == NULL)
    defattr = ippFindAttribute(supported, "output-mode-default",
			       IPP_TAG_KEYWORD);

  if ((attr = ippFindAttribute(supported, "print-color-mode-supported",
			       IPP_TAG_KEYWORD)) == NULL)
    attr = ippFindAttribute(supported, "output-mode-supported",
			    IPP_TAG_KEYWORD);

  human_readable = cfCatalogLookUpOption("print-color-mode",
					 opt_strings_catalog,
					 printer_opt_strings_catalog);
  if (attr && ippGetCount(attr) > 0)
  {
    const char *default_color = NULL;	// Default
    int default_color_set = 0;
    int first_choice = 1;

    if ((keyword = ippGetString(defattr, 0, NULL)) != NULL &&
	strcmp(keyword, "auto"))
    {
      if (!strcmp(keyword, "bi-level"))
        default_color = "FastGray";
      else if (!strcmp(keyword, "process-bi-level"))
        default_color = "ProcessFastGray";
      else if (!strcmp(keyword, "auto-monochrome"))
        default_color = "AutoGray";
      else if (!strcmp(keyword, "monochrome"))
        default_color = "Gray";
      else if (!strcmp(keyword, "process-monochrome"))
        default_color = "ProcessGray";
      else
        default_color = "RGB";
      default_color_set = 1;
    }

    cupsFilePrintf(fp, "*%% ColorModel from %s\n", ippGetName(attr));

    for (i = 0, count = ippGetCount(attr); i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL); // Keyword for color/bit depth

      if (!strcmp(keyword, "bi-level"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("bi-level", "print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel FastGray/%s: \"\"\n",
		       (human_readable2 ? human_readable2 : "Text"));

        if (!default_color_set && !default_color)
	  default_color = "FastGray";
      }
      else if (!strcmp(keyword, "process-bi-level"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("process-bi-level",
						"print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel ProcessFastGray/%s: \"\"\n",
		       (human_readable2 ? human_readable2 : "Process Text"));
      }
      else if (!strcmp(keyword, "auto-monochrome"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("auto-monochrome",
						"print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel AutoGray/%s: \"\"\n",
		       (human_readable2 ? human_readable2 : "Auto Monochrome"));
      }
      else if (!strcmp(keyword, "monochrome"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("monochrome",
						"print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel Gray/%s: \"\"\n",
		       (human_readable2 ? human_readable2 : "Monochrome"));

        if (!default_color_set &&
	    (!default_color || !strcmp(default_color, "FastGray")))
	  default_color = "Gray";
      }
      else if (!strcmp(keyword, "process-monochrome"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("process-monochrome",
						"print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel ProcessGray/%s: \"\"\n",
		       (human_readable2 ? human_readable2 :
			"Process Monochrome"));
      }
      else if (!strcmp(keyword, "color"))
      {
        if (first_choice)
	{
	  first_choice = 0;
	  cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
			 "*OrderDependency: 10 AnySetup *ColorModel\n",
			 (human_readable ? human_readable : "Color Mode"));
	}

	human_readable2 = cfCatalogLookUpChoice("color", "print-color-mode",
						opt_strings_catalog,
						printer_opt_strings_catalog);
        cupsFilePrintf(fp, "*ColorModel RGB/%s: \"\"\n",
		       (human_readable2 ? human_readable2 : "Color"));

        if (!default_color_set)
	  default_color = "RGB";

	// Apparently some printers only advertise color support, so make sure
	// we also do grayscale for these printers...
	if (!ippContainsString(attr, "monochrome") &&
	    !ippContainsString(attr, "auto-monochrome") &&
	    !ippContainsString(attr, "process-monochrome") &&
	    !ippContainsString(attr, "bi-level") &&
	    !ippContainsString(attr, "process-bi-level"))
	{
	  human_readable2 = cfCatalogLookUpChoice("monochrome",
						  "print-color-mode",
						  opt_strings_catalog,
						  printer_opt_strings_catalog);
	  cupsFilePrintf(fp, "*ColorModel Gray/%s: \"\"\n",
			 (human_readable2 ? human_readable2 : "Grayscale"));
	}
      }
    }

    if (default_pagesize != NULL && (!default_color || !defattr))
    {
      // Here we are dealing with a cluster, if the default cluster color
      // is not supplied we set it Gray
      if (default_cluster_color != NULL)
	default_color = default_cluster_color;
      else
	default_color = "Gray";
    }

    if (default_color)
      cupsFilePrintf(fp, "*DefaultColorModel: %s\n", default_color);
    if (!first_choice)
      cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
  }
  else
  {
    cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *ColorModel\n",
		   (human_readable ? human_readable : "Color Mode"));
    if (color)
    {
      // Color printer according to DNS-SD (or unknown)
      cupsFilePrintf(fp, "*DefaultColorModel: RGB\n");
      cupsFilePuts(fp, "*ColorModel RGB/Color: \"\"\n");
    }
    else
      cupsFilePrintf(fp, "*DefaultColorModel: Gray\n");
    cupsFilePuts(fp, "*ColorModel FastGray/Fast Grayscale: \"\"\n");
    cupsFilePuts(fp, "*ColorModel Gray/Grayscale: \"\"\n");
    cupsFilePuts(fp, "*CloseUI: *ColorModel\n");
  }

  //
  // Duplex...
  //

  if (((attr = ippFindAttribute(supported, "sides-supported",
				IPP_TAG_KEYWORD)) != NULL &&
       ippContainsString(attr, "two-sided-long-edge")) ||
      (attr == NULL && duplex))
  {
    human_readable = cfCatalogLookUpOption("sides", opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *Duplex/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *Duplex\n"
		   "*DefaultDuplex: None\n",
		   (human_readable ? human_readable : "2-Sided Printing"));
    human_readable = cfCatalogLookUpChoice("one-sided", "sides",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*Duplex None/%s: \"<</Duplex false>>setpagedevice\"\n",
		   (human_readable ? human_readable : "Off"));
    human_readable = cfCatalogLookUpChoice("two-sided-long-edge", "sides",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*Duplex DuplexNoTumble/%s: \"<</Duplex true/Tumble false>>setpagedevice\"\n",
		   (human_readable ? human_readable : "On (Portrait)"));
    human_readable = cfCatalogLookUpChoice("two-sided-short-edge", "sides",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*Duplex DuplexTumble/%s: \"<</Duplex true/Tumble true>>setpagedevice\"\n",
		   (human_readable ? human_readable : "On (Landscape)"));
    cupsFilePrintf(fp, "*CloseUI: *Duplex\n");

    if ((attr = ippFindAttribute(supported, "urf-supported",
				 IPP_TAG_KEYWORD)) != NULL)
    {
      for (i = 0, count = ippGetCount(attr); i < count; i ++)
      {
	const char *dm = ippGetString(attr, i, NULL); // DM value

	if (!strcasecmp(dm, "DM1"))
	{
	  cupsFilePuts(fp, "*cupsBackSide: Normal\n");
	  break;
	}
	else if (!strcasecmp(dm, "DM2"))
	{
	  cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
	  break;
	}
	else if (!strcasecmp(dm, "DM3"))
	{
	  cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
	  break;
	}
	else if (!strcasecmp(dm, "DM4"))
	{
	  cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
	  break;
	}
      }
    }
    else if ((attr = ippFindAttribute(supported,
				      "pwg-raster-document-sheet-back",
				      IPP_TAG_KEYWORD)) != NULL)
    {
      keyword = ippGetString(attr, 0, NULL); // Keyword value

      if (!strcmp(keyword, "flipped"))
        cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
      else if (!strcmp(keyword, "manual-tumble"))
        cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
      else if (!strcmp(keyword, "normal"))
        cupsFilePuts(fp, "*cupsBackSide: Normal\n");
      else
        cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
    }
  }

  //
  // Output bin...
  //

  if ((attr = ippFindAttribute(supported, "output-bin-default",
			       IPP_TAG_ZERO)) != NULL)
    ppdPwgPpdizeName(ippGetString(attr, 0, NULL), ppdname, sizeof(ppdname));
  else
    strlcpy(ppdname, "Unknown", sizeof(ppdname));

  if ((attr = ippFindAttribute(supported, "output-bin-supported",
			       IPP_TAG_ZERO)) != NULL &&
      (count = ippGetCount(attr)) > 0)
  {
    human_readable = cfCatalogLookUpOption("output-bin", opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *OutputBin/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *OutputBin\n"
		   "*DefaultOutputBin: %s\n",
		   (human_readable ? human_readable : "Output Bin"),
		   ppdname);
    attr2 = ippFindAttribute(supported, "printer-output-tray", IPP_TAG_STRING);
    for (i = 0; i < count; i ++)
    {
      keyword = ippGetString(attr, i, NULL);

      ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));

      human_readable = cfCatalogLookUpChoice((char *)keyword, "output-bin",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OutputBin %s: \"\"\n", ppdname);
      ppd_put_string(fp, lang, "OutputBin", ppdname, human_readable);
      outputorderinfofound = 0;
      faceupdown = 1;
      firsttolast = 1;
      if (attr2 && i < ippGetCount(attr2))
      {
	outbin_properties_octet = ippGetOctetString(attr2, i, &octet_str_len);
	memset(outbin_properties, 0, sizeof(outbin_properties));
	memcpy(outbin_properties, outbin_properties_octet,
	       ((size_t)octet_str_len < sizeof(outbin_properties) - 1 ?
		(size_t)octet_str_len : sizeof(outbin_properties) - 1));
	if (strcasestr(outbin_properties, "pagedelivery=faceUp"))
	{
	  outputorderinfofound = 1;
	  faceupdown = -1;
	}
	else if (strcasestr(outbin_properties, "pagedelivery=faceDown"))
	{
	  outputorderinfofound = 1;
	  faceupdown = 1;
	}
	if (strcasestr(outbin_properties, "stackingorder=lastToFirst"))
	{
	  outputorderinfofound = 1;
	  firsttolast = -1;
	}
	else if (strcasestr(outbin_properties, "stackingorder=firstToLast"))
	{
	  outputorderinfofound = 1;
	  firsttolast = 1;
	}
      }
      if (outputorderinfofound == 0) {
	if (strcasestr(keyword, "face-up"))
	{
	  outputorderinfofound = 1;
	  faceupdown = -1;
	}
	if (strcasestr(keyword, "face-down"))
	{
	  outputorderinfofound = 1;
	  faceupdown = 1;
	}
      }
      if (outputorderinfofound)
	cupsFilePrintf(fp, "*PageStackOrder %s: %s\n",
		       ppdname,
		       (firsttolast * faceupdown < 0 ? "Reverse" : "Normal"));
    }
    cupsFilePuts(fp, "*CloseUI: *OutputBin\n");
  }

  //
  // Finishing options...
  //

  if ((attr = ippFindAttribute(supported, "finishings-supported",
			       IPP_TAG_ENUM)) != NULL)
  {
    int			value;		// Enum value
    const char		*ppd_keyword;	// PPD keyword for enum
    cups_array_t	*names;		// Names we've added
    static const char * const base_keywords[] =
    {					// Base STD 92 keywords
      NULL,				// none
      "SingleAuto",			// staple
      "SingleAuto",			// punch
      NULL,				// cover
      "BindAuto",			// bind
      "SaddleStitch",			// saddle-stitch
      "EdgeStitchAuto",			// edge-stitch
      "Auto",				// fold
      NULL,				// trim
      NULL,				// bale
      NULL,				// booklet-maker
      NULL,				// jog-offset
      NULL,				// coat
      NULL				// laminate
    };

    count = ippGetCount(attr);
    names = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0,
			  (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
    fin_options = cupsArrayNew((cups_array_func_t)strcmp, NULL);

    //
    // Staple/Bind/Stitch
    //

    for (i = 0; i < count; i ++)
    {
      value   = ippGetInteger(attr, i);
      keyword = ippEnumString("finishings", value);

      if (!strncmp(keyword, "staple-", 7) ||
	  !strncmp(keyword, "bind-", 5) ||
	  !strncmp(keyword, "edge-stitch-", 12) ||
	  !strcmp(keyword, "saddle-stitch"))
        break;
    }

    if (i < count)
    {
      static const char * const staple_keywords[] =
      {					// StapleLocation keywords
	"SinglePortrait",
	"SingleRevLandscape",
	"SingleLandscape",
	"SingleRevPortrait",
	"EdgeStitchPortrait",
	"EdgeStitchLandscape",
	"EdgeStitchRevPortrait",
	"EdgeStitchRevLandscape",
	"DualPortrait",
	"DualLandscape",
	"DualRevPortrait",
	"DualRevLandscape",
	"TriplePortrait",
	"TripleLandscape",
	"TripleRevPortrait",
	"TripleRevLandscape"
      };
      static const char * const bind_keywords[] =
      {					// StapleLocation binding keywords
	"BindPortrait",
	"BindLandscape",
	"BindRevPortrait",
	"BindRevLandscape"
      };

      cupsArrayAdd(fin_options, "*StapleLocation");

      human_readable = cfCatalogLookUpChoice("staple", "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *StapleLocation/%s: PickOne\n",
		     (human_readable ? human_readable : "Staple"));
      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *StapleLocation\n");
      cupsFilePuts(fp, "*DefaultStapleLocation: None\n");
      human_readable = cfCatalogLookUpChoice("3", "finishings",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*StapleLocation None/%s: \"\"\n",
		     (human_readable ? human_readable : "None"));

      for (; i < count; i ++)
      {
        value   = ippGetInteger(attr, i);
        keyword = ippEnumString("finishings", value);

        if (strncmp(keyword, "staple-", 7) &&
	    strncmp(keyword, "bind-", 5) &&
	    strncmp(keyword, "edge-stitch-", 12) &&
	    strcmp(keyword, "saddle-stitch"))
          continue;

        if (cupsArrayFind(names, (char *)keyword))
          continue; // Already did this finishing template

        cupsArrayAdd(names, (char *)keyword);

        if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
          ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
        else if (value >= IPP_FINISHINGS_STAPLE_TOP_LEFT &&
		 value <= IPP_FINISHINGS_STAPLE_TRIPLE_BOTTOM)
          ppd_keyword = staple_keywords[value - IPP_FINISHINGS_STAPLE_TOP_LEFT];
        else if (value >= IPP_FINISHINGS_BIND_LEFT &&
		 value <= IPP_FINISHINGS_BIND_BOTTOM)
          ppd_keyword = bind_keywords[value - IPP_FINISHINGS_BIND_LEFT];
        else
          ppd_keyword = NULL;

        if (!ppd_keyword)
          continue;

	snprintf(buf, sizeof(buf), "%d", value);
	human_readable = cfCatalogLookUpChoice(buf, "finishings",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*StapleLocation %s: \"\"\n", ppd_keyword);
	ppd_put_string(fp, lang, "StapleLocation", ppd_keyword, human_readable);
	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n",
		       value, keyword, ppd_keyword);
      }

      cupsFilePuts(fp, "*CloseUI: *StapleLocation\n");
    }

    //
    // Fold
    //

    for (i = 0; i < count; i ++)
    {
      value   = ippGetInteger(attr, i);
      keyword = ippEnumString("finishings", value);

      if (!strncmp(keyword, "cups-fold-", 10) ||
	  !strcmp(keyword, "fold") ||
	  !strncmp(keyword, "fold-", 5))
        break;
    }

    if (i < count)
    {
      static const char * const fold_keywords[] =
      {					// FoldType keywords
	"Accordion",
	"DoubleGate",
	"Gate",
	"Half",
	"HalfZ",
	"LeftGate",
	"Letter",
	"Parallel",
	"XFold",
	"RightGate",
	"ZFold",
	"EngineeringZ"
      };

      cupsArrayAdd(fin_options, "*FoldType");

      human_readable = cfCatalogLookUpChoice("fold", "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *FoldType/%s: PickOne\n",
		     (human_readable ? human_readable : "Fold"));
      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *FoldType\n");
      cupsFilePuts(fp, "*DefaultFoldType: None\n");
      human_readable = cfCatalogLookUpChoice("3", "finishings",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*FoldType None/%s: \"\"\n",
		     (human_readable ? human_readable : "None"));

      for (; i < count; i ++)
      {
        value   = ippGetInteger(attr, i);
        keyword = ippEnumString("finishings", value);

        if (!strncmp(keyword, "cups-fold-", 10))
          keyword += 5;
        else if (strcmp(keyword, "fold") && strncmp(keyword, "fold-", 5))
          continue;

        if (cupsArrayFind(names, (char *)keyword))
          continue; // Already did this finishing template

        cupsArrayAdd(names, (char *)keyword);

        if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
          ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
        else if (value >= IPP_FINISHINGS_FOLD_ACCORDION &&
		 value <= IPP_FINISHINGS_FOLD_ENGINEERING_Z)
          ppd_keyword = fold_keywords[value - IPP_FINISHINGS_FOLD_ACCORDION];
        else if (value >= IPP_FINISHINGS_CUPS_FOLD_ACCORDION &&
		 value <= IPP_FINISHINGS_CUPS_FOLD_Z)
          ppd_keyword = fold_keywords[value -
				      IPP_FINISHINGS_CUPS_FOLD_ACCORDION];
        else
          ppd_keyword = NULL;

        if (!ppd_keyword)
          continue;

	snprintf(buf, sizeof(buf), "%d", value);
	human_readable = cfCatalogLookUpChoice(buf, "finishings",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*FoldType %s: \"\"\n", ppd_keyword);
	ppd_put_string(fp, lang, "FoldType", ppd_keyword, human_readable);
	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n",
		       value, keyword, ppd_keyword);
      }

      cupsFilePuts(fp, "*CloseUI: *FoldType\n");
    }

    //
    // Punch
    //

    for (i = 0; i < count; i ++)
    {
      value   = ippGetInteger(attr, i);
      keyword = ippEnumString("finishings", value);

      if (!strncmp(keyword, "cups-punch-", 11) ||
	  !strncmp(keyword, "punch-", 6))
        break;
    }

    if (i < count)
    {
      static const char * const punch_keywords[] =
      {					// PunchMedia keywords
	"SinglePortrait",
	"SingleRevLandscape",
	"SingleLandscape",
	"SingleRevPortrait",
	"DualPortrait",
	"DualLandscape",
	"DualRevPortrait",
	"DualRevLandscape",
	"TriplePortrait",
	"TripleLandscape",
	"TripleRevPortrait",
	"TripleRevLandscape",
	"QuadPortrait",
	"QuadLandscape",
	"QuadRevPortrait",
	"QuadRevLandscape",
	"MultiplePortrait",
	"MultipleLandscape",
	"MultipleRevPortrait",
	"MultipleRevLandscape"
      };

      cupsArrayAdd(fin_options, "*PunchMedia");

      human_readable = cfCatalogLookUpChoice("punch", "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *PunchMedia/%s: PickOne\n",
		     (human_readable ? human_readable : "Punch"));
      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *PunchMedia\n");
      cupsFilePuts(fp, "*DefaultPunchMedia: None\n");
      human_readable = cfCatalogLookUpChoice("3", "finishings",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*PunchMedia None/%s: \"\"\n",
		     (human_readable ? human_readable : "None"));

      for (i = 0; i < count; i ++)
      {
        value   = ippGetInteger(attr, i);
        keyword = ippEnumString("finishings", value);

        if (!strncmp(keyword, "cups-punch-", 11))
          keyword += 5;
        else if (strncmp(keyword, "punch-", 6))
          continue;

        if (cupsArrayFind(names, (char *)keyword))
          continue; // Already did this finishing template

        cupsArrayAdd(names, (char *)keyword);

        if (value >= IPP_FINISHINGS_NONE && value <= IPP_FINISHINGS_LAMINATE)
          ppd_keyword = base_keywords[value - IPP_FINISHINGS_NONE];
        else if (value >= IPP_FINISHINGS_PUNCH_TOP_LEFT &&
		 value <= IPP_FINISHINGS_PUNCH_MULTIPLE_BOTTOM)
          ppd_keyword = punch_keywords[value - IPP_FINISHINGS_PUNCH_TOP_LEFT];
        else if (value >= IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT &&
		 value <= IPP_FINISHINGS_CUPS_PUNCH_QUAD_BOTTOM)
          ppd_keyword = punch_keywords[value -
				       IPP_FINISHINGS_CUPS_PUNCH_TOP_LEFT];
        else
          ppd_keyword = NULL;

        if (!ppd_keyword)
          continue;

	snprintf(buf, sizeof(buf), "%d", value);
	human_readable = cfCatalogLookUpChoice(buf, "finishings",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*PunchMedia %s: \"\"\n", ppd_keyword);
	ppd_put_string(fp, lang, "PunchMedia", ppd_keyword, human_readable);
	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n",
		       value, keyword, ppd_keyword);
      }

      cupsFilePuts(fp, "*CloseUI: *PunchMedia\n");
    }

    //
    // Booklet
    //

    if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER))
    {
      cupsArrayAdd(fin_options, "*Booklet");

      human_readable = cfCatalogLookUpChoice("booklet-maker",
					     "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *Booklet/%s: Boolean\n",
		     (human_readable ? human_readable : "Booklet"));
      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *Booklet\n");
      cupsFilePuts(fp, "*DefaultBooklet: False\n");
      cupsFilePuts(fp, "*Booklet False: \"\"\n");
      cupsFilePuts(fp, "*Booklet True: \"\"\n");
      cupsFilePrintf(fp, "*cupsIPPFinishings %d/booklet-maker: \"*Booklet True\"\n",
		     IPP_FINISHINGS_BOOKLET_MAKER);
      cupsFilePuts(fp, "*CloseUI: *Booklet\n");
    }

    //
    // CutMedia
    //

    for (i = 0; i < count; i ++)
    {
      value   = ippGetInteger(attr, i);
      keyword = ippEnumString("finishings", value);

      if (!strcmp(keyword, "trim") || !strncmp(keyword, "trim-", 5))
        break;
    }

    if (i < count)
    {
      static const char * const trim_keywords[] =
      {				// CutMedia keywords
        "EndOfPage",
        "EndOfDoc",
        "EndOfSet",
        "EndOfJob"
      };

      cupsArrayAdd(fin_options, "*CutMedia");

      human_readable = cfCatalogLookUpChoice("trim", "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *CutMedia/%s: PickOne\n",
		     (human_readable ? human_readable : "Cut"));
      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *CutMedia\n");
      cupsFilePuts(fp, "*DefaultCutMedia: None\n");
      human_readable = cfCatalogLookUpChoice("3", "finishings",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*CutMedia None/%s: \"\"\n",
		     (human_readable ? human_readable : "None"));

      for (i = 0; i < count; i ++)
      {
        value   = ippGetInteger(attr, i);
        keyword = ippEnumString("finishings", value);

	if (strcmp(keyword, "trim") && strncmp(keyword, "trim-", 5))
          continue;

        if (cupsArrayFind(names, (char *)keyword))
          continue; // Already did this finishing template

        cupsArrayAdd(names, (char *)keyword);

        if (value == IPP_FINISHINGS_TRIM)
          ppd_keyword = "Auto";
	else
	  ppd_keyword = trim_keywords[value - IPP_FINISHINGS_TRIM_AFTER_PAGES];

	snprintf(buf, sizeof(buf), "%d", value);
	human_readable = cfCatalogLookUpChoice(buf, "finishings",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*CutMedia %s: \"\"\n", ppd_keyword);
	ppd_put_string(fp, lang, "CutMedia", ppd_keyword, human_readable);
	cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*CutMedia %s\"\n",
		       value, keyword, ppd_keyword);
      }

      cupsFilePuts(fp, "*CloseUI: *CutMedia\n");
    }

    cupsArrayDelete(names);
  }

  if ((attr = ippFindAttribute(supported, "finishings-col-database",
			       IPP_TAG_BEGIN_COLLECTION)) != NULL)
  {
    ipp_t	*finishing_col;		// Current finishing collection
    ipp_attribute_t *finishing_attr;	// Current finishing member attribute
    cups_array_t *templates;		// Finishing templates

    human_readable = cfCatalogLookUpOption("finishing-template",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickOne\n",
		   (human_readable ? human_readable : "Finishing Template"));
    cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n");
    cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: None\n");
    human_readable = cfCatalogLookUpChoice("3", "finishings",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*cupsFinishingTemplate None/%s: \"\"\n",
		   (human_readable ? human_readable : "None"));

    templates = cupsArrayNew((cups_array_func_t)strcmp, NULL);
    count     = ippGetCount(attr);

    for (i = 0; i < count; i ++)
    {
      finishing_col = ippGetCollection(attr, i);
      keyword = ippGetString(ippFindAttribute(finishing_col,
					      "finishing-template",
					      IPP_TAG_ZERO), 0, NULL);

      if (!keyword || cupsArrayFind(templates, (void *)keyword))
        continue;

      if (!strcmp(keyword, "none"))
        continue;

      cupsArrayAdd(templates, (void *)keyword);

      human_readable = cfCatalogLookUpChoice((char *)keyword,
					     "finishing-template",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      if (human_readable == NULL)
	human_readable = (char *)keyword;
      ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));
      cupsFilePrintf(fp, "*cupsFinishingTemplate %s: \"\n", ppdname);
      ppd_put_string(fp, lang, "cupsFinishingTemplate", ppdname, human_readable);
      for (finishing_attr = ippFirstAttribute(finishing_col); finishing_attr;
	   finishing_attr = ippNextAttribute(finishing_col)) {
        if (ippGetValueTag(finishing_attr) == IPP_TAG_BEGIN_COLLECTION) {
	  const char *name = ippGetName(finishing_attr);
					// Member attribute name

          if (strcmp(name, "media-size"))
            cupsFilePrintf(fp, "%% %s\n", name);
	}
      }
      cupsFilePuts(fp, "\"\n");
      cupsFilePuts(fp, "*End\n");
    }

    cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");

    if (cupsArrayCount(fin_options))
    {
      const char	*fin_option;	// Current finishing option

      cupsFilePuts(fp, "*cupsUIConstraint finishing-template: \"*cupsFinishingTemplate");
      for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
	   fin_option = (const char *)cupsArrayNext(fin_options))
        cupsFilePrintf(fp, " %s", fin_option);
      cupsFilePuts(fp, "\"\n");

      cupsFilePuts(fp, "*cupsUIResolver finishing-template: \"*cupsFinishingTemplate None");
      for (fin_option = (const char *)cupsArrayFirst(fin_options); fin_option;
	   fin_option = (const char *)cupsArrayNext(fin_options))
        cupsFilePrintf(fp, " %s None", fin_option);
      cupsFilePuts(fp, "\"\n");
    }

    cupsArrayDelete(templates);
  }

  cupsArrayDelete(fin_options);

  //
  // DefaultResolution...
  //

  xres = common_def->x;
  yres = common_def->y;
  if (xres == yres)
    cupsFilePrintf(fp, "*DefaultResolution: %ddpi\n", xres);
  else
    cupsFilePrintf(fp, "*DefaultResolution: %dx%ddpi\n", xres, yres);

  //
  // cupsPrintQuality...
  //

  if ((quality =
       ippFindAttribute(supported, "print-quality-supported",
			IPP_TAG_ENUM)) != NULL)
  {
    human_readable = cfCatalogLookUpOption("print-quality", opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
		   "*DefaultcupsPrintQuality: Normal\n",
		   (human_readable ? human_readable : "Print Quality"));
    if (ippContainsInteger(quality, IPP_QUALITY_DRAFT))
    {
      human_readable = cfCatalogLookUpChoice("3", "print-quality",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
		     (human_readable ? human_readable : "Draft"),
		     min_res->x, min_res->y);
    }
    human_readable = cfCatalogLookUpChoice("4", "print-quality",
					   opt_strings_catalog,
					   printer_opt_strings_catalog);
    cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
		   (human_readable ? human_readable : "Normal"),
		   common_def->x, common_def->y);
    if (ippContainsInteger(quality, IPP_QUALITY_HIGH))
    {
      human_readable = cfCatalogLookUpChoice("5", "print-quality",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n",
		     (human_readable ? human_readable : "High"),
		     max_res->x, max_res->y);
    }
    cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
  }

  // Only add these options if jobs get sent to the printer as PDF,
  // PWG Raster, or Apple Raster, as only then arbitrary IPP
  // attributes get passed through from the filter command line
  // to the printer by the "ipp" CUPS backend.
  if (is_pdf || is_pwg || is_apple)
  {
    //
    // Print Optimization ...
    //

    if ((attr = ippFindAttribute(supported, "print-content-optimize-default",
				 IPP_TAG_ZERO)) != NULL)
      strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
    else
      strlcpy(ppdname, "auto", sizeof(ppdname));

    if ((attr = ippFindAttribute(supported, "print-content-optimize-supported",
				 IPP_TAG_ZERO)) != NULL &&
	(count = ippGetCount(attr)) > 1)
    {
      human_readable = cfCatalogLookUpOption("print-content-optimize",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *print-content-optimize/%s: PickOne\n"
		     "*OrderDependency: 10 AnySetup *print-content-optimize\n"
		     "*Defaultprint-content-optimize: %s\n",
		     (human_readable ? human_readable : "Print Optimization"),
		     ppdname);
      for (i = 0; i < count; i ++) {
	keyword = ippGetString(attr, i, NULL);

	human_readable = cfCatalogLookUpChoice((char *)keyword,
					       "print-content-optimize",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*print-content-optimize %s%s%s: \"\"\n",
		       keyword,
		       (human_readable ? "/" : ""),
		       (human_readable ? human_readable : ""));
      }
      cupsFilePuts(fp, "*CloseUI: *print-content-optimize\n");
    }

    //
    // Print Rendering Intent ...
    //

    if ((attr = ippFindAttribute(supported, "print-rendering-intent-default",
				 IPP_TAG_ZERO)) != NULL)
      strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
    else
      strlcpy(ppdname, "auto", sizeof(ppdname));

    if ((attr = ippFindAttribute(supported, "print-rendering-intent-supported",
				 IPP_TAG_ZERO)) != NULL &&
	(count = ippGetCount(attr)) > 1)
    {
      human_readable = cfCatalogLookUpOption("print-rendering-intent",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *cupsRenderingIntent/%s: PickOne\n"
		     "*OrderDependency: 10 AnySetup *cupsRenderingIntent\n"
		     "*DefaultcupsRenderingIntent: %s\n",
		     (human_readable ? human_readable :
		      "Print Rendering Intent"),
		     ppdname);
      for (i = 0; i < count; i ++)
      {
	keyword = ippGetString(attr, i, NULL);

	human_readable = cfCatalogLookUpChoice((char *)keyword,
					       "print-rendering-intent",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*cupsRenderingIntent %s%s%s: \"<</cupsRenderingIntent (%s)>>setpagedevice\"\n",
		       keyword,
		       (human_readable ? "/" : ""),
		       (human_readable ? human_readable : ""),
		       keyword);
      }
      cupsFilePuts(fp, "*CloseUI: *cupsRenderingIntent\n");
    }

    //
    // Print Scaling ...
    //

    if ((attr = ippFindAttribute(supported, "print-scaling-default",
				 IPP_TAG_ZERO)) != NULL)
      strlcpy(ppdname, ippGetString(attr, 0, NULL), sizeof(ppdname));
    else
      strlcpy(ppdname, "auto", sizeof(ppdname));

    if ((attr = ippFindAttribute(supported, "print-scaling-supported",
				 IPP_TAG_ZERO)) != NULL &&
	(count = ippGetCount(attr)) > 1)
    {
      human_readable = cfCatalogLookUpOption("print-scaling",
					     opt_strings_catalog,
					     printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*OpenUI *print-scaling/%s: PickOne\n"
		     "*OrderDependency: 10 AnySetup *print-scaling\n"
		     "*Defaultprint-scaling: %s\n",
		     (human_readable ? human_readable : "Print Scaling"),
		     ppdname);
      for (i = 0; i < count; i ++)
      {
	keyword = ippGetString(attr, i, NULL);

	human_readable = cfCatalogLookUpChoice((char *)keyword, "print-scaling",
					       opt_strings_catalog,
					       printer_opt_strings_catalog);
	cupsFilePrintf(fp, "*print-scaling %s%s%s: \"\"\n",
		       keyword,
		       (human_readable ? "/" : ""),
		       (human_readable ? human_readable : ""));
      }
      cupsFilePuts(fp, "*CloseUI: *print-scaling\n");
    }
  }

  //
  // Phone Options for Fax..
  //

  if (is_fax)
  {
    human_readable = cfCatalogLookUpOption("Phone", opt_strings_catalog,
					   printer_opt_strings_catalog);

    cupsFilePrintf(fp, "*OpenUI *phone/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *phone\n"
		   "*Defaultphone: None\n"
		   "*phone None: \"\"\n"
		   "*CloseUI: *phone\n",
		   (human_readable ? human_readable : "Phone Number"));
    cupsFilePrintf(fp,"*Customphone True: \"\"\n"
		   "*ParamCustomphone Text: 1 string 0 64\n");

    human_readable = cfCatalogLookUpOption("faxPrefix", opt_strings_catalog,
					   printer_opt_strings_catalog);

    cupsFilePrintf(fp, "*OpenUI *faxPrefix/%s: PickOne\n"
		   "*OrderDependency: 10 AnySetup *faxPrefix\n"
		   "*DefaultfaxPrefix: None\n"
		   "*faxPrefix None: \"\"\n"
		   "*CloseUI: *faxPrefix\n",
		   (human_readable ? human_readable : "Pre-Dial Number"));
    cupsFilePrintf(fp,"*CustomfaxPrefix True: \"\"\n"
		   "*ParamCustomfaxPrefix Text: 1 string 0 64\n");
  }

  //
  // Presets...
  //

  if ((attr = ippFindAttribute(supported, "job-presets-supported",
			       IPP_TAG_BEGIN_COLLECTION)) != NULL)
  {
    for (i = 0, count = ippGetCount(attr); i < count; i ++)
    {
      ipp_t	 *preset = ippGetCollection(attr, i); // Preset collection
      const char *preset_name =         // Preset name
	ippGetString(ippFindAttribute(preset,
				      "preset-name", IPP_TAG_ZERO), 0, NULL),
		 *localized_name;	// Localized preset name
      ipp_attribute_t *member;		// Member attribute in preset
      const char *member_name;		// Member attribute name
      char       member_value[256];	// Member attribute value

      if (!preset || !preset_name)
        continue;

      ppdPwgPpdizeName(preset_name, ppdname, sizeof(ppdname));

      localized_name =
	   cfCatalogLookUpOption((char *)preset_name,
				 opt_strings_catalog,
				 printer_opt_strings_catalog);
      cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", ppdname);
      ppd_put_string(fp, lang, "APPrinterPreset", ppdname, localized_name);

      for (member = ippFirstAttribute(preset); member;
	   member = ippNextAttribute(preset))
      {
        member_name = ippGetName(member);

        if (!member_name || !strcmp(member_name, "preset-name"))
          continue;

        if (!strcmp(member_name, "finishings"))
	{
	  for (i = 0, count = ippGetCount(member); i < count; i ++)
	  {
	    const char *option = NULL;	// PPD option name

	    keyword = ippEnumString("finishings", ippGetInteger(member, i));

	    if (!strcmp(keyword, "booklet-maker"))
	    {
	      option  = "Booklet";
	      keyword = "True";
	    }
	    else if (!strncmp(keyword, "fold-", 5))
	      option = "FoldType";
	    else if (!strncmp(keyword, "punch-", 6))
	      option = "PunchMedia";
	    else if (!strncmp(keyword, "bind-", 5) ||
		     !strncmp(keyword, "edge-stitch-", 12) ||
		     !strcmp(keyword, "saddle-stitch") ||
		     !strncmp(keyword, "staple-", 7))
	      option = "StapleLocation";

	    if (option && keyword)
	      cupsFilePrintf(fp, "*%s %s\n", option, keyword);
	  }
        }
	else if (!strcmp(member_name, "finishings-col"))
	{
          ipp_t *fin_col;		// finishings-col value

          for (i = 0, count = ippGetCount(member); i < count; i ++)
	  {
            fin_col = ippGetCollection(member, i);

            if ((keyword =
		 ippGetString(ippFindAttribute(fin_col,
					       "finishing-template",
					       IPP_TAG_ZERO), 0, NULL)) != NULL)
            {
	      ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));
              cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", ppdname);
            }
          }
        }
	else if (!strcmp(member_name, "media"))
	{
	  //
          // Map media to PageSize...
	  //

          if ((pwg = pwgMediaForPWG(ippGetString(member, 0, NULL))) != NULL &&
	      pwg->ppd)
            cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
        }
	else if (!strcmp(member_name, "media-col"))
	{
          media_col = ippGetCollection(member, 0);

          if ((media_size =
	       ippGetCollection(ippFindAttribute(media_col,
						 "media-size",
						 IPP_TAG_BEGIN_COLLECTION),
				0)) != NULL)
	  {
            x_dim = ippFindAttribute(media_size, "x-dimension",
				     IPP_TAG_INTEGER);
            y_dim = ippFindAttribute(media_size, "y-dimension",
				     IPP_TAG_INTEGER);
            if ((pwg = pwgMediaForSize(ippGetInteger(x_dim, 0),
				       ippGetInteger(y_dim, 0))) != NULL &&
		pwg->ppd)
	      cupsFilePrintf(fp, "*PageSize %s\n", pwg->ppd);
          }

          if ((keyword = ippGetString(ippFindAttribute(media_col,
						       "media-source",
						       IPP_TAG_ZERO), 0,
				      NULL)) != NULL)
	  {
            ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));
            cupsFilePrintf(fp, "*InputSlot %s\n", ppdname);
	  }

          if ((keyword = ippGetString(ippFindAttribute(media_col, "media-type",
						       IPP_TAG_ZERO), 0,
				      NULL)) != NULL)
	  {
            ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname));
            cupsFilePrintf(fp, "*MediaType %s\n", ppdname);
	  }
        }
	else if (!strcmp(member_name, "print-quality"))
	{
	  //
	  // Map print-quality to cupsPrintQuality...
	  //

          int qval = ippGetInteger(member, 0);
					// print-quality value
	  static const char * const qualities[] = { "Draft", "Normal", "High" };
					// cupsPrintQuality values

          if (qval >= IPP_QUALITY_DRAFT && qval <= IPP_QUALITY_HIGH)
            cupsFilePrintf(fp, "*cupsPrintQuality %s\n",
			   qualities[qval - IPP_QUALITY_DRAFT]);
        }
	else if (!strcmp(member_name, "output-bin"))
	{
          ppdPwgPpdizeName(ippGetString(member, 0, NULL), ppdname,
			   sizeof(ppdname));
          cupsFilePrintf(fp, "*OutputBin %s\n", ppdname);
        }
	else if (!strcmp(member_name, "sides"))
	{
          keyword = ippGetString(member, 0, NULL);
          if (keyword && !strcmp(keyword, "one-sided"))
            cupsFilePuts(fp, "*Duplex None\n");
	  else if (keyword && !strcmp(keyword, "two-sided-long-edge"))
	    cupsFilePuts(fp, "*Duplex DuplexNoTumble\n");
	  else if (keyword && !strcmp(keyword, "two-sided-short-edge"))
	    cupsFilePuts(fp, "*Duplex DuplexTumble\n");
        }
	else
	{
	  //
          // Add attribute name and value as-is...
	  //

          ippAttributeString(member, member_value, sizeof(member_value));
          cupsFilePrintf(fp, "*%s %s\n", member_name, member_value);
	}
      }

      cupsFilePuts(fp, "\"\n*End\n");
    }
  }

  //
  // constraints
  //

  if (conflicts != NULL)
  {
    char* constraint;
    for (constraint = (char *)cupsArrayFirst(conflicts); constraint;
         constraint = (char *)cupsArrayNext(conflicts))
      cupsFilePrintf(fp, "%s", constraint);
  }

  //
  // Clean up and return...
  //

  free(common_def);
  free(min_res);
  free(max_res);

  if (status_msg && status_msg_size)
    snprintf(status_msg, status_msg_size,
	     "%s %sPPD generated.",
	     (is_apple ? "Apple Raster" :
	      (is_pwg ? "PWG Raster" :
	       (is_pdf ? "PDF" :
		(is_pclm ? "PCLm" :
		 "Legacy IPP printer")))),
	     (is_fax ? "Fax " : ""));

  cupsFileClose(fp);
  if (opt_strings_catalog)
    cupsArrayDelete(opt_strings_catalog);
  if (printer_opt_strings_catalog)
    cupsArrayDelete(printer_opt_strings_catalog);

  return (buffer);

  //
  // If we get here then there was a problem creating the PPD...
  //

 bad_ppd:

  if (common_res) cupsArrayDelete(common_res);
  if (common_def) free(common_def);
  if (min_res) free(min_res);
  if (max_res) free(max_res);

  cupsFileClose(fp);
  if (opt_strings_catalog)
    cupsArrayDelete(opt_strings_catalog);
  if (printer_opt_strings_catalog)
    cupsArrayDelete(printer_opt_strings_catalog);
  unlink(buffer);
  *buffer = '\0';

  if (status_msg && status_msg_size)
    snprintf(status_msg, status_msg_size,
	     "Printer does not support required IPP attributes or document formats.");

  return (NULL);
}


//
// 'http_connect()' - Connect to a URL and get the resource path.
//

static int				// O  - 1 on success, 0 on failure
http_connect(http_t     **http,		// IO - Current HTTP connection
             const char *url,		// I  - URL to connect
             char       *resource,	// I  - Resource path buffer
             size_t     ressize)	// I  - Size of resource path buffer
{
  char			scheme[32],	// URL scheme
			userpass[256],	// URL username:password
			host[256],	// URL host
			curhost[256];	// Current host
  int			port;		// URL port
  http_encryption_t	encryption;	// Type of encryption to use


  // Separate the URI...
  if (httpSeparateURI(HTTP_URI_CODING_ALL, url, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, ressize) < HTTP_URI_STATUS_OK)
    return (0);

  // Use encryption as needed..
  if (port == 443 || !strcmp(scheme, "https") || !strcmp(scheme, "ipps"))
    encryption = HTTP_ENCRYPTION_ALWAYS;
  else
    encryption = HTTP_ENCRYPTION_IF_REQUESTED;

  if (!*http || strcasecmp(host, httpGetHostname(*http, curhost, sizeof(curhost))) || httpAddrPort(httpGetAddress(*http)) != port || httpIsEncrypted(*http) != (encryption == HTTP_ENCRYPTION_ALWAYS))
  {
    httpClose(*http);
    *http = httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 5000, NULL);
  }

  return (*http != NULL);
}


/*
 * 'ppd_put_strings()' - Write localization attributes to a PPD file.
 */

static void
ppd_put_string(cups_file_t  *fp,	/* I - PPD file */
               cups_lang_t  *lang,	/* I - Language */
	       const char   *ppd_option,/* I - PPD option */
	       const char   *ppd_choice,/* I - PPD choice */
	       const char   *text)	/* I - Localized text */
{
  if (!text)
    return;

  // Add the first line of localized text...
#if CUPS_VERSION_MAJOR > 2
  cupsFilePrintf(fp, "*%s.%s %s/", cupsLangGetName(lang), ppd_option, ppd_choice);
#else
  cupsFilePrintf(fp, "*%s.%s %s/", lang->language, ppd_option, ppd_choice);
#endif // CUPS_VERSION_MAJOR > 2

  while (*text && *text != '\n')
  {
    // Escape ":" and "<"...
    if (*text == ':' || *text == '<')
      cupsFilePrintf(fp, "<%02X>", *text);
    else
      cupsFilePutChar(fp, *text);

    text ++;
  }
  cupsFilePuts(fp, ": \"\"\n");
}
