//
// PPD test program for libppd.
//
// Copyright © 2007-2018 by Apple Inc.
// Copyright © 1997-2006 by Easy Software Products.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

//
// Include necessary headers...
//

#include <ppd/ppd.h>
#include <ppd/array-private.h>
#include <ppd/raster-private.h>
#include <ppd/libcups2-private.h>
#include <sys/stat.h>
#ifdef _WIN32
#  include <io.h>
#else
#  include <unistd.h>
#  include <fcntl.h>
#endif // _WIN32
#include <math.h>


//
// Local functions...
//

static int	do_ppd_tests(const char *filename, int num_options,
			     cups_option_t *options);
static int	do_ps_tests(void);
static void	print_changes(cups_page_header_t *header, cups_page_header_t *expected);


//
// Test data...
//

static const char *dsc_code =
"[{\n"
"%%BeginFeature: *PageSize Tabloid\n"
"<</PageSize[792 1224]>>setpagedevice\n"
"%%EndFeature\n"
"} stopped cleartomark\n";
static const char *setpagedevice_code =
"<<"
"/MediaClass(Media Class)"
"/MediaColor((Media Color))"
"/MediaType(Media\\\\Type)"
"/OutputType<416263>"
"/AdvanceDistance 1000"
"/AdvanceMedia 1"
"/Collate false"
"/CutMedia 2"
"/Duplex true"
"/HWResolution[100 200]"
"/InsertSheet true"
"/Jog 3"
"/LeadingEdge 1"
"/ManualFeed true"
"/MediaPosition 8#777"
"/MediaWeight 16#fe01"
"/MirrorPrint true"
"/NegativePrint true"
"/NumCopies 1"
"/Orientation 1"
"/OutputFaceUp true"
"/PageSize[612 792.1]"
"/Separations true"
"/TraySwitch true"
"/Tumble true"
"/cupsMediaType 2"
"/cupsColorOrder 1"
"/cupsColorSpace 1"
"/cupsCompression 1"
"/cupsRowCount 1"
"/cupsRowFeed 1"
"/cupsRowStep 1"
"/cupsBorderlessScalingFactor 1.001"
"/cupsInteger0 1"
"/cupsInteger1 2"
"/cupsInteger2 3"
"/cupsInteger3 4"
"/cupsInteger4 5"
"/cupsInteger5 6"
"/cupsInteger6 7"
"/cupsInteger7 8"
"/cupsInteger8 9"
"/cupsInteger9 10"
"/cupsInteger10 11"
"/cupsInteger11 12"
"/cupsInteger12 13"
"/cupsInteger13 14"
"/cupsInteger14 15"
"/cupsInteger15 16"
"/cupsReal0 1.1"
"/cupsReal1 2.1"
"/cupsReal2 3.1"
"/cupsReal3 4.1"
"/cupsReal4 5.1"
"/cupsReal5 6.1"
"/cupsReal6 7.1"
"/cupsReal7 8.1"
"/cupsReal8 9.1"
"/cupsReal9 10.1"
"/cupsReal10 11.1"
"/cupsReal11 12.1"
"/cupsReal12 13.1"
"/cupsReal13 14.1"
"/cupsReal14 15.1"
"/cupsReal15 16.1"
"/cupsString0(1)"
"/cupsString1(2)"
"/cupsString2(3)"
"/cupsString3(4)"
"/cupsString4(5)"
"/cupsString5(6)"
"/cupsString6(7)"
"/cupsString7(8)"
"/cupsString8(9)"
"/cupsString9(10)"
"/cupsString10(11)"
"/cupsString11(12)"
"/cupsString12(13)"
"/cupsString13(14)"
"/cupsString14(15)"
"/cupsString15(16)"
"/cupsMarkerType(Marker Type)"
"/cupsRenderingIntent(Rendering Intent)"
"/cupsPageSizeName(Letter)"
"/cupsPreferredBitsPerColor 17"
">> setpagedevice";

static cups_page_header_t setpagedevice_header =
{
  "Media Class",			// MediaClass
  "(Media Color)",			// MediaColor
  "Media\\Type",			// MediaType
  "Abc",				// OutputType
  1000,					// AdvanceDistance
  CUPS_ADVANCE_FILE,			// AdvanceMedia
  CUPS_FALSE,				// Collate
  CUPS_CUT_JOB,				// CutMedia
  CUPS_TRUE,				// Duplex
  { 100, 200 },				// HWResolution
  { 0, 0, 0, 0 },			// ImagingBoundingBox
  CUPS_TRUE,				// InsertSheet
  CUPS_JOG_SET,				// Jog
  CUPS_EDGE_RIGHT,			// LeadingEdge
  { 0, 0 },				// Margins
  CUPS_TRUE,				// ManualFeed
  0777,					// MediaPosition
  0xfe01,				// MediaWeight
  CUPS_TRUE,				// MirrorPrint
  CUPS_TRUE,				// NegativePrint
  1,					// NumCopies
  CUPS_ORIENT_90,			// Orientation
  CUPS_TRUE,				// OutputFaceUp
  { 612, 792 },				// PageSize
  CUPS_TRUE,				// Separations
  CUPS_TRUE,				// TraySwitch
  CUPS_TRUE,				// Tumble
  0,					// cupsWidth
  0,					// cupsHeight
  2,					// cupsMediaType
  0,					// cupsBitsPerColor
  0,					// cupsBitsPerPixel
  0,					// cupsBytesPerLine
  CUPS_ORDER_BANDED,			// cupsColorOrder
  CUPS_CSPACE_RGB,			// cupsColorSpace
  1,					// cupsCompression
  1,					// cupsRowCount
  1,					// cupsRowFeed
  1,					// cupsRowStep
  0,					// cupsNumColors
  1.001f,				// cupsBorderlessScalingFactor
  { 612.0f, 792.1f },			// cupsPageSize
  { 0.0f, 0.0f, 0.0f, 0.0f },		// cupsImagingBBox
  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 },
					// cupsInteger[16]
  { 1.1f, 2.1f, 3.1f, 4.1f, 5.1f, 6.1f, 7.1f, 8.1f, 9.1f, 10.1f, 11.1f, 12.1f, 13.1f, 14.1f, 15.1f, 16.1f },			// cupsReal[16]
  { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13",
    "14", "15", "16" },			// cupsString[16]
  "Marker Type",			// cupsMarkerType
  "Rendering Intent",			// cupsRenderingIntent
  "Letter"				// cupsPageSizeName
};

static const char	*default_code =
			"[{\n"
			"%%BeginFeature: *InstalledDuplexer False\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *PageRegion Letter\n"
			"PageRegion=Letter\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *InputSlot Tray\n"
			"InputSlot=Tray\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *OutputBin Tray1\n"
			"OutputBin=Tray1\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *MediaType Plain\n"
			"MediaType=Plain\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *IntOption None\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *StringOption None\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n";

static const char	*custom_code =
			"[{\n"
			"%%BeginFeature: *InstalledDuplexer False\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *InputSlot Tray\n"
			"InputSlot=Tray\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *MediaType Plain\n"
			"MediaType=Plain\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *OutputBin Tray1\n"
			"OutputBin=Tray1\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *IntOption None\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *CustomStringOption True\n"
			"(value\\0502\\051)\n"
			"(value 1)\n"
			"StringOption=Custom\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *CustomPageSize True\n"
			"400\n"
			"500\n"
			"0\n"
			"0\n"
			"0\n"
			"PageSize=Custom\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n";

