]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/ppd.c
Update svn:keyword properties.
[thirdparty/cups.git] / cups / ppd.c
index c25e57ce24729c9a8a8e3cb15e20b8e0bf3e71c2..6813ea0e640628ff68a53b6492601bdc724c30fd 100644 (file)
@@ -1,25 +1,16 @@
 /*
- * "$Id: ppd.c 6445 2007-04-04 23:43:26Z mike $"
+ * "$Id$"
  *
- *   PPD file routines for the Common UNIX Printing System (CUPS).
+ *   PPD file routines for CUPS.
  *
+ *   Copyright 2007-2012 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   These coded instructions, statements, and computer programs are the
- *   property of Easy Software Products 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 please contact Easy Software Products
- *   at:
- *
- *       Attn: CUPS Licensing Information
- *       Easy Software Products
- *       44141 Airport View Drive, Suite 204
- *       Hollywood, Maryland 20636 USA
- *
- *       Voice: (301) 373-9600
- *       EMail: cups-info@cups.org
- *         WWW: http://www.cups.org
+ *   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
+ *   file is missing or damaged, see the license at "http://www.cups.org/".
  *
  *   PostScript is a trademark of Adobe Systems, Inc.
  *
  *                            LanguageEncoding.
  *   ppdLastError()         - Return the status from the last ppdOpen*().
  *   ppdOpen()              - Read a PPD file into memory.
+ *   _ppdOpen()             - Read a PPD file into memory.
  *   ppdOpen2()             - Read a PPD file into memory.
  *   ppdOpenFd()            - Read a PPD file into memory.
+ *   _ppdOpenFile()         - Read a PPD file into memory.
  *   ppdOpenFile()          - Read a PPD file into memory.
  *   ppdSetConformance()    - Set the conformance level for PPD files.
  *   ppd_add_attr()         - Add an attribute to the PPD data.
  *   ppd_add_choice()       - Add a choice to an option.
  *   ppd_add_size()         - Add a page size.
  *   ppd_compare_attrs()    - Compare two attributes.
+ *   ppd_compare_choices()  - Compare two choices...
  *   ppd_compare_coptions() - Compare two custom options.
- *   ppd_compare_cparams()  - Compare two custom parameters.
  *   ppd_compare_options()  - Compare two options.
  *   ppd_decode()           - Decode a string value...
+ *   ppd_free_filters()     - Free the filters array.
  *   ppd_free_group()       - Free a single UI group.
  *   ppd_free_option()      - Free a single option.
  *   ppd_get_coption()      - Get a custom option record.
  *   ppd_get_cparam()       - Get a custom parameter record.
  *   ppd_get_group()        - Find or create the named group as needed.
  *   ppd_get_option()       - Find or create the named option as needed.
+ *   ppd_hash_option()      - Generate a hash of the option name...
  *   ppd_read()             - Read a line from a PPD file, skipping comment
  *                            lines as necessary.
+ *   ppd_update_filters()   - Update the filters array as needed.
  */
 
 /*
  * Include necessary headers.
  */
 
-#include "globals.h"
-#include "debug.h"
-#include <stdlib.h>
+#include "cups-private.h"
+#include "ppd-private.h"
 
 
 /*
 #define PPD_TEXT       4               /* Line contained human-readable text */
 #define PPD_STRING     8               /* Line contained a string or code */
 
+#define PPD_HASHSIZE   512             /* Size of hash */
+
+
+/*
+ * Line buffer structure...
+ */
+
+typedef struct _ppd_line_s
+{
+  char         *buffer;                /* Pointer to buffer */
+  size_t       bufsize;                /* Size of the buffer */
+} _ppd_line_t;
+
 
 /*
  * Local functions...
@@ -101,11 +109,12 @@ static ppd_attr_t *ppd_add_attr(ppd_file_t *ppd, const char *name,
 static ppd_choice_t    *ppd_add_choice(ppd_option_t *option, const char *name);
 static ppd_size_t      *ppd_add_size(ppd_file_t *ppd, const char *name);
 static int             ppd_compare_attrs(ppd_attr_t *a, ppd_attr_t *b);
+static int             ppd_compare_choices(ppd_choice_t *a, ppd_choice_t *b);
 static int             ppd_compare_coptions(ppd_coption_t *a,
                                             ppd_coption_t *b);
-static int             ppd_compare_cparams(ppd_cparam_t *a, ppd_cparam_t *b);
 static int             ppd_compare_options(ppd_option_t *a, ppd_option_t *b);
 static int             ppd_decode(char *string);
+static void            ppd_free_filters(ppd_file_t *ppd);
 static void            ppd_free_group(ppd_group_t *group);
 static void            ppd_free_option(ppd_option_t *option);
 static ppd_coption_t   *ppd_get_coption(ppd_file_t *ppd, const char *name);
@@ -116,9 +125,13 @@ static ppd_group_t *ppd_get_group(ppd_file_t *ppd, const char *name,
                                       const char *text, _cups_globals_t *cg,
                                       cups_encoding_t encoding);
 static ppd_option_t    *ppd_get_option(ppd_group_t *group, const char *name);
-static int             ppd_read(cups_file_t *fp, char *keyword, char *option,
-                                char *text, char **string, int ignoreblank,
+static int             ppd_hash_option(ppd_option_t *option);
+static int             ppd_read(cups_file_t *fp, _ppd_line_t *line,
+                                char *keyword, char *option, char *text,
+                                char **string, int ignoreblank,
                                 _cups_globals_t *cg);
+static int             ppd_update_filters(ppd_file_t *ppd,
+                                          _cups_globals_t *cg);
 
 
 /*
@@ -132,7 +145,6 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   ppd_emul_t           *emul;          /* Current emulation */
   ppd_group_t          *group;         /* Current group */
   char                 **font;         /* Current font */
-  char                 **filter;       /* Current filter */
   ppd_attr_t           **attr;         /* Current attribute */
   ppd_coption_t                *coption;       /* Current custom option */
   ppd_cparam_t         *cparam;        /* Current custom parameter */
@@ -149,12 +161,13 @@ ppdClose(ppd_file_t *ppd)         /* I - PPD file record */
   * Free all strings at the top level...
   */
 
-  ppd_free(ppd->lang_encoding);
-  ppd_free(ppd->nickname);
-  ppd_free(ppd->patches);
-  ppd_free(ppd->jcl_begin);
-  ppd_free(ppd->jcl_end);
-  ppd_free(ppd->jcl_ps);
+  _cupsStrFree(ppd->lang_encoding);
+  _cupsStrFree(ppd->nickname);
+  if (ppd->patches)
+    free(ppd->patches);
+  _cupsStrFree(ppd->jcl_begin);
+  _cupsStrFree(ppd->jcl_end);
+  _cupsStrFree(ppd->jcl_ps);
 
  /*
   * Free any emulations...
@@ -164,8 +177,8 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   {
     for (i = ppd->num_emulations, emul = ppd->emulations; i > 0; i --, emul ++)
     {
-      ppd_free(emul->start);
-      ppd_free(emul->stop);
+      _cupsStrFree(emul->start);
+      _cupsStrFree(emul->stop);
     }
 
     ppd_free(ppd->emulations);
@@ -184,6 +197,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   }
 
   cupsArrayDelete(ppd->options);
+  cupsArrayDelete(ppd->marked);
 
  /*
   * Free any page sizes...
@@ -203,15 +217,7 @@ ppdClose(ppd_file_t *ppd)          /* I - PPD file record */
   * Free any filters...
   */
 
-  if (ppd->num_filters > 0)
-  {
-    for (i = ppd->num_filters, filter = ppd->filters; i > 0; i --, filter ++)
-    {
-      ppd_free(*filter);
-    }
-
-    ppd_free(ppd->filters);
-  }
+  ppd_free_filters(ppd);
 
  /*
   * Free any fonts...
@@ -220,9 +226,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   if (ppd->num_fonts > 0)
   {
     for (i = ppd->num_fonts, font = ppd->fonts; i > 0; i --, font ++)
-    {
-      ppd_free(*font);
-    }
+      _cupsStrFree(*font);
 
     ppd_free(ppd->fonts);
   }
@@ -242,7 +246,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   {
     for (i = ppd->num_attrs, attr = ppd->attrs; i > 0; i --, attr ++)
     {
-      ppd_free((*attr)->value);
+      _cupsStrFree((*attr)->value);
       ppd_free(*attr);
     }
 
@@ -268,7 +272,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
         case PPD_CUSTOM_PASSCODE :
         case PPD_CUSTOM_PASSWORD :
         case PPD_CUSTOM_STRING :
-            ppd_free(cparam->current.custom_string);
+            _cupsStrFree(cparam->current.custom_string);
            break;
 
        default :
@@ -285,6 +289,33 @@ ppdClose(ppd_file_t *ppd)          /* I - PPD file record */
 
   cupsArrayDelete(ppd->coptions);
 
