/*
 * PostScript filter for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright © 2007-2018 by Apple Inc.
 * Copyright © 1993-2007 by Easy Software Products.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include "common.h"
#include <limits.h>
#include <math.h>
#include <cups/file.h>
#include <cups/array.h>
#include <cups/language-private.h>
#include <signal.h>


/*
 * Constants...
 */

#define PSTOPS_BORDERNONE	0	/* No border or hairline border */
#define PSTOPS_BORDERTHICK	1	/* Think border */
#define PSTOPS_BORDERSINGLE	2	/* Single-line hairline border */
#define PSTOPS_BORDERSINGLE2	3	/* Single-line thick border */
#define PSTOPS_BORDERDOUBLE	4	/* Double-line hairline border */
#define PSTOPS_BORDERDOUBLE2	5	/* Double-line thick border */

#define PSTOPS_LAYOUT_LRBT	0	/* Left to right, bottom to top */
#define PSTOPS_LAYOUT_LRTB	1	/* Left to right, top to bottom */
#define PSTOPS_LAYOUT_RLBT	2	/* Right to left, bottom to top */
#define PSTOPS_LAYOUT_RLTB	3	/* Right to left, top to bottom */
#define PSTOPS_LAYOUT_BTLR	4	/* Bottom to top, left to right */
#define PSTOPS_LAYOUT_TBLR	5	/* Top to bottom, left to right */
#define PSTOPS_LAYOUT_BTRL	6	/* Bottom to top, right to left */
#define PSTOPS_LAYOUT_TBRL	7	/* Top to bottom, right to left */

#define PSTOPS_LAYOUT_NEGATEY	1	/* The bits for the layout */
#define PSTOPS_LAYOUT_NEGATEX	2	/* definitions above... */
#define PSTOPS_LAYOUT_VERTICAL	4


/*
 * Types...
 */

typedef struct				/**** Page information ****/
{
  char		*label;			/* Page label */
  int		bounding_box[4];	/* PageBoundingBox */
  off_t		offset;			/* Offset to start of page */
  ssize_t	length;			/* Number of bytes for page */
  int		num_options;		/* Number of options for this page */
  cups_option_t	*options;		/* Options for this page */
} pstops_page_t;

typedef struct				/**** Document information ****/
{
  int		page;			/* Current page */
  int		bounding_box[4];	/* BoundingBox from header */
  int		new_bounding_box[4];	/* New composite bounding box */
  int		num_options;		/* Number of document-wide options */
  cups_option_t	*options;		/* Document-wide options */
  int		normal_landscape,	/* Normal rotation for landscape? */
		saw_eof,		/* Saw the %%EOF comment? */
		slow_collate,		/* Collate copies by hand? */
		slow_duplex,		/* Duplex pages slowly? */
		slow_order,		/* Reverse pages slowly? */
		use_ESPshowpage;	/* Use ESPshowpage? */
  cups_array_t	*pages;			/* Pages in document */
  cups_file_t	*temp;			/* Temporary file, if any */
  char		tempfile[1024];		/* Temporary filename */
  int		job_id;			/* Job ID */
  const char	*user,			/* User name */
		*title;			/* Job name */
  int		copies;			/* Number of copies */
  const char	*ap_input_slot,		/* AP_FIRSTPAGE_InputSlot value */
		*ap_manual_feed,	/* AP_FIRSTPAGE_ManualFeed value */
		*ap_media_color,	/* AP_FIRSTPAGE_MediaColor value */
		*ap_media_type,		/* AP_FIRSTPAGE_MediaType value */
		*ap_page_region,	/* AP_FIRSTPAGE_PageRegion value */
		*ap_page_size;		/* AP_FIRSTPAGE_PageSize value */
  int		collate,		/* Collate copies? */
		emit_jcl,		/* Emit JCL commands? */
		fit_to_page;		/* Fit pages to media */
  const char	*input_slot,		/* InputSlot value */
		*manual_feed,		/* ManualFeed value */
		*media_color,		/* MediaColor value */
		*media_type,		/* MediaType value */
		*page_region,		/* PageRegion value */
		*page_size;		/* PageSize value */
  int		mirror,			/* doc->mirror/mirror pages */
		number_up,		/* Number of pages on each sheet */
		number_up_layout,	/* doc->number_up_layout of N-up pages */
		output_order,		/* Requested reverse output order? */
		page_border;		/* doc->page_border around pages */
  const char	*page_label,		/* page-label option, if any */
		*page_ranges,		/* page-ranges option, if any */
		*page_set;		/* page-set option, if any */
} pstops_doc_t;


/*
 * Convenience macros...
 */

#define	is_first_page(p)	(doc->number_up == 1 || \
				 ((p) % doc->number_up) == 1)
#define	is_last_page(p)		(doc->number_up == 1 || \
				 ((p) % doc->number_up) == 0)
#define is_not_last_page(p)	(doc->number_up > 1 && \
				 ((p) % doc->number_up) != 0)


/*
 * Local globals...
 */

static int		JobCanceled = 0;/* Set to 1 on SIGTERM */


/*
 * Local functions...
 */

static pstops_page_t	*add_page(pstops_doc_t *doc, const char *label);
static void		cancel_job(int sig);
static int		check_range(pstops_doc_t *doc, int page);
static void		copy_bytes(cups_file_t *fp, off_t offset,
			           size_t length);
static ssize_t		copy_comments(cups_file_t *fp, pstops_doc_t *doc,
			              ppd_file_t *ppd, char *line,
				      ssize_t linelen, size_t linesize);
static void		copy_dsc(cups_file_t *fp, pstops_doc_t *doc,
			         ppd_file_t *ppd, char *line, ssize_t linelen,
				 size_t linesize);
static void		copy_non_dsc(cups_file_t *fp, pstops_doc_t *doc,
			             ppd_file_t *ppd, char *line,
				     ssize_t linelen, size_t linesize);
static ssize_t		copy_page(cups_file_t *fp, pstops_doc_t *doc,
			          ppd_file_t *ppd, int number, char *line,
				  ssize_t linelen, size_t linesize);
static ssize_t		copy_prolog(cups_file_t *fp, pstops_doc_t *doc,
			            ppd_file_t *ppd, char *line,
				    ssize_t linelen, size_t linesize);
static ssize_t		copy_setup(cups_file_t *fp, pstops_doc_t *doc,
			           ppd_file_t *ppd, char *line,
				   ssize_t linelen, size_t linesize);
static ssize_t		copy_trailer(cups_file_t *fp, pstops_doc_t *doc,
			             ppd_file_t *ppd, int number, char *line,
				     ssize_t linelen, size_t linesize);
static void		do_prolog(pstops_doc_t *doc, ppd_file_t *ppd);
static void 		do_setup(pstops_doc_t *doc, ppd_file_t *ppd);
static void		doc_printf(pstops_doc_t *doc, const char *format, ...) _CUPS_FORMAT(2, 3);
static void		doc_puts(pstops_doc_t *doc, const char *s);
static void		doc_write(pstops_doc_t *doc, const char *s, size_t len);
static void		end_nup(pstops_doc_t *doc, int number);
static int		include_feature(ppd_file_t *ppd, const char *line,
			                int num_options,
					cups_option_t **options);
static char		*parse_text(const char *start, char **end, char *buffer,
			            size_t bufsize);
static void		set_pstops_options(pstops_doc_t *doc, ppd_file_t *ppd,
			                   char *argv[], int num_options,
			                   cups_option_t *options);
static ssize_t		skip_page(cups_file_t *fp, char *line, ssize_t linelen,
				  size_t linesize);
static void		start_nup(pstops_doc_t *doc, int number,
				  int show_border, const int *bounding_box);
static void		write_label_prolog(pstops_doc_t *doc, const char *label,
			                   float bottom, float top,
					   float width);
static void		write_labels(pstops_doc_t *doc, int orient);
static void		write_options(pstops_doc_t  *doc, ppd_file_t *ppd,
			              int num_options, cups_option_t *options);


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

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  pstops_doc_t	doc;			/* Document information */
  cups_file_t	*fp;			/* Print file */
  ppd_file_t	*ppd;			/* PPD file */
  int		num_options;		/* Number of print options */
  cups_option_t	*options;		/* Print options */
  char		line[8192];		/* Line buffer */
  ssize_t	len;			/* Length of line buffer */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
  struct sigaction action;		/* Actions for POSIX signals */
#endif /* HAVE_SIGACTION && !HAVE_SIGSET */


 /*
  * Make sure status messages are not buffered...
  */

  setbuf(stderr, NULL);

 /*
  * Ignore broken pipe signals...
  */

  signal(SIGPIPE, SIG_IGN);

 /*
  * Check command-line...
  */

  if (argc != 6 && argc != 7)
  {
    _cupsLangPrintf(stderr,
                    _("Usage: %s job-id user title copies options [file]"),
                    argv[0]);
    return (1);
  }

 /*
  * Register a signal handler to cleanly cancel a job.
  */

#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
  sigset(SIGTERM, cancel_job);
#elif defined(HAVE_SIGACTION)
  memset(&action, 0, sizeof(action));

  sigemptyset(&action.sa_mask);
  action.sa_handler = cancel_job;
  sigaction(SIGTERM, &action, NULL);
#else
  signal(SIGTERM, cancel_job);
#endif /* HAVE_SIGSET */

 /*
  * If we have 7 arguments, print the file named on the command-line.
  * Otherwise, send stdin instead...
  */

  if (argc == 6)
    fp = cupsFileStdin();
  else
  {
   /*
    * Try to open the print file...
    */

    if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
    {
      if (!JobCanceled)
      {
        fprintf(stderr, "DEBUG: Unable to open \"%s\".\n", argv[6]);
        _cupsLangPrintError("ERROR", _("Unable to open print file"));
      }

      return (1);
    }
  }

 /*
  * Read the first line to see if we have DSC comments...
  */

  if ((len = (ssize_t)cupsFileGetLine(fp, line, sizeof(line))) == 0)
  {
    fputs("DEBUG: The print file is empty.\n", stderr);
    return (1);
  }

 /*
  * Process command-line options...
  */

  options     = NULL;
  num_options = cupsParseOptions(argv[5], 0, &options);
  ppd         = SetCommonOptions(num_options, options, 1);

  set_pstops_options(&doc, ppd, argv, num_options, options);

 /*
  * Write any "exit server" options that have been selected...
  */

  ppdEmit(ppd, stdout, PPD_ORDER_EXIT);

 /*
  * Write any JCL commands that are needed to print PostScript code...
  */

  if (doc.emit_jcl)
    ppdEmitJCL(ppd, stdout, doc.job_id, doc.user, doc.title);

 /*
  * Start with a DSC header...
  */

  puts("%!PS-Adobe-3.0");

 /*
  * Skip leading PJL in the document...
  */

  while (!strncmp(line, "\033%-12345X", 9) || !strncmp(line, "@PJL ", 5))
  {
   /*
    * Yup, we have leading PJL fun, so skip it until we hit the line
    * with "ENTER LANGUAGE"...
    */

    fputs("DEBUG: Skipping PJL header...\n", stderr);

    while (strstr(line, "ENTER LANGUAGE") == NULL && strncmp(line, "%!", 2))
      if ((len = (ssize_t)cupsFileGetLine(fp, line, sizeof(line))) == 0)
        break;

    if (!strncmp(line, "%!", 2))
      break;

    if ((len = (ssize_t)cupsFileGetLine(fp, line, sizeof(line))) == 0)
      break;
  }

 /*
  * Now see if the document conforms to the Adobe Document Structuring
  * Conventions...
  */

  if (!strncmp(line, "%!PS-Adobe-", 11))
  {
   /*
    * Yes, filter the document...
    */

    copy_dsc(fp, &doc, ppd, line, len, sizeof(line));
  }
  else
  {
   /*
    * No, display an error message and treat the file as if it contains
    * a single page...
    */

    copy_non_dsc(fp, &doc, ppd, line, len, sizeof(line));
  }

 /*
  * Send %%EOF as needed...
  */

  if (!doc.saw_eof)
    puts("%%EOF");

 /*
  * End the job with the appropriate JCL command or CTRL-D...
  */

  if (doc.emit_jcl)
  {
    if (ppd && ppd->jcl_end)
      ppdEmitJCLEnd(ppd, stdout);
    else
      putchar(0x04);
  }

 /*
  * Close files and remove the temporary file if needed...
  */

  if (doc.temp)
  {
    cupsFileClose(doc.temp);
    unlink(doc.tempfile);
  }

  ppdClose(ppd);
  cupsFreeOptions(num_options, options);

  cupsFileClose(fp);

  return (0);
}