static const char	*default2_code =
			"[{\n"
			"%%BeginFeature: *InstalledDuplexer False\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *InputSlot Tray\n"
			"InputSlot=Tray\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *Quality Normal\n"
			"Quality=Normal\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *IntOption None\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n"
			"[{\n"
			"%%BeginFeature: *StringOption None\n"
			"%%EndFeature\n"
			"} stopped cleartomark\n";


//
// '_ppdRasterColorSpaceString()' - Return the colorspace name for a
//                                  cupsColorSpace value.
//

const char *
_ppdRasterColorSpaceString(
    cups_cspace_t cspace)		// I - cupsColorSpace value
{
  static const char * const cups_color_spaces[] =
  {					// Color spaces
    "W",
    "RGB",
    "RGBA",
    "K",
    "CMY",
    "YMC",
    "CMYK",
    "YMCK",
    "KCMY",
    "KCMYcm",
    "GMCK",
    "GMCS",
    "WHITE",
    "GOLD",
    "SILVER",
    "CIEXYZ",
    "CIELab",
    "RGBW",
    "SW",
    "SRGB",
    "ADOBERGB",
    "21",
    "22",
    "23",
    "24",
    "25",
    "26",
    "27",
    "28",
    "29",
    "30",
    "31",
    "ICC1",
    "ICC2",
    "ICC3",
    "ICC4",
    "ICC5",
    "ICC6",
    "ICC7",
    "ICC8",
    "ICC9",
    "ICCA",
    "ICCB",
    "ICCC",
    "ICCD",
    "ICCE",
    "ICCF",
    "47",
    "DEVICE1",
    "DEVICE2",
    "DEVICE3",
    "DEVICE4",
    "DEVICE5",
    "DEVICE6",
    "DEVICE7",
    "DEVICE8",
    "DEVICE9",
    "DEVICEA",
    "DEVICEB",
    "DEVICEC",
    "DEVICED",
    "DEVICEE",
    "DEVICEF"
  };

  if (cspace < CUPS_CSPACE_W || cspace > CUPS_CSPACE_DEVICEF)
    return ("Unknown");
  else
    return (cups_color_spaces[cspace]);
}


//
// '_log()' - Simple log function
//

void
_log(void *data,
     cf_loglevel_t level,
     const char *message,
     ...)
{
  va_list arglist;

  (void)data; // No extra data needed

  switch(level)
  {
    default:
      break;
    case CF_LOGLEVEL_ERROR:
      printf("ERROR: ");
      break;
    case CF_LOGLEVEL_FATAL:
      printf("FATAL: ");
      break;
  }
  va_start(arglist, message);
  vprintf(message, arglist);
  fflush(stdout);
  va_end(arglist);
  putchar('\n');
}


//
// 'main()' - Main entry.
//