+ /*
+  * Free constraints...
+  */
+
+  if (ppd->cups_uiconstraints)
+  {
+    _ppd_cups_uiconsts_t *consts;      /* Current constraints */
+
+
+    for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(ppd->cups_uiconstraints);
+         consts;
+        consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(ppd->cups_uiconstraints))
+    {
+      free(consts->constraints);
+      free(consts);
+    }
+
+    cupsArrayDelete(ppd->cups_uiconstraints);
+  }
+
+ /*
+  * Free any PPD cache/mapping data...
+  */
+
+  if (ppd->cache)
+    _ppdCacheDestroy(ppd->cache);
+
  /*
   * Free the whole record...
   */
@@ -296,7 +327,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
 /*
  * 'ppdErrorString()' - Returns the text assocated with a status.
  *
- * @since CUPS 1.1.19@
+ * @since CUPS 1.1.19/OS X 10.3@
  */
 
 const char *                           /* O - Status string */
@@ -324,11 +355,14 @@ ppdErrorString(ppd_status_t status)       /* I - PPD status */
                  _("Illegal option keyword string"),
                  _("Illegal translation string"),
                  _("Illegal whitespace character"),
-                 _("Bad custom parameter")
+                 _("Bad custom parameter"),
+                 _("Missing option keyword"),
+                 _("Bad value string"),
+                 _("Missing CloseGroup")
                };
 
 
-  if (status < PPD_OK || status > PPD_ILLEGAL_WHITESPACE)
+  if (status < PPD_OK || status >= PPD_MAX_STATUS)
     return (_cupsLangString(cupsLangDefault(), _("Unknown")));
   else
     return (_cupsLangString(cupsLangDefault(), messages[status]));
@@ -343,17 +377,17 @@ ppdErrorString(ppd_status_t status)       /* I - PPD status */
 cups_encoding_t                                /* O - CUPS encoding value */
 _ppdGetEncoding(const char *name)      /* I - LanguageEncoding string */
 {
-  if (!strcasecmp(name, "ISOLatin1"))
+  if (!_cups_strcasecmp(name, "ISOLatin1"))
     return (CUPS_ISO8859_1);
-  else if (!strcasecmp(name, "ISOLatin2"))
+  else if (!_cups_strcasecmp(name, "ISOLatin2"))
     return (CUPS_ISO8859_2);
-  else if (!strcasecmp(name, "ISOLatin5"))
+  else if (!_cups_strcasecmp(name, "ISOLatin5"))
     return (CUPS_ISO8859_5);
-  else if (!strcasecmp(name, "JIS83-RKSJ"))
-    return (CUPS_WINDOWS_932);
-  else if (!strcasecmp(name, "MacStandard"))
+  else if (!_cups_strcasecmp(name, "JIS83-RKSJ"))
+    return (CUPS_JIS_X0213);
+  else if (!_cups_strcasecmp(name, "MacStandard"))
     return (CUPS_MAC_ROMAN);
-  else if (!strcasecmp(name, "WindowsANSI"))
+  else if (!_cups_strcasecmp(name, "WindowsANSI"))
     return (CUPS_WINDOWS_1252);
   else
     return (CUPS_UTF8);
@@ -363,7 +397,7 @@ _ppdGetEncoding(const char *name)   /* I - LanguageEncoding string */
 /*
  * 'ppdLastError()' - Return the status from the last ppdOpen*().
  *
- * @since CUPS 1.1.19@
+ * @since CUPS 1.1.19/OS X 10.3@
  */
 
 ppd_status_t                           /* O - Status code */
@@ -381,50 +415,19 @@ ppdLastError(int *line)                   /* O - Line number */
 
 
 /*
- * 'ppdOpen()' - Read a PPD file into memory.
- */
-
-ppd_file_t *                           /* O - PPD file record */
-ppdOpen(FILE *fp)                      /* I - File to read from */
-{
-  ppd_file_t   *ppd;                   /* PPD file record */
-  cups_file_t  *cf;                    /* CUPS file */
-
-
- /*
-  * Reopen the stdio file as a CUPS file...
-  */
-
-  if ((cf = cupsFileOpenFd(fileno(fp), "r")) == NULL)
-    return (NULL);
-
- /*
-  * Load the PPD file using the newer API...
-  */
-
-  ppd = ppdOpen2(cf);
-
- /*
-  * Close the CUPS file and return the PPD...
-  */
-
-  cupsFileClose(cf);
-
-  return (ppd);
-}
-
-
-/*
- * 'ppdOpen2()' - Read a PPD file into memory.
+ * '_ppdOpen()' - Read a PPD file into memory.
  *
- * @since CUPS 1.2@
+ * @since CUPS 1.2/OS X 10.5@
  */
 
-ppd_file_t *                           /* O - PPD file record */
-ppdOpen2(cups_file_t *fp)              /* I - File to read from */
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+_ppdOpen(
+    cups_file_t                *fp,            /* I - File to read from */
+    _ppd_localization_t        localization)   /* I - Localization to load */
 {
   int                  i, j, k;        /* Looping vars */
   int                  count;          /* Temporary count */
+  _ppd_line_t          line;           /* Line buffer */
   ppd_file_t           *ppd;           /* PPD file record */
   ppd_group_t          *group,         /* Current group */
                        *subgroup;      /* Current sub-group */
@@ -448,12 +451,19 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
   ppd_section_t                section;        /* Order dependency section */
   ppd_profile_t                *profile;       /* Pointer to color profile */
   char                 **filter;       /* Pointer to filter */
-  cups_lang_t          *language;      /* Default language */
   struct lconv         *loc;           /* Locale data */
   int                  ui_keyword;     /* Is this line a UI keyword? */
+  cups_lang_t          *lang;          /* Language data */
   cups_encoding_t      encoding;       /* Encoding of PPD file */
   _cups_globals_t      *cg = _cupsGlobals();
                                        /* Global data */
+  char                 custom_name[PPD_MAX_NAME];
+                                       /* CustomFoo attribute name */
+  ppd_attr_t           *custom_attr;   /* CustomFoo attribute */
+  char                 ll[4],          /* Language + '.' */
+                       ll_CC[7];       /* Language + country + '.' */
+  size_t               ll_len = 0,     /* Language length */
+                       ll_CC_len = 0;  /* Language + country length */
   static const char * const ui_keywords[] =
                        {
 #ifdef CUPS_USE_FULL_UI_KEYWORDS_LIST
@@ -515,8 +525,15 @@ ppdOpen2(cups_file_t *fp)          /* I - File to read from */
                          "PageSize"
 #endif /* CUPS_USE_FULL_UI_KEYWORDS_LIST */
                        };
+  static const char * const color_keywords[] = /* Keywords associated with color profiles */
+                       {
+                         ".cupsICCProfile",
+                         ".ColorModel",
+                       };
 
 
+  DEBUG_printf(("_ppdOpen(fp=%p)", fp));
+
  /*
   * Default to "OK" status...
   */
@@ -534,13 +551,35 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
     return (NULL);
   }
 
+ /*
+  * If only loading a single localization set up the strings to match...
+  */
+
+  if (localization == _PPD_LOCALIZATION_DEFAULT)
+  {
+    if ((lang = cupsLangDefault()) == NULL)
+      return (NULL);
+
+    snprintf(ll_CC, sizeof(ll_CC), "%s.", lang->language);
+    snprintf(ll, sizeof(ll), "%2.2s.", lang->language);
+
+    ll_CC_len = strlen(ll_CC);
+    ll_len    = strlen(ll);
+
+    DEBUG_printf(("2_ppdOpen: Loading localizations matching \"%s\" and \"%s\"",
+                  ll_CC, ll));
+  }
+
  /*
   * Grab the first line and make sure it reads '*PPD-Adobe: "major.minor"'...
   */
 
-  mask = ppd_read(fp, keyword, name, text, &string, 0, cg);
+  line.buffer  = NULL;
+  line.bufsize = 0;
 
-  DEBUG_printf(("mask=%x, keyword=\"%s\"...\n", mask, keyword));
+  mask = ppd_read(fp, &line, keyword, name, text, &string, 0, cg);
+
+  DEBUG_printf(("2_ppdOpen: mask=%x, keyword=\"%s\"...", mask, keyword));
 
   if (mask == 0 ||
       strcmp(keyword, "PPD-Adobe") ||
@@ -553,14 +592,15 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
     if (cg->ppd_status == PPD_OK)
       cg->ppd_status = PPD_MISSING_PPDADOBE4;
 
-    ppd_free(string);
+    _cupsStrFree(string);
+    ppd_free(line.buffer);
 
     return (NULL);
   }
 
-  DEBUG_printf(("ppdOpen: keyword = %s, string = %p\n", keyword, string));
+  DEBUG_printf(("2_ppdOpen: keyword=%s, string=%p", keyword, string));
 
-  ppd_free(string);
+  _cupsStrFree(string);
 
  /*
   * Allocate memory for the PPD file record...
@@ -570,23 +610,19 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
   {
     cg->ppd_status = PPD_ALLOC_ERROR;
 
+    _cupsStrFree(string);
+    ppd_free(line.buffer);
+
     return (NULL);
   }
 
-  ppd->language_level = 1;
+  ppd->language_level = 2;
   ppd->color_device   = 0;
-  ppd->colorspace     = PPD_CS_GRAY;
+  ppd->colorspace     = PPD_CS_N;
   ppd->landscape      = -90;
   ppd->coptions       = cupsArrayNew((cups_array_func_t)ppd_compare_coptions,
                                      NULL);
 
- /*
-  * Get the default language for the user...
-  */
-
-  language = cupsLangDefault();
-  loc      = localeconv();
-
  /*
   * Read lines from the PPD file and add them to the file record...
   */
@@ -597,34 +633,16 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
   choice     = NULL;
   ui_keyword = 0;
   encoding   = CUPS_ISO8859_1;
+  loc        = localeconv();
 
-  while ((mask = ppd_read(fp, keyword, name, text, &string, 1, cg)) != 0)
+  while ((mask = ppd_read(fp, &line, keyword, name, text, &string, 1, cg)) != 0)
   {
-#ifdef DEBUG
-    printf("mask = %x, keyword = \"%s\"", mask, keyword);
+    DEBUG_printf(("2_ppdOpen: mask=%x, keyword=\"%s\", name=\"%s\", "
+                  "text=\"%s\", string=%d chars...", mask, keyword, name, text,
+                 string ? (int)strlen(string) : 0));
 
-    if (name[0] != '\0')
-      printf(", name = \"%s\"", name);
-
-    if (text[0] != '\0')
-      printf(", text = \"%s\"", text);
-
-    if (string != NULL)
-    {
-      if (strlen(string) > 40)
-        printf(", string = %p", string);
-      else
-        printf(", string = \"%s\"", string);
-    }
-
-    puts("");
-#endif /* DEBUG */
-
-    if (strcmp(keyword, "CloseUI") && strcmp(keyword, "CloseGroup") &&
-       strcmp(keyword, "CloseSubGroup") && strncmp(keyword, "Default", 7) &&
-        strcmp(keyword, "JCLCloseUI") && strcmp(keyword, "JCLOpenUI") &&
-       strcmp(keyword, "OpenUI") && strcmp(keyword, "OpenGroup") &&
-       strcmp(keyword, "OpenSubGroup") && string == NULL)
+    if (strncmp(keyword, "Default", 7) && !string &&
+        cg->ppd_conform != PPD_CONFORM_RELAXED)
     {
      /*
       * Need a string value!
@@ -634,6 +652,8 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
 
       goto error;
     }
+    else if (!string)
+      continue;
 
    /*
     * Certain main keywords (as defined by the PPD spec) may be used
@@ -654,6 +674,50 @@ ppdOpen2(cups_file_t *fp)          /* I - File to read from */
       ui_keyword = 0;
     }
 
+   /*
+    * If we are filtering out keyword localizations, see if this line needs to
+    * be used...
+    */
+
+    if (localization != _PPD_LOCALIZATION_ALL &&
+        (temp = strchr(keyword, '.')) != NULL &&
+        ((temp - keyword) == 2 || (temp - keyword) == 5) &&
+        _cups_isalpha(keyword[0]) &&
+        _cups_isalpha(keyword[1]) &&
+        (keyword[2] == '.' ||
+         (keyword[2] == '_' && _cups_isalpha(keyword[3]) &&
+          _cups_isalpha(keyword[4]) && keyword[5] == '.')))
+    {
+      if (localization == _PPD_LOCALIZATION_NONE ||
+         (localization == _PPD_LOCALIZATION_DEFAULT &&
+          strncmp(ll_CC, keyword, ll_CC_len) &&
+          strncmp(ll, keyword, ll_len)))
+      {
+       DEBUG_printf(("2_ppdOpen: Ignoring localization: \"%s\"\n", keyword));
+       continue;
+      }
+      else if (localization == _PPD_LOCALIZATION_ICC_PROFILES)
+      {
+       /*
+        * Only load localizations for the color profile related keywords...
+        */
+
+       for (i = 0;
+            i < (int)(sizeof(color_keywords) / sizeof(color_keywords[0]));
+            i ++)
+       {
+         if (!_cups_strcasecmp(temp, color_keywords[i]))
+           break;
+       }
+
+       if (i >= (int)(sizeof(color_keywords) / sizeof(color_keywords[0])))
+       {
+         DEBUG_printf(("2_ppdOpen: Ignoring localization: \"%s\"\n", keyword));
+         continue;
+       }
+      }
+    }
+
     if (option == NULL &&
         (mask & (PPD_KEYWORD | PPD_OPTION | PPD_STRING)) ==
            (PPD_KEYWORD | PPD_OPTION | PPD_STRING))
@@ -670,7 +734,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
 
         ui_keyword = 1;
 
-        DEBUG_printf(("**** FOUND ADOBE UI KEYWORD %s WITHOUT OPENUI!\n",
+        DEBUG_printf(("2_ppdOpen: FOUND ADOBE UI KEYWORD %s WITHOUT OPENUI!",
                      keyword));
 
         if (!group)
@@ -679,7 +743,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
                                     encoding)) == NULL)
            goto error;
 
-          DEBUG_printf(("Adding to group %s...\n", group->text));
+          DEBUG_printf(("2_ppdOpen: Adding to group %s...", group->text));
           option = ppd_get_option(group, keyword);
          group  = NULL;
        }
