]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/ppd-cache.c
Add cupsFinishingTemplate to generated PPDs.
[thirdparty/cups.git] / cups / ppd-cache.c
index 3c99f18bf3793ed2503f69fa162d7a6a161a2d98..925028790efdf6c506813717c1fd4480e6143f1c 100644 (file)
@@ -3,13 +3,7 @@
  *
  * Copyright 2010-2017 by Apple Inc.
  *
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. and are protected by Federal copyright
- * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- * which should have been included with this file.  If this file is
- * missing or damaged, see the license at "http://www.cups.org/".
- *
- * This file is subject to the Apple OS-Developed Software exception.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
  * Local functions...
  */
 
+static int     cups_get_url(http_t **http, const char *url, char *name, size_t namesize);
 static void    pwg_add_finishing(cups_array_t *finishings, ipp_finishings_t template, const char *name, const char *value);
 static int     pwg_compare_finishings(_pwg_finishings_t *a,
                                       _pwg_finishings_t *b);
 static void    pwg_free_finishings(_pwg_finishings_t *f);
-static void    pwg_free_material(_pwg_material_t *m);
 static void    pwg_ppdize_name(const char *ipp, char *name, size_t namesize);
 static void    pwg_ppdize_resolution(ipp_attribute_t *attr, int element, int *xres, int *yres, char *name, size_t namesize);
 static void    pwg_unppdize_name(const char *ppd, char *name, size_t namesize,
@@ -50,21 +44,20 @@ static void pwg_unppdize_name(const char *ppd, char *name, size_t namesize,
  * attributes and values and adds them to the specified IPP request.
  */
 
-int                                            /* O - New number of copies */
-_cupsConvertOptions(ipp_t           *request,  /* I - IPP request */
-                    ppd_file_t      *ppd,      /* I - PPD file */
-                   _ppd_cache_t    *pc,        /* I - PPD cache info */
-                   ipp_attribute_t *media_col_sup,
-                                               /* I - media-col-supported values */
-                   ipp_attribute_t *doc_handling_sup,
-                                               /* I - multiple-document-handling-supported values */
-                   ipp_attribute_t *print_color_mode_sup,
-                                               /* I - Printer supports print-color-mode */
-                   const char    *user,        /* I - User info */
-                   const char    *format,      /* I - document-format value */
-                   int           copies,       /* I - Number of copies */
-                   int           num_options,  /* I - Number of options */
-                   cups_option_t *options)     /* I - Options */
+int                                    /* O - New number of copies */
+_cupsConvertOptions(
+    ipp_t           *request,          /* I - IPP request */
+    ppd_file_t      *ppd,              /* I - PPD file */
+    _ppd_cache_t    *pc,               /* I - PPD cache info */
+    ipp_attribute_t *media_col_sup,    /* I - media-col-supported values */
+    ipp_attribute_t *doc_handling_sup, /* I - multiple-document-handling-supported values */
+    ipp_attribute_t *print_color_mode_sup,
+                                       /* I - Printer supports print-color-mode */
+    const char    *user,               /* I - User info */
+    const char    *format,             /* I - document-format value */
+    int           copies,              /* I - Number of copies */
+    int           num_options,         /* I - Number of options */
+    cups_option_t *options)            /* I - Options */
 {
   int          i;                      /* Looping var */
   const char   *keyword,               /* PWG keyword */
@@ -80,6 +73,8 @@ _cupsConvertOptions(ipp_t           *request, /* I - IPP request */
   int          num_finishings = 0,     /* Number of finishing values */
                finishings[10];         /* Finishing enum values */
   ppd_choice_t *choice;                /* Marked choice */
+  int           finishings_copies = copies;
+                                        /* Number of copies for finishings */
 
 
  /*
@@ -368,13 +363,13 @@ _cupsConvertOptions(ipp_t           *request,     /* I - IPP request */
   {
     ippAddIntegers(request, IPP_TAG_JOB, IPP_TAG_ENUM, "finishings", num_finishings, finishings);
 
-    if (copies > 1 && (keyword = cupsGetOption("job-impressions", num_options, options)) != NULL)
+    if (copies != finishings_copies && (keyword = cupsGetOption("job-impressions", num_options, options)) != NULL)
     {
      /*
       * Send job-pages-per-set attribute to apply finishings correctly...
       */
 
-      ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-pages-per-set", atoi(keyword) / copies);
+      ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-pages-per-set", atoi(keyword) / finishings_copies);
     }
   }
 
@@ -505,53 +500,6 @@ _ppdCacheCreateWithFile(
       _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad PPD cache file."), 1);
       goto create_error;
     }
-    else if (!_cups_strcasecmp(line, "3D"))
-    {
-      pc->cups_3d = _cupsStrAlloc(value);
-    }
-    else if (!_cups_strcasecmp(line, "LayerOrder"))
-    {
-      pc->cups_layer_order = _cupsStrAlloc(value);
-    }
-    else if (!_cups_strcasecmp(line, "Accuracy"))
-    {
-      sscanf(value, "%d%d%d", pc->cups_accuracy + 0, pc->cups_accuracy + 1, pc->cups_accuracy + 2);
-    }
-    else if (!_cups_strcasecmp(line, "Volume"))
-    {
-      sscanf(value, "%d%d%d", pc->cups_volume + 0, pc->cups_volume + 1, pc->cups_volume + 2);
-    }
-    else if (!_cups_strcasecmp(line, "Material"))
-    {
-     /*
-      * Material key "name" name=value ... name=value
-      */
-
-      if ((valueptr = strchr(value, ' ')) != NULL)
-      {
-       _pwg_material_t *material = (_pwg_material_t *)calloc(1, sizeof(_pwg_material_t));
-
-        *valueptr++ = '\0';
-
-        material->key = _cupsStrAlloc(value);
-
-        if (*valueptr == '\"')
-       {
-         value = valueptr + 1;
-         if ((valueptr = strchr(value, '\"')) != NULL)
-         {
-           *valueptr++ = '\0';
-           material->name = _cupsStrAlloc(value);
-           material->num_props = cupsParseOptions(valueptr, 0, &material->props);
-         }
-       }
-
-       if (!pc->materials)
-         pc->materials = cupsArrayNew3(NULL, NULL, NULL, 0, NULL, (cups_afree_func_t)pwg_free_material);
-
-        cupsArrayAdd(pc->materials, material);
-      }
-    }
     else if (!_cups_strcasecmp(line, "Filter"))
     {
       if (!pc->filters)
@@ -932,6 +880,8 @@ _ppdCacheCreateWithFile(
       else
         pc->mandatory = _cupsArrayNewStrings(value, ' ');
     }
+    else if (!_cups_strcasecmp(line, "StringsURI"))
+      pc->strings_uri = _cupsStrAlloc(value);
     else if (!_cups_strcasecmp(line, "SupportFile"))
     {
       if (!pc->support_files)
@@ -1879,6 +1829,13 @@ _ppdCacheCreateWithPPD(ppd_file_t *ppd)  /* I - PPD file */
   if ((ppd_attr = ppdFindAttr(ppd, "cupsMandatory", NULL)) != NULL)
     pc->mandatory = _cupsArrayNewStrings(ppd_attr->value, ' ');
 
+ /*
+  * Strings (remote) file...
+  */
+
+  if ((ppd_attr = ppdFindAttr(ppd, "cupsStringsURI", NULL)) != NULL)
+    pc->strings_uri = _cupsStrAlloc(ppd_attr->value);
+
  /*
   * Support files...
   */
@@ -1895,42 +1852,6 @@ _ppdCacheCreateWithPPD(ppd_file_t *ppd)  /* I - PPD file */
   if ((ppd_attr = ppdFindAttr(ppd, "APPrinterIconPath", NULL)) != NULL)
     cupsArrayAdd(pc->support_files, ppd_attr->value);
 
- /*
-  * 3D stuff...
-  */
-
-  if ((ppd_attr = ppdFindAttr(ppd, "cups3D", NULL)) != NULL)
-    pc->cups_3d = _cupsStrAlloc(ppd_attr->value);
-
-  if ((ppd_attr = ppdFindAttr(ppd, "cupsLayerOrder", NULL)) != NULL)
-    pc->cups_layer_order = _cupsStrAlloc(ppd_attr->value);
-
-  if ((ppd_attr = ppdFindAttr(ppd, "cupsAccuracy", NULL)) != NULL)
-    sscanf(ppd_attr->value, "%d%d%d", pc->cups_accuracy + 0, pc->cups_accuracy + 1, pc->cups_accuracy + 2);
-
-  if ((ppd_attr = ppdFindAttr(ppd, "cupsVolume", NULL)) != NULL)
-    sscanf(ppd_attr->value, "%d%d%d", pc->cups_volume + 0, pc->cups_volume + 1, pc->cups_volume + 2);
-
-  for (ppd_attr = ppdFindAttr(ppd, "cupsMaterial", NULL);
-       ppd_attr;
-       ppd_attr = ppdFindNextAttr(ppd, "cupsMaterial", NULL))
-  {
-   /*
-    * *cupsMaterial key/name: "name=value ... name=value"
-    */
-
-    _pwg_material_t    *material = (_pwg_material_t *)calloc(1, sizeof(_pwg_material_t));
-
-    material->key = _cupsStrAlloc(ppd_attr->name);
-    material->name = _cupsStrAlloc(ppd_attr->text);
-    material->num_props = cupsParseOptions(ppd_attr->value, 0, &material->props);
-
-    if (!pc->materials)
-      pc->materials = cupsArrayNew3(NULL, NULL, NULL, 0, NULL, (cups_afree_func_t)pwg_free_material);
-
-    cupsArrayAdd(pc->materials, material);
-  }
-
  /*
   * Return the cache data...
   */
@@ -2038,11 +1959,6 @@ _ppdCacheDestroy(_ppd_cache_t *pc)       /* I - PPD cache and mapping data */
 
   cupsArrayDelete(pc->support_files);
 
-  _cupsStrFree(pc->cups_3d);
-  _cupsStrFree(pc->cups_layer_order);
-
-  cupsArrayDelete(pc->materials);
-
   free(pc);
 }
 
@@ -2174,11 +2090,16 @@ _ppdCacheGetFinishingValues(
 
   DEBUG_printf(("_ppdCacheGetFinishingValues(pc=%p, num_options=%d, options=%p, max_values=%d, values=%p)", pc, num_options, options, max_values, values));
 
-  if (!pc || !pc->finishings || num_options < 1 || max_values < 1 || !values)
+  if (!pc || max_values < 1 || !values)
   {
     DEBUG_puts("_ppdCacheGetFinishingValues: Bad arguments, returning 0.");
     return (0);
   }
+  else if (!pc->finishings)
+  {
+    DEBUG_puts("_ppdCacheGetFinishingValues: No finishings support, returning 0.");
+    return (0);
+  }
 
  /*
   * Go through the finishings options and see what is set...
@@ -2204,7 +2125,7 @@ _ppdCacheGetFinishingValues(
 
     if (i == 0)
     {
-      DEBUG_printf(("_ppdCacheGetFinishingValues: Adding %d.", f->value));
+      DEBUG_printf(("_ppdCacheGetFinishingValues: Adding %d (%s)", f->value, ippEnumString("finishings", f->value)));
 
       values[num_values ++] = f->value;
 
@@ -2213,6 +2134,17 @@ _ppdCacheGetFinishingValues(
     }
   }
 
+  if (num_values == 0)
+  {
+   /*
+    * Always have at least "finishings" = 'none'...
+    */
+
+    DEBUG_puts("_ppdCacheGetFinishingValues: Adding 3 (none).");
+    values[0] = IPP_FINISHINGS_NONE;
+    num_values ++;
+  }
+
   DEBUG_printf(("_ppdCacheGetFinishingValues: Returning %d.", num_values));
 
   return (num_values);
@@ -2800,7 +2732,6 @@ _ppdCacheWriteFile(
   cups_option_t                *option;        /* Current option */
   const char           *value;         /* Filter/pre-filter value */
   char                 newfile[1024];  /* New filename */
-  _pwg_material_t      *m;             /* Material */
 
 
  /*
@@ -2972,6 +2903,13 @@ _ppdCacheWriteFile(
        value = (char *)cupsArrayNext(pc->mandatory))
     cupsFilePutConf(fp, "Mandatory", value);
 
+ /*
+  * (Remote) strings file...
+  */
+
+  if (pc->strings_uri)
+    cupsFilePutConf(fp, "StringsURI", pc->strings_uri);
+
  /*
   * Support files...
   */
@@ -2981,32 +2919,6 @@ _ppdCacheWriteFile(
        value = (char *)cupsArrayNext(pc->support_files))
     cupsFilePutConf(fp, "SupportFile", value);
 
- /*
-  * 3D stuff...
-  */
-
-  if (pc->cups_3d)
-    cupsFilePutConf(fp, "3D", pc->cups_3d);
-
-  if (pc->cups_layer_order)
-    cupsFilePutConf(fp, "LayerOrder", pc->cups_layer_order);
-
-  if (pc->cups_accuracy[0] || pc->cups_accuracy[0] || pc->cups_accuracy[2])
-    cupsFilePrintf(fp, "Accuracy %d %d %d\n", pc->cups_accuracy[0], pc->cups_accuracy[1], pc->cups_accuracy[2]);
-
-  if (pc->cups_volume[0] || pc->cups_volume[0] || pc->cups_volume[2])
-    cupsFilePrintf(fp, "Volume %d %d %d\n", pc->cups_volume[0], pc->cups_volume[1], pc->cups_volume[2]);
-
-  for (m = (_pwg_material_t *)cupsArrayFirst(pc->materials);
-       m;
-       m = (_pwg_material_t *)cupsArrayNext(pc->materials))
-  {
-    cupsFilePrintf(fp, "Material %s \"%s\"", m->key, m->name);
-    for (i = 0; i < m->num_props; i ++)
-      cupsFilePrintf(fp, " %s=%s", m->props[i].name, m->props[i].value);
-    cupsFilePuts(fp, "\n");
-  }
-
  /*
   * IPP attributes, if any...
   */
@@ -3066,10 +2978,15 @@ _ppdCreateFromIPP(char   *buffer,       /* I - Filename buffer */
                        is_pwg = 0;     /* Does the printer support PWG Raster? */
   pwg_media_t          *pwg;           /* PWG media size */
   int                  xres, yres;     /* Resolution values */
+  int                   resolutions[1000];
+                                        /* Array of resolution indices */
   cups_lang_t          *lang = cupsLangDefault();
                                        /* Localization info */
+  cups_array_t         *strings = NULL;/* Printer strings file */
   struct lconv         *loc = localeconv();
                                        /* Locale data */
+  cups_array_t         *fin_options = NULL;
+                                       /* Finishing options */
   static const char * const finishings[][2] =
   {                                    /* Finishings strings */
     { "bale", _("Bale") },
@@ -3212,7 +3129,31 @@ _ppdCreateFromIPP(char   *buffer,        /* I - Filename buffer */
 
   cupsFilePrintf(fp, "*cupsVersion: %d.%d\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR);
   cupsFilePuts(fp, "*cupsSNMPSupplies: False\n");
-  cupsFilePuts(fp, "*cupsLanguages: \"en\"\n");
+  cupsFilePrintf(fp, "*cupsLanguages: \"%s\"\n", lang->language);
+
+  if ((attr = ippFindAttribute(response, "printer-more-info", IPP_TAG_URI)) != NULL)
+    cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL));
+
+  if ((attr = ippFindAttribute(response, "printer-charge-info-uri", IPP_TAG_URI)) != NULL)
+    cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0, NULL));
+
+  if ((attr = ippFindAttribute(response, "printer-strings-uri", IPP_TAG_URI)) != NULL)
+  {
+    http_t     *http = NULL;           /* Connection to printer */
+    char       stringsfile[1024];      /* Temporary strings file */
+
+    if (cups_get_url(&http, ippGetString(attr, 0, NULL), stringsfile, sizeof(stringsfile)))
+    {
+      cupsFilePrintf(fp, "*cupsStringsURI: \"%s\"\n", ippGetString(attr, 0, NULL));
+
+      strings = _cupsMessageLoad(stringsfile, _CUPS_MESSAGE_STRINGS | _CUPS_MESSAGE_UNQUOTE);
+
+      unlink(stringsfile);
+    }
+
+    if (http)
+      httpClose(http);
+  }
 
  /*
   * Filters...
@@ -3537,6 +3478,19 @@ _ppdCreateFromIPP(char   *buffer,        /* I - Filename buffer */
       { "cardboard", _("Cardboard") },
       { "cardstock", _("Cardstock") },
       { "cd", _("CD") },
+      { "com.hp.advanced-photo", _("Advanced Photo Paper") }, /* HP */
+      { "com.hp.brochure-glossy", _("Glossy Brochure Paper") }, /* HP */
+      { "com.hp.brochure-matte", _("Matte Brochure Paper") }, /* HP */
+      { "com.hp.cover-matte", _("Matte Cover Paper") }, /* HP */
+      { "com.hp.ecosmart-lite", _("Office Recycled Paper") }, /* HP */
+      { "com.hp.everyday-glossy", _("Everyday Glossy Photo Paper") }, /* HP */
+      { "com.hp.everyday-matte", _("Everyday Matte Paper") }, /* HP */
+      { "com.hp.extra-heavy", _("Extra Heavyweight Paper") }, /* HP */
+      { "com.hp.intermediate", _("Multipurpose Paper") }, /* HP */
+      { "com.hp.mid-weight", _("Mid-Weight Paper") }, /* HP */
+      { "com.hp.premium-inkjet", _("Premium Inkjet Paper") }, /* HP */
+      { "com.hp.premium-photo", _("Premium Photo Glossy Paper") }, /* HP */
+      { "com.hp.premium-presentation-matte", _("Premium Presentation Matte Paper") }, /* HP */
       { "continuous", _("Continuous") },
       { "continuous-long", _("Continuous Long") },
       { "continuous-short", _("Continuous Short") },
@@ -3584,6 +3538,10 @@ _ppdCreateFromIPP(char   *buffer,        /* I - Filename buffer */
       { "gravure-cylinder", _("Gravure Cylinder") },
       { "image-setter-paper", _("Image Setter Paper") },
       { "imaging-cylinder", _("Imaging Cylinder") },
+      { "jp.co.canon_photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
+      { "jp.co.canon_photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
+      { "jp.co.canon-photo-paper-plus-glossy-ii", _("Photo Paper Plus Glossy II") }, /* Canon */
+      { "jp.co.canon-photo-paper-pro-platinum", _("Photo Paper Pro Platinum") }, /* Canon */
       { "labels", _("Labels") },
       { "labels-colored", _("Colored Labels") },
       { "labels-glossy", _("Glossy Labels") },
@@ -3609,7 +3567,7 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
       { "paper", _("Paper") },
       { "photo", _("Photo Paper") }, /* HP mis-spelling */
       { "photographic", _("Photo Paper") },
-      { "photographic-archival", _("Photographic Archival") },
+      { "photographic-archival", _("Archival Photo Paper") },
       { "photographic-film", _("Photo Film") },
       { "photographic-glossy", _("Glossy Photo Paper") },
       { "photographic-high-gloss", _("High Gloss Photo Paper") },
@@ -3636,14 +3594,14 @@ _ppdCreateFromIPP(char   *buffer,       /* I - Filename buffer */
       { "single-face", _("Single Face") },
       { "single-wall", _("Single Wall Cardboard") },
       { "sleeve", _("Sleeve") },
-      { "stationery", _("Stationery") },
-      { "stationery-archival", _("Stationery Archival") },
+      { "stationery", _("Plain Paper") },
+      { "stationery-archival", _("Archival Paper") },
       { "stationery-coated", _("Coated Paper") },
-      { "stationery-cotton", _("Stationery Cotton") },
+      { "stationery-cotton", _("Cotton Paper") },
       { "stationery-fine", _("Vellum Paper") },
       { "stationery-heavyweight", _("Heavyweight Paper") },
-      { "stationery-heavyweight-coated", _("Stationery Heavyweight Coated") },
-      { "stationery-inkjet", _("Stationery Inkjet Paper") },
+      { "stationery-heavyweight-coated", _("Heavyweight Coated Paper") },
+      { "stationery-inkjet", _("Inkjet Paper") },
       { "stationery-letterhead", _("Letterhead") },
       { "stationery-lightweight", _("Lightweight Paper") },
       { "stationery-preprinted", _("Preprinted Paper") },
@@ -3666,13 +3624,22 @@ _ppdCreateFromIPP(char   *buffer,       /* I - Filename buffer */
       pwg_ppdize_name(keyword, ppdname, sizeof(ppdname));
 
       for (j = 0; j < (int)(sizeof(media_types) / sizeof(media_types[0])); j ++)
-        if (!strcmp(keyword, media_types[i][0]))
+        if (!strcmp(keyword, media_types[j][0]))
           break;
 
       if (j < (int)(sizeof(media_types) / sizeof(media_types[0])))
         cupsFilePrintf(fp, "*MediaType %s/%s: \"<</MediaType(%s)>>setpagedevice\"\n", ppdname, _cupsLangString(lang, media_types[j][1]), ppdname);
       else
-        cupsFilePrintf(fp, "*MediaType %s/%s: \"<</MediaType(%s)>>setpagedevice\"\n", ppdname, keyword, ppdname);
+      {
+        char           msg[256];       /* Message key */
+        const char     *str;           /* Localized string */
+
+        snprintf(msg, sizeof(msg), "media-type.%s", keyword);
+        if ((str = _cupsMessageLookup(strings, msg)) == msg)
+          str = keyword;
+
+        cupsFilePrintf(fp, "*MediaType %s/%s: \"<</MediaType(%s)>>setpagedevice\"\n", ppdname, str, ppdname);
+      }
     }
     cupsFilePuts(fp, "*CloseUI: *MediaType\n");
   }
@@ -3681,8 +3648,8 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
   * ColorModel...
   */
 
-  if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL)
-    if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) == NULL)
+  if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) == NULL)
+    if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL)
       if ((attr = ippFindAttribute(response, "print-color-mode-supported", IPP_TAG_KEYWORD)) == NULL)
         attr = ippFindAttribute(response, "output-mode-supported", IPP_TAG_KEYWORD);
 
@@ -3695,7 +3662,7 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
       const char *keyword = ippGetString(attr, i, NULL);
                                        /* Keyword for color/bit depth */
 
-      if (!strcmp(keyword, "black_1") || !strcmp(keyword, "bi-level") || !strcmp(keyword, "process-bi-level"))
+      if (!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") || !strcmp(keyword, "process-bi-level"))
       {
         if (!default_color)
          cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
@@ -3706,7 +3673,7 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
         if (!default_color)
          default_color = "FastGray";
       }
-      else if (!strcmp(keyword, "sgray_8") || !strcmp(keyword, "W8") || !strcmp(keyword, "monochrome") || !strcmp(keyword, "process-monochrome"))
+      else if (!strcasecmp(keyword, "sgray_8") || !strcmp(keyword, "W8") || !strcmp(keyword, "monochrome") || !strcmp(keyword, "process-monochrome"))
       {
         if (!default_color)
          cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
@@ -3717,7 +3684,7 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
         if (!default_color || !strcmp(default_color, "FastGray"))
          default_color = "Gray";
       }
-      else if (!strcmp(keyword, "srgb_8") || !strcmp(keyword, "SRGB24") || !strcmp(keyword, "color"))
+      else if (!strcasecmp(keyword, "srgb_8") || !strcmp(keyword, "SRGB24") || !strcmp(keyword, "color"))
       {
         if (!default_color)
          cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
@@ -3727,7 +3694,7 @@ _ppdCreateFromIPP(char   *buffer, /* I - Filename buffer */
 
        default_color = "RGB";
       }
-      else if (!strcmp(keyword, "adobe-rgb_16") || !strcmp(keyword, "ADOBERGB48"))
+      else if (!strcasecmp(keyword, "adobe-rgb_16") || !strcmp(keyword, "ADOBERGB48"))
       {
         if (!default_color)
          cupsFilePrintf(fp, "*OpenUI *ColorModel/%s: PickOne\n"
@@ -3761,7 +3728,36 @@ _ppdCreateFromIPP(char   *buffer,        /* I - Filename buffer */
                       "*Duplex DuplexTumble/%s: \"<</Duplex true/Tumble true>>setpagedevice\"\n"
                       "*CloseUI: *Duplex\n", _cupsLangString(lang, _("2-Sided Printing")), _cupsLangString(lang, _("Off (1-Sided)")), _cupsLangString(lang, _("Long-Edge (Portrait)")), _cupsLangString(lang, _("Short-Edge (Landscape)")));
 
-    if ((attr = ippFindAttribute(response, "pwg-raster-document-sheet-back", IPP_TAG_KEYWORD)) != NULL)
+    if ((attr = ippFindAttribute(response, "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 (!_cups_strcasecmp(dm, "DM1"))
+        {
+          cupsFilePuts(fp, "*cupsBackSide: Normal\n");
+          break;
+        }
+        else if (!_cups_strcasecmp(dm, "DM2"))
+        {
+          cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
+          break;
+        }
+        else if (!_cups_strcasecmp(dm, "DM3"))
+        {
+          cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
+          break;
+        }
+        else if (!_cups_strcasecmp(dm, "DM4"))
+        {
+          cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
+          break;
+        }
+      }
+    }
+    else if ((attr = ippFindAttribute(response, "pwg-raster-document-sheet-back", IPP_TAG_KEYWORD)) != NULL)
     {
       const char *keyword = ippGetString(attr, 0, NULL);
                                        /* Keyword value */
@@ -3775,35 +3771,6 @@ _ppdCreateFromIPP(char   *buffer,        /* I - Filename buffer */
       else
         cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
     }
-    else if ((attr = ippFindAttribute(response, "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 (!_cups_strcasecmp(dm, "DM1"))
-       {
-         cupsFilePuts(fp, "*cupsBackSide: Normal\n");
-         break;
-       }
-       else if (!_cups_strcasecmp(dm, "DM2"))
-       {
-         cupsFilePuts(fp, "*cupsBackSide: Flipped\n");
-         break;
-       }
-       else if (!_cups_strcasecmp(dm, "DM3"))
-       {
-         cupsFilePuts(fp, "*cupsBackSide: Rotated\n");
-         break;
-       }
-       else if (!_cups_strcasecmp(dm, "DM4"))
-       {
-         cupsFilePuts(fp, "*cupsBackSide: ManualTumble\n");
-         break;
-       }
-      }
-    }
   }
 
  /*
@@ -3883,130 +3850,268 @@ _ppdCreateFromIPP(char   *buffer,     /* I - Filename buffer */
   * Finishing options...
   */
 
-  if ((attr = ippFindAttribute(response, "finishings-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL)
+  if ((attr = ippFindAttribute(response, "finishings-supported", IPP_TAG_ENUM)) != NULL)
   {
-    ipp_t              *col;           /* Collection value */
-    ipp_attribute_t    *template;      /* "finishing-template" member */
     const char         *name;          /* String name */
-    int                        value;          /* Enum value, if any */
+    int                        value;          /* Enum value */
     cups_array_t       *names;         /* Names we've added */
 
-    count = ippGetCount(attr);
-    names = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
+    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);
 
-    cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickMany\n"
-                      "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n"
-                      "*DefaultcupsFinishingTemplate: none\n"
-                      "*cupsFinishingTemplate none/%s: \"\"\n"
-                      "*cupsIPPFinishings 3/none: \"*cupsFinishingTemplate none\"\n", _cupsLangString(lang, _("Finishing")), _cupsLangString(lang, _("No Finishing")));
+   /*
+    * Staple/Bind/Stitch
+    */
 
     for (i = 0; i < count; i ++)
     {
-      col      = ippGetCollection(attr, i);
-      template = ippFindAttribute(col, "finishing-template", IPP_TAG_ZERO);
+      value = ippGetInteger(attr, i);
+      name  = ippEnumString("finishings", value);
 
-      if ((name = ippGetString(template, 0, NULL)) == NULL || !strcmp(name, "none"))
-        continue;
+      if (!strncmp(name, "staple-", 7) || !strncmp(name, "bind-", 5) || !strncmp(name, "edge-stitch-", 12) || !strcmp(name, "saddle-stitch"))
+        break;
+    }
 
-      if (cupsArrayFind(names, (char *)name))
-        continue;                      /* Already did this finishing template */
+    if (i < count)
+    {
+      cupsArrayAdd(fin_options, "*StapleLocation");
 
-      cupsArrayAdd(names, (char *)name);
+      cupsFilePrintf(fp, "*OpenUI *StapleLocation/%s: PickOne\n", _cupsLangString(lang, _("Staple")));
+      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *StapleLocation\n");
+      cupsFilePuts(fp, "*DefaultStapleLocation: None\n");
+      cupsFilePrintf(fp, "*StapleLocation None/%s: \"\"\n", _cupsLangString(lang, _("None")));
 
-      for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
+      for (; i < count; i ++)
       {
-        if (!strcmp(finishings[j][0], name))
-       {
-          cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\"\n", name, _cupsLangString(lang, finishings[j][1]));
+        value = ippGetInteger(attr, i);
+        name  = ippEnumString("finishings", value);
 
-         value = ippEnumValue("finishings", name);
+        if (strncmp(name, "staple-", 7) && strncmp(name, "bind-", 5) && strncmp(name, "edge-stitch-", 12) && strcmp(name, "saddle-stitch"))
+          continue;
 
-         if (value)
-           cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*cupsFinishingTemplate %s\"\n", value, name, name);
-          break;
-       }
+        if (cupsArrayFind(names, (char *)name))
+          continue;                    /* Already did this finishing template */
+
+        cupsArrayAdd(names, (char *)name);
+
+        for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
+        {
+          if (!strcmp(finishings[j][0], name))
+          {
+            cupsFilePrintf(fp, "*StapleLocation %s/%s: \"\"\n", name, _cupsLangString(lang, finishings[j][1]));
+            cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n", value, name, name);
+            break;
+          }
+        }
       }
+
+      cupsFilePuts(fp, "*CloseUI: *StapleLocation\n");
     }
 
-    cupsArrayDelete(names);
+   /*
+    * Fold
+    */
 
-    cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");
+    for (i = 0; i < count; i ++)
+    {
+      value = ippGetInteger(attr, i);
+      name  = ippEnumString("finishings", value);
+
+      if (!strncmp(name, "fold-", 5))
+        break;
+    }
+
+    if (i < count)
+    {
+      cupsArrayAdd(fin_options, "*FoldType");
+
+      cupsFilePrintf(fp, "*OpenUI *FoldType/%s: PickOne\n", _cupsLangString(lang, _("Fold")));
+      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *FoldType\n");
+      cupsFilePuts(fp, "*DefaultFoldType: None\n");
+      cupsFilePrintf(fp, "*FoldType None/%s: \"\"\n", _cupsLangString(lang, _("None")));
+
+      for (; i < count; i ++)
+      {
+        value = ippGetInteger(attr, i);
+        name  = ippEnumString("finishings", value);
+
+        if (strncmp(name, "fold-", 5))
+          continue;
+
+        if (cupsArrayFind(names, (char *)name))
+          continue;                    /* Already did this finishing template */
+
+        cupsArrayAdd(names, (char *)name);
+
+        for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
+        {
+          if (!strcmp(finishings[j][0], name))
+          {
+            cupsFilePrintf(fp, "*FoldType %s/%s: \"\"\n", name, _cupsLangString(lang, finishings[j][1]));
+            cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n", value, name, name);
+            break;
+          }
+        }
+      }
+
+      cupsFilePuts(fp, "*CloseUI: *FoldType\n");
+    }
+
+   /*
+    * Punch
+    */
+
+    for (i = 0; i < count; i ++)
+    {
+      value = ippGetInteger(attr, i);
+      name  = ippEnumString("finishings", value);
+
+      if (!strncmp(name, "punch-", 6))
+        break;
+    }
+
+    if (i < count)
+    {
+      cupsArrayAdd(fin_options, "*PunchMedia");
+
+      cupsFilePrintf(fp, "*OpenUI *PunchMedia/%s: PickOne\n", _cupsLangString(lang, _("Punch")));
+      cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *PunchMedia\n");
+      cupsFilePuts(fp, "*DefaultPunchMedia: None\n");
+      cupsFilePrintf(fp, "*PunchMedia None/%s: \"\"\n", _cupsLangString(lang, _("None")));
+
+      for (i = 0; i < count; i ++)
+      {
+        value = ippGetInteger(attr, i);
+        name  = ippEnumString("finishings", value);
+
+        if (strncmp(name, "punch-", 6))
+          continue;
+
+        if (cupsArrayFind(names, (char *)name))
+          continue;                    /* Already did this finishing template */
+
+        cupsArrayAdd(names, (char *)name);
+
+        for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
+        {
+          if (!strcmp(finishings[j][0], name))
+          {
+            cupsFilePrintf(fp, "*PunchMedia %s/%s: \"\"\n", name, _cupsLangString(lang, finishings[j][1]));
+            cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n", value, name, name);
+            break;
+          }
+        }
+      }
+
+      cupsFilePuts(fp, "*CloseUI: *PunchMedia\n");
+    }
+
+   /*
+    * Booklet
+    */
+
+    if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER))
+    {
+      cupsArrayAdd(fin_options, "*Booklet");
+
+      cupsFilePrintf(fp, "*OpenUI *Booklet/%s: Boolean\n", _cupsLangString(lang, _("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");
+    }
+
+    cupsArrayDelete(names);
   }
-  else if ((attr = ippFindAttribute(response, "finishings-supported", IPP_TAG_ENUM)) != NULL && (count = ippGetCount(attr)) > 1 )
+
+  if ((attr = ippFindAttribute(response, "finishings-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL)
   {
-    const char         *name;          /* String name */
-    int                        value;          /* Enum value, if any */
+    ipp_t      *finishing_col;         /* Current finishing collection */
+    const char *template,              /* Current finishing template */
+               *loctemplate;           /* Localized template name */
+    cups_array_t *templates;           /* Finishing templates */
 
-    count = ippGetCount(attr);
+    cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickOne\n", _cupsLangString(lang, _("Finishing Preset")));
+    cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n");
+    cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: None\n");
+    cupsFilePrintf(fp, "*cupsFinishingTemplate None/%s: \"\"\n", _cupsLangString(lang, _("None")));
 
-    cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickMany\n"
-                      "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n"
-                      "*DefaultcupsFinishingTemplate: none\n"
-                      "*cupsFinishingTemplate none/%s: \"\"\n"
-                      "*cupsIPPFinishings 3/none: \"*cupsFinishingTemplate none\"\n", _cupsLangString(lang, _("Finishing")), _cupsLangString(lang, _("No Finishing")));
+    templates = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+    count     = ippGetCount(attr);
 
     for (i = 0; i < count; i ++)
     {
-      if ((value = ippGetInteger(attr, i)) == 3)
+      finishing_col = ippGetCollection(attr, i);
+      template      = ippGetString(ippFindAttribute(finishing_col, "finishing_template", IPP_TAG_ZERO), 0, NULL);
+
+      if (!template || cupsArrayFind(templates, (void *)template))
         continue;
 
-      name = ippEnumString("finishings", value);
-      for (j = 0; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
+      if (strncmp(template, "fold-", 5) && (strstr(template, "-bottom") || strstr(template, "-left") || strstr(template, "-right") || strstr(template, "-top")))
+        continue;
+
+      cupsArrayAdd(templates, (void *)template);
+
+      for (j = 0, loctemplate = NULL; j < (int)(sizeof(finishings) / sizeof(finishings[0])); j ++)
       {
-        if (!strcmp(finishings[j][0], name))
-       {
-          cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\"\n", name, _cupsLangString(lang, finishings[j][1]));
-         cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*cupsFinishingTemplate %s\"\n", value, name, name);
+        if (!strcmp(finishings[j][0], template))
+        {
+          loctemplate = _cupsLangString(lang, finishings[j][1]);
           break;
-       }
+        }
       }
+
+      if (!loctemplate)
+      {
+        char           msg[256];       /* Message key */
+
+        snprintf(msg, sizeof(msg), "finishing-template.%s", template);
+        if ((loctemplate = _cupsMessageLookup(strings, msg)) == msg)
+          loctemplate = template;
+      }
+
+      cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\"\n", template, loctemplate);
     }
 
     cupsFilePuts(fp, "*CloseUI: *cupsFinishingTemplate\n");
+
+    if (cupsArrayCount(fin_options))
+    {
+      const char       *fin_option;    /* Current finishing option */
+
+      cupsFilePuts(fp, "*cupsUIConstraint finisings: \"*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 finisings: \"*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);
+
  /*
   * cupsPrintQuality and DefaultResolution...
   */
 
   quality = ippFindAttribute(response, "print-quality-supported", IPP_TAG_ENUM);
 
-  if ((attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
-  {
-    count = ippGetCount(attr);
-
-    pwg_ppdize_resolution(attr, count / 2, &xres, &yres, ppdname, sizeof(ppdname));
-    cupsFilePrintf(fp, "*DefaultResolution: %s\n", ppdname);
-
-    cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
-                      "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
-                      "*DefaultcupsPrintQuality: Normal\n", _cupsLangString(lang, _("Print Quality")));
-    if (count > 2 || ippContainsInteger(quality, IPP_QUALITY_DRAFT))
-    {
-      pwg_ppdize_resolution(attr, 0, &xres, &yres, NULL, 0);
-      cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), xres, yres);
-    }
-    pwg_ppdize_resolution(attr, count / 2, &xres, &yres, NULL, 0);
-    cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Normal")), xres, yres);
-    if (count > 1 || ippContainsInteger(quality, IPP_QUALITY_HIGH))
-    {
-      if (count > 1)
-        pwg_ppdize_resolution(attr, count - 1, &xres, &yres, NULL, 0);
-      else
-        pwg_ppdize_resolution(attr, 0, &xres, &yres, NULL, 0);
-      cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("High")), xres, yres);
-    }
-
-    cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
-  }
-  else if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
+  if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
   {
-    int lowdpi = 0, hidpi = 0;         /* Lower and higher resolution */
+    int lowdpi = 0, hidpi = 0;    /* Lower and higher resolution */
 
     for (i = 0, count = ippGetCount(attr); i < count; i ++)
     {
       const char *rs = ippGetString(attr, i, NULL);
-                                       /* RS value */
+          /* RS value */
 
       if (_cups_strncasecmp(rs, "RS", 2))
         continue;
@@ -4036,18 +4141,82 @@ _ppdCreateFromIPP(char   *buffer,       /* I - Filename buffer */
       cupsFilePrintf(fp, "*DefaultResolution: %ddpi\n", lowdpi);
 
       cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
-                        "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
-                        "*DefaultcupsPrintQuality: Normal\n", _cupsLangString(lang, _("Print Quality")));
+       "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
+       "*DefaultcupsPrintQuality: Normal\n", _cupsLangString(lang, _("Print Quality")));
       if ((lowdpi & 1) == 0)
-       cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), lowdpi, lowdpi / 2);
+  cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), lowdpi, lowdpi / 2);
       else if (ippContainsInteger(quality, IPP_QUALITY_DRAFT))
-       cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), lowdpi, lowdpi);
+  cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), lowdpi, lowdpi);
       cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Normal")), lowdpi, lowdpi);
       if (hidpi > lowdpi || ippContainsInteger(quality, IPP_QUALITY_HIGH))
-       cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("High")), hidpi, hidpi);
+  cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("High")), hidpi, hidpi);
       cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
     }
   }