int					// O - Exit status
main(int  argc,				// I - Number of command-line arguments
     char *argv[])			// I - Command-line arguments
{
  int		i;			// Looping var
  ppd_file_t	*ppd = NULL;		// PPD file loaded from disk
  int		status;			// Status of tests (0 = success, 1 = fail)
  int		conflicts;		// Number of conflicts
  char		*s;			// String
  char		buffer[8192];		// String buffer
  const char	*text,			// Localized text
		*val;			// Option value
  int		num_options;		// Number of options
  cups_option_t	*options;		// Options
  ppd_size_t	minsize,		// Minimum size
		maxsize,		// Maximum size
		*size;			// Current size
  ppd_attr_t	*attr;			// Current attribute
  ppd_cache_t	*pc;			// PPD cache


  status = 0;

  if (argc == 1)
  {
    //
    // Setup directories for locale stuff...
    //

    if (access("locale", 0))
    {
      mkdir("locale", 0777);
      mkdir("locale/fr", 0777);
      i = symlink("../../../locale/cups_fr.po", "locale/fr/cups_fr.po");
      mkdir("locale/zh_TW", 0777);
      i = symlink("../../../locale/cups_zh_TW.po", "locale/zh_TW/cups_zh_TW.po");
    }

    putenv("LOCALEDIR=locale");
    putenv("SOFTWARE=CUPS");

    //
    // Do tests with test.ppd...
    //

    fputs("ppdOpenFile(\"ppd/test.ppd\"): ", stdout);

    if ((ppd = ppdOpenFileWithLocalization("ppd/test.ppd", PPD_LOCALIZATION_ALL)) != NULL)
      puts("PASS");
    else
    {
      ppd_status_t	err;		// Last error in file
      int		line;		// Line number in file


      status ++;
      err = ppdLastError(&line);

      printf("FAIL (%s on line %d)\n", ppdErrorString(err), line);
    }

    fputs("ppdFindAttr(wildcard): ", stdout);
    if ((attr = ppdFindAttr(ppd, "cupsTest", NULL)) == NULL)
    {
      status ++;
      puts("FAIL (not found)");
    }
    else if (strcmp(attr->name, "cupsTest") || strcmp(attr->spec, "Foo"))
    {
      status ++;
      printf("FAIL (got \"%s %s\")\n", attr->name, attr->spec);
    }
    else
      puts("PASS");

    fputs("ppdFindNextAttr(wildcard): ", stdout);
    if ((attr = ppdFindNextAttr(ppd, "cupsTest", NULL)) == NULL)
    {
      status ++;
      puts("FAIL (not found)");
    }
    else if (strcmp(attr->name, "cupsTest") || strcmp(attr->spec, "Bar"))
    {
      status ++;
      printf("FAIL (got \"%s %s\")\n", attr->name, attr->spec);
    }
    else
      puts("PASS");

    fputs("ppdFindAttr(Foo): ", stdout);
    if ((attr = ppdFindAttr(ppd, "cupsTest", "Foo")) == NULL)
    {
      status ++;
      puts("FAIL (not found)");
    }
    else if (strcmp(attr->name, "cupsTest") || strcmp(attr->spec, "Foo"))
    {
      status ++;
      printf("FAIL (got \"%s %s\")\n", attr->name, attr->spec);
    }
    else
      puts("PASS");

    fputs("ppdFindNextAttr(Foo): ", stdout);
    if ((attr = ppdFindNextAttr(ppd, "cupsTest", "Foo")) != NULL)
    {
      status ++;
      printf("FAIL (got \"%s %s\")\n", attr->name, attr->spec);
    }
    else
      puts("PASS");

    fputs("ppdMarkDefaults: ", stdout);
    ppdMarkDefaults(ppd);

    if ((conflicts = ppdConflicts(ppd)) == 0)
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (%d conflicts)\n", conflicts);
    }

    fputs("ppdEmitString (defaults): ", stdout);
    if ((s = ppdEmitString(ppd, PPD_ORDER_ANY, 0.0)) != NULL &&
	!strcmp(s, default_code))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (%d bytes instead of %d)\n", s ? (int)strlen(s) : 0,
	     (int)strlen(default_code));

      if (s)
	puts(s);
    }

    if (s)
      free(s);

    fputs("ppdEmitString (custom size and string): ", stdout);
    ppdMarkOption(ppd, "PageSize", "Custom.400x500");
    ppdMarkOption(ppd, "StringOption",
		  "{String1=\"value 1\" String2=value(2)}");

    if ((s = ppdEmitString(ppd, PPD_ORDER_ANY, 0.0)) != NULL &&
	!strcmp(s, custom_code))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (%d bytes instead of %d)\n", s ? (int)strlen(s) : 0,
	     (int)strlen(custom_code));

      if (s)
	puts(s);
    }

    if (s)
      free(s);

    //
    // Test constraints...
    //

    fputs("ppdGetConflicts(InputSlot=Envelope): ", stdout);
    ppdMarkOption(ppd, "PageSize", "Letter");

    num_options = ppdGetConflicts(ppd, "InputSlot", "Envelope", &options);
    if (num_options != 2 ||
        (val = cupsGetOption("PageRegion", num_options, options)) == NULL ||
	_ppd_strcasecmp(val, "Letter") ||
	(val = cupsGetOption("PageSize", num_options, options)) == NULL ||
	_ppd_strcasecmp(val, "Letter"))
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
      status ++;
    }
    else
      puts("PASS");

    fputs("ppdConflicts(): ", stdout);
    ppdMarkOption(ppd, "InputSlot", "Envelope");

    if ((conflicts = ppdConflicts(ppd)) == 2)
      puts("PASS (2)");
    else
    {
      printf("FAIL (%d)\n", conflicts);
      status ++;
    }

    fputs("ppdResolveConflicts(InputSlot=Envelope): ", stdout);
    num_options = 0;
    options     = NULL;
    if (!ppdResolveConflicts(ppd, "InputSlot", "Envelope", &num_options,
                             &options))
    {
      puts("FAIL (Unable to resolve)");
      status ++;
    }
    else if (num_options != 2 ||
             !cupsGetOption("PageSize", num_options, options))
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
      status ++;
    }
    else
      puts("PASS (Resolved by changing PageSize)");

    cupsFreeOptions(num_options, options);

    fputs("ppdResolveConflicts(No option/choice): ", stdout);
    num_options = 0;
    options     = NULL;
    if (ppdResolveConflicts(ppd, NULL, NULL, &num_options, &options) &&
        num_options == 1 && !_ppd_strcasecmp(options[0].name, "InputSlot") &&
	!_ppd_strcasecmp(options[0].value, "Tray"))
      puts("PASS (Resolved by changing InputSlot)");
    else if (num_options > 0)
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
      status ++;
    }
    else
    {
      puts("FAIL (Unable to resolve)");
      status ++;
    }
    cupsFreeOptions(num_options, options);

    fputs("ppdInstallableConflict(): ", stdout);
    if (ppdInstallableConflict(ppd, "Duplex", "DuplexNoTumble") &&
        !ppdInstallableConflict(ppd, "Duplex", "None"))
      puts("PASS");
    else if (!ppdInstallableConflict(ppd, "Duplex", "DuplexNoTumble"))
    {
      puts("FAIL (Duplex=DuplexNoTumble did not conflict)");
      status ++;
    }
    else
    {
      puts("FAIL (Duplex=None conflicted)");
      status ++;
    }

    //
    // ppdPageSizeLimits
    //

    fputs("ppdPageSizeLimits: ", stdout);
    if (ppdPageSizeLimits(ppd, &minsize, &maxsize))
    {
      if (fabs(minsize.width - 36.0) > 0.001 ||
	  fabs(minsize.length - 36.0) > 0.001 ||
          fabs(maxsize.width - 1080.0) > 0.001 ||
	  fabs(maxsize.length - 86400.0) > 0.001)
      {
        printf("FAIL (got min=%.3fx%.3f, max=%.3fx%.3f, "
	       "expected min=36x36, max=1080x86400)\n", minsize.width,
	       minsize.length, maxsize.width, maxsize.length);
        status ++;
      }
      else
        puts("PASS");
    }
    else
    {
      puts("FAIL (returned 0)");
      status ++;
    }

    //
    // ppdMarkOptions with PWG and IPP size names.
    //

    fputs("ppdMarkOptions(media=iso-a4): ", stdout);
    num_options = cupsAddOption("media", "iso-a4", 0, &options);
    ppdMarkOptions(ppd, num_options, options);
    cupsFreeOptions(num_options, options);

    size = ppdPageSize(ppd, NULL);
    if (!size || strcmp(size->name, "A4"))
    {
      printf("FAIL (%s)\n", size ? size->name : "unknown");
      status ++;
    }
    else
      puts("PASS");

    fputs("ppdMarkOptions(media=na_letter_8.5x11in): ", stdout);
    num_options = cupsAddOption("media", "na_letter_8.5x11in", 0, &options);
    ppdMarkOptions(ppd, num_options, options);
    cupsFreeOptions(num_options, options);

    size = ppdPageSize(ppd, NULL);
    if (!size || strcmp(size->name, "Letter"))
    {
      printf("FAIL (%s)\n", size ? size->name : "unknown");
      status ++;
    }
    else
      puts("PASS");

    fputs("ppdMarkOptions(media=oe_letter-fullbleed_8.5x11in): ", stdout);
    num_options = cupsAddOption("media", "oe_letter-fullbleed_8.5x11in", 0,
                                &options);
    ppdMarkOptions(ppd, num_options, options);
    cupsFreeOptions(num_options, options);

    size = ppdPageSize(ppd, NULL);
    if (!size || strcmp(size->name, "Letter.Fullbleed"))
    {
      printf("FAIL (%s)\n", size ? size->name : "unknown");
      status ++;
    }
    else
      puts("PASS");

    fputs("ppdMarkOptions(media=A4): ", stdout);
    num_options = cupsAddOption("media", "A4", 0, &options);
    ppdMarkOptions(ppd, num_options, options);
    cupsFreeOptions(num_options, options);

    size = ppdPageSize(ppd, NULL);
    if (!size || strcmp(size->name, "A4"))
    {
      printf("FAIL (%s)\n", size ? size->name : "unknown");
      status ++;
    }
    else
      puts("PASS");

    //
    // Custom sizes...
    //

    fputs("ppdMarkOptions(media=Custom.8x10in): ", stdout);
    num_options = cupsAddOption("media", "Custom.8x10in", 0, &options);
    ppdMarkOptions(ppd, num_options, options);
    cupsFreeOptions(num_options, options);

    size = ppdPageSize(ppd, NULL);
    if (!size || strcmp(size->name, "Custom") ||
        fabs(size->width - 576.0) > 0.001 ||
        fabs(size->length - 720.0) > 0.001)
    {
      printf("FAIL (%s - %gx%g)\n", size ? size->name : "unknown",
             size ? size->width : 0.0, size ? size->length : 0.0);
      status ++;
    }
    else
      puts("PASS");

    //
    // Test localization...
    //

    // Force US English base locale
    putenv("LANG=en");
    putenv("LC_ALL=en");
    putenv("LC_CTYPE=en");
    putenv("LC_MESSAGES=en");

    fputs("ppdLocalizeIPPReason(text): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", NULL, buffer, sizeof(buffer)) &&
        !strcmp(buffer, "Foo Reason"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"Foo Reason\")\n", buffer);
    }

    fputs("ppdLocalizeIPPReason(http): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", "http", buffer, sizeof(buffer)) &&
        !strcmp(buffer, "http://foo/bar.html"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"http://foo/bar.html\")\n", buffer);
    }

    fputs("ppdLocalizeIPPReason(help): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", "help", buffer, sizeof(buffer)) &&
        !strcmp(buffer, "help:anchor='foo'%20bookID=Vendor%20Help"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"help:anchor='foo'%%20bookID=Vendor%%20Help\")\n", buffer);
    }

    fputs("ppdLocalizeIPPReason(file): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", "file", buffer, sizeof(buffer)) &&
        !strcmp(buffer, "/help/foo/bar.html"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"/help/foo/bar.html\")\n", buffer);
    }

    // Force French
    putenv("LANG=fr");
    putenv("LC_ALL=fr");
    putenv("LC_CTYPE=fr");
    putenv("LC_MESSAGES=fr");

    fputs("ppdLocalizeIPPReason(fr text): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", NULL, buffer, sizeof(buffer)) &&
        !strcmp(buffer, "La Long Foo Reason"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"La Long Foo Reason\")\n", buffer);
    }

    // Force Simplified Chinese
    putenv("LANG=zh_TW");
    putenv("LC_ALL=zh_TW");
    putenv("LC_CTYPE=zh_TW");
    putenv("LC_MESSAGES=zh_TW");

    fputs("ppdLocalizeIPPReason(zh_TW text): ", stdout);
    if (ppdLocalizeIPPReason(ppd, "foo", NULL, buffer, sizeof(buffer)) &&
        !strcmp(buffer, "Number 1 Foo Reason"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"Number 1 Foo Reason\")\n", buffer);
    }

    //
    // cupsMarkerName localization...
    //

    // Force US English base locale
    putenv("LANG=en");
    putenv("LC_ALL=en");
    putenv("LC_CTYPE=en");
    putenv("LC_MESSAGES=en");

    fputs("ppdLocalizeMarkerName(bogus): ", stdout);

    if ((text = ppdLocalizeMarkerName(ppd, "bogus")) != NULL)
    {
      status ++;
      printf("FAIL (\"%s\" instead of NULL)\n", text);
    }
    else
      puts("PASS");

    fputs("ppdLocalizeMarkerName(cyan): ", stdout);

    if ((text = ppdLocalizeMarkerName(ppd, "cyan")) != NULL &&
        !strcmp(text, "Cyan Toner"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"Cyan Toner\")\n",
             text ? text : "(null)");
    }

    // Force French locale
    putenv("LANG=fr");
    putenv("LC_ALL=fr");
    putenv("LC_CTYPE=fr");
    putenv("LC_MESSAGES=fr");

    fputs("ppdLocalizeMarkerName(fr cyan): ", stdout);
    if ((text = ppdLocalizeMarkerName(ppd, "cyan")) != NULL &&
        !strcmp(text, "La Toner Cyan"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"La Toner Cyan\")\n",
             text ? text : "(null)");
    }

    // Force Simplified Chinese locale
    putenv("LANG=zh_TW");
    putenv("LC_ALL=zh_TW");
    putenv("LC_CTYPE=zh_TW");
    putenv("LC_MESSAGES=zh_TW");

    fputs("ppdLocalizeMarkerName(zh_TW cyan): ", stdout);
    if ((text = ppdLocalizeMarkerName(ppd, "cyan")) != NULL &&
        !strcmp(text, "Number 1 Cyan Toner"))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (\"%s\" instead of \"Number 1 Cyan Toner\")\n",
             text ? text : "(null)");
    }

    ppdClose(ppd);

    // Force US English base locale
    putenv("LANG=en");
    putenv("LC_ALL=en");
    putenv("LC_CTYPE=en");
    putenv("LC_MESSAGES=en");

    //
    // Test new constraints...
    //

    fputs("ppdOpenFile(\"ppd/test2.ppd\"): ", stdout);

    if ((ppd = ppdOpenFile("ppd/test2.ppd")) != NULL)
      puts("PASS");
    else
    {
      ppd_status_t	err;		// Last error in file
      int		line;		// Line number in file


      status ++;
      err = ppdLastError(&line);

      printf("FAIL (%s on line %d)\n", ppdErrorString(err), line);
    }

    fputs("ppdMarkDefaults: ", stdout);
    ppdMarkDefaults(ppd);

    if ((conflicts = ppdConflicts(ppd)) == 0)
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (%d conflicts)\n", conflicts);
    }

    fputs("ppdEmitString (defaults): ", stdout);
    if ((s = ppdEmitString(ppd, PPD_ORDER_ANY, 0.0)) != NULL &&
	!strcmp(s, default2_code))
      puts("PASS");
    else
    {
      status ++;
      printf("FAIL (%d bytes instead of %d)\n", s ? (int)strlen(s) : 0,
	     (int)strlen(default2_code));

      if (s)
	puts(s);
    }

    if (s)
      free(s);

    fputs("ppdConflicts(): ", stdout);
    ppdMarkOption(ppd, "PageSize", "Env10");
    ppdMarkOption(ppd, "InputSlot", "Envelope");
    ppdMarkOption(ppd, "Quality", "Photo");

    if ((conflicts = ppdConflicts(ppd)) == 1)
      puts("PASS (1)");
    else
    {
      printf("FAIL (%d)\n", conflicts);
      status ++;
    }

    fputs("ppdResolveConflicts(Quality=Photo): ", stdout);
    num_options = 0;
    options     = NULL;
    if (ppdResolveConflicts(ppd, "Quality", "Photo", &num_options,
                             &options))
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
      status ++;
    }
    else
      puts("PASS (Unable to resolve)");
    cupsFreeOptions(num_options, options);

    fputs("ppdResolveConflicts(No option/choice): ", stdout);
    num_options = 0;
    options     = NULL;
    if (ppdResolveConflicts(ppd, NULL, NULL, &num_options, &options) &&
        num_options == 1 && !_ppd_strcasecmp(options->name, "Quality") &&
	!_ppd_strcasecmp(options->value, "Normal"))
      puts("PASS");
    else if (num_options > 0)
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
      status ++;
    }
    else
    {
      puts("FAIL (Unable to resolve!)");
      status ++;
    }
    cupsFreeOptions(num_options, options);

    fputs("ppdResolveConflicts(loop test): ", stdout);
    ppdMarkOption(ppd, "PageSize", "A4");
    ppdMarkOption(ppd, "InputSlot", "Tray");
    ppdMarkOption(ppd, "Quality", "Photo");
    num_options = 0;
    options     = NULL;
    if (!ppdResolveConflicts(ppd, NULL, NULL, &num_options, &options))
      puts("PASS");
    else if (num_options > 0)
    {
      printf("FAIL (%d options:", num_options);
      for (i = 0; i < num_options; i ++)
        printf(" %s=%s", options[i].name, options[i].value);
      puts(")");
    }
    else
      puts("FAIL (No conflicts!)");

    fputs("ppdInstallableConflict(): ", stdout);
    if (ppdInstallableConflict(ppd, "Duplex", "DuplexNoTumble") &&
        !ppdInstallableConflict(ppd, "Duplex", "None"))
      puts("PASS");
    else if (!ppdInstallableConflict(ppd, "Duplex", "DuplexNoTumble"))
    {
      puts("FAIL (Duplex=DuplexNoTumble did not conflict)");
      status ++;
    }
    else
    {
      puts("FAIL (Duplex=None conflicted)");
      status ++;
    }

    //
    // ppdPageSizeLimits
    //

    ppdMarkDefaults(ppd);

    fputs("ppdPageSizeLimits(default): ", stdout);
    if (ppdPageSizeLimits(ppd, &minsize, &maxsize))
    {
      if (fabs(minsize.width - 36.0) > 0.001 ||
	  fabs(minsize.length - 36.0) > 0.001 ||
          fabs(maxsize.width - 1080.0) > 0.001 ||
	  fabs(maxsize.length - 86400.0) > 0.001)
      {
        printf("FAIL (got min=%.0fx%.0f, max=%.0fx%.0f, "
	       "expected min=36x36, max=1080x86400)\n", minsize.width,
	       minsize.length, maxsize.width, maxsize.length);
        status ++;
      }
      else
        puts("PASS");
    }
    else
    {
      puts("FAIL (returned 0)");
      status ++;
    }

    ppdMarkOption(ppd, "InputSlot", "Manual");

    fputs("ppdPageSizeLimits(InputSlot=Manual): ", stdout);
    if (ppdPageSizeLimits(ppd, &minsize, &maxsize))
    {
      if (fabs(minsize.width - 100.0) > 0.001 ||
	  fabs(minsize.length - 100.0) > 0.001 ||
          fabs(maxsize.width - 1000.0) > 0.001 ||
	  fabs(maxsize.length - 1000.0) > 0.001)
      {
        printf("FAIL (got min=%.0fx%.0f, max=%.0fx%.0f, "
	       "expected min=100x100, max=1000x1000)\n", minsize.width,
	       minsize.length, maxsize.width, maxsize.length);
        status ++;
      }
      else
        puts("PASS");
    }
    else
    {
      puts("FAIL (returned 0)");
      status ++;
    }

    ppdMarkOption(ppd, "Quality", "Photo");

    fputs("ppdPageSizeLimits(Quality=Photo): ", stdout);
    if (ppdPageSizeLimits(ppd, &minsize, &maxsize))
    {
      if (fabs(minsize.width - 200.0) > 0.001 ||
	  fabs(minsize.length - 200.0) > 0.001 ||
          fabs(maxsize.width - 1000.0) > 0.001 ||
	  fabs(maxsize.length - 1000.0) > 0.001)
      {
        printf("FAIL (got min=%.0fx%.0f, max=%.0fx%.0f, "
	       "expected min=200x200, max=1000x1000)\n", minsize.width,
	       minsize.length, maxsize.width, maxsize.length);
        status ++;
      }
      else
        puts("PASS");
    }
    else
    {
      puts("FAIL (returned 0)");
      status ++;
    }

    ppdMarkOption(ppd, "InputSlot", "Tray");

    fputs("ppdPageSizeLimits(Quality=Photo): ", stdout);
    if (ppdPageSizeLimits(ppd, &minsize, &maxsize))
    {
      if (fabs(minsize.width - 300.0) > 0.001 ||
	  fabs(minsize.length - 300.0) > 0.001 ||
          fabs(maxsize.width - 1080.0) > 0.001 ||
	  fabs(maxsize.length - 86400.0) > 0.001)
      {
        printf("FAIL (got min=%.0fx%.0f, max=%.0fx%.0f, "
	       "expected min=300x300, max=1080x86400)\n", minsize.width,
	       minsize.length, maxsize.width, maxsize.length);
        status ++;
      }
      else
        puts("PASS");
    }
    else
    {
      puts("FAIL (returned 0)");
      status ++;
    }

    status += do_ps_tests();
  }
  else if (!strcmp(argv[1], "--raster"))
  {
    for (status = 0, num_options = 0, options = NULL, i = 1; i < argc; i ++)
    {
      if (argv[i][0] == '-')
      {
        if (argv[i][1] == 'o')
        {
          if (argv[i][2])
            num_options = cupsParseOptions(argv[i] + 2, num_options, &options);
          else
          {
            i ++;
            if (i < argc)
              num_options = cupsParseOptions(argv[i], num_options, &options);
            else
            {
              puts("Usage: testppd --raster [-o name=value ...] [filename.ppd ...]");
              return (1);
            }
          }
        }
        else
        {
	  puts("Usage: testppd --raster [-o name=value ...] [filename.ppd ...]");
	  return (1);
        }
      }
      else
	status += do_ppd_tests(argv[i], num_options, options);
    }

    cupsFreeOptions(num_options, options);
  }
  else if (!strcmp(argv[1], "dump") && argc == 3)
  {
    ppdCollectionDumpCache(argv[2], _log, NULL);
  }
  else if (strchr(argv[1], ',') ||
	   (s = strcasestr(argv[1], ".ppd")) == NULL ||
	   s[4] != '\0')
  {
    // First argument is not FILE.ppd but DIR or DIR,DIR,... ->
    // PPD directory list -> See whether we have valid PPD collection

    cups_array_t *dirlist = _ppdArrayNewStrings(argv[1], ',');
    cups_array_t *ppd_collections = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL);
    ppd_collection_t *col;
    cups_option_t *options = NULL;
    int num_options = 0;
    int i;
    char *testname = NULL;

    for (s = (char *)cupsArrayGetFirst(dirlist), i = 1;
	 s;
	 s = (char *)cupsArrayGetNext(dirlist), i ++)
    {
      col = (ppd_collection_t *)calloc(1, sizeof(ppd_collection_t));
      col->path = s;
      printf("Searching directory %d, %s ...\n", i, col->path); 
      col->name = (char *)calloc(20, sizeof(char));
      snprintf(col->name, 19, "%d", i);
      cupsArrayAdd(ppd_collections, col);
    }

    for (i = 2; i < argc; i ++)
      if (strlen(argv[i]) > 0 && argv[i][0] != '=')
      {
	if ((s = strchr(argv[i], '=')) == NULL)
	  s = "";
	else
	{
	  *s = '\0';
	  s ++;
	}
	num_options = cupsAddOption(argv[i], s, num_options, &options);
      }

    cups_array_t *ppds =
      ppdCollectionListPPDs(ppd_collections, 0, num_options, options,
			    _log, NULL);

    if (ppds)
    {
      if (cupsGetOption("only-makes", num_options, options))
      {
	printf("Found %d manufacturers.\n\n", (int)cupsArrayGetCount(ppds));
	for (s = (char *)cupsArrayGetFirst(ppds);
	     s;
	     s = (char *)cupsArrayGetNext(ppds))
	  puts(s);
      }
      else
      {
	ppd_info_t *ppd;
	printf("Found %d PPD files.\n\n", (int)cupsArrayGetCount(ppds));
	puts("mtime,size,model_number,type,filename,name,languages0,products0,"
	     "psversions0,make,make_and_model,device_id,scheme");
	for (ppd = (ppd_info_t *)cupsArrayGetFirst(ppds);
	     ppd;
	     ppd = (ppd_info_t *)cupsArrayGetNext(ppds))
	{
	  printf("%d,%ld,%d,%d,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
		 "\"%s\",\"%s\"\n",
		 (int)ppd->record.mtime, (long)ppd->record.size,
		 ppd->record.model_number, ppd->record.type,
		 ppd->record.filename,
		 ppd->record.name, ppd->record.languages[0],
		 ppd->record.products[0],
		 ppd->record.psversions[0], ppd->record.make,
		 ppd->record.make_and_model, ppd->record.device_id,
		 ppd->record.scheme);
	  testname = ppd->record.name;
	}
      }
    }

    if (testname)
    {
      if ((ppd = ppdOpen2(ppdCollectionGetPPD(testname, ppd_collections,
					      _log, NULL))) == NULL)
      {
	ppd_status_t	err;		// Last error in file
	int		line;		// Line number in file

	status ++;
	err = ppdLastError(&line);

	printf("%s: %s on line %d\n", argv[1], ppdErrorString(err), line);
      }
      else
      {
	printf("PPD %s: %s\n", testname, ppd->nickname);
	ppdClose(ppd);
      }
    }
    else
      printf("%s: No PPD files found.\n", argv[1]);

    cupsArrayDelete(dirlist);
    for (col = (ppd_collection_t *)cupsArrayGetFirst(ppd_collections);
	 col;
	 col = (ppd_collection_t *)cupsArrayGetNext(ppd_collections))
    {
      free(col->name);
      free(col);
    }
    cupsArrayDelete(ppd_collections);
    if (ppds)
    {
      for (s = (char *)cupsArrayGetFirst(ppds);
	   s;
	   s = (char *)cupsArrayGetNext(ppds))
	free(s);
      cupsArrayDelete(ppds);
    }
    if (num_options)
      cupsFreeOptions(num_options, options);

    return (0);
  }
  else
  {
    const char	*name;			// PPD name
    char	*filename;		// Name of actual file containing PPD
    struct stat	fileinfo;		// File information

    name = argv[1];
    filename = strdup(name);
    if ((s = strchr(filename, ':')) != NULL)
      *s = '\0';

    if (lstat(filename, &fileinfo))
    {
      printf("%s: %s\n", filename, strerror(errno));
      return (1);
    }

    if (S_ISLNK(fileinfo.st_mode))
    {
      char	realfile[1024];		// Real file path
      ssize_t	realsize;		// Size of real file path


      if ((realsize = readlink(filename, realfile, sizeof(realfile) - 1)) < 0)
        strlcpy(realfile, "Unknown", sizeof(realfile));
      else
        realfile[realsize] = '\0';

      if (stat(realfile, &fileinfo))
	printf("%s: symlink to \"%s\", %s\n", filename, realfile,
	       strerror(errno));
      else
	printf("%s: symlink to \"%s\", %ld bytes\n", filename, realfile,
	       (long)fileinfo.st_size);
    }
    else
      printf("%s: regular file, %ld bytes\n", filename, (long)fileinfo.st_size);

    if (s)
      printf("\nPPD file %s from %s:\n\n", s + 1, filename);
    else
      printf("\nPPD file %s:\n\n", filename);

    if ((ppd = ppdOpen2(ppdCollectionGetPPD(name, NULL, _log, NULL))) ==
	NULL)
    {
      ppd_status_t	err;		// Last error in file
      int		line;		// Line number in file


      status ++;
      err = ppdLastError(&line);

      printf("%s: %s on line %d\n", argv[1], ppdErrorString(err), line);
    }
    else
    {
      int		j, k;		// Looping vars
      ppd_group_t	*group;		// Option group
      ppd_option_t	*option;	// Option
      ppd_coption_t	*coption;	// Custom option
      ppd_cparam_t	*cparam;	// Custom parameter
      ppd_const_t	*c;		// UIConstraints
      char		lang[255],	// LANG environment variable
			lc_all[255],	// LC_ALL environment variable
			lc_ctype[255],	// LC_CTYPE environment variable
			lc_messages[255];// LC_MESSAGES environment variable


      if (argc > 2)
      {
        snprintf(lang, sizeof(lang), "LANG=%s", argv[2]);
	putenv(lang);
        snprintf(lc_all, sizeof(lc_all), "LC_ALL=%s", argv[2]);
	putenv(lc_all);
        snprintf(lc_ctype, sizeof(lc_ctype), "LC_CTYPE=%s", argv[2]);
	putenv(lc_ctype);
        snprintf(lc_messages, sizeof(lc_messages), "LC_MESSAGES=%s", argv[2]);
	putenv(lc_messages);
      }

      ppdLocalize(ppd);
      ppdMarkDefaults(ppd);

      if (argc > 3)
      {
        text = ppdLocalizeIPPReason(ppd, argv[3], NULL, buffer, sizeof(buffer));
	printf("ppdLocalizeIPPReason(%s)=%s\n", argv[3],
	       text ? text : "(null)");
	free(filename);
	return (text == NULL);
      }

      for (i = ppd->num_groups, group = ppd->groups;
	   i > 0;
	   i --, group ++)
      {
	printf("%s (%s):\n", group->name, group->text);

	for (j = group->num_options, option = group->options;
	     j > 0;
	     j --, option ++)
	{
	  printf("    %s (%s):\n", option->keyword, option->text);

	  for (k = 0; k < option->num_choices; k ++)
	    printf("        - %s%s (%s)\n",
	           option->choices[k].marked ? "*" : "",
		   option->choices[k].choice, option->choices[k].text);

          if ((coption = ppdFindCustomOption(ppd, option->keyword)) != NULL)
	  {
	    for (cparam = (ppd_cparam_t *)cupsArrayGetFirst(coption->params);
	         cparam;
		 cparam = (ppd_cparam_t *)cupsArrayGetNext(coption->params))
            {
	      switch (cparam->type)
	      {
	        case PPD_CUSTOM_UNKNOWN :
		    printf("              %s(%s): PPD_CUSTOM_UNKNOWN (error)\n",
			   cparam->name, cparam->text);
	            break;

	        case PPD_CUSTOM_CURVE :
		    printf("              %s(%s): PPD_CUSTOM_CURVE (%g to %g)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_curve,
			   cparam->maximum.custom_curve);
		    break;

	        case PPD_CUSTOM_INT :
		    printf("              %s(%s): PPD_CUSTOM_INT (%d to %d)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_int,
			   cparam->maximum.custom_int);
		    break;

	        case PPD_CUSTOM_INVCURVE :
		    printf("              %s(%s): PPD_CUSTOM_INVCURVE (%g to %g)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_invcurve,
			   cparam->maximum.custom_invcurve);
		    break;

	        case PPD_CUSTOM_PASSCODE :
		    printf("              %s(%s): PPD_CUSTOM_PASSCODE (%d to %d)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_passcode,
			   cparam->maximum.custom_passcode);
		    break;

	        case PPD_CUSTOM_PASSWORD :
		    printf("              %s(%s): PPD_CUSTOM_PASSWORD (%d to %d)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_password,
			   cparam->maximum.custom_password);
		    break;

	        case PPD_CUSTOM_POINTS :
		    printf("              %s(%s): PPD_CUSTOM_POINTS (%g to %g)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_points,
			   cparam->maximum.custom_points);
		    break;

	        case PPD_CUSTOM_REAL :
		    printf("              %s(%s): PPD_CUSTOM_REAL (%g to %g)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_real,
			   cparam->maximum.custom_real);
		    break;

	        case PPD_CUSTOM_STRING :
		    printf("              %s(%s): PPD_CUSTOM_STRING (%d to %d)\n",
		           cparam->name, cparam->text,
			   cparam->minimum.custom_string,
			   cparam->maximum.custom_string);
		    break;
	      }
	    }
	  }
	}
      }

      puts("\nSizes:");
      for (i = ppd->num_sizes, size = ppd->sizes; i > 0; i --, size ++)
        printf("    %s = %gx%g, [%g %g %g %g]\n", size->name, size->width,
	       size->length, size->left, size->bottom, size->right, size->top);

      puts("\nConstraints:");

      for (i = ppd->num_consts, c = ppd->consts; i > 0; i --, c ++)
        printf("    *UIConstraints: *%s %s *%s %s\n", c->option1, c->choice1,
	       c->option2, c->choice2);
      if (ppd->num_consts == 0)
        puts("    NO CONSTRAINTS");

      puts("\nFilters:");

      for (i = 0; i < ppd->num_filters; i ++)
        printf("    %s\n", ppd->filters[i]);

      if (ppd->num_filters == 0)
        puts("    NO FILTERS");

      puts("\nAttributes:");

      for (attr = (ppd_attr_t *)cupsArrayGetFirst(ppd->sorted_attrs);
           attr;
	   attr = (ppd_attr_t *)cupsArrayGetNext(ppd->sorted_attrs))
        printf("    *%s %s/%s: \"%s\"\n", attr->name, attr->spec,
	       attr->text, attr->value ? attr->value : "");

      puts("\nPPD Cache:");
      if ((pc = ppdCacheCreateWithPPD(ppd)) == NULL)
        printf("    Unable to create: %s\n", cupsGetErrorString());
      else
      {
        ppdCacheWriteFile(pc, "t.cache", NULL);
        puts("    Wrote t.cache.");
      }
    }

    if (!strncmp(argv[1], "-d", 2))
      unlink(filename);
    free(filename);
  }