@@ -714,7 +778,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
              !strcmp(ppd->attrs[j]->name + 7, keyword) &&
              ppd->attrs[j]->value)
          {
-           DEBUG_printf(("Setting Default%s to %s via attribute...\n",
+           DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                          option->keyword, ppd->attrs[j]->value));
            strlcpy(option->defchoice, ppd->attrs[j]->value,
                    sizeof(option->defchoice));
@@ -744,7 +808,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
       * Say all PPD files are UTF-8, since we convert to UTF-8...
       */
 
-      ppd->lang_encoding = strdup("UTF-8");
+      ppd->lang_encoding = _cupsStrAlloc("UTF-8");
       encoding           = _ppdGetEncoding(string);
     }
     else if (!strcmp(keyword, "LanguageVersion"))
@@ -765,10 +829,10 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
 
         cupsCharsetToUTF8(utf8, string, sizeof(utf8), encoding);
-       ppd->nickname = strdup((char *)utf8);
+       ppd->nickname = _cupsStrAlloc((char *)utf8);
       }
       else
-        ppd->nickname = strdup(string);
+        ppd->nickname = _cupsStrAlloc(string);
     }
     else if (!strcmp(keyword, "Product"))
       ppd->product = string;
@@ -778,17 +842,17 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       ppd->ttrasterizer = string;
     else if (!strcmp(keyword, "JCLBegin"))
     {
-      ppd->jcl_begin = strdup(string);
+      ppd->jcl_begin = _cupsStrAlloc(string);
       ppd_decode(ppd->jcl_begin);      /* Decode quoted string */
     }
     else if (!strcmp(keyword, "JCLEnd"))
     {
-      ppd->jcl_end = strdup(string);
+      ppd->jcl_end = _cupsStrAlloc(string);
       ppd_decode(ppd->jcl_end);                /* Decode quoted string */
     }
     else if (!strcmp(keyword, "JCLToPSInterpreter"))
     {
-      ppd->jcl_ps = strdup(string);
+      ppd->jcl_ps = _cupsStrAlloc(string);
       ppd_decode(ppd->jcl_ps);         /* Decode quoted string */
     }
     else if (!strcmp(keyword, "AccurateScreensSupport"))
@@ -811,6 +875,13 @@ ppdOpen2(cups_file_t *fp)          /* I - File to read from */
         profile = realloc(ppd->profiles, sizeof(ppd_profile_t) *
                                         (ppd->num_profiles + 1));
 
+      if (!profile)
+      {
+        cg->ppd_status = PPD_ALLOC_ERROR;
+
+       goto error;
+      }
+
       ppd->profiles     = profile;
       profile           += ppd->num_profiles;
       ppd->num_profiles ++;
@@ -840,8 +911,6 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
 
       if (filter == NULL)
       {
-        ppd_free(filter);
-
         cg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
@@ -852,11 +921,10 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       ppd->num_filters ++;
 
      /*
-      * Copy filter string and prevent it from being freed below...
+      * Retain a copy of the filter string...
       */
 
-      *filter = string;
-      string  = NULL;
+      *filter = _cupsStrRetain(string);
     }
     else if (!strcmp(keyword, "Throughput"))
       ppd->throughput = atoi(string);
@@ -878,9 +946,9 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
 
        goto error;
       }
-      
+
       ppd->fonts                 = tempfonts;
-      ppd->fonts[ppd->num_fonts] = strdup(name);
+      ppd->fonts[ppd->num_fonts] = _cupsStrAlloc(name);
       ppd->num_fonts ++;
     }
     else if (!strncmp(keyword, "ParamCustom", 11))
@@ -915,7 +983,8 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
       * Get the parameter data...
       */
 