+  else if ((attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
+  {
+   /*
+    * Make a sorted list of resolutions.
+    */
+
+    count = ippGetCount(attr);
+    if (count > (int)(sizeof(resolutions) / sizeof(resolutions[0])))
+      count = (int)(sizeof(resolutions) / sizeof(resolutions[0]));
+
+    resolutions[0] = 0; /* Not in loop to silence Clang static analyzer... */
+    for (i = 1; i < count; i ++)
+      resolutions[i] = i;
+
+    for (i = 0; i < (count - 1); i ++)
+    {
+      for (j = i + 1; j < count; j ++)
+      {
+        int       ix, iy,               /* First X and Y resolution */
+                  jx, jy,               /* Second X and Y resolution */
+                  temp;                 /* Swap variable */
+        ipp_res_t units;                /* Resolution units */
+
+        ix = ippGetResolution(attr, resolutions[i], &iy, &units);
+        jx = ippGetResolution(attr, resolutions[j], &jy, &units);
+
+        if (ix > jx || (ix == jx && iy > jy))
+        {
+         /*
+          * Swap these two resolutions...
+          */
+
+          temp           = resolutions[i];
+          resolutions[i] = resolutions[j];
+          resolutions[j] = temp;
+        }
+      }
+    }
+
+   /*
+    * Generate print quality options...
+    */
+
+    pwg_ppdize_resolution(attr, resolutions[count / 2], &xres, &yres, ppdname, sizeof(ppdname));
+    cupsFilePrintf(fp, "*DefaultResolution: %s\n", ppdname);
+
+    cupsFilePrintf(fp, "*OpenUI *cupsPrintQuality/%s: PickOne\n"
+           "*OrderDependency: 10 AnySetup *cupsPrintQuality\n"
+           "*DefaultcupsPrintQuality: Normal\n", _cupsLangString(lang, _("Print Quality")));
+    if (count > 2 || ippContainsInteger(quality, IPP_QUALITY_DRAFT))
+    {
+      pwg_ppdize_resolution(attr, resolutions[0], &xres, &yres, NULL, 0);
+      cupsFilePrintf(fp, "*cupsPrintQuality Draft/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Draft")), xres, yres);
+    }
+    pwg_ppdize_resolution(attr, resolutions[count / 2], &xres, &yres, NULL, 0);
+    cupsFilePrintf(fp, "*cupsPrintQuality Normal/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("Normal")), xres, yres);
+    if (count > 1 || ippContainsInteger(quality, IPP_QUALITY_HIGH))
+    {
+      pwg_ppdize_resolution(attr, resolutions[count - 1], &xres, &yres, NULL, 0);
+      cupsFilePrintf(fp, "*cupsPrintQuality High/%s: \"<</HWResolution[%d %d]>>setpagedevice\"\n", _cupsLangString(lang, _("High")), xres, yres);
+    }
+
+    cupsFilePuts(fp, "*CloseUI: *cupsPrintQuality\n");
+  }
   else if (is_apple || is_pwg)
     goto bad_ppd;
   else
@@ -4254,6 +4423,62 @@ _pwgPageSizeForMedia(
 }
 
 
+/*
+ * 'cups_get_url()' - Get a copy of the file at the given URL.
+ */
+
+static int                             /* O  - 1 on success, 0 on failure */
+cups_get_url(http_t     **http,                /* IO - Current HTTP connection */
+             const char *url,          /* I  - URL to get */
+             char       *name,         /* I  - Temporary filename */
+             size_t     namesize)      /* I  - Size of temporary filename buffer */
+{
+  char                 scheme[32],     /* URL scheme */
+                       userpass[256],  /* URL username:password */
+                       host[256],      /* URL host */
+                       curhost[256],   /* Current host */
+                       resource[256];  /* URL resource */
+  int                  port;           /* URL port */
+  http_encryption_t    encryption;     /* Type of encryption to use */
+  http_status_t                status;         /* Status of GET request */
+  int                  fd;             /* Temporary file */
+
+
+  if (httpSeparateURI(HTTP_URI_CODING_ALL, url, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+    return (0);
+
+  if (port == 443 || !strcmp(scheme, "https"))
+    encryption = HTTP_ENCRYPTION_ALWAYS;
+  else
+    encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+  if (!*http || strcasecmp(host, httpGetHostname(*http, curhost, sizeof(curhost))) || httpAddrPort(httpGetAddress(*http)) != port)
+  {
+    httpClose(*http);
+    *http = httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 5000, NULL);
+  }
+
+  if (!*http)
+    return (0);
+
+  if ((fd = cupsTempFd(name, (int)namesize)) < 0)
+    return (0);
+
+  status = cupsGetFd(*http, resource, fd);
+
+  close(fd);
+
+  if (status != HTTP_STATUS_OK)
+  {
+    unlink(name);
+    *name = '\0';
+    return (0);
+  }
+
+  return (1);
+}
+
+
 /*
  * 'pwg_add_finishing()' - Add a finishings value.
  */
@@ -4304,22 +4529,6 @@ pwg_free_finishings(
 }
 
 
-/*
- * 'pwg_free_material()' - Free a material value.
- */
-
-static void
-pwg_free_material(_pwg_material_t *m)  /* I - Material value */
-{
-  _cupsStrFree(m->key);
-  _cupsStrFree(m->name);
-
-  cupsFreeOptions(m->num_props, m->props);
-
-  free(m);
-}
-
-
 /*
  * 'pwg_ppdize_name()' - Convert an IPP keyword to a PPD keyword.
  */