]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Bring back PPD file loader for attributes to ippeveprinter.
authorMichael R Sweet <msweet@msweet.org>
Tue, 16 Dec 2025 18:03:51 +0000 (13:03 -0500)
committerMichael R Sweet <msweet@msweet.org>
Tue, 16 Dec 2025 18:03:51 +0000 (13:03 -0500)
tools/ippeveprinter.c

index 22500d937835b63b1b23c0bae8d6becfcb986b15..982103bee48245c9ef3593c053e38ef249c0469a 100644 (file)
@@ -18,6 +18,9 @@
 
 #include <cups/cups-private.h>
 #include <cups/dnssd.h>
+#if !CUPS_LITE
+#  include <cups/ppd-private.h>
+#endif /* !CUPS_LITE */
 
 #include <limits.h>
 #include <sys/stat.h>
@@ -273,6 +276,9 @@ static void         ipp_send_uri(ippeve_client_t *client);
 static void            ipp_validate_job(ippeve_client_t *client);
 static ipp_t           *load_ippfile_attributes(const char *servername, int serverport, const char *filename, cups_array_t *docformats);
 static ipp_t           *load_legacy_attributes(const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats);
+#if !CUPS_LITE
+static ipp_t           *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats);
+#endif /* !CUPS_LITE */
 #if HAVE_LIBPAM
 static int             pam_func(int, const struct pam_message **, struct pam_response **, void *);
 #endif // HAVE_LIBPAM