-      if (sscanf(string, "%d%32s%64s%64s", &corder, ctype, cminimum,
+      if (!string ||
+          sscanf(string, "%d%32s%64s%64s", &corder, ctype, cminimum,
                  cmaximum) != 4)
       {
         cg->ppd_status = PPD_BAD_CUSTOM_PARAM;
@@ -1005,37 +1074,14 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
     }
     else if (!strncmp(keyword, "Custom", 6) && !strcmp(name, "True") && !option)
     {
-      DEBUG_puts("Processing Custom option...");
+      ppd_option_t     *custom_option; /* Custom option */
+
+      DEBUG_puts("2_ppdOpen: Processing Custom option...");
 
      /*
       * Get the option and custom option...
       */
 
-      if ((option = ppdFindOption(ppd, keyword + 6)) == NULL)
-      {
-       ppd_group_t     *gtemp;         /* Temporary group */
-
-
-        DEBUG_printf(("%s option not found for %s...\n", keyword + 6, keyword));
-
-       if ((gtemp = ppd_get_group(ppd, "General", _("General"), cg,
-                                  encoding)) == NULL)
-       {
-         DEBUG_puts("Unable to get general group!");
-
-         goto error;
-       }
-
-       if ((option = ppd_get_option(gtemp, keyword + 6)) == NULL)
-       {
-         DEBUG_printf(("Unable to get %s option!\n", keyword + 6));
-
-          cg->ppd_status = PPD_ALLOC_ERROR;
-
-         goto error;
-       }
-      }
-
       if (!ppd_get_coption(ppd, keyword + 6))
       {
         cg->ppd_status = PPD_ALLOC_ERROR;
@@ -1043,25 +1089,35 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
        goto error;
       }
 
-     /*
-      * Add the "custom" option...
-      */
+      if (option && !_cups_strcasecmp(option->keyword, keyword + 6))
+        custom_option = option;
+      else
+        custom_option = ppdFindOption(ppd, keyword + 6);
 
-      if ((choice = ppd_add_choice(option, "Custom")) == NULL)
+      if (custom_option)
       {
-       DEBUG_puts("Unable to add Custom choice!");
+       /*
+       * Add the "custom" option...
+       */
 
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        if ((choice = ppdFindChoice(custom_option, "Custom")) == NULL)
+         if ((choice = ppd_add_choice(custom_option, "Custom")) == NULL)
+         {
+           DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-       goto error;
-      }
+           cg->ppd_status = PPD_ALLOC_ERROR;
 
-      strlcpy(choice->text, text[0] ? text : _("Custom"),
-              sizeof(choice->text));
+           goto error;
+         }
 
-      choice->code = string;
-      string       = NULL;             /* Don't add as an attribute below */
-      option       = NULL;
+       strlcpy(choice->text, text[0] ? text : _("Custom"),
+               sizeof(choice->text));
+
+       choice->code = _cupsStrAlloc(string);
+
+       if (custom_option->section == PPD_ORDER_JCL)
+         ppd_decode(choice->code);
+      }
 
      /*
       * Now process custom page sizes specially...
@@ -1069,41 +1125,34 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
       if (!strcmp(keyword, "CustomPageSize"))
       {
-       ppd->variable_sizes = 1;
-
        /*
        * Add a "Custom" page size entry...
        */
 
-       ppd_add_size(ppd, "Custom");
-
-       if ((option = ppdFindOption(ppd, "PageRegion")) == NULL)
-       {
-         ppd_group_t   *gtemp;         /* Temporary group */
-
-         if ((gtemp = ppd_get_group(ppd, "General", _("General"), cg,
-                                    encoding)) == NULL)
-         {
-           DEBUG_puts("Unable to get general group!");
+       ppd->variable_sizes = 1;
 
-           goto error;
-         }
+       ppd_add_size(ppd, "Custom");
 
-         option = ppd_get_option(gtemp, "PageRegion");
-        }
+       if (option && !_cups_strcasecmp(option->keyword, "PageRegion"))
+         custom_option = option;
+       else
+         custom_option = ppdFindOption(ppd, "PageRegion");
 
-       if ((choice = ppd_add_choice(option, "Custom")) == NULL)
+        if (custom_option)
        {
-         DEBUG_puts("Unable to add Custom choice!");
+         if ((choice = ppdFindChoice(custom_option, "Custom")) == NULL)
+           if ((choice = ppd_add_choice(custom_option, "Custom")) == NULL)
+           {
+             DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-         cg->ppd_status = PPD_ALLOC_ERROR;
+             cg->ppd_status = PPD_ALLOC_ERROR;
 
-         goto error;
-       }
+             goto error;
+           }
 
-       strlcpy(choice->text, text[0] ? text : _("Custom"),
-               sizeof(choice->text));
-        option = NULL;
+         strlcpy(choice->text, text[0] ? text : _("Custom"),
+                 sizeof(choice->text));
+        }
       }
     }
     else if (!strcmp(keyword, "LandscapeOrientation"))
@@ -1113,7 +1162,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       else if (!strcmp(string, "Plus90"))
         ppd->landscape = 90;
     }
-    else if (!strcmp(keyword, "Emulators"))
+    else if (!strcmp(keyword, "Emulators") && string)
     {
       for (count = 1, sptr = string; sptr != NULL;)
         if ((sptr = strchr(sptr, ' ')) != NULL)
@@ -1124,7 +1173,12 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
        }
 
       ppd->num_emulations = count;
-      ppd->emulations     = calloc(count, sizeof(ppd_emul_t));
+      if ((ppd->emulations = calloc(count, sizeof(ppd_emul_t))) == NULL)
+      {
+        cg->ppd_status = PPD_ALLOC_ERROR;
+
+       goto error;
+      }
 
       for (i = 0, sptr = string; i < count; i ++)
       {
@@ -1164,6 +1218,37 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
     }
     else if (!strcmp(keyword, "JobPatchFile"))
     {
+     /*
+      * CUPS STR #3421: Check for "*JobPatchFile: int: string"
+      */
+
+      if (isdigit(*string & 255))
+      {
+        for (sptr = string + 1; isdigit(*sptr & 255); sptr ++);
+
+        if (*sptr == ':')
+        {
+         /*
+          * Found "*JobPatchFile: int: string"...
+          */
+
+          cg->ppd_status = PPD_BAD_VALUE;
+
+         goto error;
+        }
+      }
+
+      if (!name[0] && cg->ppd_conform == PPD_CONFORM_STRICT)
+      {
+       /*
+        * Found "*JobPatchFile: string"...
+        */
+
+        cg->ppd_status = PPD_MISSING_OPTION_KEYWORD;
+
+       goto error;
+      }
+
       if (ppd->patches == NULL)
         ppd->patches = strdup(string);
       else
@@ -1179,7 +1264,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
         ppd->patches = temp;
 
-        strcpy(ppd->patches + strlen(ppd->patches), string);
+        memcpy(ppd->patches + strlen(ppd->patches), string, strlen(string) + 1);
       }
     }
     else if (!strcmp(keyword, "OpenUI"))
@@ -1199,13 +1284,15 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       * Add an option record to the current sub-group, group, or file...
       */
 
+      DEBUG_printf(("2_ppdOpen: name=\"%s\" (%d)", name, (int)strlen(name)));
+
       if (name[0] == '*')
         _cups_strcpy(name, name + 1); /* Eliminate leading asterisk */
 
-      for (i = (int)strlen(name) - 1; i > 0 && isspace(name[i] & 255); i --)
+      for (i = (int)strlen(name) - 1; i > 0 && _cups_isspace(name[i]); i --)
         name[i] = '\0'; /* Eliminate trailing spaces */
 
-      DEBUG_printf(("OpenUI of %s in group %s...\n", name,
+      DEBUG_printf(("2_ppdOpen: OpenUI of %s in group %s...", name,
                     group ? group->text : "(null)"));
 
       if (subgroup != NULL)
@@ -1216,7 +1303,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
                                   encoding)) == NULL)
          goto error;
 
-        DEBUG_printf(("Adding to group %s...\n", group->text));
+        DEBUG_printf(("2_ppdOpen: Adding to group %s...", group->text));
         option = ppd_get_option(group, name);
        group  = NULL;
       }