#ifdef __APPLE__
  if (getenv("MallocStackLogging") && getenv("MallocStackLoggingNoCompact"))
  {
    char	command[1024];		// malloc_history command

    snprintf(command, sizeof(command), "malloc_history %d -all_by_size",
	     getpid());
    fflush(stdout);
    system(command);
  }
#endif // __APPLE__

  ppdClose(ppd);

  return (status);
}


//
// 'do_ppd_tests()' - Test the default option commands in a PPD file.
//

static int				// O - Number of errors
do_ppd_tests(const char    *filename,	// I - PPD file
             int           num_options,	// I - Number of options
             cups_option_t *options)	// I - Options
{
  ppd_file_t		*ppd;		// PPD file data
  cups_page_header_t	header;		// Page header


  printf("\"%s\": ", filename);
  fflush(stdout);

  if ((ppd = ppdOpenFile(filename)) == NULL)
  {
    ppd_status_t	status;		// Status from PPD loader
    int			line;		// Line number containing error


    status = ppdLastError(&line);

    puts("FAIL (bad PPD file)");
    printf("    %s on line %d\n", ppdErrorString(status), line);

    return (1);
  }

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

  if (ppdRasterInterpretPPD(&header, ppd, 0, NULL, NULL))
  {
    puts("FAIL (error from function)");
    puts(_ppdRasterErrorString());

    return (1);
  }
  else
  {
    puts("PASS");

    return (0);
  }
}