/*
 * 'add_page()' - Add a page to the pages array.
 */

static pstops_page_t *			/* O - New page info object */
add_page(pstops_doc_t *doc,		/* I - Document information */
         const char   *label)		/* I - Page label */
{
  pstops_page_t	*pageinfo;		/* New page info object */


  if (!doc->pages)
    doc->pages = cupsArrayNew(NULL, NULL);

  if (!doc->pages)
  {
    _cupsLangPrintError("EMERG", _("Unable to allocate memory for pages array"));
    exit(1);
  }

  if ((pageinfo = calloc(1, sizeof(pstops_page_t))) == NULL)
  {
    _cupsLangPrintError("EMERG", _("Unable to allocate memory for page info"));
    exit(1);
  }

  pageinfo->label  = strdup(label);
  pageinfo->offset = cupsFileTell(doc->temp);

  cupsArrayAdd(doc->pages, pageinfo);

  doc->page ++;

  return (pageinfo);
}


/*
 * 'cancel_job()' - Flag the job as canceled.
 */

static void
cancel_job(int sig)			/* I - Signal number (unused) */
{
  (void)sig;

  JobCanceled = 1;
}


/*
 * 'check_range()' - Check to see if the current page is selected for
 *                   printing.
 */

static int				/* O - 1 if selected, 0 otherwise */
check_range(pstops_doc_t *doc,		/* I - Document information */
            int          page)		/* I - Page number */
{
  const char	*range;			/* Pointer into range string */
  int		lower, upper;		/* Lower and upper page numbers */


  if (doc->page_set)
  {
   /*
    * See if we only print even or odd pages...
    */

    if (!_cups_strcasecmp(doc->page_set, "even") && (page & 1))
      return (0);

    if (!_cups_strcasecmp(doc->page_set, "odd") && !(page & 1))
      return (0);
  }

  if (!doc->page_ranges)
    return (1);				/* No range, print all pages... */

  for (range = doc->page_ranges; *range != '\0';)
  {
    if (*range == '-')
    {
      lower = 1;
      range ++;
      upper = (int)strtol(range, (char **)&range, 10);
    }
    else
    {
      lower = (int)strtol(range, (char **)&range, 10);

      if (*range == '-')
      {
        range ++;
	if (!isdigit(*range & 255))
	  upper = 65535;
	else
	  upper = (int)strtol(range, (char **)&range, 10);
      }
      else
        upper = lower;
    }

    if (page >= lower && page <= upper)
      return (1);

    if (*range == ',')
      range ++;
    else
      break;
  }

  return (0);
}


/*
 * 'copy_bytes()' - Copy bytes from the input file to stdout.
 */

static void
copy_bytes(cups_file_t *fp,		/* I - File to read from */
           off_t       offset,		/* I - Offset to page data */
           size_t      length)		/* I - Length of page data */
{
  char		buffer[8192];		/* Data buffer */
  ssize_t	nbytes;			/* Number of bytes read */
  size_t	nleft;			/* Number of bytes left/remaining */


  nleft = length;

  if (cupsFileSeek(fp, offset) < 0)
  {
    _cupsLangPrintError("ERROR", _("Unable to see in file"));
    return;
  }

  while (nleft > 0 || length == 0)
  {
    if (nleft > sizeof(buffer) || length == 0)
      nbytes = sizeof(buffer);
    else
      nbytes = (ssize_t)nleft;

    if ((nbytes = cupsFileRead(fp, buffer, (size_t)nbytes)) < 1)
      return;

    nleft -= (size_t)nbytes;

    fwrite(buffer, 1, (size_t)nbytes, stdout);
  }
}


/*
 * 'copy_comments()' - Copy all of the comments section.
 *
 * This function expects "line" to be filled with a comment line.
 * On return, "line" will contain the next line in the file, if any.
 */