@@ -335,6 +341,9 @@ main(int  argc,                             // I - Number of command-line args
                *make = "Example",      // Manufacturer
                *model = "Printer",     // Model
                *name = NULL,           // Printer name
+#if !CUPS_LITE
+               *ppdfile = NULL,        /* PPD file */
+#endif /* !CUPS_LITE */
                *strings = NULL,        // Strings file
                *subtypes = "_print";   // DNS-SD service subtype
   bool         legacy = false,         // Legacy mode?
@@ -430,6 +439,16 @@ main(int  argc,                            // I - Number of command-line args
              legacy = true;
              break;
 
+#if !CUPS_LITE
+          case 'P' : /* -P filename.ppd */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+              ppdfile = argv[i];
+              break;
+#endif /* !CUPS_LITE */
+
          case 'S' : // -S filename.strings
              i ++;
              if (i >= argc)
@@ -571,8 +590,13 @@ main(int  argc,                            // I - Number of command-line args
   if (!name)
     usage(1);
 
+#if CUPS_LITE
   if (attrfile != NULL && legacy)
     usage(1);
+#else
+  if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1)
+    usage(1);
+#endif /* CUPS_LITE */
 
   // Apply defaults as needed...
   if (!directory[0])
@@ -604,9 +628,25 @@ main(int  argc,                            // I - Number of command-line args
 
   // Create the printer...
   if (attrfile)
+  {
     attrs = load_ippfile_attributes(servername, serverport, attrfile, docformats);
+  }
+#if !CUPS_LITE
+  else if (ppdfile)
+  {
+    attrs = load_ppd_attributes(ppdfile, docformats);
+
+    if (!command)
+      command = "ippeveps";
+
+    if (!output_format)
+      output_format = "application/postscript";
+  }
+#endif /* !CUPS_LITE */
   else
+  {
     attrs = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats);
+  }
 
   if (!docformats && !ippFindAttribute(attrs, "document-format-supported", IPP_TAG_MIMETYPE))
     docformats = cupsArrayNewStrings(ppm_color > 0 ? "image/jpeg,image/pwg-raster,image/urf": "image/pwg-raster,image/urf", ',');
@@ -4680,6 +4720,636 @@ load_legacy_attributes(
 }
 
 
+#if !CUPS_LITE
+//
+// 'load_ppd_attributes()' - Load IPP attributes from a PPD file.
+//
+
+static ipp_t *                         // O - IPP attributes or `NULL` on error
+load_ppd_attributes(
+    const char   *ppdfile,             // I - PPD filename
+    cups_array_t *docformats)          // I - document-format-supported values
+{
+  int          i, j;                   // Looping vars
+  ipp_t                *attrs;                 // Attributes
+  ipp_attribute_t *attr;               // Current attribute
+  ipp_t                *col,                   // Current collection value
+               *size;                  // Current media-size collection value
+  ppd_file_t   *ppd;                   // PPD data
+  ppd_attr_t   *ppd_attr;              // PPD attribute
+  ppd_choice_t *ppd_choice;            // PPD choice
+  ppd_size_t   *ppd_size;              // Default PPD size
+  pwg_size_t   *pwg_size,              // Current PWG size
+               *default_size = NULL;   // Default PWG size
+  const char   *default_source = NULL, // Default media source
+               *default_type = NULL;   // Default media type
+  pwg_map_t    *pwg_map;               // Mapping from PWG to PPD keywords
+  _ppd_cache_t *pc;                    // PPD cache
+  _pwg_finishings_t *finishings;       // Current finishings value
+  const char   *template;              // Current finishings-template value
+  int          num_margins;            // Number of media-xxx-margin-supported values
+  int          margins[10];            // media-xxx-margin-supported values
+  int          xres,                   // Default horizontal resolution
+               yres;                   // Default vertical resolution
+  int          num_urf;                // Number of urf-supported values
+  const char   *urf[10];               // urf-supported values
+  char         urf_rs[32];             // RS value
+  static const int     orientation_requested_supported[4] =
+  {                                    // orientation-requested-supported values
+    IPP_ORIENT_PORTRAIT,
+    IPP_ORIENT_LANDSCAPE,
+    IPP_ORIENT_REVERSE_LANDSCAPE,
+    IPP_ORIENT_REVERSE_PORTRAIT
+  };
+  static const char * const overrides_supported[] =
+  {                                    // overrides-supported
+    "document-numbers",
+    "media",
+    "media-col",
+    "orientation-requested",
+    "pages"
+  };
+  static const char * const print_color_mode_supported[] =
+  {                                    // print-color-mode-supported values
+    "monochrome"
+  };
+  static const char * const print_color_mode_supported_color[] =
+  {                                    // print-color-mode-supported values
+    "auto",
+    "color",
+    "monochrome"
+  };
+  static const int     print_quality_supported[] =
+  {                                    // print-quality-supported values
+    IPP_QUALITY_DRAFT,
+    IPP_QUALITY_NORMAL,
+    IPP_QUALITY_HIGH
+  };
+  static const char * const printer_supply[] =
+  {                                    // printer-supply values
+    "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
+        "maxcapacity=100;level=25;colorantname=unknown;",
+    "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
+        "maxcapacity=100;level=75;colorantname=black;"
+  };
+  static const char * const printer_supply_color[] =
+  {                                    // printer-supply values
+    "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;"
+        "maxcapacity=100;level=25;colorantname=unknown;",
+    "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;"
+        "maxcapacity=100;level=75;colorantname=black;",
+    "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;"
+        "maxcapacity=100;level=50;colorantname=cyan;",
+    "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;"
+        "maxcapacity=100;level=33;colorantname=magenta;",
+    "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;"
+        "maxcapacity=100;level=67;colorantname=yellow;"
+  };
+  static const char * const printer_supply_description[] =
+  {                                    // printer-supply-description values
+    "Toner Waste Tank",
+    "Black Toner"
+  };
+  static const char * const printer_supply_description_color[] =
+  {                                    // printer-supply-description values
+    "Ink Waste Tank",
+    "Black Ink",
+    "Cyan Ink",
+    "Magenta Ink",
+    "Yellow Ink"
+  };
+  static const char * const pwg_raster_document_type_supported[] =
+  {
+    "black_1",
+    "sgray_8"
+  };
+  static const char * const pwg_raster_document_type_supported_color[] =
+  {
+    "black_1",
+    "sgray_8",
+    "srgb_8",
+    "srgb_16"
+  };
+  static const char * const sides_supported[] =
+  {                                    // sides-supported values
+    "one-sided",
+    "two-sided-long-edge",
+    "two-sided-short-edge"
+  };
+
+
+  // Open the PPD file...
+  if ((ppd = ppdOpenFile(ppdfile)) == NULL)
+  {
+    ppd_status_t       status;         // Load error
+
+    status = ppdLastError(&i);
+    _cupsLangPrintf(stderr, _("ippeveprinter: Unable to open \"%s\": %s on line %d."), ppdfile, ppdErrorString(status), i);
+    return (NULL);
+  }
+
+  ppdMarkDefaults(ppd);
+
+  pc = _ppdCacheCreateWithPPD(cupsLangDefault(), ppd);
+
+  if ((ppd_size = ppdPageSize(ppd, NULL)) != NULL)
+  {
+    // Look up default size...
+    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+    {
+      if (!strcmp(pwg_size->map.ppd, ppd_size->name))
+      {
+        default_size = pwg_size;
+        break;
+      }
+    }
+  }
+
+  if (!default_size)
+  {
+    // Default to A4 or Letter...
+    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+    {
+      if (!strcmp(pwg_size->map.ppd, "Letter") || !strcmp(pwg_size->map.ppd, "A4"))
+      {
+        default_size = pwg_size;
+        break;
+      }
+    }
+
+    if (!default_size)
+      default_size = pc->sizes;                // Last resort: first size
+  }
+
+  if ((ppd_choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL)
+    default_source = _ppdCacheGetSource(pc, ppd_choice->choice);
+
+  if ((ppd_choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL)
+    default_source = _ppdCacheGetType(pc, ppd_choice->choice);
+
+  if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL)
+  {
+    // Use the PPD-defined default resolution...
+    if ((i = sscanf(ppd_attr->value, "%dx%d", &xres, &yres)) == 1)
+      yres = xres;
+    else if (i < 0)
+      xres = yres = 300;
+  }
+  else
+  {
+    // Use default of 300dpi...
+    xres = yres = 300;
+  }
+
+  snprintf(urf_rs, sizeof(urf_rs), "RS%d", yres < xres ? yres : xres);
+
+  num_urf = 0;
+  urf[num_urf ++] = "V1.4";
+  urf[num_urf ++] = "CP1";
+  urf[num_urf ++] = urf_rs;
+  urf[num_urf ++] = "W8";
+  if (pc->sides_2sided_long)
+    urf[num_urf ++] = "DM1";
+  if (ppd->color_device)
+    urf[num_urf ++] = "SRGB24";
+
+  // PostScript printers accept PDF via one of the CUPS PDF to PostScript
+  // filters, along with PostScript (of course) and JPEG...
+  cupsArrayAdd(docformats, "application/pdf");
+  cupsArrayAdd(docformats, "application/postscript");
+  cupsArrayAdd(docformats, "image/jpeg");
+
+  // Create the attributes...
+  attrs = ippNew();
+
+  // color-supported
+  ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device);
+
+  // copies-default
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);
+
+  // copies-supported
+  ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
+
+  // document-password-supported
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127);
+
+  // finishing-template-supported
+  attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template-supported", cupsArrayCount(pc->templates) + 1, NULL, NULL);
+  ippSetString(attrs, &attr, 0, "none");
+  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+    ippSetString(attrs, &attr, i, template);
+
+  // finishings-col-database
+  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount(pc->templates) + 1, NULL);
+
+  col = ippNew();
+  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+  ippSetCollection(attrs, &attr, 0, col);
+  ippDelete(col);
+
+  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+  {
+    col = ippNew();
+    ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
+    ippSetCollection(attrs, &attr, i, col);
+    ippDelete(col);
+  }
+
+  // finishings-col-default
+  col = ippNew();
+  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+  ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col);
+  ippDelete(col);
+
+  // finishings-col-ready
+  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-ready", cupsArrayCount(pc->templates) + 1, NULL);
+
+  col = ippNew();
+  ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+  ippSetCollection(attrs, &attr, 0, col);
+  ippDelete(col);
+
+  for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+  {
+    col = ippNew();
+    ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
+    ippSetCollection(attrs, &attr, i, col);
+    ippDelete(col);
+  }
+
+  // finishings-col-supported
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishings-col-supported", NULL, "finishing-template");
+
+  // finishings-default
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);
+
+  // finishings-ready
+  attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", cupsArrayCount(pc->finishings) + 1, NULL);
+  ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
+  for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
+    ippSetInteger(attrs, &attr, i, (int)finishings->value);
+
+  // finishings-supported
+  attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", cupsArrayCount(pc->finishings) + 1, NULL);
+  ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
+  for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
+    ippSetInteger(attrs, &attr, i, (int)finishings->value);
+
+  // media-bottom-margin-supported
+  for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+  {
+    for (j = 0; j < num_margins; j ++)
+    {
+      if (margins[j] == pwg_size->bottom)
+        break;
+    }
+
+    if (j >= num_margins)
+      margins[num_margins ++] = pwg_size->bottom;
+  }
+
+  for (i = 0; i < (num_margins - 1); i ++)
+  {
+    for (j = i + 1; j < num_margins; j ++)
+    {
+      if (margins[i] > margins[j])
+      {
+        int mtemp = margins[i];
+
+        margins[i] = margins[j];
+        margins[j] = mtemp;
+      }
+    }
+  }
+
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", num_margins, margins);
+
+  // media-col-database
+  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", pc->num_sizes, NULL);
+  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+  {
+    size = create_media_size(pwg_size->width, pwg_size->length);
+    col  = create_media_col(pwg_size->map.pwg, /*source*/NULL, /*type*/NULL, size, pwg_size->bottom, pwg_size->left, pwg_size->right, pwg_size->top);
+    ippSetCollection(attrs, &attr, i, col);
+    ippDelete(col);
+    ippDelete(size);
+  }
+
+  // media-col-default
+  size = create_media_size(default_size->width, default_size->length);
+  col  = create_media_col(default_size->map.pwg, default_source, default_type, size, default_size->bottom, default_size->left, default_size->right, default_size->top);
+  ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col);
+  ippDelete(col);
+  ippDelete(size);
+
+  // media-col-ready
+  size = create_media_size(default_size->width, default_size->length);
+  col  = create_media_col(default_size->map.pwg, default_source, default_type, size, default_size->bottom, default_size->left, default_size->right, default_size->top);
+  ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col);
+  ippDelete(col);
+  ippDelete(size);
+
+  // media-default
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, default_size->map.pwg);
+
+  // media-left-margin-supported
+  for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+  {
+    for (j = 0; j < num_margins; j ++)
+    {
+      if (margins[j] == pwg_size->left)
+        break;
+    }
+
+    if (j >= num_margins)
+      margins[num_margins ++] = pwg_size->left;
+  }
+
+  for (i = 0; i < (num_margins - 1); i ++)
+  {
+    for (j = i + 1; j < num_margins; j ++)
+    {
+      if (margins[i] > margins[j])
+      {
+        int mtemp = margins[i];
+
+        margins[i] = margins[j];
+        margins[j] = mtemp;
+      }
+    }
+  }
+
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", num_margins, margins);
+
+  // media-ready
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, default_size->map.pwg);
+
+  // media-right-margin-supported
+  for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+  {
+    for (j = 0; j < num_margins; j ++)
+    {
+      if (margins[j] == pwg_size->right)
+        break;
+    }
+
+    if (j >= num_margins)
+      margins[num_margins ++] = pwg_size->right;
+  }
+
+  for (i = 0; i < (num_margins - 1); i ++)
+  {
+    for (j = i + 1; j < num_margins; j ++)
+    {
+      if (margins[i] > margins[j])
+      {
+        int mtemp = margins[i];
+
+        margins[i] = margins[j];
+        margins[j] = mtemp;
+      }
+    }
+  }
+
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", num_margins, margins);
+
+  // media-supported
+  attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-supported", pc->num_sizes, NULL, NULL);
+  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+    ippSetString(attrs, &attr, i, pwg_size->map.pwg);
+
+  // media-size-supported
+  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", pc->num_sizes, NULL);
+  for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+  {
+    col = create_media_size(pwg_size->width, pwg_size->length);
+    ippSetCollection(attrs, &attr, i, col);
+    ippDelete(col);
+  }
+
+  // media-source-supported
+  if (pc->num_sources > 0)
+  {
+    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sources, NULL,  NULL);
+    for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i ++, pwg_map ++)
+      ippSetString(attrs, &attr, i, pwg_map->pwg);
+  }
+  else
+  {
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", NULL, "auto");
+  }
+
+  // media-top-margin-supported
+  for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+  {
+    for (j = 0; j < num_margins; j ++)
+    {
+      if (margins[j] == pwg_size->top)
+        break;
+    }
+
+    if (j >= num_margins)
+      margins[num_margins ++] = pwg_size->top;
+  }
+
+  for (i = 0; i < (num_margins - 1); i ++)
+  {
+    for (j = i + 1; j < num_margins; j ++)
+    {
+      if (margins[i] > margins[j])
+      {
+        int mtemp = margins[i];
+
+        margins[i] = margins[j];
+        margins[j] = mtemp;
+      }
+    }
+  }
+
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", num_margins, margins);
+
+  // media-type-supported
+  if (pc->num_types > 0)
+  {
+    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type-supported", pc->num_types, NULL,  NULL);
+    for (i = 0, pwg_map = pc->types; i < pc->num_types; i ++, pwg_map ++)
+      ippSetString(attrs, &attr, i, pwg_map->pwg);
+  }
+  else
+  {
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", NULL, "auto");
+  }
+
+  // orientation-requested-default
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT);
+
+  // orientation-requested-supported
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported);
+
+  // output-bin-default
+  if (pc->num_bins > 0)
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, pc->bins->pwg);
+  else
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");
+
+  // output-bin-supported
+  if (pc->num_bins > 0)
+  {
+    attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_bins, NULL,  NULL);
+    for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i ++, pwg_map ++)
+      ippSetString(attrs, &attr, i, pwg_map->pwg);
+  }
+  else
+  {
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");
+  }
+
+  // overrides-supported
+  ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported);
+
+  // page-ranges-supported
+  ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);
+
+  // pages-per-minute
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppd->throughput);
+
+  // pages-per-minute-color
+  if (ppd->color_device)
+    ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppd->throughput);
+
+  // print-color-mode-default
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppd->color_device ? "auto" : "monochrome");
+
+  // print-color-mode-supported
+  if (ppd->color_device)
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color);
+  else
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported);
+
+  // print-content-optimize-default
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");
+
+  // print-content-optimize-supported
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");
+
+  // print-quality-default
+  ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);
+
+  // print-quality-supported
+  ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported);
+
+  // print-rendering-intent-default
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");
+
+  // print-rendering-intent-supported
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");
+
+  // printer-device-id
+  if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL)
+  {
+    // Use the device ID string from the PPD...
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value);
+  }
+  else
+  {
+    // Synthesize a device ID string...
+    char       device_id[1024];                // Device ID string
+
+    snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;CMD:PS;", ppd->manufacturer, ppd->modelname);
+
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
+  }
+
+  // printer-input-tray
+  if (pc->num_sources > 0)
+  {
+    for (i = 0, attr = NULL; i < pc->num_sources; i ++)
+    {
+      char     input_tray[1024];       // printer-input-tray value
+
+      if (!strcmp(pc->sources[i].pwg, "manual") || strstr(pc->sources[i].pwg, "-man") != NULL)
+        snprintf(input_tray, sizeof(input_tray), "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s", pc->sources[i].pwg);
+      else
+        snprintf(input_tray, sizeof(input_tray), "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s", pc->sources[i].pwg);
+
+      if (attr)
+        ippSetOctetString(attrs, &attr, i, input_tray, (int)strlen(input_tray));
+      else
+        attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen(input_tray));
+    }
+  }
+  else
+  {
+    static const char *printer_input_tray = "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto";
+
+    ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray, (int)strlen(printer_input_tray));
+  }
+
+  // printer-make-and-model
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, ppd->nickname);
+
+  // printer-resolution-default
+  ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres);
+
+  // printer-resolution-supported
+  ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, xres, yres);
+
+  // printer-supply and printer-supply-description
+  if (ppd->color_device)
+  {
+    attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen(printer_supply_color[0]));
+    for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++)
+      ippSetOctetString(attrs, &attr, i, printer_supply_color[i], (int)strlen(printer_supply_color[i]));
+
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color);
+  }
+  else
+  {
+    attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen(printer_supply[0]));
+    for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++)
+      ippSetOctetString(attrs, &attr, i, printer_supply[i], (int)strlen(printer_supply[i]));
+
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description);
+  }
+
+  // pwg-raster-document-xxx-supported
+  if (cupsArrayFind(docformats, (void *)"image/pwg-raster"))
+  {
+    ippAddResolution(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xres, yres);
+
+    if (pc->sides_2sided_long)
+      ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal");
+
+    if (ppd->color_device)
+      ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color);
+    else
+      ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported);
+  }
+
+  // sides-default
+  ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");
+
+  // sides-supported
+  if (pc->sides_2sided_long)
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported);
+  else
+    ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided");
+
+  // urf-supported
+  if (cupsArrayFind(docformats, (void *)"image/urf"))
+    ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", num_urf, NULL, urf);
+
+  // Free the PPD file and return the attributes...
+  _ppdCacheDestroy(pc);
+
+  ppdClose(ppd);
+
+  return (attrs);
+}
+#endif // !CUPS_LITE
+
+
 #if HAVE_LIBPAM
 //
 // 'pam_func()' - PAM conversation function.
@@ -7059,6 +7729,9 @@ usage(int status)                 // O - Exit status
   _cupsLangPuts(stdout, _("-F output-type/subtype  Set the output format for the printer"));
   _cupsLangPuts(stdout, _("-K keypath              Set location of server X.509 certificates and keys."));
   _cupsLangPuts(stdout, _("-M manufacturer         Set manufacturer name (default=Test)"));
+#if !CUPS_LITE
+  _cupsLangPuts(stdout, _("-P filename.ppd         Load printer attributes from PPD file"));
+#endif /* !CUPS_LITE */
   _cupsLangPuts(stdout, _("-S filename.strings     Set strings file"));
   _cupsLangPuts(stdout, _("-V version              Set default IPP version"));
   _cupsLangPuts(stdout, _("-a filename.conf        Load printer attributes from conf file"));