//
// 'do_ps_tests()' - Test standard PostScript commands.
//

static int
do_ps_tests(void)
{
  cups_page_header_t	header;		// Page header
  int			preferred_bits;	// Preferred bits
  int			errors = 0;	// Number of errors


  //
  // Test PS exec code...
  //

  fputs("ppdRasterExecPS(\"setpagedevice\"): ", stdout);
  fflush(stdout);

  memset(&header, 0, sizeof(header));
  header.Collate = CUPS_TRUE;
  preferred_bits = 0;

  if (ppdRasterExecPS(&header, &preferred_bits, setpagedevice_code))
  {
    puts("FAIL (error from function)");
    puts(_ppdRasterErrorString());
    errors ++;
  }
  else if (preferred_bits != 17 ||
           memcmp(&header, &setpagedevice_header, sizeof(header)))
  {
    puts("FAIL (bad header)");

    if (preferred_bits != 17)
      printf("    cupsPreferredBitsPerColor %d, expected 17\n",
             preferred_bits);

    print_changes(&setpagedevice_header, &header);
    errors ++;
  }
  else
    puts("PASS");

  fputs("ppdRasterExecPS(\"roll\"): ", stdout);
  fflush(stdout);

  if (ppdRasterExecPS(&header, &preferred_bits,
                        "792 612 0 0 0\n"
			"pop pop pop\n"
			"<</PageSize[5 -2 roll]/ImagingBBox null>>"
			"setpagedevice\n"))
  {
    puts("FAIL (error from function)");
    puts(_ppdRasterErrorString());
    errors ++;
  }
  else if (header.PageSize[0] != 792 || header.PageSize[1] != 612)
  {
    printf("FAIL (PageSize [%d %d], expected [792 612])\n", header.PageSize[0],
           header.PageSize[1]);
    errors ++;
  }
  else
    puts("PASS");

  fputs("ppdRasterExecPS(\"dup index\"): ", stdout);
  fflush(stdout);

  if (ppdRasterExecPS(&header, &preferred_bits,
                        "true false dup\n"
			"<</Collate 4 index"
			"/Duplex 5 index"
			"/Tumble 6 index>>setpagedevice\n"
			"pop pop pop"))
  {
    puts("FAIL (error from function)");
    puts(_ppdRasterErrorString());
    errors ++;
  }
  else
  {
    if (!header.Collate)
    {
      printf("FAIL (Collate false, expected true)\n");
      errors ++;
    }

    if (header.Duplex)
    {
      printf("FAIL (Duplex true, expected false)\n");
      errors ++;
    }

    if (header.Tumble)
    {
      printf("FAIL (Tumble true, expected false)\n");
      errors ++;
    }

    if(header.Collate && !header.Duplex && !header.Tumble)
      puts("PASS");
  }

  fputs("ppdRasterExecPS(\"%%Begin/EndFeature code\"): ", stdout);
  fflush(stdout);

  if (ppdRasterExecPS(&header, &preferred_bits, dsc_code))
  {
    puts("FAIL (error from function)");
    puts(_ppdRasterErrorString());
    errors ++;
  }
  else if (header.PageSize[0] != 792 || header.PageSize[1] != 1224)
  {
    printf("FAIL (bad PageSize [%d %d], expected [792 1224])\n",
           header.PageSize[0], header.PageSize[1]);
    errors ++;
  }
  else
    puts("PASS");

  return (errors);
}