static ssize_t				/* O - Length of next line */
copy_comments(cups_file_t  *fp,		/* I - File to read from */
              pstops_doc_t *doc,	/* I - Document info */
	      ppd_file_t   *ppd,	/* I - PPD file */
              char         *line,	/* I - Line buffer */
	      ssize_t      linelen,	/* I - Length of initial line */
	      size_t       linesize)	/* I - Size of line buffer */
{
  int	saw_bounding_box,		/* Saw %%BoundingBox: comment? */
	saw_for,			/* Saw %%For: comment? */
	saw_pages,			/* Saw %%Pages: comment? */
	saw_title;			/* Saw %%Title: comment? */


 /*
  * Loop until we see %%EndComments or a non-comment line...
  */

  saw_bounding_box = 0;
  saw_for          = 0;
  saw_pages        = 0;
  saw_title        = 0;

  while (line[0] == '%')
  {
   /*
    * Strip trailing whitespace...
    */

    while (linelen > 0)
    {
      linelen --;

      if (!isspace(line[linelen] & 255))
        break;
      else
        line[linelen] = '\0';
    }

   /*
    * Log the header...
    */

    fprintf(stderr, "DEBUG: %s\n", line);

   /*
    * Pull the headers out...
    */

    if (!strncmp(line, "%%Pages:", 8))
    {
      int	pages;			/* Number of pages */

      if (saw_pages)
	fputs("DEBUG: A duplicate %%Pages: comment was seen.\n", stderr);

      saw_pages = 1;

      if (Duplex && (pages = atoi(line + 8)) > 0 && pages <= doc->number_up)
      {
       /*
        * Since we will only be printing on a single page, disable duplexing.
	*/

	Duplex           = 0;
	doc->slow_duplex = 0;

	if (cupsGetOption("sides", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("sides", "one-sided",
	                                   doc->num_options, &(doc->options));

	if (cupsGetOption("Duplex", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("Duplex", "None",
	                                   doc->num_options, &(doc->options));

	if (cupsGetOption("EFDuplex", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("EFDuplex", "None",
	                                   doc->num_options, &(doc->options));

	if (cupsGetOption("EFDuplexing", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("EFDuplexing", "False",
	                                   doc->num_options, &(doc->options));

	if (cupsGetOption("KD03Duplex", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("KD03Duplex", "None",
	                                   doc->num_options, &(doc->options));

	if (cupsGetOption("JCLDuplex", doc->num_options, doc->options))
	  doc->num_options = cupsAddOption("JCLDuplex", "None",
	                                   doc->num_options, &(doc->options));

	ppdMarkOption(ppd, "Duplex", "None");
	ppdMarkOption(ppd, "EFDuplex", "None");
	ppdMarkOption(ppd, "EFDuplexing", "False");
	ppdMarkOption(ppd, "KD03Duplex", "None");
	ppdMarkOption(ppd, "JCLDuplex", "None");
      }
    }
    else if (!strncmp(line, "%%BoundingBox:", 14))
    {
      if (saw_bounding_box)
	fputs("DEBUG: A duplicate %%BoundingBox: comment was seen.\n", stderr);
      else if (strstr(line + 14, "(atend)"))
      {
       /*
        * Do nothing for now but use the default imageable area...
	*/
      }
      else if (sscanf(line + 14, "%d%d%d%d", doc->bounding_box + 0,
	              doc->bounding_box + 1, doc->bounding_box + 2,
		      doc->bounding_box + 3) != 4)
      {
	fputs("DEBUG: A bad %%BoundingBox: comment was seen.\n", stderr);

	doc->bounding_box[0] = (int)PageLeft;
	doc->bounding_box[1] = (int)PageBottom;
	doc->bounding_box[2] = (int)PageRight;
	doc->bounding_box[3] = (int)PageTop;
      }

      saw_bounding_box = 1;
    }
    else if (!strncmp(line, "%%For:", 6))
    {
      saw_for = 1;
      doc_printf(doc, "%s\n", line);
    }
    else if (!strncmp(line, "%%Title:", 8))
    {
      saw_title = 1;
      doc_printf(doc, "%s\n", line);
    }
    else if (!strncmp(line, "%cupsRotation:", 14))
    {
     /*
      * Reset orientation of document?
      */

      int orient = (atoi(line + 14) / 90) & 3;

      if (orient != Orientation)
      {
       /*
        * Yes, update things so that the pages come out right...
	*/

	Orientation = (4 - Orientation + orient) & 3;
	UpdatePageVars();
	Orientation = orient;
      }
    }
    else if (!strcmp(line, "%%EndComments"))
    {
      linelen = (ssize_t)cupsFileGetLine(fp, line, linesize);
      break;
    }
    else if (strncmp(line, "%!", 2) && strncmp(line, "%cups", 5))
      doc_printf(doc, "%s\n", line);

    if ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) == 0)
      break;
  }

  if (!saw_bounding_box)
    fputs("DEBUG: There wasn't a %%BoundingBox: comment in the header.\n",
          stderr);

  if (!saw_pages)
    fputs("DEBUG: There wasn't a %%Pages: comment in the header.\n", stderr);

  if (!saw_for)
    WriteTextComment("For", doc->user);

  if (!saw_title)
    WriteTextComment("Title", doc->title);

  if (doc->copies != 1 && (!doc->collate || !doc->slow_collate))
  {
   /*
    * Tell the document processor the copy and duplex options
    * that are required...
    */

    doc_printf(doc, "%%%%Requirements: numcopies(%d)%s%s\n", doc->copies,
               doc->collate ? " collate" : "",
	       Duplex ? " duplex" : "");

   /*
    * Apple uses RBI comments for various non-PPD options...
    */

    doc_printf(doc, "%%RBINumCopies: %d\n", doc->copies);
  }
  else
  {
   /*
    * Tell the document processor the duplex option that is required...
    */

    if (Duplex)
      doc_puts(doc, "%%Requirements: duplex\n");

   /*
    * Apple uses RBI comments for various non-PPD options...
    */

    doc_puts(doc, "%RBINumCopies: 1\n");
  }

  doc_puts(doc, "%%Pages: (atend)\n");
  doc_puts(doc, "%%BoundingBox: (atend)\n");
  doc_puts(doc, "%%EndComments\n");

  return (linelen);
}


/*
 * 'copy_dsc()' - Copy a DSC-conforming document.
 *
 * This function expects "line" to be filled with the %!PS-Adobe comment line.
 */

static void
copy_dsc(cups_file_t  *fp,		/* I - File to read from */
         pstops_doc_t *doc,		/* I - Document info */
         ppd_file_t   *ppd,		/* I - PPD file */
	 char         *line,		/* I - Line buffer */
	 ssize_t      linelen,		/* I - Length of initial line */
	 size_t       linesize)		/* I - Size of line buffer */
{
  int		number;			/* Page number */
  pstops_page_t	*pageinfo;		/* Page information */


 /*
  * Make sure we use ESPshowpage for EPS files...
  */

  if (strstr(line, "EPSF"))
  {
    doc->use_ESPshowpage = 1;
    doc->number_up       = 1;
  }

 /*
  * Start sending the document with any commands needed...
  */

  fprintf(stderr, "DEBUG: Before copy_comments - %s", line);
  linelen = copy_comments(fp, doc, ppd, line, linelen, linesize);

 /*
  * Now find the prolog section, if any...
  */

  fprintf(stderr, "DEBUG: Before copy_prolog - %s", line);
  linelen = copy_prolog(fp, doc, ppd, line, linelen, linesize);

 /*
  * Then the document setup section...
  */

  fprintf(stderr, "DEBUG: Before copy_setup - %s", line);
  linelen = copy_setup(fp, doc, ppd, line, linelen, linesize);

 /*
  * Copy until we see %%Page:...
  */

  while (strncmp(line, "%%Page:", 7) && strncmp(line, "%%Trailer", 9))
  {
    doc_write(doc, line, (size_t)linelen);

    if ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) == 0)
      break;
  }

 /*
  * Then process pages until we have no more...
  */

  number = 0;

  fprintf(stderr, "DEBUG: Before page loop - %s", line);
  while (!strncmp(line, "%%Page:", 7))
  {
    if (JobCanceled)
      break;

    number ++;

    if (check_range(doc, (number - 1) / doc->number_up + 1))
    {
      fprintf(stderr, "DEBUG: Copying page %d...\n", number);
      linelen = copy_page(fp, doc, ppd, number, line, linelen, linesize);
    }
    else
    {
      fprintf(stderr, "DEBUG: Skipping page %d...\n", number);
      linelen = skip_page(fp, line, linelen, linesize);
    }
  }

 /*
  * Finish up the last page(s)...
  */

  if (number && is_not_last_page(number) && cupsArrayLast(doc->pages) &&
      check_range(doc, (number - 1) / doc->number_up + 1))
  {
    pageinfo = (pstops_page_t *)cupsArrayLast(doc->pages);

    start_nup(doc, doc->number_up, 0, doc->bounding_box);
    doc_puts(doc, "showpage\n");
    end_nup(doc, doc->number_up);

    pageinfo->length = (ssize_t)(cupsFileTell(doc->temp) - pageinfo->offset);
  }

  if (doc->slow_duplex && (doc->page & 1))
  {
   /*
    * Make sure we have an even number of pages...
    */

    pageinfo = add_page(doc, "(filler)");

    if (!doc->slow_order)
    {
      if (!ppd || !ppd->num_filters)
	fprintf(stderr, "PAGE: %d %d\n", doc->page,
        	doc->slow_collate ? 1 : doc->copies);

      printf("%%%%Page: (filler) %d\n", doc->page);
    }

    start_nup(doc, doc->number_up, 0, doc->bounding_box);
    doc_puts(doc, "showpage\n");
    end_nup(doc, doc->number_up);

    pageinfo->length = (ssize_t)(cupsFileTell(doc->temp) - pageinfo->offset);
  }

 /*
  * Make additional copies as necessary...
  */

  number = doc->slow_order ? 0 : doc->page;

  if (doc->temp && !JobCanceled && cupsArrayCount(doc->pages) > 0)
  {
    int	copy;				/* Current copy */


   /*
    * Reopen the temporary file for reading...
    */

    cupsFileClose(doc->temp);

    doc->temp = cupsFileOpen(doc->tempfile, "r");

   /*
    * Make the copies...
    */

    if (doc->slow_collate)
      copy = !doc->slow_order;
    else
      copy = doc->copies - 1;

    for (; copy < doc->copies; copy ++)
    {
      if (JobCanceled)
	break;

     /*
      * Send end-of-job stuff followed by any start-of-job stuff required
      * for the JCL options...
      */

      if (number && doc->emit_jcl && ppd && ppd->jcl_end)
      {
       /*
        * Send the trailer...
	*/

        puts("%%Trailer");
	printf("%%%%Pages: %d\n", cupsArrayCount(doc->pages));
	if (doc->number_up > 1 || doc->fit_to_page)
	  printf("%%%%BoundingBox: %.0f %.0f %.0f %.0f\n",
		 PageLeft, PageBottom, PageRight, PageTop);
	else
	  printf("%%%%BoundingBox: %d %d %d %d\n",
		 doc->new_bounding_box[0], doc->new_bounding_box[1],
		 doc->new_bounding_box[2], doc->new_bounding_box[3]);
        puts("%%EOF");

       /*
        * Start a new document...
	*/

        ppdEmitJCLEnd(ppd, stdout);
        ppdEmitJCL(ppd, stdout, doc->job_id, doc->user, doc->title);

	puts("%!PS-Adobe-3.0");

	number = 0;
      }

     /*
      * Copy the prolog as needed...
      */

      if (!number)
      {
        pageinfo = (pstops_page_t *)cupsArrayFirst(doc->pages);
	copy_bytes(doc->temp, 0, (size_t)pageinfo->offset);
      }

     /*
      * Then copy all of the pages...
      */

      pageinfo = doc->slow_order ? (pstops_page_t *)cupsArrayLast(doc->pages) :
                                   (pstops_page_t *)cupsArrayFirst(doc->pages);

      while (pageinfo)
      {
        if (JobCanceled)
	  break;

        number ++;

	if (!ppd || !ppd->num_filters)
	  fprintf(stderr, "PAGE: %d %d\n", number,
	          doc->slow_collate ? 1 : doc->copies);

	if (doc->number_up > 1)
	{
	  printf("%%%%Page: (%d) %d\n", number, number);
	  printf("%%%%PageBoundingBox: %.0f %.0f %.0f %.0f\n",
		 PageLeft, PageBottom, PageRight, PageTop);
	}
	else
	{
          printf("%%%%Page: %s %d\n", pageinfo->label, number);
	  printf("%%%%PageBoundingBox: %d %d %d %d\n",
		 pageinfo->bounding_box[0], pageinfo->bounding_box[1],
		 pageinfo->bounding_box[2], pageinfo->bounding_box[3]);
	}

	copy_bytes(doc->temp, pageinfo->offset, (size_t)pageinfo->length);

	pageinfo = doc->slow_order ? (pstops_page_t *)cupsArrayPrev(doc->pages) :
                                     (pstops_page_t *)cupsArrayNext(doc->pages);
      }
    }
  }

 /*
  * Restore the old showpage operator as needed...
  */

  if (doc->use_ESPshowpage)
    puts("userdict/showpage/ESPshowpage load put\n");

 /*
  * Write/copy the trailer...
  */

  if (!JobCanceled)
    copy_trailer(fp, doc, ppd, number, line, linelen, linesize);
}


/*
 * 'copy_non_dsc()' - Copy a document that does not conform to the DSC.
 *
 * This function expects "line" to be filled with the %! comment line.
 */

static void
copy_non_dsc(cups_file_t  *fp,		/* I - File to read from */
             pstops_doc_t *doc,		/* I - Document info */
             ppd_file_t   *ppd,		/* I - PPD file */
	     char         *line,	/* I - Line buffer */
	     ssize_t      linelen,	/* I - Length of initial line */
	     size_t       linesize)	/* I - Size of line buffer */
{
  int		copy;			/* Current copy */
  char		buffer[8192];		/* Copy buffer */
  ssize_t	bytes;			/* Number of bytes copied */


  (void)linesize;

 /*
  * First let the user know that they are attempting to print a file
  * that may not print correctly...
  */

  fputs("DEBUG: This document does not conform to the Adobe Document "
        "Structuring Conventions and may not print correctly.\n", stderr);

 /*
  * Then write a standard DSC comment section...
  */

  printf("%%%%BoundingBox: %.0f %.0f %.0f %.0f\n", PageLeft, PageBottom,
         PageRight, PageTop);

  if (doc->slow_collate && doc->copies > 1)
    printf("%%%%Pages: %d\n", doc->copies);
  else
    puts("%%Pages: 1");

  WriteTextComment("For", doc->user);
  WriteTextComment("Title", doc->title);

  if (doc->copies != 1 && (!doc->collate || !doc->slow_collate))
  {
   /*
    * Tell the document processor the copy and duplex options
    * that are required...
    */

    printf("%%%%Requirements: numcopies(%d)%s%s\n", doc->copies,
           doc->collate ? " collate" : "",
	   Duplex ? " duplex" : "");

   /*
    * Apple uses RBI comments for various non-PPD options...
    */

    printf("%%RBINumCopies: %d\n", doc->copies);
  }
  else
  {
   /*
    * Tell the document processor the duplex option that is required...
    */

    if (Duplex)
      puts("%%Requirements: duplex");

   /*
    * Apple uses RBI comments for various non-PPD options...
    */

    puts("%RBINumCopies: 1");
  }

  puts("%%EndComments");

 /*
  * Then the prolog...
  */

  puts("%%BeginProlog");

  do_prolog(doc, ppd);

  puts("%%EndProlog");

 /*
  * Then the setup section...
  */

  puts("%%BeginSetup");

  do_setup(doc, ppd);

  puts("%%EndSetup");

 /*
  * Finally, embed a copy of the file inside a %%Page...
  */

  if (!ppd || !ppd->num_filters)
    fprintf(stderr, "PAGE: 1 %d\n", doc->temp ? 1 : doc->copies);

  puts("%%Page: 1 1");
  puts("%%BeginPageSetup");
  ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
  puts("%%EndPageSetup");
  puts("%%BeginDocument: nondsc");

  fwrite(line, (size_t)linelen, 1, stdout);

  if (doc->temp)
    cupsFileWrite(doc->temp, line, (size_t)linelen);

  while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
  {
    fwrite(buffer, 1, (size_t)bytes, stdout);

    if (doc->temp)
      cupsFileWrite(doc->temp, buffer, (size_t)bytes);
  }

  puts("%%EndDocument");

  if (doc->use_ESPshowpage)
  {
    WriteLabels(Orientation);
    puts("ESPshowpage");
  }

  if (doc->temp && !JobCanceled)
  {
   /*
    * Reopen the temporary file for reading...
    */

    cupsFileClose(doc->temp);

    doc->temp = cupsFileOpen(doc->tempfile, "r");

   /*
    * Make the additional copies as needed...
    */

    for (copy = 1; copy < doc->copies; copy ++)
    {
      if (JobCanceled)
	break;

      if (!ppd || !ppd->num_filters)
	fputs("PAGE: 1 1\n", stderr);

      printf("%%%%Page: %d %d\n", copy + 1, copy + 1);
      puts("%%BeginPageSetup");
      ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
      puts("%%EndPageSetup");
      puts("%%BeginDocument: nondsc");

      copy_bytes(doc->temp, 0, 0);

      puts("%%EndDocument");

      if (doc->use_ESPshowpage)
      {
	WriteLabels(Orientation);
        puts("ESPshowpage");
      }
    }
  }

 /*
  * Restore the old showpage operator as needed...
  */

  if (doc->use_ESPshowpage)
    puts("userdict/showpage/ESPshowpage load put\n");
}


/*
 * 'copy_page()' - Copy a page description.
 *
 * This function expects "line" to be filled with a %%Page comment line.
 * On return, "line" will contain the next line in the file, if any.
 */

static ssize_t				/* O - Length of next line */
copy_page(cups_file_t  *fp,		/* I - File to read from */
          pstops_doc_t *doc,		/* I - Document info */
          ppd_file_t   *ppd,		/* I - PPD file */
	  int          number,		/* I - Current page number */
	  char         *line,		/* I - Line buffer */
	  ssize_t      linelen,		/* I - Length of initial line */
	  size_t       linesize)	/* I - Size of line buffer */
{
  char		label[256],		/* Page label string */
		*ptr;			/* Pointer into line */
  int		level;			/* Embedded document level */
  pstops_page_t	*pageinfo;		/* Page information */
  int		first_page;		/* First page on N-up output? */
  int		has_page_setup = 0;	/* Does the page have %%Begin/EndPageSetup? */
  int		bounding_box[4];	/* PageBoundingBox */


 /*
  * Get the page label for this page...
  */

  first_page = is_first_page(number);

  if (!parse_text(line + 7, &ptr, label, sizeof(label)))
  {
    fputs("DEBUG: There was a bad %%Page: comment in the file.\n", stderr);
    label[0] = '\0';
    number   = doc->page;
  }
  else if (strtol(ptr, &ptr, 10) == LONG_MAX || !isspace(*ptr & 255))
  {
    fputs("DEBUG: There was a bad %%Page: comment in the file.\n", stderr);
    number = doc->page;
  }

 /*
  * Create or update the current output page...
  */

  if (first_page)
    pageinfo = add_page(doc, label);
  else
    pageinfo = (pstops_page_t *)cupsArrayLast(doc->pages);

 /*
  * Handle first page override...
  */

  if (doc->ap_input_slot || doc->ap_manual_feed)
  {
    if ((doc->page == 1 && (!doc->slow_order || !Duplex)) ||
        (doc->page == 2 && doc->slow_order && Duplex))
    {
     /*
      * First page/sheet gets AP_FIRSTPAGE_* options...
      */

      pageinfo->num_options = cupsAddOption("InputSlot", doc->ap_input_slot,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("ManualFeed",
                                            doc->ap_input_slot ? "False" :
					        doc->ap_manual_feed,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("MediaColor", doc->ap_media_color,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("MediaType", doc->ap_media_type,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("PageRegion", doc->ap_page_region,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("PageSize", doc->ap_page_size,
                                            pageinfo->num_options,
					    &(pageinfo->options));
    }
    else if (doc->page == (Duplex + 2))
    {
     /*
      * Second page/sheet gets default options...
      */

      pageinfo->num_options = cupsAddOption("InputSlot", doc->input_slot,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("ManualFeed",
                                            doc->input_slot ? "False" :
					        doc->manual_feed,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("MediaColor", doc->media_color,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("MediaType", doc->media_type,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("PageRegion", doc->page_region,
                                            pageinfo->num_options,
					    &(pageinfo->options));
      pageinfo->num_options = cupsAddOption("PageSize", doc->page_size,
                                            pageinfo->num_options,
					    &(pageinfo->options));
    }
  }

 /*
  * Scan comments until we see something other than %%Page*: or
  * %%Include*...
  */

  memcpy(bounding_box, doc->bounding_box, sizeof(bounding_box));

  while ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) > 0)
  {
    if (!strncmp(line, "%%PageBoundingBox:", 18))
    {
     /*
      * %%PageBoundingBox: llx lly urx ury
      */

      if (sscanf(line + 18, "%d%d%d%d", bounding_box + 0,
                 bounding_box + 1, bounding_box + 2,
		 bounding_box + 3) != 4)
      {
	fputs("DEBUG: There was a bad %%PageBoundingBox: comment in the file.\n", stderr);
        memcpy(bounding_box, doc->bounding_box,
	       sizeof(bounding_box));
      }
      else if (doc->number_up == 1 && !doc->fit_to_page  && Orientation)
      {
        int	temp_bbox[4];		/* Temporary bounding box */


        memcpy(temp_bbox, bounding_box, sizeof(temp_bbox));

        fprintf(stderr, "DEBUG: Orientation = %d\n", Orientation);
        fprintf(stderr, "DEBUG: original bounding_box = [ %d %d %d %d ]\n",
		bounding_box[0], bounding_box[1],
		bounding_box[2], bounding_box[3]);
        fprintf(stderr, "DEBUG: PageWidth = %.1f, PageLength = %.1f\n",
	        PageWidth, PageLength);

        switch (Orientation)
	{
	  case 1 : /* Landscape */
	      bounding_box[0] = (int)(PageLength - temp_bbox[3]);
	      bounding_box[1] = temp_bbox[0];
	      bounding_box[2] = (int)(PageLength - temp_bbox[1]);
	      bounding_box[3] = temp_bbox[2];
              break;

	  case 2 : /* Reverse Portrait */
	      bounding_box[0] = (int)(PageWidth - temp_bbox[2]);
	      bounding_box[1] = (int)(PageLength - temp_bbox[3]);
	      bounding_box[2] = (int)(PageWidth - temp_bbox[0]);
	      bounding_box[3] = (int)(PageLength - temp_bbox[1]);
              break;

	  case 3 : /* Reverse Landscape */
	      bounding_box[0] = temp_bbox[1];
	      bounding_box[1] = (int)(PageWidth - temp_bbox[2]);
	      bounding_box[2] = temp_bbox[3];
	      bounding_box[3] = (int)(PageWidth - temp_bbox[0]);
              break;
	}

        fprintf(stderr, "DEBUG: updated bounding_box = [ %d %d %d %d ]\n",
		bounding_box[0], bounding_box[1],
		bounding_box[2], bounding_box[3]);
      }
    }
#if 0
    else if (!strncmp(line, "%%PageCustomColors:", 19) ||
             !strncmp(line, "%%PageMedia:", 12) ||
	     !strncmp(line, "%%PageOrientation:", 18) ||
	     !strncmp(line, "%%PageProcessColors:", 20) ||
	     !strncmp(line, "%%PageRequirements:", 19) ||
	     !strncmp(line, "%%PageResources:", 16))
    {
     /*
      * Copy literal...
      */
    }
#endif /* 0 */
    else if (!strncmp(line, "%%PageCustomColors:", 19))
    {
     /*
      * %%PageCustomColors: ...
      */
    }
    else if (!strncmp(line, "%%PageMedia:", 12))
    {
     /*
      * %%PageMedia: ...
      */
    }
    else if (!strncmp(line, "%%PageOrientation:", 18))
    {
     /*
      * %%PageOrientation: ...
      */
    }
    else if (!strncmp(line, "%%PageProcessColors:", 20))
    {
     /*
      * %%PageProcessColors: ...
      */
    }
    else if (!strncmp(line, "%%PageRequirements:", 19))
    {
     /*
      * %%PageRequirements: ...
      */
    }
    else if (!strncmp(line, "%%PageResources:", 16))
    {
     /*
      * %%PageResources: ...
      */
    }
    else if (!strncmp(line, "%%IncludeFeature:", 17))
    {
     /*
      * %%IncludeFeature: *MainKeyword OptionKeyword
      */

      if (doc->number_up == 1 &&!doc->fit_to_page)
	pageinfo->num_options = include_feature(ppd, line,
	                                        pageinfo->num_options,
                                        	&(pageinfo->options));
    }
    else if (!strncmp(line, "%%BeginPageSetup", 16))
    {
      has_page_setup = 1;
      break;
    }
    else
      break;
  }

  if (doc->number_up == 1)
  {
   /*
    * Update the document's composite and page bounding box...
    */

    memcpy(pageinfo->bounding_box, bounding_box,
           sizeof(pageinfo->bounding_box));

    if (bounding_box[0] < doc->new_bounding_box[0])
      doc->new_bounding_box[0] = bounding_box[0];
    if (bounding_box[1] < doc->new_bounding_box[1])
      doc->new_bounding_box[1] = bounding_box[1];
    if (bounding_box[2] > doc->new_bounding_box[2])
      doc->new_bounding_box[2] = bounding_box[2];
    if (bounding_box[3] > doc->new_bounding_box[3])
      doc->new_bounding_box[3] = bounding_box[3];
  }

 /*
  * Output the page header as needed...
  */

  if (!doc->slow_order && first_page)
  {
    if (!ppd || !ppd->num_filters)
      fprintf(stderr, "PAGE: %d %d\n", doc->page,
	      doc->slow_collate ? 1 : doc->copies);

    if (doc->number_up > 1)
    {
      printf("%%%%Page: (%d) %d\n", doc->page, doc->page);
      printf("%%%%PageBoundingBox: %.0f %.0f %.0f %.0f\n",
	     PageLeft, PageBottom, PageRight, PageTop);
    }
    else
    {
      printf("%%%%Page: %s %d\n", pageinfo->label, doc->page);
      printf("%%%%PageBoundingBox: %d %d %d %d\n",
	     pageinfo->bounding_box[0], pageinfo->bounding_box[1],
	     pageinfo->bounding_box[2], pageinfo->bounding_box[3]);
    }
  }

 /*
  * Copy any page setup commands...
  */

  if (first_page)
    doc_puts(doc, "%%BeginPageSetup\n");

  if (has_page_setup)
  {
    int	feature = 0;			/* In a Begin/EndFeature block? */

    while ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) > 0)
    {
      if (!strncmp(line, "%%EndPageSetup", 14))
	break;
      else if (!strncmp(line, "%%BeginFeature:", 15))
      {
	feature = 1;

	if (doc->number_up > 1 || doc->fit_to_page)
	  continue;
      }
      else if (!strncmp(line, "%%EndFeature", 12))
      {
	feature = 0;

	if (doc->number_up > 1 || doc->fit_to_page)
	  continue;
      }
      else if (!strncmp(line, "%%IncludeFeature:", 17))
      {
	pageinfo->num_options = include_feature(ppd, line,
						pageinfo->num_options,
						&(pageinfo->options));
	continue;
      }
      else if (!strncmp(line, "%%Include", 9))
	continue;

      if (line[0] != '%' && !feature)
        break;

      if (!feature || (doc->number_up == 1 && !doc->fit_to_page))
	doc_write(doc, line, (size_t)linelen);
    }

   /*
    * Skip %%EndPageSetup...
    */

    if (linelen > 0 && !strncmp(line, "%%EndPageSetup", 14))
      linelen = (ssize_t)cupsFileGetLine(fp, line, linesize);
  }

  if (first_page)
  {
    char	*page_setup;		/* PageSetup commands to send */


    if (pageinfo->num_options > 0)
      write_options(doc, ppd, pageinfo->num_options, pageinfo->options);

   /*
    * Output commands for the current page...
    */

    page_setup = ppdEmitString(ppd, PPD_ORDER_PAGE, 0);

    if (page_setup)
    {
      doc_puts(doc, page_setup);
      free(page_setup);
    }
  }

 /*
  * Prep for the start of the page description...
  */

  start_nup(doc, number, 1, bounding_box);

  if (first_page)
    doc_puts(doc, "%%EndPageSetup\n");

 /*
  * Read the rest of the page description...
  */

  level = 0;

  do
  {
    if (level == 0 &&
        (!strncmp(line, "%%Page:", 7) ||
	 !strncmp(line, "%%Trailer", 9) ||
	 !strncmp(line, "%%EOF", 5)))
      break;
    else if (!strncmp(line, "%%BeginDocument", 15) ||
	     !strncmp(line, "%ADO_BeginApplication", 21))
    {
      doc_write(doc, line, (size_t)linelen);

      level ++;
    }
    else if ((!strncmp(line, "%%EndDocument", 13) ||
	      !strncmp(line, "%ADO_EndApplication", 19)) && level > 0)
    {
      doc_write(doc, line, (size_t)linelen);

      level --;
    }
    else if (!strncmp(line, "%%BeginBinary:", 14) ||
             (!strncmp(line, "%%BeginData:", 12) &&
	      !strstr(line, "ASCII") && !strstr(line, "Hex")))
    {
     /*
      * Copy binary data...
      */

      int	bytes;			/* Bytes of data */


      doc_write(doc, line, (size_t)linelen);

      bytes = atoi(strchr(line, ':') + 1);

      while (bytes > 0)
      {
	if ((size_t)bytes > linesize)
	  linelen = cupsFileRead(fp, line, linesize);
	else
	  linelen = cupsFileRead(fp, line, (size_t)bytes);

	if (linelen < 1)
	{
	  line[0] = '\0';
	  perror("ERROR: Early end-of-file while reading binary data");
	  return (0);
	}

        doc_write(doc, line, (size_t)linelen);

	bytes -= linelen;
      }
    }
    else
      doc_write(doc, line, (size_t)linelen);
  }
  while ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) > 0);

 /*
  * Finish up this page and return...
  */

  end_nup(doc, number);

  pageinfo->length = (ssize_t)(cupsFileTell(doc->temp) - pageinfo->offset);

  return (linelen);
}


/*
 * 'copy_prolog()' - Copy the document prolog section.
 *
 * This function expects "line" to be filled with a %%BeginProlog comment line.
 * On return, "line" will contain the next line in the file, if any.
 */

static ssize_t				/* O - Length of next line */
copy_prolog(cups_file_t  *fp,		/* I - File to read from */
            pstops_doc_t *doc,		/* I - Document info */
            ppd_file_t   *ppd,		/* I - PPD file */
	    char         *line,		/* I - Line buffer */
	    ssize_t      linelen,	/* I - Length of initial line */
	    size_t       linesize)	/* I - Size of line buffer */
{
  while (strncmp(line, "%%BeginProlog", 13))
  {
    if (!strncmp(line, "%%BeginSetup", 12) || !strncmp(line, "%%Page:", 7))
      break;

    doc_write(doc, line, (size_t)linelen);

    if ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) == 0)
      break;
  }

  doc_puts(doc, "%%BeginProlog\n");

  do_prolog(doc, ppd);

  if (!strncmp(line, "%%BeginProlog", 13))
  {
    while ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) > 0)
    {
      if (!strncmp(line, "%%EndProlog", 11) ||
          !strncmp(line, "%%BeginSetup", 12) ||
          !strncmp(line, "%%Page:", 7))
        break;

      doc_write(doc, line, (size_t)linelen);
    }

    if (!strncmp(line, "%%EndProlog", 11))
      linelen = (ssize_t)cupsFileGetLine(fp, line, linesize);
    else
      fputs("DEBUG: The %%EndProlog comment is missing.\n", stderr);
  }

  doc_puts(doc, "%%EndProlog\n");

  return (linelen);
}


/*
 * 'copy_setup()' - Copy the document setup section.
 *
 * This function expects "line" to be filled with a %%BeginSetup comment line.
 * On return, "line" will contain the next line in the file, if any.
 */

static ssize_t				/* O - Length of next line */
copy_setup(cups_file_t  *fp,		/* I - File to read from */
           pstops_doc_t *doc,		/* I - Document info */
           ppd_file_t   *ppd,		/* I - PPD file */
	   char         *line,		/* I - Line buffer */
	   ssize_t      linelen,	/* I - Length of initial line */
	   size_t       linesize)	/* I - Size of line buffer */
{
  int		num_options;		/* Number of options */
  cups_option_t	*options;		/* Options */


  while (strncmp(line, "%%BeginSetup", 12))
  {
    if (!strncmp(line, "%%Page:", 7))
      break;

    doc_write(doc, line, (size_t)linelen);

    if ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) == 0)
      break;
  }

  doc_puts(doc, "%%BeginSetup\n");

  do_setup(doc, ppd);

  num_options = 0;
  options     = NULL;

  if (!strncmp(line, "%%BeginSetup", 12))
  {
    while (strncmp(line, "%%EndSetup", 10))
    {
      if (!strncmp(line, "%%Page:", 7))
        break;
      else if (!strncmp(line, "%%IncludeFeature:", 17))
      {
       /*
	* %%IncludeFeature: *MainKeyword OptionKeyword
	*/

        if (doc->number_up == 1 && !doc->fit_to_page)
	  num_options = include_feature(ppd, line, num_options, &options);
      }
      else if (strncmp(line, "%%BeginSetup", 12))
        doc_write(doc, line, (size_t)linelen);

      if ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) == 0)
	break;
    }

    if (!strncmp(line, "%%EndSetup", 10))
      linelen = (ssize_t)cupsFileGetLine(fp, line, linesize);
    else
      fputs("DEBUG: The %%EndSetup comment is missing.\n", stderr);
  }

  if (num_options > 0)
  {
    write_options(doc, ppd, num_options, options);
    cupsFreeOptions(num_options, options);
  }

  doc_puts(doc, "%%EndSetup\n");

  return (linelen);
}


/*
 * 'copy_trailer()' - Copy the document trailer.
 *
 * This function expects "line" to be filled with a %%Trailer comment line.
 * On return, "line" will contain the next line in the file, if any.
 */

static ssize_t				/* O - Length of next line */
copy_trailer(cups_file_t  *fp,		/* I - File to read from */
             pstops_doc_t *doc,		/* I - Document info */
             ppd_file_t   *ppd,		/* I - PPD file */
	     int          number,	/* I - Number of pages */
	     char         *line,	/* I - Line buffer */
	     ssize_t      linelen,	/* I - Length of initial line */
	     size_t       linesize)	/* I - Size of line buffer */
{
 /*
  * Write the trailer comments...
  */

  (void)ppd;

  puts("%%Trailer");

  while (linelen > 0)
  {
    if (!strncmp(line, "%%EOF", 5))
      break;
    else if (strncmp(line, "%%Trailer", 9) &&
             strncmp(line, "%%Pages:", 8) &&
             strncmp(line, "%%BoundingBox:", 14))
      fwrite(line, 1, (size_t)linelen, stdout);

    linelen = (ssize_t)cupsFileGetLine(fp, line, linesize);
  }

  fprintf(stderr, "DEBUG: Wrote %d pages...\n", number);

  printf("%%%%Pages: %d\n", number);
  if (doc->number_up > 1 || doc->fit_to_page)
    printf("%%%%BoundingBox: %.0f %.0f %.0f %.0f\n",
	   PageLeft, PageBottom, PageRight, PageTop);
  else
    printf("%%%%BoundingBox: %d %d %d %d\n",
	   doc->new_bounding_box[0], doc->new_bounding_box[1],
	   doc->new_bounding_box[2], doc->new_bounding_box[3]);

  return (linelen);
}


/*
 * 'do_prolog()' - Send the necessary document prolog commands.
 */

static void
do_prolog(pstops_doc_t *doc,		/* I - Document information */
          ppd_file_t   *ppd)		/* I - PPD file */
{
  char	*ps;				/* PS commands */


 /*
  * Send the document prolog commands...
  */

  if (ppd && ppd->patches)
  {
    doc_puts(doc, "%%BeginFeature: *JobPatchFile 1\n");
    doc_puts(doc, ppd->patches);
    doc_puts(doc, "\n%%EndFeature\n");
  }

  if ((ps = ppdEmitString(ppd, PPD_ORDER_PROLOG, 0.0)) != NULL)
  {
    doc_puts(doc, ps);
    free(ps);
  }

 /*
  * Define ESPshowpage here so that applications that define their
  * own procedure to do a showpage pick it up...
  */

  if (doc->use_ESPshowpage)
    doc_puts(doc, "userdict/ESPshowpage/showpage load put\n"
	          "userdict/showpage{}put\n");
}


/*
 * 'do_setup()' - Send the necessary document setup commands.
 */

static void
do_setup(pstops_doc_t *doc,		/* I - Document information */
         ppd_file_t   *ppd)		/* I - PPD file */
{
  char	*ps;				/* PS commands */


 /*
  * Disable CTRL-D so that embedded files don't cause printing
  * errors...
  */

  doc_puts(doc, "% Disable CTRL-D as an end-of-file marker...\n");
  doc_puts(doc, "userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");

 /*
  * Mark job options...
  */

  cupsMarkOptions(ppd, doc->num_options, doc->options);

 /*
  * Send all the printer-specific setup commands...
  */

  if ((ps = ppdEmitString(ppd, PPD_ORDER_DOCUMENT, 0.0)) != NULL)
  {
    doc_puts(doc, ps);
    free(ps);
  }

  if ((ps = ppdEmitString(ppd, PPD_ORDER_ANY, 0.0)) != NULL)
  {
    doc_puts(doc, ps);
    free(ps);
  }

 /*
  * Set the number of copies for the job...
  */

  if (doc->copies != 1 && (!doc->collate || !doc->slow_collate))
  {
    doc_printf(doc, "%%RBIBeginNonPPDFeature: *NumCopies %d\n", doc->copies);
    doc_printf(doc,
               "%d/languagelevel where{pop languagelevel 2 ge}{false}ifelse\n"
               "{1 dict begin/NumCopies exch def currentdict end "
	       "setpagedevice}\n"
	       "{userdict/#copies 3 -1 roll put}ifelse\n", doc->copies);
    doc_puts(doc, "%RBIEndNonPPDFeature\n");
  }

 /*
  * If we are doing N-up printing, disable setpagedevice...
  */

  if (doc->number_up > 1)
  {
    doc_puts(doc, "userdict/CUPSsetpagedevice/setpagedevice load put\n");
    doc_puts(doc, "userdict/setpagedevice{pop}bind put\n");
  }

 /*
  * Make sure we have rectclip and rectstroke procedures of some sort...
  */

  doc_puts(doc,
           "% x y w h ESPrc - Clip to a rectangle.\n"
	   "userdict/ESPrc/rectclip where{pop/rectclip load}\n"
	   "{{newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"
	   "neg 0 rlineto closepath clip newpath}bind}ifelse put\n");

  doc_puts(doc,
           "% x y w h ESPrf - Fill a rectangle.\n"
	   "userdict/ESPrf/rectfill where{pop/rectfill load}\n"
	   "{{gsave newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"
	   "neg 0 rlineto closepath fill grestore}bind}ifelse put\n");

  doc_puts(doc,
           "% x y w h ESPrs - Stroke a rectangle.\n"
	   "userdict/ESPrs/rectstroke where{pop/rectstroke load}\n"
	   "{{gsave newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"
	   "neg 0 rlineto closepath stroke grestore}bind}ifelse put\n");

 /*
  * Write the page and label prologs...
  */

  if (doc->number_up == 2 || doc->number_up == 6)
  {
   /*
    * For 2- and 6-up output, rotate the labels to match the orientation
    * of the pages...
    */

    if (Orientation & 1)
      write_label_prolog(doc, doc->page_label, PageBottom,
                         PageWidth - PageLength + PageTop, PageLength);
    else
      write_label_prolog(doc, doc->page_label, PageLeft, PageRight,
                         PageLength);
  }
  else
    write_label_prolog(doc, doc->page_label, PageBottom, PageTop, PageWidth);
}


/*
 * 'doc_printf()' - Send a formatted string to stdout and/or the temp file.
 *
 * This function should be used for all page-level output that is affected
 * by ordering, collation, etc.
 */

static void
doc_printf(pstops_doc_t *doc,		/* I - Document information */
           const char   *format,	/* I - Printf-style format string */
	   ...)				/* I - Additional arguments as needed */
{
  va_list	ap;			/* Pointer to arguments */
  char		buffer[1024];		/* Output buffer */
  ssize_t	bytes;			/* Number of bytes to write */


  va_start(ap, format);
  bytes = vsnprintf(buffer, sizeof(buffer), format, ap);
  va_end(ap);

  if ((size_t)bytes > sizeof(buffer))
  {
    _cupsLangPrintFilter(stderr, "ERROR",
                         _("Buffer overflow detected, aborting."));
    exit(1);
  }

  doc_write(doc, buffer, (size_t)bytes);
}


/*
 * 'doc_puts()' - Send a nul-terminated string to stdout and/or the temp file.
 *
 * This function should be used for all page-level output that is affected
 * by ordering, collation, etc.
 */

static void
doc_puts(pstops_doc_t *doc,		/* I - Document information */
         const char   *s)		/* I - String to send */
{
  doc_write(doc, s, strlen(s));
}


/*
 * 'doc_write()' - Send data to stdout and/or the temp file.
 */

static void
doc_write(pstops_doc_t *doc,		/* I - Document information */
          const char   *s,		/* I - Data to send */
	  size_t       len)		/* I - Number of bytes to send */
{
  if (!doc->slow_order)
    fwrite(s, 1, len, stdout);

  if (doc->temp)
    cupsFileWrite(doc->temp, s, len);
}


/*
 * 'end_nup()' - End processing for N-up printing.
 */

static void
end_nup(pstops_doc_t *doc,		/* I - Document information */
        int          number)		/* I - Page number */
{
  if (doc->number_up > 1)
    doc_puts(doc, "userdict/ESPsave get restore\n");

  switch (doc->number_up)
  {
    case 1 :
	if (doc->use_ESPshowpage)
	{
	  write_labels(doc, Orientation);
          doc_puts(doc, "ESPshowpage\n");
	}
	break;

    case 2 :
    case 6 :
	if (is_last_page(number) && doc->use_ESPshowpage)
	{
	  if (Orientation & 1)
	  {
	   /*
	    * Rotate the labels back to portrait...
	    */

	    write_labels(doc, Orientation - 1);
	  }
	  else if (Orientation == 0)
	  {
	   /*
	    * Rotate the labels to landscape...
	    */

	    write_labels(doc, doc->normal_landscape ? 1 : 3);
	  }
	  else
	  {
	   /*
	    * Rotate the labels to landscape...
	    */

	    write_labels(doc, doc->normal_landscape ? 3 : 1);
	  }

          doc_puts(doc, "ESPshowpage\n");
	}
        break;

    default :
	if (is_last_page(number) && doc->use_ESPshowpage)
	{
	  write_labels(doc, Orientation);
          doc_puts(doc, "ESPshowpage\n");
	}
        break;
  }

  fflush(stdout);
}


/*
 * 'include_feature()' - Include a printer option/feature command.
 */

static int				/* O  - New number of options */
include_feature(
    ppd_file_t    *ppd,			/* I  - PPD file */
    const char    *line,		/* I  - DSC line */
    int           num_options,		/* I  - Number of options */
    cups_option_t **options)		/* IO - Options */
{
  char		name[255],		/* Option name */
		value[255];		/* Option value */
  ppd_option_t	*option;		/* Option in file */


 /*
  * Get the "%%IncludeFeature: *Keyword OptionKeyword" values...
  */

  if (sscanf(line + 17, "%254s%254s", name, value) != 2)
  {
    fputs("DEBUG: The %%IncludeFeature: comment is not valid.\n", stderr);
    return (num_options);
  }

 /*
  * Find the option and choice...
  */

  if ((option = ppdFindOption(ppd, name + 1)) == NULL)
  {
    _cupsLangPrintFilter(stderr, "WARNING", _("Unknown option \"%s\"."),
                         name + 1);
    return (num_options);
  }

  if (option->section == PPD_ORDER_EXIT ||
      option->section == PPD_ORDER_JCL)
  {
    _cupsLangPrintFilter(stderr, "WARNING",
                         _("Option \"%s\" cannot be included via "
			   "%%%%IncludeFeature."), name + 1);
    return (num_options);
  }

  if (!ppdFindChoice(option, value))
  {
    _cupsLangPrintFilter(stderr, "WARNING",
			 _("Unknown choice \"%s\" for option \"%s\"."),
			 value, name + 1);
    return (num_options);
  }

 /*
  * Add the option to the option array and return...
  */

  return (cupsAddOption(name + 1, value, num_options, options));
}


/*
 * 'parse_text()' - Parse a text value in a comment.
 *
 * This function parses a DSC text value as defined on page 36 of the
 * DSC specification.  Text values are either surrounded by parenthesis
 * or whitespace-delimited.
 *
 * The value returned is the literal characters for the entire text
 * string, including any parenthesis and escape characters.
 */

static char *				/* O - Value or NULL on error */
parse_text(const char *start,		/* I - Start of text value */
           char       **end,		/* O - End of text value */
	   char       *buffer,		/* I - Buffer */
           size_t     bufsize)		/* I - Size of buffer */
{
  char	*bufptr,			/* Pointer in buffer */
	*bufend;			/* End of buffer */
  int	level;				/* Parenthesis level */


 /*
  * Skip leading whitespace...
  */

  while (isspace(*start & 255))
    start ++;

 /*
  * Then copy the value...
  */

  level  = 0;
  bufptr = buffer;
  bufend = buffer + bufsize - 1;

  while (*start && bufptr < bufend)
  {
    if (isspace(*start & 255) && !level)
      break;

    *bufptr++ = *start;

    if (*start == '(')
      level ++;
    else if (*start == ')')
    {
      if (!level)
      {
        start ++;
        break;
      }
      else
        level --;
    }
    else if (*start == '\\')
    {
     /*
      * Copy escaped character...
      */

      int	i;			/* Looping var */


      for (i = 1;
           i <= 3 && isdigit(start[i] & 255) && bufptr < bufend;
	   *bufptr++ = start[i], i ++);
    }

    start ++;
  }

  *bufptr = '\0';

 /*
  * Return the value and new pointer into the line...
  */

  if (end)
    *end = (char *)start;

  if (bufptr == bufend)
    return (NULL);
  else
    return (buffer);
}


/*
 * 'set_pstops_options()' - Set pstops options.
 */

static void
set_pstops_options(
    pstops_doc_t  *doc,			/* I - Document information */
    ppd_file_t    *ppd,			/* I - PPD file */
    char          *argv[],		/* I - Command-line arguments */
    int           num_options,		/* I - Number of options */
    cups_option_t *options)		/* I - Options */
{
  const char	*val;			/* Option value */
  int		intval;			/* Integer option value */
  ppd_attr_t	*attr;			/* PPD attribute */
  ppd_option_t	*option;		/* PPD option */
  ppd_choice_t	*choice;		/* PPD choice */
  const char	*content_type;		/* Original content type */
  int		max_copies;		/* Maximum number of copies supported */


 /*
  * Initialize document information structure...
  */

  memset(doc, 0, sizeof(pstops_doc_t));

  doc->job_id = atoi(argv[1]);
  doc->user   = argv[2];
  doc->title  = argv[3];
  doc->copies = atoi(argv[4]);

  if (ppd && ppd->landscape > 0)
    doc->normal_landscape = 1;

  doc->bounding_box[0] = (int)PageLeft;
  doc->bounding_box[1] = (int)PageBottom;
  doc->bounding_box[2] = (int)PageRight;
  doc->bounding_box[3] = (int)PageTop;

  doc->new_bounding_box[0] = INT_MAX;
  doc->new_bounding_box[1] = INT_MAX;
  doc->new_bounding_box[2] = INT_MIN;
  doc->new_bounding_box[3] = INT_MIN;

 /*
  * AP_FIRSTPAGE_* and the corresponding non-first-page options.
  */

  doc->ap_input_slot  = cupsGetOption("AP_FIRSTPAGE_InputSlot", num_options,
                                      options);
  doc->ap_manual_feed = cupsGetOption("AP_FIRSTPAGE_ManualFeed", num_options,
                                      options);
  doc->ap_media_color = cupsGetOption("AP_FIRSTPAGE_MediaColor", num_options,
                                      options);
  doc->ap_media_type  = cupsGetOption("AP_FIRSTPAGE_MediaType", num_options,
                                      options);
  doc->ap_page_region = cupsGetOption("AP_FIRSTPAGE_PageRegion", num_options,
                                      options);
  doc->ap_page_size   = cupsGetOption("AP_FIRSTPAGE_PageSize", num_options,
                                      options);

  if ((choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL)
    doc->input_slot = choice->choice;
  if ((choice = ppdFindMarkedChoice(ppd, "ManualFeed")) != NULL)
    doc->manual_feed = choice->choice;
  if ((choice = ppdFindMarkedChoice(ppd, "MediaColor")) != NULL)
    doc->media_color = choice->choice;
  if ((choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL)
    doc->media_type = choice->choice;
  if ((choice = ppdFindMarkedChoice(ppd, "PageRegion")) != NULL)
    doc->page_region = choice->choice;
  if ((choice = ppdFindMarkedChoice(ppd, "PageSize")) != NULL)
    doc->page_size = choice->choice;

 /*
  * collate, multiple-document-handling
  */

  if ((val = cupsGetOption("multiple-document-handling", num_options, options)) != NULL)
  {
   /*
    * This IPP attribute is unnecessarily complicated...
    *
    *   single-document, separate-documents-collated-copies, and
    *   single-document-new-sheet all require collated copies.
    *
    *   separate-documents-uncollated-copies allows for uncollated copies.
    */

    doc->collate = _cups_strcasecmp(val, "separate-documents-uncollated-copies") != 0;
  }

  if ((val = cupsGetOption("Collate", num_options, options)) != NULL &&
      (!_cups_strcasecmp(val, "true") ||!_cups_strcasecmp(val, "on") ||
       !_cups_strcasecmp(val, "yes")))
    doc->collate = 1;

 /*
  * emit-jcl
  */

  if ((val = cupsGetOption("emit-jcl", num_options, options)) != NULL &&
      (!_cups_strcasecmp(val, "false") || !_cups_strcasecmp(val, "off") ||
       !_cups_strcasecmp(val, "no") || !strcmp(val, "0")))
    doc->emit_jcl = 0;
  else
    doc->emit_jcl = 1;

 /*
  * fit-to-page/ipp-attribute-fidelity
  *
  * (Only for original PostScript content)
  */

  if ((content_type = getenv("CONTENT_TYPE")) == NULL)
    content_type = "application/postscript";

  if (!_cups_strcasecmp(content_type, "application/postscript"))
  {
    if ((val = cupsGetOption("fit-to-page", num_options, options)) != NULL &&
	!_cups_strcasecmp(val, "true"))
      doc->fit_to_page = 1;
    else if ((val = cupsGetOption("ipp-attribute-fidelity", num_options,
                                  options)) != NULL &&
	     !_cups_strcasecmp(val, "true"))
      doc->fit_to_page = 1;
  }

 /*
  * mirror/MirrorPrint
  */

  if ((choice = ppdFindMarkedChoice(ppd, "MirrorPrint")) != NULL)
  {
    val = choice->choice;
    choice->marked = 0;
  }
  else
    val = cupsGetOption("mirror", num_options, options);

  if (val && (!_cups_strcasecmp(val, "true") || !_cups_strcasecmp(val, "on") ||
              !_cups_strcasecmp(val, "yes")))
    doc->mirror = 1;

 /*
  * number-up
  */

  if ((val = cupsGetOption("number-up", num_options, options)) != NULL)
  {
    switch (intval = atoi(val))
    {
      case 1 :
      case 2 :
      case 4 :
      case 6 :
      case 9 :
      case 16 :
          doc->number_up = intval;
	  break;
      default :
          _cupsLangPrintFilter(stderr, "ERROR",
	                       _("Unsupported number-up value %d, using "
				 "number-up=1."), intval);
          doc->number_up = 1;
	  break;
    }
  }
  else
    doc->number_up = 1;

 /*
  * number-up-layout
  */

  if ((val = cupsGetOption("number-up-layout", num_options, options)) != NULL)
  {
    if (!_cups_strcasecmp(val, "lrtb"))
      doc->number_up_layout = PSTOPS_LAYOUT_LRTB;
    else if (!_cups_strcasecmp(val, "lrbt"))
      doc->number_up_layout = PSTOPS_LAYOUT_LRBT;
    else if (!_cups_strcasecmp(val, "rltb"))
      doc->number_up_layout = PSTOPS_LAYOUT_RLTB;
    else if (!_cups_strcasecmp(val, "rlbt"))
      doc->number_up_layout = PSTOPS_LAYOUT_RLBT;
    else if (!_cups_strcasecmp(val, "tblr"))
      doc->number_up_layout = PSTOPS_LAYOUT_TBLR;
    else if (!_cups_strcasecmp(val, "tbrl"))
      doc->number_up_layout = PSTOPS_LAYOUT_TBRL;
    else if (!_cups_strcasecmp(val, "btlr"))
      doc->number_up_layout = PSTOPS_LAYOUT_BTLR;
    else if (!_cups_strcasecmp(val, "btrl"))
      doc->number_up_layout = PSTOPS_LAYOUT_BTRL;
    else
    {
      _cupsLangPrintFilter(stderr, "ERROR",
                           _("Unsupported number-up-layout value %s, using "
			     "number-up-layout=lrtb."), val);
      doc->number_up_layout = PSTOPS_LAYOUT_LRTB;
    }
  }
  else
    doc->number_up_layout = PSTOPS_LAYOUT_LRTB;

 /*
  * OutputOrder
  */

  if ((val = cupsGetOption("OutputOrder", num_options, options)) != NULL)
  {
    if (!_cups_strcasecmp(val, "Reverse"))
      doc->output_order = 1;
  }
  else if (ppd)
  {
   /*
    * Figure out the right default output order from the PPD file...
    */

    if ((choice = ppdFindMarkedChoice(ppd, "OutputBin")) != NULL &&
        (attr = ppdFindAttr(ppd, "PageStackOrder", choice->choice)) != NULL &&
	attr->value)
      doc->output_order = !_cups_strcasecmp(attr->value, "Reverse");
    else if ((attr = ppdFindAttr(ppd, "DefaultOutputOrder", NULL)) != NULL &&
             attr->value)
      doc->output_order = !_cups_strcasecmp(attr->value, "Reverse");
  }

 /*
  * page-border
  */

  if ((val = cupsGetOption("page-border", num_options, options)) != NULL)
  {
    if (!_cups_strcasecmp(val, "none"))
      doc->page_border = PSTOPS_BORDERNONE;
    else if (!_cups_strcasecmp(val, "single"))
      doc->page_border = PSTOPS_BORDERSINGLE;
    else if (!_cups_strcasecmp(val, "single-thick"))
      doc->page_border = PSTOPS_BORDERSINGLE2;
    else if (!_cups_strcasecmp(val, "double"))
      doc->page_border = PSTOPS_BORDERDOUBLE;
    else if (!_cups_strcasecmp(val, "double-thick"))
      doc->page_border = PSTOPS_BORDERDOUBLE2;
    else
    {
      _cupsLangPrintFilter(stderr, "ERROR",
                           _("Unsupported page-border value %s, using "
			     "page-border=none."), val);
      doc->page_border = PSTOPS_BORDERNONE;
    }
  }
  else
    doc->page_border = PSTOPS_BORDERNONE;

 /*
  * page-label
  */

  doc->page_label = cupsGetOption("page-label", num_options, options);

 /*
  * page-ranges
  */

  doc->page_ranges = cupsGetOption("page-ranges", num_options, options);

 /*
  * page-set
  */

  doc->page_set = cupsGetOption("page-set", num_options, options);

 /*
  * Now figure out if we have to force collated copies, etc.
  */

  if ((attr = ppdFindAttr(ppd, "cupsMaxCopies", NULL)) != NULL)
    max_copies = atoi(attr->value);
  else if (ppd && ppd->manual_copies)
    max_copies = 1;
  else
    max_copies = 9999;

  if (doc->copies > max_copies)
    doc->collate = 1;
  else if (ppd && ppd->manual_copies && Duplex && doc->copies > 1)
  {
   /*
    * Force collated copies when printing a duplexed document to
    * a non-PS printer that doesn't do hardware copy generation.
    * Otherwise the copies will end up on the front/back side of
    * each page.
    */

    doc->collate = 1;
  }

 /*
  * See if we have to filter the fast or slow way...
  */

  if (doc->collate && doc->copies > 1)
  {
   /*
    * See if we need to manually collate the pages...
    */

    doc->slow_collate = 1;

    if (doc->copies <= max_copies &&
        (choice = ppdFindMarkedChoice(ppd, "Collate")) != NULL &&
        !_cups_strcasecmp(choice->choice, "True"))
    {
     /*
      * Hardware collate option is selected, see if the option is
      * conflicting - if not, collate in hardware.  Otherwise,
      * turn the hardware collate option off...
      */

      if ((option = ppdFindOption(ppd, "Collate")) != NULL &&
          !option->conflicted)
	doc->slow_collate = 0;
      else
        ppdMarkOption(ppd, "Collate", "False");
    }
  }
  else
    doc->slow_collate = 0;

  if (!ppdFindOption(ppd, "OutputOrder") && doc->output_order)
    doc->slow_order = 1;
  else
    doc->slow_order = 0;

  if (Duplex &&
       (doc->slow_collate || doc->slow_order ||
        ((attr = ppdFindAttr(ppd, "cupsEvenDuplex", NULL)) != NULL &&
	 attr->value && !_cups_strcasecmp(attr->value, "true"))))
    doc->slow_duplex = 1;
  else
    doc->slow_duplex = 0;

 /*
  * Create a temporary file for page data if we need to filter slowly...
  */

  if (doc->slow_order || doc->slow_collate)
  {
    if ((doc->temp = cupsTempFile2(doc->tempfile,
                                   sizeof(doc->tempfile))) == NULL)
    {
      perror("DEBUG: Unable to create temporary file");
      exit(1);
    }
  }

 /*
  * Figure out if we should use ESPshowpage or not...
  */

  if (doc->page_label || getenv("CLASSIFICATION") || doc->number_up > 1 ||
      doc->page_border)
  {
   /*
    * Yes, use ESPshowpage...
    */

    doc->use_ESPshowpage = 1;
  }

  fprintf(stderr, "DEBUG: slow_collate=%d, slow_duplex=%d, slow_order=%d\n",
          doc->slow_collate, doc->slow_duplex, doc->slow_order);
}


/*
 * 'skip_page()' - Skip past a page that won't be printed.
 */

static ssize_t				/* O - Length of next line */
skip_page(cups_file_t *fp,		/* I - File to read from */
          char        *line,		/* I - Line buffer */
	  ssize_t     linelen,		/* I - Length of initial line */
          size_t      linesize)		/* I - Size of line buffer */
{
  int	level;				/* Embedded document level */


  level = 0;

  while ((linelen = (ssize_t)cupsFileGetLine(fp, line, linesize)) > 0)
  {
    if (level == 0 &&
        (!strncmp(line, "%%Page:", 7) || !strncmp(line, "%%Trailer", 9)))
      break;
    else if (!strncmp(line, "%%BeginDocument", 15) ||
	     !strncmp(line, "%ADO_BeginApplication", 21))
      level ++;
    else if ((!strncmp(line, "%%EndDocument", 13) ||
	      !strncmp(line, "%ADO_EndApplication", 19)) && level > 0)
      level --;
    else if (!strncmp(line, "%%BeginBinary:", 14) ||
             (!strncmp(line, "%%BeginData:", 12) &&
	      !strstr(line, "ASCII") && !strstr(line, "Hex")))
    {
     /*
      * Skip binary data...
      */

      ssize_t	bytes;			/* Bytes of data */

      bytes = atoi(strchr(line, ':') + 1);

      while (bytes > 0)
      {
	if ((size_t)bytes > linesize)
	  linelen = (ssize_t)cupsFileRead(fp, line, linesize);
	else
	  linelen = (ssize_t)cupsFileRead(fp, line, (size_t)bytes);

	if (linelen < 1)
	{
	  line[0] = '\0';
	  perror("ERROR: Early end-of-file while reading binary data");
	  return (0);
	}

	bytes -= linelen;
      }
    }
  }

  return (linelen);
}


/*
 * 'start_nup()' - Start processing for N-up printing.
 */

static void
start_nup(pstops_doc_t *doc,		/* I - Document information */
          int          number,		/* I - Page number */
	  int          show_border,	/* I - Show the border? */
	  const int    *bounding_box)	/* I - BoundingBox value */
{
  int		pos;			/* Position on page */
  int		x, y;			/* Relative position of subpage */
  double	w, l,			/* Width and length of subpage */
		tx, ty;			/* Translation values for subpage */
  double	pagew,			/* Printable width of page */
		pagel;			/* Printable height of page */
  int		bboxx,			/* BoundingBox X origin */
		bboxy,			/* BoundingBox Y origin */
		bboxw,			/* BoundingBox width */
		bboxl;			/* BoundingBox height */
  double	margin = 0;		/* Current margin for border */


  if (doc->number_up > 1)
    doc_puts(doc, "userdict/ESPsave save put\n");

  pos   = (number - 1) % doc->number_up;
  pagew = PageRight - PageLeft;
  pagel = PageTop - PageBottom;

  if (doc->fit_to_page)
  {
    bboxx = bounding_box[0];
    bboxy = bounding_box[1];
    bboxw = bounding_box[2] - bounding_box[0];
    bboxl = bounding_box[3] - bounding_box[1];
  }
  else
  {
    bboxx = 0;
    bboxy = 0;
    bboxw = (int)PageWidth;
    bboxl = (int)PageLength;
  }

  fprintf(stderr, "DEBUG: pagew = %.1f, pagel = %.1f\n", pagew, pagel);
  fprintf(stderr, "DEBUG: bboxx = %d, bboxy = %d, bboxw = %d, bboxl = %d\n",
          bboxx, bboxy, bboxw, bboxl);
  fprintf(stderr, "DEBUG: PageLeft = %.1f, PageRight = %.1f\n",
          PageLeft, PageRight);
  fprintf(stderr, "DEBUG: PageTop = %.1f, PageBottom = %.1f\n",
          PageTop, PageBottom);
  fprintf(stderr, "DEBUG: PageWidth = %.1f, PageLength = %.1f\n",
          PageWidth, PageLength);

  switch (Orientation)
  {
    case 1 : /* Landscape */
        doc_printf(doc, "%.1f 0.0 translate 90 rotate\n", PageLength);
        break;
    case 2 : /* Reverse Portrait */
        doc_printf(doc, "%.1f %.1f translate 180 rotate\n", PageWidth,
	           PageLength);
        break;
    case 3 : /* Reverse Landscape */
        doc_printf(doc, "0.0 %.1f translate -90 rotate\n", PageWidth);
        break;
  }

 /*
  * Mirror the page as needed...
  */

  if (doc->mirror)
    doc_printf(doc, "%.1f 0.0 translate -1 1 scale\n", PageWidth);

 /*
  * Offset and scale as necessary for fit_to_page/fit-to-page/number-up...
  */

  if (Duplex && doc->number_up > 1 && ((number / doc->number_up) & 1))
    doc_printf(doc, "%.1f %.1f translate\n", PageWidth - PageRight, PageBottom);
  else if (doc->number_up > 1 || doc->fit_to_page)
    doc_printf(doc, "%.1f %.1f translate\n", PageLeft, PageBottom);

  switch (doc->number_up)
  {
    default :
        if (doc->fit_to_page)
	{
          w = pagew;
          l = w * bboxl / bboxw;

          if (l > pagel)
          {
            l = pagel;
            w = l * bboxw / bboxl;
          }

          tx = 0.5 * (pagew - w);
          ty = 0.5 * (pagel - l);

	  doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n", tx, ty,
	             w / bboxw, l / bboxl);
	}
	else
          w = PageWidth;
	break;

    case 2 :
        if (Orientation & 1)
	{
          x = pos & 1;

          if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	    x = 1 - x;

          w = pagel;
          l = w * bboxl / bboxw;

          if (l > (pagew * 0.5))
          {
            l = pagew * 0.5;
            w = l * bboxw / bboxl;
          }

          tx = 0.5 * (pagew * 0.5 - l);
          ty = 0.5 * (pagel - w);

          if (doc->normal_landscape)
            doc_printf(doc, "0.0 %.1f translate -90 rotate\n", pagel);
	  else
	    doc_printf(doc, "%.1f 0.0 translate 90 rotate\n", pagew);

          doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
                     ty, tx + pagew * 0.5 * x, w / bboxw, l / bboxl);
        }
	else
	{
          x = pos & 1;

          if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	    x = 1 - x;

          l = pagew;
          w = l * bboxw / bboxl;

          if (w > (pagel * 0.5))
          {
            w = pagel * 0.5;
            l = w * bboxl / bboxw;
          }

          tx = 0.5 * (pagel * 0.5 - w);
          ty = 0.5 * (pagew - l);

          if (doc->normal_landscape)
	    doc_printf(doc, "%.1f 0.0 translate 90 rotate\n", pagew);
	  else
            doc_printf(doc, "0.0 %.1f translate -90 rotate\n", pagel);

          doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
                     tx + pagel * 0.5 * x, ty, w / bboxw, l / bboxl);
        }
        break;

    case 4 :
        if (doc->number_up_layout & PSTOPS_LAYOUT_VERTICAL)
	{
	  x = (pos / 2) & 1;
          y = pos & 1;
        }
	else
	{
          x = pos & 1;
	  y = (pos / 2) & 1;
        }

        if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	  x = 1 - x;

	if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	  y = 1 - y;

        w = pagew * 0.5;
	l = w * bboxl / bboxw;

	if (l > (pagel * 0.5))
	{
	  l = pagel * 0.5;
	  w = l * bboxw / bboxl;
	}

        tx = 0.5 * (pagew * 0.5 - w);
        ty = 0.5 * (pagel * 0.5 - l);

	doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
	           tx + x * pagew * 0.5, ty + y * pagel * 0.5,
	           w / bboxw, l / bboxl);
        break;

    case 6 :
        if (Orientation & 1)
	{
	  if (doc->number_up_layout & PSTOPS_LAYOUT_VERTICAL)
	  {
	    x = pos / 3;
	    y = pos % 3;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	      x = 1 - x;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	      y = 2 - y;
	  }
	  else
	  {
	    x = pos & 1;
	    y = pos / 2;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	      x = 1 - x;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	      y = 2 - y;
	  }

          w = pagel * 0.5;
          l = w * bboxl / bboxw;

          if (l > (pagew * 0.333))
          {
            l = pagew * 0.333;
            w = l * bboxw / bboxl;
          }

          tx = 0.5 * (pagel - 2 * w);
          ty = 0.5 * (pagew - 3 * l);

          if (doc->normal_landscape)
            doc_printf(doc, "0 %.1f translate -90 rotate\n", pagel);
	  else
	    doc_printf(doc, "%.1f 0 translate 90 rotate\n", pagew);

          doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
                     tx + x * w, ty + y * l, l / bboxl, w / bboxw);
        }
	else
	{
	  if (doc->number_up_layout & PSTOPS_LAYOUT_VERTICAL)
	  {
	    x = pos / 2;
	    y = pos & 1;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	      x = 2 - x;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	      y = 1 - y;
	  }
	  else
	  {
	    x = pos % 3;
	    y = pos / 3;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	      x = 2 - x;

            if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	      y = 1 - y;
	  }

          l = pagew * 0.5;
          w = l * bboxw / bboxl;

          if (w > (pagel * 0.333))
          {
            w = pagel * 0.333;
            l = w * bboxl / bboxw;
          }

	  tx = 0.5 * (pagel - 3 * w);
	  ty = 0.5 * (pagew - 2 * l);

          if (doc->normal_landscape)
	    doc_printf(doc, "%.1f 0 translate 90 rotate\n", pagew);
	  else
            doc_printf(doc, "0 %.1f translate -90 rotate\n", pagel);

          doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
                     tx + w * x, ty + l * y, w / bboxw, l / bboxl);

        }
        break;

    case 9 :
        if (doc->number_up_layout & PSTOPS_LAYOUT_VERTICAL)
	{
	  x = (pos / 3) % 3;
          y = pos % 3;
        }
	else
	{
          x = pos % 3;
	  y = (pos / 3) % 3;
        }

        if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	  x = 2 - x;

	if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	  y = 2 - y;

        w = pagew * 0.333;
	l = w * bboxl / bboxw;

	if (l > (pagel * 0.333))
	{
	  l = pagel * 0.333;
	  w = l * bboxw / bboxl;
	}

        tx = 0.5 * (pagew * 0.333 - w);
        ty = 0.5 * (pagel * 0.333 - l);

	doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
	           tx + x * pagew * 0.333, ty + y * pagel * 0.333,
	           w / bboxw, l / bboxl);
        break;

    case 16 :
        if (doc->number_up_layout & PSTOPS_LAYOUT_VERTICAL)
	{
	  x = (pos / 4) & 3;
          y = pos & 3;
        }
	else
	{
          x = pos & 3;
	  y = (pos / 4) & 3;
        }

        if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEX)
	  x = 3 - x;

	if (doc->number_up_layout & PSTOPS_LAYOUT_NEGATEY)
	  y = 3 - y;

        w = pagew * 0.25;
	l = w * bboxl / bboxw;

	if (l > (pagel * 0.25))
	{
	  l = pagel * 0.25;
	  w = l * bboxw / bboxl;
	}

        tx = 0.5 * (pagew * 0.25 - w);
        ty = 0.5 * (pagel * 0.25 - l);

	doc_printf(doc, "%.1f %.1f translate %.3f %.3f scale\n",
	           tx + x * pagew * 0.25, ty + y * pagel * 0.25,
	           w / bboxw, l / bboxl);
        break;
  }

 /*
  * Draw borders as necessary...
  */

  if (doc->page_border && show_border)
  {
    int		rects;			/* Number of border rectangles */
    double	fscale;			/* Scaling value for points */


    rects  = (doc->page_border & PSTOPS_BORDERDOUBLE) ? 2 : 1;
    fscale = PageWidth / w;
    margin = 2.25 * fscale;

   /*
    * Set the line width and color...
    */

    doc_puts(doc, "gsave\n");
    doc_printf(doc, "%.3f setlinewidth 0 setgray newpath\n",
               (doc->page_border & PSTOPS_BORDERTHICK) ? 0.5 * fscale :
	                                                 0.24 * fscale);

   /*
    * Draw border boxes...
    */

    for (; rects > 0; rects --, margin += 2 * fscale)
      if (doc->number_up > 1)
	doc_printf(doc, "%.1f %.1f %.1f %.1f ESPrs\n",
		   margin,
		   margin,
		   bboxw - 2 * margin,
		   bboxl - 2 * margin);
      else
	doc_printf(doc, "%.1f %.1f %.1f %.1f ESPrs\n",
        	   PageLeft + margin,
		   PageBottom + margin,
		   PageRight - PageLeft - 2 * margin,
		   PageTop - PageBottom - 2 * margin);

   /*
    * Restore pen settings...
    */

    doc_puts(doc, "grestore\n");
  }

  if (doc->fit_to_page)
  {
   /*
    * Offset the page by its bounding box...
    */

    doc_printf(doc, "%d %d translate\n", -bounding_box[0],
               -bounding_box[1]);
  }

  if (doc->fit_to_page || doc->number_up > 1)
  {
   /*
    * Clip the page to the page's bounding box...
    */

    doc_printf(doc, "%.1f %.1f %.1f %.1f ESPrc\n",
               bboxx + margin, bboxy + margin,
               bboxw - 2 * margin, bboxl - 2 * margin);
  }
}