@@ -1254,7 +1341,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
            !strcmp(ppd->attrs[j]->name + 7, name) &&
            ppd->attrs[j]->value)
        {
-         DEBUG_printf(("Setting Default%s to %s via attribute...\n",
+         DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                        option->keyword, ppd->attrs[j]->value));
          strlcpy(option->defchoice, ppd->attrs[j]->value,
                  sizeof(option->defchoice));
@@ -1282,8 +1369,36 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
 
       option->section = PPD_ORDER_ANY;
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
+
+     /*
+      * Add a custom option choice if we have already seen a CustomFoo
+      * attribute...
+      */
+
+      if (!_cups_strcasecmp(name, "PageRegion"))
+        strlcpy(custom_name, "CustomPageSize", sizeof(custom_name));
+      else
+        snprintf(custom_name, sizeof(custom_name), "Custom%s", name);
+
+      if ((custom_attr = ppdFindAttr(ppd, custom_name, "True")) != NULL)
+      {
+        if ((choice = ppdFindChoice(option, "Custom")) == NULL)
+         if ((choice = ppd_add_choice(option, "Custom")) == NULL)
+         {
+           DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
+
+           cg->ppd_status = PPD_ALLOC_ERROR;
+
+           goto error;
+         }
+
+       strlcpy(choice->text,
+               custom_attr->text[0] ? custom_attr->text : _("Custom"),
+               sizeof(choice->text));
+        choice->code = _cupsStrRetain(custom_attr->value);
+      }
     }
     else if (!strcmp(keyword, "JCLOpenUI"))
     {
@@ -1345,7 +1460,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
            !strcmp(ppd->attrs[j]->name + 7, name) &&
            ppd->attrs[j]->value)
        {
-         DEBUG_printf(("Setting Default%s to %s via attribute...\n",
+         DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                        option->keyword, ppd->attrs[j]->value));
          strlcpy(option->defchoice, ppd->attrs[j]->value,
                  sizeof(option->defchoice));
@@ -1361,14 +1476,38 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       option->section = PPD_ORDER_JCL;
       group = NULL;
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
+
+     /*
+      * Add a custom option choice if we have already seen a CustomFoo
+      * attribute...
+      */
+
+      snprintf(custom_name, sizeof(custom_name), "Custom%s", name);
+
+      if ((custom_attr = ppdFindAttr(ppd, custom_name, "True")) != NULL)
+      {
+       if ((choice = ppd_add_choice(option, "Custom")) == NULL)
+       {
+         DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
+
+         cg->ppd_status = PPD_ALLOC_ERROR;
+
+         goto error;
+       }
+
+       strlcpy(choice->text,
+               custom_attr->text[0] ? custom_attr->text : _("Custom"),
+               sizeof(choice->text));
+        choice->code = _cupsStrRetain(custom_attr->value);
+      }
     }
     else if (!strcmp(keyword, "CloseUI") || !strcmp(keyword, "JCLCloseUI"))
     {
       option = NULL;
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "OpenGroup"))
@@ -1415,18 +1554,17 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       if (group == NULL)
        goto error;
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "CloseGroup"))
     {
       group = NULL;
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
-    else if (!strcmp(keyword, "OrderDependency") ||
-             !strcmp(keyword, "NonUIOrderDependency"))
+    else if (!strcmp(keyword, "OrderDependency"))
     {
       order = (float)_cupsStrScand(string, &sptr, loc);
 
@@ -1481,7 +1619,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
        option->order   = order;
       }
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (!strncmp(keyword, "Default", 7))
@@ -1525,11 +1663,11 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
         * Set the default as part of the current option...
        */
 
-        DEBUG_printf(("Setting %s to %s...\n", keyword, string));
+        DEBUG_printf(("2_ppdOpen: Setting %s to %s...", keyword, string));
 
         strlcpy(option->defchoice, string, sizeof(option->defchoice));
 
-        DEBUG_printf(("%s is now %s...\n", keyword, option->defchoice));
+        DEBUG_printf(("2_ppdOpen: %s is now %s...", keyword, option->defchoice));
       }
       else
       {
@@ -1542,7 +1680,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
         if ((toption = ppdFindOption(ppd, keyword + 7)) != NULL)
        {
-         DEBUG_printf(("Setting %s to %s...\n", keyword, string));
+         DEBUG_printf(("2_ppdOpen: Setting %s to %s...", keyword, string));
          strlcpy(toption->defchoice, string, sizeof(toption->defchoice));
        }
       }
@@ -1550,6 +1688,12 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
     else if (!strcmp(keyword, "UIConstraints") ||
              !strcmp(keyword, "NonUIConstraints"))
     {
+      if (!string)
+      {
+       cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+       goto error;
+      }
+
       if (ppd->num_consts == 0)
        constraint = calloc(2, sizeof(ppd_const_t));
       else
@@ -1613,7 +1757,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
             constraint->choice1[0] = '\0';
             constraint->choice2[0] = '\0';
            break;
-           
+
        case 3 : /* Two options, one choice... */
           /*
            * Check for broken constraints like "* Option"...
@@ -1667,7 +1811,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
               constraint->choice2[0] = '\0';
            }
            break;
-           
+
        case 4 : /* Two options, two choices... */
           /*
            * Check for broken constraints like "* Option"...
@@ -1715,57 +1859,11 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
            break;
       }
 
-     /*
-      * For CustomPageSize and InputSlot/ManualFeed, create a duplicate
-      * constraint for PageRegion...
-      */
-
-      if (!strcasecmp(constraint->option1, "CustomPageSize") &&
-          (!strcasecmp(constraint->option2, "InputSlot") ||
-          !strcasecmp(constraint->option2, "ManualFeed")))
-      {
-        ppd->num_consts ++;
-
-        strcpy(constraint[1].option1, "PageRegion");
-       strcpy(constraint[1].choice1, "Custom");
-       strcpy(constraint[1].option2, constraint->option2);
-       strcpy(constraint[1].choice2, constraint->choice2);
-      }
-      else if (!strcasecmp(constraint->option2, "CustomPageSize") &&
-               (!strcasecmp(constraint->option1, "InputSlot") ||
-               !strcasecmp(constraint->option1, "ManualFeed")))
-      {
-        ppd->num_consts ++;
-
-       strcpy(constraint[1].option1, constraint->option1);
-       strcpy(constraint[1].choice1, constraint->choice1);
-        strcpy(constraint[1].option2, "PageRegion");
-       strcpy(constraint[1].choice2, "Custom");
-      }
-
-     /*
-      * Handle CustomFoo option constraints...
-      */
-
-      if (!strncasecmp(constraint->option1, "Custom", 6) &&
-          !strcasecmp(constraint->choice1, "True"))
-      {
-        _cups_strcpy(constraint->option1, constraint->option1 + 6);
-       strcpy(constraint->choice1, "Custom");
-      }
-
-      if (!strncasecmp(constraint->option2, "Custom", 6) &&
-          !strcasecmp(constraint->choice2, "True"))
-      {
-        _cups_strcpy(constraint->option2, constraint->option2 + 6);
-       strcpy(constraint->choice2, "Custom");
-      }
-
      /*
       * Don't add this one as an attribute...
       */
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "PaperDimension"))
@@ -1787,7 +1885,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       size->width  = (float)_cupsStrScand(string, &sptr, loc);
       size->length = (float)_cupsStrScand(sptr, NULL, loc);
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "ImageableArea"))
@@ -1811,7 +1909,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       size->right  = (float)_cupsStrScand(sptr, &sptr, loc);
       size->top    = (float)_cupsStrScand(sptr, NULL, loc);
 
-      ppd_free(string);
+      _cupsStrFree(string);
       string = NULL;
     }
     else if (option != NULL &&
@@ -1819,7 +1917,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
                 (PPD_KEYWORD | PPD_OPTION | PPD_STRING) &&
             !strcmp(keyword, option->keyword))
     {
-      DEBUG_printf(("group = %p, subgroup = %p\n", group, subgroup));
+      DEBUG_printf(("2_ppdOpen: group=%p, subgroup=%p", group, subgroup));
 
       if (!strcmp(keyword, "PageSize"))
       {
@@ -1835,15 +1933,20 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       * Add the option choice...
       */
 
-      choice = ppd_add_choice(option, name);
+      if ((choice = ppd_add_choice(option, name)) == NULL)
+      {
+        cg->ppd_status = PPD_ALLOC_ERROR;
+
+       goto error;
+      }
 
       if (text[0])
         cupsCharsetToUTF8((cups_utf8_t *)choice->text, text,
                           sizeof(choice->text), encoding);
       else if (!strcmp(name, "True"))
-        strcpy(choice->text, _("Yes"));
+        strlcpy(choice->text, _("Yes"), sizeof(choice->text));
       else if (!strcmp(name, "False"))
-        strcpy(choice->text, _("No"));
+        strlcpy(choice->text, _("No"), sizeof(choice->text));
       else
         strlcpy(choice->text, name, sizeof(choice->text));
 
@@ -1862,18 +1965,29 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
         (mask & (PPD_KEYWORD | PPD_STRING)) == (PPD_KEYWORD | PPD_STRING))
       ppd_add_attr(ppd, keyword, name, text, string);
     else
-      ppd_free(string);
+      _cupsStrFree(string);
   }
 
  /*
-  * Reset language preferences...
+  * Check for a missing CloseGroup...
   */
 
-  cupsLangFree(language);
+  if (group && cg->ppd_conform == PPD_CONFORM_STRICT)
+  {
+    cg->ppd_status = PPD_MISSING_CLOSE_GROUP;
+    goto error;
+  }
+
+  ppd_free(line.buffer);
+
+ /*
+  * Reset language preferences...
+  */
 
 #ifdef DEBUG
-  if (!feof(fp))
-    printf("Premature EOF at %lu...\n", (unsigned long)ftell(fp));
+  if (!cupsFileEOF(fp))
+    DEBUG_printf(("1_ppdOpen: Premature EOF at %lu...\n",
+                  (unsigned long)cupsFileTell(fp)));
 #endif /* DEBUG */
 
   if (cg->ppd_status != PPD_OK)
@@ -1887,12 +2001,25 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
     return (NULL);
   }
 
+ /*
+  * Update the filters array as needed...
+  */
+
+  if (!ppd_update_filters(ppd, cg))
+  {
+    ppdClose(ppd);
+
+    return (NULL);
+  }
+
  /*
   * Create the sorted options array and set the option back-pointer for
   * each choice and custom option...
   */
 