//
// 'print_changes()' - Print differences in the page header.
//

static void
print_changes(
    cups_page_header_t *header,	// I - Actual page header
    cups_page_header_t *expected)	// I - Expected page header
{
  int	i;				// Looping var


  if (strcmp(header->MediaClass, expected->MediaClass))
    printf("    MediaClass (%s), expected (%s)\n", header->MediaClass,
           expected->MediaClass);

  if (strcmp(header->MediaColor, expected->MediaColor))
    printf("    MediaColor (%s), expected (%s)\n", header->MediaColor,
           expected->MediaColor);

  if (strcmp(header->MediaType, expected->MediaType))
    printf("    MediaType (%s), expected (%s)\n", header->MediaType,
           expected->MediaType);

  if (strcmp(header->OutputType, expected->OutputType))
    printf("    OutputType (%s), expected (%s)\n", header->OutputType,
           expected->OutputType);

  if (header->AdvanceDistance != expected->AdvanceDistance)
    printf("    AdvanceDistance %d, expected %d\n", header->AdvanceDistance,
           expected->AdvanceDistance);

  if (header->AdvanceMedia != expected->AdvanceMedia)
    printf("    AdvanceMedia %d, expected %d\n", header->AdvanceMedia,
           expected->AdvanceMedia);

  if (header->Collate != expected->Collate)
    printf("    Collate %d, expected %d\n", header->Collate,
           expected->Collate);

  if (header->CutMedia != expected->CutMedia)
    printf("    CutMedia %d, expected %d\n", header->CutMedia,
           expected->CutMedia);

  if (header->Duplex != expected->Duplex)
    printf("    Duplex %d, expected %d\n", header->Duplex,
           expected->Duplex);

  if (header->HWResolution[0] != expected->HWResolution[0] ||
      header->HWResolution[1] != expected->HWResolution[1])
    printf("    HWResolution [%d %d], expected [%d %d]\n",
           header->HWResolution[0], header->HWResolution[1],
           expected->HWResolution[0], expected->HWResolution[1]);

  if (memcmp(header->ImagingBoundingBox, expected->ImagingBoundingBox,
             sizeof(header->ImagingBoundingBox)))
    printf("    ImagingBoundingBox [%d %d %d %d], expected [%d %d %d %d]\n",
           header->ImagingBoundingBox[0],
           header->ImagingBoundingBox[1],
           header->ImagingBoundingBox[2],
           header->ImagingBoundingBox[3],
           expected->ImagingBoundingBox[0],
           expected->ImagingBoundingBox[1],
           expected->ImagingBoundingBox[2],
           expected->ImagingBoundingBox[3]);

  if (header->InsertSheet != expected->InsertSheet)
    printf("    InsertSheet %d, expected %d\n", header->InsertSheet,
           expected->InsertSheet);

  if (header->Jog != expected->Jog)
    printf("    Jog %d, expected %d\n", header->Jog,
           expected->Jog);

  if (header->LeadingEdge != expected->LeadingEdge)
    printf("    LeadingEdge %d, expected %d\n", header->LeadingEdge,
           expected->LeadingEdge);

  if (header->Margins[0] != expected->Margins[0] ||
      header->Margins[1] != expected->Margins[1])
    printf("    Margins [%d %d], expected [%d %d]\n",
           header->Margins[0], header->Margins[1],
           expected->Margins[0], expected->Margins[1]);

  if (header->ManualFeed != expected->ManualFeed)
    printf("    ManualFeed %d, expected %d\n", header->ManualFeed,
           expected->ManualFeed);

  if (header->MediaPosition != expected->MediaPosition)
    printf("    MediaPosition %d, expected %d\n", header->MediaPosition,
           expected->MediaPosition);

  if (header->MediaWeight != expected->MediaWeight)
    printf("    MediaWeight %d, expected %d\n", header->MediaWeight,
           expected->MediaWeight);

  if (header->MirrorPrint != expected->MirrorPrint)
    printf("    MirrorPrint %d, expected %d\n", header->MirrorPrint,
           expected->MirrorPrint);

  if (header->NegativePrint != expected->NegativePrint)
    printf("    NegativePrint %d, expected %d\n", header->NegativePrint,
           expected->NegativePrint);

  if (header->NumCopies != expected->NumCopies)
    printf("    NumCopies %d, expected %d\n", header->NumCopies,
           expected->NumCopies);

  if (header->Orientation != expected->Orientation)
    printf("    Orientation %d, expected %d\n", header->Orientation,
           expected->Orientation);

  if (header->OutputFaceUp != expected->OutputFaceUp)
    printf("    OutputFaceUp %d, expected %d\n", header->OutputFaceUp,
           expected->OutputFaceUp);

  if (header->PageSize[0] != expected->PageSize[0] ||
      header->PageSize[1] != expected->PageSize[1])
    printf("    PageSize [%d %d], expected [%d %d]\n",
           header->PageSize[0], header->PageSize[1],
           expected->PageSize[0], expected->PageSize[1]);

  if (header->Separations != expected->Separations)
    printf("    Separations %d, expected %d\n", header->Separations,
           expected->Separations);

  if (header->TraySwitch != expected->TraySwitch)
    printf("    TraySwitch %d, expected %d\n", header->TraySwitch,
           expected->TraySwitch);

  if (header->Tumble != expected->Tumble)
    printf("    Tumble %d, expected %d\n", header->Tumble,
           expected->Tumble);

  if (header->cupsWidth != expected->cupsWidth)
    printf("    cupsWidth %d, expected %d\n", header->cupsWidth,
           expected->cupsWidth);

  if (header->cupsHeight != expected->cupsHeight)
    printf("    cupsHeight %d, expected %d\n", header->cupsHeight,
           expected->cupsHeight);

  if (header->cupsMediaType != expected->cupsMediaType)
    printf("    cupsMediaType %d, expected %d\n", header->cupsMediaType,
           expected->cupsMediaType);

  if (header->cupsBitsPerColor != expected->cupsBitsPerColor)
    printf("    cupsBitsPerColor %d, expected %d\n", header->cupsBitsPerColor,
           expected->cupsBitsPerColor);

  if (header->cupsBitsPerPixel != expected->cupsBitsPerPixel)
    printf("    cupsBitsPerPixel %d, expected %d\n", header->cupsBitsPerPixel,
           expected->cupsBitsPerPixel);

  if (header->cupsBytesPerLine != expected->cupsBytesPerLine)
    printf("    cupsBytesPerLine %d, expected %d\n", header->cupsBytesPerLine,
           expected->cupsBytesPerLine);

  if (header->cupsColorOrder != expected->cupsColorOrder)
    printf("    cupsColorOrder %d, expected %d\n", header->cupsColorOrder,
           expected->cupsColorOrder);

  if (header->cupsColorSpace != expected->cupsColorSpace)
    printf("    cupsColorSpace %s, expected %s\n",
	   _ppdRasterColorSpaceString(header->cupsColorSpace),
           _ppdRasterColorSpaceString(expected->cupsColorSpace));

  if (header->cupsCompression != expected->cupsCompression)
    printf("    cupsCompression %d, expected %d\n", header->cupsCompression,
           expected->cupsCompression);

  if (header->cupsRowCount != expected->cupsRowCount)
    printf("    cupsRowCount %d, expected %d\n", header->cupsRowCount,
           expected->cupsRowCount);

  if (header->cupsRowFeed != expected->cupsRowFeed)
    printf("    cupsRowFeed %d, expected %d\n", header->cupsRowFeed,
           expected->cupsRowFeed);

  if (header->cupsRowStep != expected->cupsRowStep)
    printf("    cupsRowStep %d, expected %d\n", header->cupsRowStep,
           expected->cupsRowStep);

  if (header->cupsNumColors != expected->cupsNumColors)
    printf("    cupsNumColors %d, expected %d\n", header->cupsNumColors,
           expected->cupsNumColors);

  if (fabs(header->cupsBorderlessScalingFactor -
	   expected->cupsBorderlessScalingFactor) > 0.001)
    printf("    cupsBorderlessScalingFactor %g, expected %g\n",
           header->cupsBorderlessScalingFactor,
           expected->cupsBorderlessScalingFactor);

  if (fabs(header->cupsPageSize[0] - expected->cupsPageSize[0]) > 0.001 ||
      fabs(header->cupsPageSize[1] - expected->cupsPageSize[1]) > 0.001)
    printf("    cupsPageSize [%g %g], expected [%g %g]\n",
           header->cupsPageSize[0], header->cupsPageSize[1],
           expected->cupsPageSize[0], expected->cupsPageSize[1]);

  if (fabs(header->cupsImagingBBox[0] - expected->cupsImagingBBox[0]) > 0.001 ||
      fabs(header->cupsImagingBBox[1] - expected->cupsImagingBBox[1]) > 0.001 ||
      fabs(header->cupsImagingBBox[2] - expected->cupsImagingBBox[2]) > 0.001 ||
      fabs(header->cupsImagingBBox[3] - expected->cupsImagingBBox[3]) > 0.001)
    printf("    cupsImagingBBox [%g %g %g %g], expected [%g %g %g %g]\n",
           header->cupsImagingBBox[0], header->cupsImagingBBox[1],
           header->cupsImagingBBox[2], header->cupsImagingBBox[3],
           expected->cupsImagingBBox[0], expected->cupsImagingBBox[1],
           expected->cupsImagingBBox[2], expected->cupsImagingBBox[3]);

  for (i = 0; i < 16; i ++)
    if (header->cupsInteger[i] != expected->cupsInteger[i])
      printf("    cupsInteger%d %d, expected %d\n", i, header->cupsInteger[i],
             expected->cupsInteger[i]);

  for (i = 0; i < 16; i ++)
    if (fabs(header->cupsReal[i] - expected->cupsReal[i]) > 0.001)
      printf("    cupsReal%d %g, expected %g\n", i, header->cupsReal[i],
             expected->cupsReal[i]);

  for (i = 0; i < 16; i ++)
    if (strcmp(header->cupsString[i], expected->cupsString[i]))
      printf("    cupsString%d (%s), expected (%s)\n", i,
	     header->cupsString[i], expected->cupsString[i]);

  if (strcmp(header->cupsMarkerType, expected->cupsMarkerType))
    printf("    cupsMarkerType (%s), expected (%s)\n", header->cupsMarkerType,
           expected->cupsMarkerType);

  if (strcmp(header->cupsRenderingIntent, expected->cupsRenderingIntent))
    printf("    cupsRenderingIntent (%s), expected (%s)\n",
           header->cupsRenderingIntent,
           expected->cupsRenderingIntent);

  if (strcmp(header->cupsPageSizeName, expected->cupsPageSizeName))
    printf("    cupsPageSizeName (%s), expected (%s)\n",
           header->cupsPageSizeName,
           expected->cupsPageSizeName);
}