/*
 * 'write_label_prolog()' - Write the prolog with the classification
 *                          and page label.
 */

static void
write_label_prolog(pstops_doc_t *doc,	/* I - Document info */
                   const char   *label,	/* I - Page label */
		   float        bottom,	/* I - Bottom position in points */
		   float        top,	/* I - Top position in points */
		   float        width)	/* I - Width in points */
{
  const char	*classification;	/* CLASSIFICATION environment variable */
  const char	*ptr;			/* Temporary string pointer */


 /*
  * First get the current classification...
  */

  if ((classification = getenv("CLASSIFICATION")) == NULL)
    classification = "";
  if (strcmp(classification, "none") == 0)
    classification = "";

 /*
  * If there is nothing to show, bind an empty 'write labels' procedure
  * and return...
  */

  if (!classification[0] && (label == NULL || !label[0]))
  {
    doc_puts(doc, "userdict/ESPwl{}bind put\n");
    return;
  }

 /*
  * Set the classification + page label string...
  */

  doc_puts(doc, "userdict");
  if (!strcmp(classification, "confidential"))
    doc_puts(doc, "/ESPpl(CONFIDENTIAL");
  else if (!strcmp(classification, "classified"))
    doc_puts(doc, "/ESPpl(CLASSIFIED");
  else if (!strcmp(classification, "secret"))
    doc_puts(doc, "/ESPpl(SECRET");
  else if (!strcmp(classification, "topsecret"))
    doc_puts(doc, "/ESPpl(TOP SECRET");
  else if (!strcmp(classification, "unclassified"))
    doc_puts(doc, "/ESPpl(UNCLASSIFIED");
  else
  {
    doc_puts(doc, "/ESPpl(");

    for (ptr = classification; *ptr; ptr ++)
    {
      if (*ptr < 32 || *ptr > 126)
        doc_printf(doc, "\\%03o", *ptr);
      else if (*ptr == '_')
        doc_puts(doc, " ");
      else if (*ptr == '(' || *ptr == ')' || *ptr == '\\')
	doc_printf(doc, "\\%c", *ptr);
      else
        doc_printf(doc, "%c", *ptr);
    }
  }

  if (label)
  {
    if (classification[0])
      doc_puts(doc, " - ");

   /*
    * Quote the label string as needed...
    */

    for (ptr = label; *ptr; ptr ++)
    {
      if (*ptr < 32 || *ptr > 126)
        doc_printf(doc, "\\%03o", *ptr);
      else if (*ptr == '(' || *ptr == ')' || *ptr == '\\')
	doc_printf(doc, "\\%c", *ptr);
      else
        doc_printf(doc, "%c", *ptr);
    }
  }

  doc_puts(doc, ")put\n");

 /*
  * Then get a 14 point Helvetica-Bold font...
  */

  doc_puts(doc, "userdict/ESPpf /Helvetica-Bold findfont 14 scalefont put\n");

 /*
  * Finally, the procedure to write the labels on the page...
  */

  doc_puts(doc, "userdict/ESPwl{\n");
  doc_puts(doc, "  ESPpf setfont\n");
  doc_printf(doc, "  ESPpl stringwidth pop dup 12 add exch -0.5 mul %.0f add\n",
             width * 0.5f);
  doc_puts(doc, "  1 setgray\n");
  doc_printf(doc, "  dup 6 sub %.0f 3 index 20 ESPrf\n", bottom - 2.0);
  doc_printf(doc, "  dup 6 sub %.0f 3 index 20 ESPrf\n", top - 18.0);
  doc_puts(doc, "  0 setgray\n");
  doc_printf(doc, "  dup 6 sub %.0f 3 index 20 ESPrs\n", bottom - 2.0);
  doc_printf(doc, "  dup 6 sub %.0f 3 index 20 ESPrs\n", top - 18.0);
  doc_printf(doc, "  dup %.0f moveto ESPpl show\n", bottom + 2.0);
  doc_printf(doc, "  %.0f moveto ESPpl show\n", top - 14.0);
  doc_puts(doc, "pop\n");
  doc_puts(doc, "}bind put\n");
}