-  ppd->options = cupsArrayNew((cups_array_func_t)ppd_compare_options, NULL);
+  ppd->options = cupsArrayNew2((cups_array_func_t)ppd_compare_options, NULL,
+                               (cups_ahash_func_t)ppd_hash_option,
+                              PPD_HASHSIZE);
 
   for (i = ppd->num_groups, group = ppd->groups;
        i > 0;
@@ -1915,6 +2042,12 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
     }
   }
 
+ /*
+  * Create an array to track the marked choices...
+  */
+
+  ppd->marked = cupsArrayNew((cups_array_func_t)ppd_compare_choices, NULL);
+
  /*
   * Return the PPD file structure...
   */
@@ -1927,21 +2060,67 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
   error:
 
-  ppd_free(string);
+  _cupsStrFree(string);
+  ppd_free(line.buffer);
 
   ppdClose(ppd);
 
-  cupsLangFree(language);
-
   return (NULL);
 }
 
 
 /*
- * 'ppdOpenFd()' - Read a PPD file into memory.
+ * 'ppdOpen()' - Read a PPD file into memory.
  */
 
 ppd_file_t *                           /* O - PPD file record */
+ppdOpen(FILE *fp)                      /* I - File to read from */
+{
+  ppd_file_t   *ppd;                   /* PPD file record */
+  cups_file_t  *cf;                    /* CUPS file */
+
+
+ /*
+  * Reopen the stdio file as a CUPS file...
+  */
+
+  if ((cf = cupsFileOpenFd(fileno(fp), "r")) == NULL)
+    return (NULL);
+
+ /*
+  * Load the PPD file using the newer API...
+  */
+
+  ppd = _ppdOpen(cf, _PPD_LOCALIZATION_DEFAULT);
+
+ /*
+  * Close the CUPS file and return the PPD...
+  */
+
+  cupsFileClose(cf);
+
+  return (ppd);
+}
+
+
+/*
+ * 'ppdOpen2()' - Read a PPD file into memory.
+ *
+ * @since CUPS 1.2/OS X 10.5@
+ */
+
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+ppdOpen2(cups_file_t *fp)              /* I - File to read from */
+{
+  return _ppdOpen(fp, _PPD_LOCALIZATION_DEFAULT);
+}
+
+
+/*
+ * 'ppdOpenFd()' - Read a PPD file into memory.
+ */
+
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
 ppdOpenFd(int fd)                      /* I - File to read from */
 {
   cups_file_t          *fp;            /* CUPS file pointer */
@@ -1988,11 +2167,12 @@ ppdOpenFd(int fd)                       /* I - File to read from */
 
 
 /*
- * 'ppdOpenFile()' - Read a PPD file into memory.
+ * '_ppdOpenFile()' - Read a PPD file into memory.
  */
 
-ppd_file_t *                           /* O - PPD file record */
-ppdOpenFile(const char *filename)      /* I - File to read from */
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+_ppdOpenFile(const char                  *filename,    /* I - File to read from */
+            _ppd_localization_t  localization) /* I - Localization to load */
 {
   cups_file_t          *fp;            /* File pointer */
   ppd_file_t           *ppd;           /* PPD file record */
@@ -2023,7 +2203,7 @@ ppdOpenFile(const char *filename) /* I - File to read from */
 
   if ((fp = cupsFileOpen(filename, "r")) != NULL)
   {
-    ppd = ppdOpen2(fp);
+    ppd = _ppdOpen(fp, localization);
 
     cupsFileClose(fp);
   }
@@ -2037,10 +2217,21 @@ ppdOpenFile(const char *filename)       /* I - File to read from */
 }
 
 
+/*
+ * 'ppdOpenFile()' - Read a PPD file into memory.
+ */
+
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+ppdOpenFile(const char *filename)      /* I - File to read from */
+{
+  return _ppdOpenFile(filename, _PPD_LOCALIZATION_DEFAULT);
+}
+
+
 /*
  * 'ppdSetConformance()' - Set the conformance level for PPD files.
  *
- * @since CUPS 1.1.20@
+ * @since CUPS 1.1.20/OS X 10.4@
  */
 
 void
@@ -2198,37 +2389,31 @@ static int                              /* O - Result of comparison */
 ppd_compare_attrs(ppd_attr_t *a,       /* I - First attribute */
                   ppd_attr_t *b)       /* I - Second attribute */
 {
-  int  ret;                            /* Result of comparison */
-
-
-  if ((ret = strcasecmp(a->name, b->name)) != 0)
-    return (ret);
-  else
-    return (strcasecmp(a->spec, b->spec));
+  return (_cups_strcasecmp(a->name, b->name));
 }
 
 
 /*
- * 'ppd_compare_coptions()' - Compare two custom options.
+ * 'ppd_compare_choices()' - Compare two choices...
  */
 
 static int                             /* O - Result of comparison */
-ppd_compare_coptions(ppd_coption_t *a, /* I - First option */
-                     ppd_coption_t *b) /* I - Second option */
+ppd_compare_choices(ppd_choice_t *a,   /* I - First choice */
+                    ppd_choice_t *b)   /* I - Second choice */
 {
-  return (strcasecmp(a->keyword, b->keyword));
+  return (strcmp(a->option->keyword, b->option->keyword));
 }
 
 
 /*
- * 'ppd_compare_cparams()' - Compare two custom parameters.
+ * 'ppd_compare_coptions()' - Compare two custom options.
  */
 
 static int                             /* O - Result of comparison */
-ppd_compare_cparams(ppd_cparam_t *a,   /* I - First parameter */
-                    ppd_cparam_t *b)   /* I - Second parameter */
+ppd_compare_coptions(ppd_coption_t *a, /* I - First option */
+                     ppd_coption_t *b) /* I - Second option */
 {
-  return (strcasecmp(a->name, b->name));
+  return (_cups_strcasecmp(a->keyword, b->keyword));
 }
 
 
@@ -2240,7 +2425,7 @@ static int                                /* O - Result of comparison */
 ppd_compare_options(ppd_option_t *a,   /* I - First option */
                     ppd_option_t *b)   /* I - Second option */
 {
-  return (strcasecmp(a->keyword, b->keyword));
+  return (_cups_strcasecmp(a->keyword, b->keyword));
 }
 
 
@@ -2268,7 +2453,7 @@ ppd_decode(char *string)          /* I - String to decode */
       inptr ++;
       while (isxdigit(*inptr & 255))
       {
-       if (isalpha(*inptr))
+       if (_cups_isalpha(*inptr))
          *outptr = (tolower(*inptr) - 'a' + 10) << 4;
        else
          *outptr = (*inptr - '0') << 4;
@@ -2278,7 +2463,7 @@ ppd_decode(char *string)          /* I - String to decode */
         if (!isxdigit(*inptr & 255))
          break;
 
-       if (isalpha(*inptr))
+       if (_cups_isalpha(*inptr))
          *outptr |= tolower(*inptr) - 'a' + 10;
        else
          *outptr |= *inptr - '0';
@@ -2301,6 +2486,30 @@ ppd_decode(char *string)         /* I - String to decode */
 }
 
 
+/*
+ * 'ppd_free_filters()' - Free the filters array.
+ */
+
+static void
+ppd_free_filters(ppd_file_t *ppd)      /* I - PPD file */
+{
+  int  i;                              /* Looping var */
+  char **filter;                       /* Current filter */
+
+
+  if (ppd->num_filters > 0)
+  {
+    for (i = ppd->num_filters, filter = ppd->filters; i > 0; i --, filter ++)
+      _cupsStrFree(*filter);
+
+    ppd_free(ppd->filters);
+
+    ppd->num_filters = 0;
+    ppd->filters     = NULL;
+  }
+}
+
+
 /*
  * 'ppd_free_group()' - Free a single UI group.
  */
@@ -2352,7 +2561,7 @@ ppd_free_option(ppd_option_t *option)     /* I - Option to free */
          i > 0;
          i --, choice ++)
     {
-      ppd_free(choice->code);
+      _cupsStrFree(choice->code);
     }
 
     ppd_free(option->choices);