/*
 * 'write_labels()' - Write the actual page labels.
 *
 * This function is a copy of the one in common.c since we need to
 * use doc_puts/doc_printf instead of puts/printf...
 */

static void
write_labels(pstops_doc_t *doc,		/* I - Document information */
             int          orient)	/* I - Orientation of the page */
{
  float	width,				/* Width of page */
	length;				/* Length of page */


  doc_puts(doc, "gsave\n");

  if ((orient ^ Orientation) & 1)
  {
    width  = PageLength;
    length = PageWidth;
  }
  else
  {
    width  = PageWidth;
    length = PageLength;
  }

  switch (orient & 3)
  {
    case 1 : /* Landscape */
        doc_printf(doc, "%.1f 0.0 translate 90 rotate\n", length);
        break;
    case 2 : /* Reverse Portrait */
        doc_printf(doc, "%.1f %.1f translate 180 rotate\n", width, length);
        break;
    case 3 : /* Reverse Landscape */
        doc_printf(doc, "0.0 %.1f translate -90 rotate\n", width);
        break;
  }

  doc_puts(doc, "ESPwl\n");
  doc_puts(doc, "grestore\n");
}


/*
 * 'write_options()' - Write options provided via %%IncludeFeature.
 */

static void
write_options(
    pstops_doc_t  *doc,		/* I - Document */
    ppd_file_t    *ppd,		/* I - PPD file */
    int           num_options,	/* I - Number of options */
    cups_option_t *options)	/* I - Options */
{
  int		i;		/* Looping var */
  ppd_option_t	*option;	/* PPD option */
  float		min_order;	/* Minimum OrderDependency value */
  char		*doc_setup,	/* DocumentSetup commands to send */
		*any_setup;	/* AnySetup commands to send */


 /*
  * Figure out the minimum OrderDependency value...
  */

  if ((option = ppdFindOption(ppd, "PageRegion")) != NULL)
    min_order = option->order;
  else
    min_order = 999.0f;

  for (i = 0; i < num_options; i ++)
    if ((option = ppdFindOption(ppd, options[i].name)) != NULL &&
	option->order < min_order)
      min_order = option->order;

 /*
  * Mark and extract them...
  */

  cupsMarkOptions(ppd, num_options, options);

  doc_setup = ppdEmitString(ppd, PPD_ORDER_DOCUMENT, min_order);
  any_setup = ppdEmitString(ppd, PPD_ORDER_ANY, min_order);

 /*
  * Then send them out...
  */

  if (doc->number_up > 1)
  {
   /*
    * Temporarily restore setpagedevice so we can set the options...
    */

    doc_puts(doc, "userdict/setpagedevice/CUPSsetpagedevice load put\n");
  }

  if (doc_setup)
  {
    doc_puts(doc, doc_setup);
    free(doc_setup);
  }

  if (any_setup)
  {
    doc_puts(doc, any_setup);
    free(any_setup);
  }

  if (doc->number_up > 1)
  {
   /*
    * Disable setpagedevice again...
    */

    doc_puts(doc, "userdict/setpagedevice{pop}bind put\n");
  }
}