@@ -2387,7 +2596,7 @@ ppd_get_coption(ppd_file_t *ppd,  /* I - PPD file */
 
   strlcpy(copt->keyword, name, sizeof(copt->keyword));
 
-  copt->params = cupsArrayNew((cups_array_func_t)ppd_compare_cparams, NULL);
+  copt->params = cupsArrayNew((cups_array_func_t)NULL, NULL);
 
   cupsArrayAdd(ppd->coptions, copt);
 
@@ -2457,7 +2666,7 @@ ppd_get_group(ppd_file_t      *ppd,       /* I - PPD file */
   ppd_group_t  *group;                 /* Group */
 
 
-  DEBUG_printf(("ppd_get_group(ppd=%p, name=\"%s\", text=\"%s\", cg=%p)\n",
+  DEBUG_printf(("7ppd_get_group(ppd=%p, name=\"%s\", text=\"%s\", cg=%p)",
                 ppd, name, text, cg));
 
   for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
@@ -2466,7 +2675,7 @@ ppd_get_group(ppd_file_t      *ppd,       /* I - PPD file */
 
   if (i == 0)
   {
-    DEBUG_printf(("Adding group %s...\n", name));
+    DEBUG_printf(("8ppd_get_group: Adding group %s...", name));
 
     if (cg->ppd_conform == PPD_CONFORM_STRICT && strlen(text) >= sizeof(group->text))
     {
@@ -2474,7 +2683,7 @@ ppd_get_group(ppd_file_t      *ppd,       /* I - PPD file */
 
       return (NULL);
     }
-           
+
     if (ppd->num_groups == 0)
       group = malloc(sizeof(ppd_group_t));
     else
@@ -2515,7 +2724,7 @@ ppd_get_option(ppd_group_t *group,        /* I - Group */
   ppd_option_t *option;                /* Option */
 
 
-  DEBUG_printf(("ppd_get_option(group=%p(\"%s\"), name=\"%s\")\n",
+  DEBUG_printf(("7ppd_get_option(group=%p(\"%s\"), name=\"%s\")",
                 group, group->name, name));
 
   for (i = group->num_options, option = group->options; i > 0; i --, option ++)
@@ -2545,6 +2754,24 @@ ppd_get_option(ppd_group_t *group,       /* I - Group */
 }
 
 
+/*
+ * 'ppd_hash_option()' - Generate a hash of the option name...
+ */
+
+static int                             /* O - Hash index */
+ppd_hash_option(ppd_option_t *option)  /* I - Option */
+{
+  int          hash = 0;               /* Hash index */
+  const char   *k;                     /* Pointer into keyword */
+
+
+  for (hash = option->keyword[0], k = option->keyword + 1; *k;)
+    hash = 33 * hash + *k++;
+
+  return (hash & 511);
+}
+
+
 /*
  * 'ppd_read()' - Read a line from a PPD file, skipping comment lines as
  *                necessary.
@@ -2552,6 +2779,7 @@ ppd_get_option(ppd_group_t *group,        /* I - Group */
 
 static int                             /* O - Bitmask of fields read */
 ppd_read(cups_file_t    *fp,           /* I - File to read from */
+         _ppd_line_t    *line,         /* I - Line buffer */
          char           *keyword,      /* O - Keyword from line */
         char           *option,        /* O - Option from line */
          char           *text,         /* O - Human-readable text from line */
@@ -2570,16 +2798,8 @@ ppd_read(cups_file_t    *fp,             /* I - File to read from */
                *optptr,                /* Option pointer */
                *textptr,               /* Text pointer */
                *strptr,                /* Pointer into string */
-               *lineptr,               /* Current position in line buffer */
-               *line;                  /* Line buffer */
-  int          linesize;               /* Current size of line buffer */
-
- /*
-  * Range check everything...
-  */
+               *lineptr;               /* Current position in line buffer */
 
-  if (!fp || !keyword || !option || !text || !string)
-    return (0);
 
  /*
   * Now loop until we have a valid line...
@@ -2588,11 +2808,15 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
   *string   = NULL;
   col       = 0;
   startline = cg->ppd_line + 1;
-  linesize  = 1024;
-  line      = malloc(linesize);
 
-  if (!line)
-    return (0);
+  if (!line->buffer)
+  {
+    line->bufsize = 1024;
+    line->buffer  = malloc(1024);
+
+    if (!line->buffer)
+      return (0);
+  }
 
   do
   {
@@ -2600,13 +2824,13 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
     * Read the line...
     */
 
-    lineptr  = line;
+    lineptr  = line->buffer;
     endquote = 0;
     colon    = 0;
 
     while ((ch = cupsFileGetChar(fp)) != EOF)
     {
-      if (lineptr >= (line + linesize - 1))
+      if (lineptr >= (line->buffer + line->bufsize - 1))
       {
        /*
         * Expand the line buffer...
@@ -2615,8 +2839,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
         char *temp;                    /* Temporary line pointer */
 
 
-        linesize += 1024;
-       if (linesize > 262144)
+        line->bufsize += 1024;
+       if (line->bufsize > 262144)
        {
         /*
          * Don't allow lines longer than 256k!
@@ -2625,24 +2849,20 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
           cg->ppd_line   = startline;
           cg->ppd_status = PPD_LINE_TOO_LONG;
 
-         free(line);
-
          return (0);
        }
 
-        temp = realloc(line, linesize);
+        temp = realloc(line->buffer, line->bufsize);
        if (!temp)
        {
           cg->ppd_line   = startline;
           cg->ppd_status = PPD_LINE_TOO_LONG;
 
-         free(line);
-
          return (0);
        }
 
-        lineptr = temp + (lineptr - line);
-       line    = temp;
+        lineptr      = temp + (lineptr - line->buffer);
+       line->buffer = temp;
       }
 
       if (ch == '\r' || ch == '\n')
@@ -2670,7 +2890,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
            cupsFileGetChar(fp);
        }
 
-       if (lineptr == line && ignoreblank)
+       if (lineptr == line->buffer && ignoreblank)
           continue;                    /* Skip blank lines */
 
        ch = '\n';
@@ -2689,8 +2909,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
         cg->ppd_line   = startline;
         cg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
-        free(line);
-
         return (0);
       }
       else if (ch != 0x1a)
@@ -2711,12 +2929,10 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
           cg->ppd_line   = startline;
           cg->ppd_status = PPD_LINE_TOO_LONG;
 
-          free(line);
-
           return (0);
        }
 
-       if (ch == ':' && strncmp(line, "*%", 2) != 0)
+       if (ch == ':' && strncmp(line->buffer, "*%", 2) != 0)
          colon = 1;
 
        if (ch == '\"' && colon)
@@ -2749,8 +2965,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
            if (ch == 0x0a)
              cupsFileGetChar(fp);
          }
-
-         ch = '\n';
        }
        else if (ch < ' ' && ch != '\t' && cg->ppd_conform == PPD_CONFORM_STRICT)
        {
@@ -2761,8 +2975,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
           cg->ppd_line   = startline;
           cg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
-          free(line);
-
           return (0);
        }
        else if (ch != 0x1a)
@@ -2778,8 +2990,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
             cg->ppd_line   = startline;
             cg->ppd_status = PPD_LINE_TOO_LONG;
 
-            free(line);
-
             return (0);
          }
        }
@@ -2824,8 +3034,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
           cg->ppd_line   = startline;
           cg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
-          free(line);
-
           return (0);
        }
        else if (ch != 0x1a)
@@ -2841,61 +3049,53 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
             cg->ppd_line   = startline;
             cg->ppd_status = PPD_LINE_TOO_LONG;
 
-            free(line);
-
             return (0);
          }
        }
     }
 
-    if (lineptr > line && lineptr[-1] == '\n')
+    if (lineptr > line->buffer && lineptr[-1] == '\n')
       lineptr --;
 
     *lineptr = '\0';
 
-    DEBUG_printf(("LINE = \"%s\"\n", line));
+    DEBUG_printf(("9ppd_read: LINE=\"%s\"", line->buffer));
 
    /*
-    * The dynamically created PPDs for older style Mac OS X
+    * The dynamically created PPDs for older style OS X
     * drivers include a large blob of data inserted as comments
     * at the end of the file.  As an optimization we can stop
     * reading the PPD when we get to the start of this data.
     */
 
-    if (!strcmp(line, "*%APLWORKSET START"))
-    {
-      free(line);
+    if (!strcmp(line->buffer, "*%APLWORKSET START"))
       return (0);
-    }
 
-    if (ch == EOF && lineptr == line)
-    {
-      free(line);
+    if (ch == EOF && lineptr == line->buffer)
       return (0);
-    }
 
    /*
     * Now parse it...
     */
 
     mask    = 0;
-    lineptr = line + 1;
+    lineptr = line->buffer + 1;
 
     keyword[0] = '\0';
     option[0]  = '\0';
     text[0]    = '\0';
     *string    = NULL;
 
-    if ((!line[0] ||                   /* Blank line */
-         !strncmp(line, "*%", 2) ||    /* Comment line */
-         !strcmp(line, "*End")) &&     /* End of multi-line string */
+    if ((!line->buffer[0] ||           /* Blank line */
+         !strncmp(line->buffer, "*%", 2) || /* Comment line */
+         !strcmp(line->buffer, "*End")) && /* End of multi-line string */
         ignoreblank)                   /* Ignore these? */
     {
       startline = cg->ppd_line + 1;
       continue;
     }
 
-    if (!strcmp(line, "*"))            /* (Bad) comment line */
+    if (!strcmp(line->buffer, "*"))    /* (Bad) comment line */
     {
       if (cg->ppd_conform == PPD_CONFORM_RELAXED)
       {
@@ -2907,34 +3107,29 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
         cg->ppd_line   = startline;
         cg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
 
-        free(line);
         return (0);
       }
     }
 
-    if (line[0] != '*')                        /* All lines start with an asterisk */
+    if (line->buffer[0] != '*')                /* All lines start with an asterisk */
     {
      /*
       * Allow lines consisting of just whitespace...
       */
 
-      for (lineptr = line; *lineptr; lineptr ++)
-        if (!isspace(*lineptr & 255))
+      for (lineptr = line->buffer; *lineptr; lineptr ++)
+        if (*lineptr && !_cups_isspace(*lineptr))
          break;
 
       if (*lineptr)
       {
         cg->ppd_status = PPD_MISSING_ASTERISK;
-        free(line);
         return (0);
       }
       else if (ignoreblank)
         continue;
       else
-      {
-        free(line);
         return (0);
-      }
     }
 
    /*
@@ -2943,13 +3138,12 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
     keyptr = keyword;
 
-    while (*lineptr != '\0' && *lineptr != ':' && !isspace(*lineptr & 255))
+    while (*lineptr && *lineptr != ':' && !_cups_isspace(*lineptr))
     {
       if (*lineptr <= ' ' || *lineptr > 126 || *lineptr == '/' ||
           (keyptr - keyword) >= (PPD_MAX_NAME - 1))
       {
         cg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
-        free(line);
        return (0);
       }
 
@@ -2963,27 +3157,24 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
     mask |= PPD_KEYWORD;
 
-/*    DEBUG_printf(("keyword = \"%s\", lineptr = \"%s\"\n", keyword, lineptr));*/
-
-    if (isspace(*lineptr & 255))
+    if (_cups_isspace(*lineptr))
     {
      /*
       * Get an option name...
       */
 
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
         lineptr ++;
 
       optptr = option;
 
-      while (*lineptr != '\0' && !isspace(*lineptr & 255) && *lineptr != ':' &&
+      while (*lineptr && !_cups_isspace(*lineptr) && *lineptr != ':' &&
              *lineptr != '/')
       {
        if (*lineptr <= ' ' || *lineptr > 126 ||
            (optptr - option) >= (PPD_MAX_NAME - 1))
         {
           cg->ppd_status = PPD_ILLEGAL_OPTION_KEYWORD;
-          free(line);
          return (0);
        }
 
@@ -2992,20 +3183,17 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
       *optptr = '\0';
 
-      if (isspace(*lineptr & 255) && cg->ppd_conform == PPD_CONFORM_STRICT)
+      if (_cups_isspace(*lineptr) && cg->ppd_conform == PPD_CONFORM_STRICT)
       {
         cg->ppd_status = PPD_ILLEGAL_WHITESPACE;
-        free(line);
        return (0);
       }
 
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
        lineptr ++;
 
       mask |= PPD_OPTION;
 
-/*      DEBUG_printf(("option = \"%s\", lineptr = \"%s\"\n", option, lineptr));*/
-
       if (*lineptr == '/')
       {
        /*
@@ -3013,7 +3201,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
        */
 
         lineptr ++;
-       
+
        textptr = text;
 
        while (*lineptr != '\0' && *lineptr != '\n' && *lineptr != ':')
@@ -3022,7 +3210,6 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
              (textptr - text) >= (PPD_MAX_LINE - 1))
          {
            cg->ppd_status = PPD_ILLEGAL_TRANSLATION;
-            free(line);
            return (0);
          }
 
@@ -3035,24 +3222,20 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
        if (textlen > PPD_MAX_TEXT && cg->ppd_conform == PPD_CONFORM_STRICT)
        {
          cg->ppd_status = PPD_ILLEGAL_TRANSLATION;
-          free(line);
          return (0);
        }
-           
+
        mask |= PPD_TEXT;
       }
-
-/*      DEBUG_printf(("text = \"%s\", lineptr = \"%s\"\n", text, lineptr));*/
     }
 
-    if (isspace(*lineptr & 255) && cg->ppd_conform == PPD_CONFORM_STRICT)
+    if (_cups_isspace(*lineptr) && cg->ppd_conform == PPD_CONFORM_STRICT)
     {
       cg->ppd_status = PPD_ILLEGAL_WHITESPACE;
-      free(line);
       return (0);
     }
 
-    while (isspace(*lineptr & 255))
+    while (_cups_isspace(*lineptr))
       lineptr ++;
 
     if (*lineptr == ':')
@@ -3062,45 +3245,154 @@ ppd_read(cups_file_t    *fp,           /* I - File to read from */
       */
 
       lineptr ++;
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
         lineptr ++;
 
       strptr = lineptr + strlen(lineptr) - 1;
-      while (strptr >= lineptr && isspace(*strptr & 255))
+      while (strptr >= lineptr && _cups_isspace(*strptr))
         *strptr-- = '\0';
 
       if (*strptr == '\"')
       {
        /*
-        * Quoted string by itself...
+        * Quoted string by itself, remove quotes...
        */
 
-       *string = malloc(strlen(lineptr) + 1);
-
-       strptr = *string;
-
-       for (; *lineptr != '\0'; lineptr ++)
-         if (*lineptr != '\"')
-           *strptr++ = *lineptr;
-
-       *strptr = '\0';
+        *strptr = '\0';
+       lineptr ++;
       }
-      else
-        *string = strdup(lineptr);
 
-/*      DEBUG_printf(("string = \"%s\", lineptr = \"%s\"\n", *string, lineptr));*/
+      *string = _cupsStrAlloc(lineptr);
 
       mask |= PPD_STRING;
     }
   }
   while (mask == 0);
 
-  free(line);
-
   return (mask);
 }
 
 
 /*
- * End of "$Id: ppd.c 6445 2007-04-04 23:43:26Z mike $".
+ * 'ppd_update_filters()' - Update the filters array as needed.
+ *
+ * This function re-populates the filters array with cupsFilter2 entries that
+ * have been stripped of the destination MIME media types and any maxsize hints.
+ *
+ * (All for backwards-compatibility)
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+ppd_update_filters(ppd_file_t      *ppd,/* I - PPD file */
+                   _cups_globals_t *cg)        /* I - Global data */
+{
+  ppd_attr_t   *attr;                  /* Current cupsFilter2 value */
+  char         srcsuper[16],           /* Source MIME media type */
+               srctype[256],
+               dstsuper[16],           /* Destination MIME media type */
+               dsttype[256],
+               program[1024],          /* Command to run */
+               *ptr,                   /* Pointer into command to run */
+               buffer[1024],           /* Re-written cupsFilter value */
+               **filter;               /* Current filter */
+  int          cost;                   /* Cost of filter */
+
+
+  DEBUG_printf(("4ppd_update_filters(ppd=%p, cg=%p)", ppd, cg));
+
+ /*
+  * See if we have any cupsFilter2 lines...
+  */
+
+  if ((attr = ppdFindAttr(ppd, "cupsFilter2", NULL)) == NULL)
+  {
+    DEBUG_puts("5ppd_update_filters: No cupsFilter2 keywords present.");
+    return (1);
+  }
+
+ /*
+  * Yes, free the cupsFilter-defined filters and re-build...
+  */
+
+  ppd_free_filters(ppd);
+
+  do
+  {
+   /*
+    * Parse the cupsFilter2 string:
+    *
+    *   src/type dst/type cost program
+    *   src/type dst/type cost maxsize(n) program
+    */
+
+    DEBUG_printf(("5ppd_update_filters: cupsFilter2=\"%s\"", attr->value));
+
+    if (sscanf(attr->value, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
+              srcsuper, srctype, dstsuper, dsttype, &cost, program) != 6)
+    {
+      DEBUG_puts("5ppd_update_filters: Bad cupsFilter2 line.");
+      cg->ppd_status = PPD_BAD_VALUE;
+
+      return (0);
+    }
+
+    DEBUG_printf(("5ppd_update_filters: srcsuper=\"%s\", srctype=\"%s\", "
+                  "dstsuper=\"%s\", dsttype=\"%s\", cost=%d, program=\"%s\"",
+                 srcsuper, srctype, dstsuper, dsttype, cost, program));
+
+    if (!strncmp(program, "maxsize(", 8) &&
+        (ptr = strchr(program + 8, ')')) != NULL)
+    {
+      DEBUG_puts("5ppd_update_filters: Found maxsize(nnn).");
+
+      ptr ++;
+      while (_cups_isspace(*ptr))
+       ptr ++;
+
+      _cups_strcpy(program, ptr);
+      DEBUG_printf(("5ppd_update_filters: New program=\"%s\"", program));
+    }
+
+   /*
+    * Convert to cupsFilter format:
+    *
+    *   src/type cost program
+    */
+
+    snprintf(buffer, sizeof(buffer), "%s/%s %d %s", srcsuper, srctype, cost,
+             program);
+    DEBUG_printf(("5ppd_update_filters: Adding \"%s\".", buffer));
+
+   /*
+    * Add a cupsFilter-compatible string to the filters array.
+    */
+
+    if (ppd->num_filters == 0)
+      filter = malloc(sizeof(char *));
+    else
+      filter = realloc(ppd->filters, sizeof(char *) * (ppd->num_filters + 1));
+
+    if (filter == NULL)
+    {
+      DEBUG_puts("5ppd_update_filters: Out of memory.");
+      cg->ppd_status = PPD_ALLOC_ERROR;
+
+      return (0);
+    }
+
+    ppd->filters     = filter;
+    filter           += ppd->num_filters;
+    ppd->num_filters ++;
+
+    *filter = _cupsStrAlloc(buffer);
+  }
+  while ((attr = ppdFindNextAttr(ppd, "cupsFilter2", NULL)) != NULL);
+
+  DEBUG_puts("5ppd_update_filters: Completed OK.");
+  return (1);
+}
+
+
+/*
+ * End of "$Id$".
  */