/*
 *   PDF to PostScript filter front-end for CUPS.
 *
 *   Copyright 2007-2011 by Apple Inc.
 *   Copyright 1997-2006 by Easy Software Products.
 *   Copyright 2011-2013 by Till Kamppeter
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "COPYING"
 *   which should have been included with this file.
 *
 * Contents:
 *
 *   parsePDFTOPDFComment() - Check whether we are executed after pdftopdf
 *   remove_options()       - Remove unwished entries from an option list
 *   log_command_line()     - Log the command line of a program which we call
 *   main()                 - Main entry for filter...
 *   cancel_job()           - Flag the job as canceled.
 */

/*
 * Include necessary headers...
 */

#include <config.h>
#include <cups/cups.h>
#include <cups/ppd.h>
#include <cups/file.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <cupsfilters/image-private.h>

#define MAX_CHECK_COMMENT_LINES	20

/*
 * Type definitions
 */

typedef unsigned renderer_t;
enum renderer_e {GS = 0, PDFTOPS = 1, ACROREAD = 2, PDFTOCAIRO = 3, MUPDF = 4, HYBRID = 5};

/*
 * Local functions...
 */

static void		cancel_job(int sig);


/*
 * Local globals...
 */

static int		job_canceled = 0;
int			pdftopdfapplied = 0;
char			deviceCopies[32] = "1";
int			deviceCollate = 0;
char                    make_model[128] = "";


/*
 * When calling the "pstops" filter we exclude the following options from its
 * command line as we have applied these options already to the PDF input,
 * either on the "pdftops"/Ghostscript call in this filter or by use of the
 * "pdftopdf" filter before this filter.
 */

const char *pstops_exclude_general[] = {
  "fitplot",
  "fit-to-page",
  "landscape",
  "orientation-requested",
  NULL
};

const char *pstops_exclude_page_management[] = {
  "brightness",
  "Collate",
  "cupsEvenDuplex",
  "gamma",
  "hue",
  "ipp-attribute-fidelity",
  "MirrorPrint",
  "mirror",
  "multiple-document-handling",
  "natural-scaling",
  "number-up",
  "number-up-layout",
  "OutputOrder",
  "page-border",
  "page-bottom",
  "page-label",
  "page-left",
  "page-ranges",
  "page-right",
  "page-set",
  "page-top",
  "position",
  "saturation",
  "scaling",
  NULL
};


/*
 * Options which we do not pass on to the mupdftoraster and rastertops
 * filters when we render via MuPDF
 */

const char *mupdf_exclude_general[] = {
  "Resolution",
  NULL
};


/*
 * Check whether we were called after the "pdftopdf" filter and extract
 * parameters passed over by "pdftopdf" in the header comments of the PDF
 * file
 */

static void parsePDFTOPDFComment(char *filename)
{
  char buf[4096];
  int i;
  FILE *fp;

  if ((fp = fopen(filename,"rb")) == 0) {
    fprintf(stderr, "ERROR: pdftops - cannot open print file \"%s\"\n",
            filename);
    return;
  }

  /* skip until PDF start header */
  while (fgets(buf,sizeof(buf),fp) != 0) {
    if (strncmp(buf,"%PDF",4) == 0) {
      break;
    }
  }
  for (i = 0;i < MAX_CHECK_COMMENT_LINES;i++) {
    if (fgets(buf,sizeof(buf),fp) == 0) break;
    if (strncmp(buf,"%%PDFTOPDFNumCopies",19) == 0) {
      char *p;

      p = strchr(buf+19,':') + 1;
      while (*p == ' ' || *p == '\t') p++;
      strncpy(deviceCopies, p, sizeof(deviceCopies));
      deviceCopies[sizeof(deviceCopies) - 1] = '\0';
      p = deviceCopies + strlen(deviceCopies) - 1;
      while (*p == ' ' || *p == '\t'  || *p == '\r'  || *p == '\n') p--;
      *(p + 1) = '\0';
      pdftopdfapplied = 1;
    } else if (strncmp(buf,"%%PDFTOPDFCollate",17) == 0) {
      char *p;

      p = strchr(buf+17,':') + 1;
      while (*p == ' ' || *p == '\t') p++;
      if (strncasecmp(p,"true",4) == 0) {
	deviceCollate = 1;
      } else {
	deviceCollate = 0;
      }
      pdftopdfapplied = 1;
    } else if (strcmp(buf,"% This file was generated by pdftopdf") == 0) {
      pdftopdfapplied = 1;
    }
  }

  fclose(fp);
}


/*
 * Remove all options in option_list from the string option_str, including
 * option values after an "=" sign and preceded "no" before boolean options
 */

void remove_options(char *options_str, const char **option_list)
{
  const char	**option;		/* Option to be removed now */
  char		*option_start,		/* Start of option in string */
		*option_end;		/* End of option in string */

  for (option = option_list; *option; option ++)
  {
    option_start = options_str;

    while ((option_start = strcasestr(option_start, *option)) != NULL)
    {
      if (!option_start[strlen(*option)] ||
          isspace(option_start[strlen(*option)] & 255) ||
          option_start[strlen(*option)] == '=')
      {
        /*
         * Strip option...
         */

        option_end = option_start + strlen(*option);

        /* Remove preceding "no" of boolean option */
        if ((option_start - options_str) >= 2 &&
            !strncasecmp(option_start - 2, "no", 2))
          option_start -= 2;

	/* Is match of the searched option name really at the beginning of
	   the name of the option in the command line? */
	if ((option_start > options_str) &&
	    (!isspace(*(option_start - 1) & 255)))
	{
	  /* Prevent the same option to be found again. */
	  option_start += 1;
	  /* Skip */
	  continue;
	}

        /* Remove "=" and value */
        while (*option_end && !isspace(*option_end & 255))
          option_end ++;

        /* Remove spaces up to next option */
        while (*option_end && isspace(*option_end & 255))
          option_end ++;

        memmove(option_start, option_end, strlen(option_end) + 1);
      } else {
        /* Prevent the same option to be found again. */
        option_start += 1;
      }
    }
  }
}


/*
 * Before calling any command line utility, log its command line in CUPS'
 * debug mode
 */

void
log_command_line(const char* file, char *const argv[])
{
  int i;
  char *apos;

  /* Debug output: Full command line of program to be called */
  fprintf(stderr, "DEBUG: Running command line for %s:",
	  (file ? file : argv[0]));
  if (file)
    fprintf(stderr, " %s", file);
  for (i = (file ? 1 : 0); argv[i]; i ++) {
    if ((strchr(argv[i],' ')) || (strchr(argv[i],'\t')))
      apos = "'";
    else
      apos = "";
    fprintf(stderr, " %s%s%s", apos, argv[i], apos);
  }
  fprintf(stderr, "\n");
}


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

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  renderer_t    renderer = CUPS_PDFTOPS_RENDERER; /* Renderer: gs or pdftops or acroread or pdftocairo or hybrid */
  int		fd = 0;			/* Copy file descriptor */
  char		*filename,		/* PDF file to convert */
		tempfile[1024];		/* Temporary file */
  char		buffer[8192];		/* Copy buffer */
  int		bytes;			/* Bytes copied */
  int		num_options;		/* Number of options */
  cups_option_t	*options;		/* Options */
  const char	*val;			/* Option value */
  ppd_file_t	*ppd;			/* PPD file */
  char		resolution[128] = "";   /* Output resolution */
  int           xres = 0, yres = 0,     /* resolution values */
                mres, res,
                maxres = CUPS_PDFTOPS_MAX_RESOLUTION,
                                        /* Maximum image rendering resolution */
                numvalues;              /* Number of values actually read */
  ppd_choice_t  *choice;
  ppd_attr_t    *attr;
  cups_page_header2_t header;
  cups_file_t	*fp;			/* Post-processing input file */
  int		pdf_pid,		/* Process ID for pdftops/gs */
		pdf_argc = 0,		/* Number of args for pdftops/gs */
		pstops_pid,		/* Process ID of pstops filter */
		pstops_pipe[2],		/* Pipe to pstops filter */
		need_post_proc = 0,     /* Post-processing needed? */
		post_proc_pid = 0,	/* Process ID of post-processing */
		post_proc_pipe[2],	/* Pipe to post-processing */
		need_rastertops = 0,    /* rastertops (for MuPDF) needed */
		rastertops_pid = 0,	/* Process ID of rastertops */
		rastertops_pipe[2],	/* Pipe to rastertops */
		wait_children,		/* Number of child processes left */
		wait_pid,		/* Process ID from wait() */
		wait_status,		/* Status from child */
		exit_status = 0;	/* Exit status */
  char		*pdf_argv[100],		/* Arguments for pdftops/gs */
		pstops_path[1024],	/* Path to pstops program */
		*pstops_argv[7],	/* Arguments for pstops filter */
		*pstops_options,	/* Options for pstops filter */
		*pstops_end,		/* End of pstops filter option */
		rastertops_path[1024],	/* Path to rastertops program */
		*rastertops_argv[7],	/* Arguments for rastertops filter */
		mupdftoraster_path[1024], /* Path to mupdftoraster program */
                *mupdf_options,		/* Options for mupdftoraster and
					   rastertops filters */
		*mupdf_end,		/* End of mupdftoraster and rastertops
					   filter option */
		*ptr;			/* Pointer into value */
  const char	*cups_serverbin;	/* CUPS_SERVERBIN environment
					   variable */
  int		duplex, tumble;         /* Duplex settings for PPD-less
					   printing */
#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);

 /*
  * Make sure we have the right number of arguments for CUPS!
  */

  if (argc < 6 || argc > 7)
  {
    fprintf(stderr, "Usage: %s job user title copies options [file]\n",
	    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 */

 /*
  * Copy stdin if needed...
  */

  if (argc == 6)
  {
   /*
    * Copy stdin to a temp file...
    */

    if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
    {
      perror("DEBUG: Unable to copy PDF file");
      return (1);
    }

    fprintf(stderr, "DEBUG: pdftops - copying to temp print file \"%s\"\n",
            tempfile);

    while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0)
      bytes = write(fd, buffer, bytes);

    close(fd);

    filename = tempfile;
  }
  else
  {
   /*
    * Use the filename on the command-line...
    */

    filename    = argv[6];
    tempfile[0] = '\0';
  }

 /*
  * Read out copy counts and collate setting passed over by pdftopdf
  */

  parsePDFTOPDFComment(filename);

 /*
  * Read out the options from the fifth command line argument
  */

  num_options = cupsParseOptions(argv[5], 0, &options);

 /*
  * Load the PPD file and mark options...
  */

  ppd = ppdOpenFile(getenv("PPD"));
  if (ppd)
  {
    ppdMarkDefaults(ppd);
    cupsMarkOptions(ppd, num_options, options);
  }

  if ((val = cupsGetOption("make-and-model", num_options, options)) != NULL)
  {
    strncpy(make_model, val, sizeof(make_model));
    if (strlen(val) > 127)
      make_model[127] = '\0';
    for (ptr = make_model; *ptr; ptr ++)
      if (*ptr == '-') *ptr = ' ';
  }
  else if (ppd)
  {
    snprintf(make_model, sizeof(make_model), "%s %s", ppd->manufacturer,
	     ppd->product + 1);
    make_model[strlen(make_model) - 1] = '\0';
  }
  fprintf(stderr, "DEBUG: Printer make and model: %s\n", make_model);

 /*
  * Select the PDF renderer: Ghostscript (gs), Poppler (pdftops),
  * Adobe Reader (arcoread), Poppler with Cairo (pdftocairo), or
  * Hybrid (hybrid, Poppler for Brother, Minolta, Konica Minolta, Dell, and
  * old HP LaserJets and Ghostscript otherwise)
  */

  if ((val = cupsGetOption("pdftops-renderer", num_options, options)) != NULL)
  {
    if (strcasecmp(val, "gs") == 0)
      renderer = GS;
    else if (strcasecmp(val, "pdftops") == 0)
      renderer = PDFTOPS;
    else if (strcasecmp(val, "acroread") == 0)
      renderer = ACROREAD;
    else if (strcasecmp(val, "pdftocairo") == 0)
      renderer = PDFTOCAIRO;
    else if (strcasecmp(val, "mupdf") == 0)
      renderer = MUPDF;
    else if (strcasecmp(val, "hybrid") == 0)
      renderer = HYBRID;
    else
      fprintf(stderr,
	      "WARNING: Invalid value for \"pdftops-renderer\": \"%s\"\n", val);
  }

  if (renderer == HYBRID)
  {
    if (make_model[0] &&
	(!strncasecmp(make_model, "Brother", 7) ||
	 !strncasecmp(make_model, "Dell", 4) ||
	 strcasestr(make_model, "Minolta") ||
	 (!strncasecmp(make_model, "Apple", 5) &&
	  (ptr = strcasestr(make_model, "LaserWriter")) &&
	  (ptr = strcasestr(ptr + 11, "12")) &&
	  (ptr = strcasestr(ptr + 2, "640")))))
    {
      fprintf(stderr, "DEBUG: Switching to Poppler's pdftops instead of Ghostscript for Brother, Minolta, Konica Minolta, Dell, and Apple LaserWriter 12/640 to work around bugs in the printer's PS interpreters\n");
      renderer = PDFTOPS;
    }
    else
      renderer = GS;
   /*
    * Use Poppler instead of Ghostscript for old HP LaserJet printers due to
    * a bug in their PS interpreters. They are very slow with Ghostscript.
    * A LaserJet is considered old if its model number does not have a letter
    * in the beginning, like LaserJet 3 or LaserJet 4000, not LaserJet P2015.
    * See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=742765
    */
    if (make_model[0] &&
	((!strncasecmp(make_model, "HP", 2) ||
	  !strncasecmp(make_model, "Hewlett-Packard", 15) ||
	  !strncasecmp(make_model, "Hewlett Packard", 15)) &&
	 (ptr = strcasestr(make_model, "LaserJet"))))
    {
      for (ptr += 8; *ptr; ptr ++)
      {
	if (isspace(*ptr)) continue;
	if (isdigit(*ptr))
	{
	  fprintf(stderr, "DEBUG: Switching to Poppler's pdftops instead of Ghostscript for old HP LaserJet (\"LaserJet <number>\", no letters before <number>) printers to work around bugs in the printer's PS interpreters\n");
	  renderer = PDFTOPS;
	}
	break;
      }
    }
  }

 /*
  * Build the pstops command-line...
  */

  if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
    cups_serverbin = CUPS_SERVERBIN;

  snprintf(pstops_path, sizeof(pstops_path), "%s/filter/pstops",
           cups_serverbin);

  pstops_options = strdup(argv[5]);

  /*
   * Strip options which "pstops" does not need to apply any more
   */
  remove_options(pstops_options, pstops_exclude_general);
  if (pdftopdfapplied)
    remove_options(pstops_options, pstops_exclude_page_management);

  if (pdftopdfapplied && deviceCollate)
  {
   /*
    * Add collate option to the pstops call if pdftopdf has found out that the
    * printer does hardware collate.
    */

    pstops_options = realloc(pstops_options, strlen(pstops_options) + 9);
    if (!pstops_options) {
      fprintf(stderr, "ERROR: Can't allocate pstops_options\n");
      exit(2);
    }   
    pstops_end = pstops_options + strlen(pstops_options);
    strcpy(pstops_end, " Collate");
  }

  pstops_argv[0] = argv[0];		/* Printer */
  pstops_argv[1] = argv[1];		/* Job */
  pstops_argv[2] = argv[2];		/* User */
  pstops_argv[3] = argv[3];		/* Title */
  if (pdftopdfapplied)
    pstops_argv[4] = deviceCopies;     	/* Copies */
  else
    pstops_argv[4] = argv[4];		/* Copies */
  pstops_argv[5] = pstops_options;	/* Options */
  pstops_argv[6] = NULL;

  log_command_line("pstops", pstops_argv);

 /*
  * Build the rastertops command-line (MuPDF only)...
  */

  if (renderer == MUPDF)
  {
    if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
      cups_serverbin = CUPS_SERVERBIN;

    snprintf(rastertops_path, sizeof(rastertops_path), "%s/filter/rastertops",
	     cups_serverbin);

    mupdf_options = strdup(argv[5]);
    remove_options(mupdf_options, mupdf_exclude_general);

    rastertops_argv[0] = argv[0];		/* Printer */
    rastertops_argv[1] = argv[1];		/* Job */
    rastertops_argv[2] = argv[2];		/* User */
    rastertops_argv[3] = argv[3];		/* Title */
    if (pdftopdfapplied)
      rastertops_argv[4] = deviceCopies;     	/* Copies */
    else
      rastertops_argv[4] = argv[4];		/* Copies */
    rastertops_argv[5] = mupdf_options;	/* Options */
    rastertops_argv[6] = NULL;

    log_command_line("rastertops", rastertops_argv);
  }

 /*
  * Build the command-line for the pdftops, gs, pdftocairo, or
  * acroread filter...
  */

  if (renderer == PDFTOPS)
  {
    pdf_argv[0] = (char *)"pdftops";
    pdf_argc    = 1;
  }
  else if (renderer == GS)
  {
    pdf_argv[0] = (char *)"gs";
    pdf_argv[1] = (char *)"-q";
    pdf_argv[2] = (char *)"-dNOPAUSE";
    pdf_argv[3] = (char *)"-dBATCH";
    pdf_argv[4] = (char *)"-dSAFER";
    pdf_argv[5] = (char *)"-dNOMEDIAATTRS";
#    ifdef HAVE_GHOSTSCRIPT_PS2WRITE
    pdf_argv[6] = (char *)"-sDEVICE=ps2write";
#    else
    pdf_argv[6] = (char *)"-sDEVICE=pswrite";
#    endif /* HAVE_GHOSTSCRIPT_PS2WRITE */
    pdf_argv[7] = (char *)"-dShowAcroForm";
    pdf_argv[8] = (char *)"-sOUTPUTFILE=%stdout";
    pdf_argc    = 9;
  }
  else if (renderer == PDFTOCAIRO)
  {
    pdf_argv[0] = (char *)"pdftocairo";
    pdf_argv[1] = (char *)"-ps";
    pdf_argc    = 2;
  }
  else if (renderer == ACROREAD)
  {
    pdf_argv[0] = (char *)"acroread";
    pdf_argv[1] = (char *)"-toPostScript";
    pdf_argc    = 2;
  }
  else if (renderer == MUPDF)
  {
    snprintf(mupdftoraster_path, sizeof(mupdftoraster_path),
	     "%s/filter/mupdftoraster", cups_serverbin);
    pdf_argv[0] = "mupdftoraster";
    pdf_argv[1] = argv[1];		/* Job */
    pdf_argv[2] = argv[2];		/* User */
    pdf_argv[3] = argv[3];		/* Title */
    if (pdftopdfapplied)
      pdf_argv[4] = deviceCopies;     	/* Copies */
    else
      pdf_argv[4] = argv[4];		/* Copies */
    pdf_argv[5] = mupdf_options;	/* Options */
    pdf_argv[6] = filename;
    pdf_argv[7] = NULL;
    pdf_argc    = 8;
  }

 /*
  * Set language level and TrueType font handling...
  */

  if (ppd)
  {
    if (ppd->language_level == 1)
    {
      if (renderer == PDFTOPS)
      {
	pdf_argv[pdf_argc++] = (char *)"-level1";
	pdf_argv[pdf_argc++] = (char *)"-noembtt";
      }
      else if (renderer == GS)
	pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=1";
      else if (renderer == PDFTOCAIRO)
        fprintf(stderr, "WARNING: Level 1 PostScript not supported by pdftocairo.");
      else if (renderer == ACROREAD)
        fprintf(stderr, "WARNING: Level 1 PostScript not supported by acroread.");
      else if (renderer == MUPDF)
        fprintf(stderr, "WARNING: Level 1 PostScript not supported by rastertops.");
    }
    else if (ppd->language_level == 2)
    {
      if (renderer == PDFTOPS)
      {
	pdf_argv[pdf_argc++] = (char *)"-level2";
	if (!ppd->ttrasterizer)
	  pdf_argv[pdf_argc++] = (char *)"-noembtt";
      }
      else if (renderer == GS)
	pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=2";
      else /* PDFTOCAIRO || ACROREAD */
        pdf_argv[pdf_argc++] = (char *)"-level2";
    }
    else
    {
      if (renderer == PDFTOPS)
      {
        /* Do not emit PS Level 3 with Poppler on HP PostScript laser printers
	   as some do not like it. See https://bugs.launchpad.net/bugs/277404.*/
	if (!make_model[0] ||
	    ((!strncasecmp(make_model, "HP", 2) ||
	      !strncasecmp(make_model, "Hewlett-Packard", 15) ||
	      !strncasecmp(make_model, "Hewlett Packard", 15)) &&
	     (strcasestr(make_model, "LaserJet"))))
	  pdf_argv[pdf_argc++] = (char *)"-level2";
	else
	  pdf_argv[pdf_argc++] = (char *)"-level3";
      }
      else if (renderer == GS)
	pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=3";
      else /* PDFTOCAIRO || ACROREAD */
        pdf_argv[pdf_argc++] = (char *)"-level3";
    }
  }
  else
  {
    if (renderer == PDFTOPS)
    {
      /* Do not emit PS Level 3 with Poppler on HP PostScript laser printers
	 as some do not like it. See https://bugs.launchpad.net/bugs/277404.*/
      if (!make_model[0] ||
	  ((!strncasecmp(make_model, "HP", 2) ||
	    !strncasecmp(make_model, "Hewlett-Packard", 15) ||
	    !strncasecmp(make_model, "Hewlett Packard", 15)) &&
	   (strcasestr(make_model, "LaserJet"))))
	pdf_argv[pdf_argc++] = (char *)"-level2";
      else
	pdf_argv[pdf_argc++] = (char *)"-level3";
      pdf_argv[pdf_argc++] = (char *)"-noembtt";
    }
    else if (renderer == GS)
      pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=3";
    else /* PDFTOCAIRO || ACROREAD */
      pdf_argv[pdf_argc++] = (char *)"-level3";
  }

#ifdef HAVE_POPPLER_PDFTOPS_WITH_ORIGPAGESIZES
  if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO))
  {
   /*
    *  Use the page sizes of the original PDF document, this way documents
    *  which contain pages of different sizes can be printed correctly
    */

    pdf_argv[pdf_argc++] = (char *)"-origpagesizes";
    pdf_argv[pdf_argc++] = (char *)"-nocenter";
  }
  else
#endif /* HAVE_POPPLER_PDFTOPS_WITH_ORIGPAGESIZES */
  if (renderer == ACROREAD)
  {
   /*
    * Use the page sizes of the original PDF document, this way documents
    * which contain pages of different sizes can be printed correctly
    */

    pdf_argv[pdf_argc++] = (char *)"-choosePaperByPDFPageSize";
  }

 /*
  * Set output resolution ...
  */

  if (ppd)
  {
    /* Ignore error exits of cupsRasterInterpretPPD(), if it found a resolution
       setting before erroring it is OK for us */
    cupsRasterInterpretPPD(&header, ppd, num_options, options, NULL);
    /* 100 dpi is default, this means that if we have 100 dpi here this
       method failed to find the printing resolution */
    if (header.HWResolution[0] > 100 && header.HWResolution[1] > 100)
    {
      xres = header.HWResolution[0];
      yres = header.HWResolution[1];
    }
    else if ((choice = ppdFindMarkedChoice(ppd, "Resolution")) != NULL)
      strncpy(resolution, choice->choice, sizeof(resolution));
    else if ((attr = ppdFindAttr(ppd,"DefaultResolution",NULL)) != NULL)
      strncpy(resolution, attr->value, sizeof(resolution));
    resolution[sizeof(resolution)-1] = '\0';
    if ((xres == 0) && (yres == 0) &&
	((numvalues = sscanf(resolution, "%dx%d", &xres, &yres)) <= 0))
      fprintf(stderr,
	      "DEBUG: No resolution information found in the PPD file.\n");
  }
  if ((xres == 0) && (yres == 0))
  {
    if ((val = cupsGetOption("printer-resolution", num_options,
			   options)) != NULL ||
	(val = cupsGetOption("Resolution", num_options, options)) != NULL)
    {
      xres = yres = strtol(val, (char **)&ptr, 10);
      if (ptr > val && xres > 0)
      {
	if (*ptr == 'x')
	  yres = strtol(ptr + 1, (char **)&ptr, 10);
      }

      if (ptr <= val || xres <= 0 || yres <= 0 || !ptr ||
	  (*ptr != '\0' &&
	   strcasecmp(ptr, "dpi") &&
	   strcasecmp(ptr, "dpc") &&
	   strcasecmp(ptr, "dpcm")))
      {
	fprintf(stderr, "DEBUG: Bad resolution value \"%s\".\n", val);
      }
      else
      {
	if (!strcasecmp(ptr, "dpc") ||
	    !strcasecmp(ptr, "dpcm"))
	{
	  xres = xres * 254 / 100;
	  yres = yres * 254 / 100;
	}
      }
    }
  }
  if ((xres > 0) || (yres > 0))
  {
    if (yres == 0) yres = xres;
    if (xres == 0) xres = yres;
    if (xres > yres)
      res = yres;
    else
      res = xres;
  }
  else
    res = 300;

 /*
  * Get the ceiling for the image rendering resolution
  */

  if ((val = cupsGetOption("pdftops-max-image-resolution", num_options, options)) != NULL)
  {
    if ((numvalues = sscanf(val, "%d", &mres)) > 0)
      maxres = mres;
    else
      fprintf(stderr,
	      "WARNING: Invalid value for \"pdftops-max-image-resolution\": \"%s\"\n", val);
  }

 /*
  * Reduce the image rendering resolution to not exceed a given maximum
  * to make processing of jobs by the PDF->PS converter and the printer faster
  *
  * maxres = 0 means no limit
  */

  if (maxres)
    while (res > maxres)
      res = res / 2;

  if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO))
  {
#ifdef HAVE_POPPLER_PDFTOPS_WITH_RESOLUTION
   /*
    * Set resolution to avoid slow processing by the printer when the
    * resolution of embedded images does not match the printer's resolution
    */
    pdf_argv[pdf_argc++] = (char *)"-r";
    snprintf(resolution, sizeof(resolution), "%d", res);
    pdf_argv[pdf_argc++] = resolution;
    fprintf(stderr, "DEBUG: Using image rendering resolution %d dpi\n", res);
#endif /* HAVE_POPPLER_PDFTOPS_WITH_RESOLUTION */
    pdf_argv[pdf_argc++] = filename;
    pdf_argv[pdf_argc++] = (char *)"-";
  }
  else if (renderer == GS)
  {
   /*
    * Set resolution to avoid slow processing by the printer when the
    * resolution of embedded images does not match the printer's resolution
    */
    snprintf(resolution, 127, "-r%d", res);
    pdf_argv[pdf_argc++] = resolution;
    fprintf(stderr, "DEBUG: Using image rendering resolution %d dpi\n", res);
   /*
    * PostScript debug mode: If you send a job with "lpr -o psdebug" Ghostscript
    * will not compress the pages, so that the PostScript code can get
    * analysed. This is especially important if a PostScript printer errors or
    * misbehaves on Ghostscript's output.
    * On Kyocera and Utax (uses Kyocera hard- and software) printers we always
    * suppress page compression, to avoid slow processing of raster images.
    */
    val = cupsGetOption("psdebug", num_options, options);
    if ((val && strcasecmp(val, "no") && strcasecmp(val, "off") &&
	 strcasecmp(val, "false")) ||
	(make_model[0] &&
	 (!strncasecmp(make_model, "Kyocera", 7) ||
	  !strncasecmp(make_model, "Utax", 4))))
    {
      fprintf(stderr, "DEBUG: Deactivated compression of pages in Ghostscript's PostScript output (\"psdebug\" debug mode or Kyocera/Utax printer)\n");
      pdf_argv[pdf_argc++] = (char *)"-dCompressPages=false";
    }
   /*
    * The PostScript interpreters on many printers have bugs which make
    * the interpreter crash, error out, or otherwise misbehave on too
    * heavily compressed input files, especially if code with compressed
    * elements is compressed again. Therefore we reduce compression here.
    */
    pdf_argv[pdf_argc++] = (char *)"-dCompressFonts=false";
    pdf_argv[pdf_argc++] = (char *)"-dNoT3CCITT";
    if (make_model[0] &&
	!strncasecmp(make_model, "Brother", 7))
    {
      fprintf(stderr, "DEBUG: Deactivation of Ghostscript's image compression for Brother printers to workarounmd PS interpreter bug\n");
      pdf_argv[pdf_argc++] = (char *)"-dEncodeMonoImages=false";
      pdf_argv[pdf_argc++] = (char *)"-dEncodeColorImages=false";
    }
   /*
    * Toshiba's PS interpreters have an issue with how we handle
    * TrueType/Type42 fonts, therefore we add command line options to turn
    * the TTF outlines into bitmaps, usually Type 3 PostScript fonts, only
    * large glyphs into normal image data.
    * See https://bugs.launchpad.net/bugs/978120
    */
    if (make_model[0] &&
	!strncasecmp(make_model, "Toshiba", 7))
    {
      fprintf(stderr, "DEBUG: To work around a bug in Toshiba's PS interpreters turn TTF font glyphs into bitmaps, usually Type 3 PS fonts, or images for large characters\n");
      pdf_argv[pdf_argc++] = (char *)"-dHaveTrueTypes=false";
    }
    pdf_argv[pdf_argc++] = (char *)"-dNOINTERPOLATE";
    pdf_argv[pdf_argc++] = (char *)"-c";
    if (make_model[0] &&
	!strncasecmp(make_model, "Toshiba", 7))
      pdf_argv[pdf_argc++] = (char *)"<< /MaxFontItem 500000 >> setuserparams";
    pdf_argv[pdf_argc++] = (char *)"save pop";
    pdf_argv[pdf_argc++] = (char *)"-f";
    pdf_argv[pdf_argc++] = filename;
  }
  else if (renderer == MUPDF)
  {
   /*
    * Add Resolution option to avoid slow processing by the printer when the
    * resolution of embedded images does not match the printer's resolution
    */
    mupdf_options = realloc(mupdf_options, strlen(mupdf_options) + 30);
    if (!mupdf_options) {
      fprintf(stderr, "ERROR: Can't allocate mupdf_options\n");
      exit(2);
    }
    mupdf_end = mupdf_options + strlen(mupdf_options);
    snprintf(mupdf_end, 30, " Resolution=%ddpi", res);
    pdf_argv[5] = mupdf_options;
    rastertops_argv[5] = mupdf_options;
  }

  pdf_argv[pdf_argc] = NULL;

  log_command_line(NULL, pdf_argv);

 /*
  * Do we need post-processing of the PostScript output to work around bugs
  * of the printer's PostScript interpreter?
  */

  if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO) ||
      (renderer == MUPDF))
    need_post_proc = 0;
  else if (renderer == GS)
    need_post_proc =
      (make_model[0] &&
       (!strncasecmp(make_model, "Kyocera", 7) ||
	!strncasecmp(make_model, "Utax", 4) ||
	!strncasecmp(make_model, "Brother", 7)) ? 1 : 0);
  else
    need_post_proc = 1;

 /*
  * Do we need post-processing of the PostScript output to apply option
  * settings when doing PPD-less printing?
  */

  if (!ppd)
    need_post_proc = 1;

 /*
  * Do we need to run rastertops when we are using mupdftoraster as PDF
  * renderer
  */

  if (renderer == MUPDF)
    need_rastertops = 1;

 /*
  * Execute "pdftops/gs/mupdftoraster [ | rastertops ] [ | PS post-processing ]
  *          | pstops"...
  */


 /*
  * Create a pipe for each pair of subsequent processes. The variables
  * are named by the receiving process.
  */
  
  if (pipe(pstops_pipe))
  {
    perror("DEBUG: Unable to create pipe for pstops");

    exit_status = 1;
    goto error;
  }

  if (need_rastertops)
  {
    if (pipe(rastertops_pipe))
    {
      perror("DEBUG: Unable to create pipe for rastertops");

      exit_status = 1;
      goto error;
    }
  }

  if (need_post_proc)
  {
    if (pipe(post_proc_pipe))
    {
      perror("DEBUG: Unable to create pipe for post-processing");

      exit_status = 1;
      goto error;
    }
  }

  if ((pdf_pid = fork()) == 0)
  {
   /*
    * Child comes here...
    */

    if (need_rastertops)
    {
      dup2(rastertops_pipe[1], 1);
      close(rastertops_pipe[0]);
      close(rastertops_pipe[1]);
      if (need_post_proc)
      {
	close(post_proc_pipe[0]);
	close(post_proc_pipe[1]);
      }
    }
    else if (need_post_proc)
    {
      dup2(post_proc_pipe[1], 1);
      close(post_proc_pipe[0]);
      close(post_proc_pipe[1]);
    }
    else
      dup2(pstops_pipe[1], 1);
    close(pstops_pipe[0]);
    close(pstops_pipe[1]);

    if (renderer == PDFTOPS)
    {
      execvp(CUPS_POPPLER_PDFTOPS, pdf_argv);
      perror("DEBUG: Unable to execute pdftops program");
    }
    else if (renderer == GS)
    {
      execvp(CUPS_GHOSTSCRIPT, pdf_argv);
      perror("DEBUG: Unable to execute gs program");
    }
    else if (renderer == PDFTOCAIRO)
    {
      execvp(CUPS_POPPLER_PDFTOCAIRO, pdf_argv);
      perror("DEBUG: Unable to execute pdftocairo program");
    }
    else if (renderer == ACROREAD)
    {
      /*
       * use filename as stdin for acroread to force output to stdout
       */

      if ((fd = open(filename, O_RDONLY)))
      {
        dup2(fd, 0);
        close(fd);
      }
     
      execvp(CUPS_ACROREAD, pdf_argv);
      perror("DEBUG: Unable to execute acroread program");
    }
    else if (renderer == MUPDF)
    {
      execvp(mupdftoraster_path, pdf_argv);
      perror("DEBUG: Unable to execute mupdftoraster program");
    }

    exit(1);
  }
  else if (pdf_pid < 0)
  {
   /*
    * Unable to fork!
    */

    if (renderer == PDFTOPS)
      perror("DEBUG: Unable to execute pdftops program");
    else if (renderer == GS)
      perror("DEBUG: Unable to execute gs program");
    else if (renderer == PDFTOCAIRO)
      perror("DEBUG: Unable to execute pdftocairo program");
    else if (renderer == ACROREAD)
      perror("DEBUG: Unable to execute acroread program");
    else if (renderer == MUPDF)
      perror("DEBUG: Unable to execute mupdftoraster program");

    exit_status = 1;
    goto error;
  }

  fprintf(stderr, "DEBUG: Started filter %s (PID %d)\n", pdf_argv[0], pdf_pid);

  if (need_rastertops)
  {
    if ((rastertops_pid = fork()) == 0)
    {
     /*
      * Child comes here...
      */

      dup2(rastertops_pipe[0], 0);
      close(rastertops_pipe[0]);
      close(rastertops_pipe[1]);
      if (need_post_proc)
      {
	dup2(post_proc_pipe[1], 1);
	close(post_proc_pipe[0]);
	close(post_proc_pipe[1]);
      }
      else
	dup2(pstops_pipe[1], 1);
      close(pstops_pipe[0]);
      close(pstops_pipe[1]);

      execvp(rastertops_path, rastertops_argv);
      perror("DEBUG: Unable to execute rastertops program");
    }
    else if (post_proc_pid < 0)
    {
     /*
      * Unable to fork!
      */

      perror("DEBUG: Unable to execute rastertops program");

      exit_status = 1;
      goto error;
    }

    fprintf(stderr, "DEBUG: Started filter rastertops (PID %d)\n",
	    rastertops_pid);
  }

  if (need_post_proc)
  {
    if ((post_proc_pid = fork()) == 0)
    {
     /*
      * Child comes here...
      */

      dup2(post_proc_pipe[0], 0);
      close(post_proc_pipe[0]);
      close(post_proc_pipe[1]);
      dup2(pstops_pipe[1], 1);
      close(pstops_pipe[0]);
      close(pstops_pipe[1]);
      if (need_rastertops)
      {
	close(rastertops_pipe[0]);
	close(rastertops_pipe[1]);
      }

      fp = cupsFileStdin();

      if (renderer == ACROREAD)
      {
       /*
        * Set %Title and %For from filter arguments since acroread inserts
        * garbage for these when using -toPostScript
        */

        while ((bytes = cupsFileGetLine(fp, buffer, sizeof(buffer))) > 0 &&
               strncmp(buffer, "%%BeginProlog", 13))
        {
          if (strncmp(buffer, "%%Title", 7) == 0)
            printf("%%%%Title: %s\n", argv[3]);
          else if (strncmp(buffer, "%%For", 5) == 0)
            printf("%%%%For: %s\n", argv[2]);
          else
            printf("%s", buffer);
        }

       /*
	* Copy the rest of the file
	*/
	while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
	  fwrite(buffer, 1, bytes, stdout);
      }
      else
      {

       /*
	* Copy everything until after initial comments (Prolog section)
	*/
	while ((bytes = cupsFileGetLine(fp, buffer, sizeof(buffer))) > 0 &&
	       strncmp(buffer, "%%BeginProlog", 13) &&
	       strncmp(buffer, "%%EndProlog", 11) &&
	       strncmp(buffer, "%%BeginSetup", 12) &&
	       strncmp(buffer, "%%Page:", 7))
	  printf("%s", buffer);

	if (bytes > 0)
	{
	 /*
	  * Insert PostScript interpreter bug fix code in the beginning of
	  * the Prolog section (before the first active PostScript code)
	  */
	  if (strncmp(buffer, "%%BeginProlog", 13))
	  {
	    /* No Prolog section, create one */
	    fprintf(stderr, "DEBUG: Adding Prolog section for workaround PostScript code\n");
	    puts("%%BeginProlog");
	  }
	  else
	    printf("%s", buffer);

	  if (renderer == GS && make_model[0])
	  {

	   /*
	    * Kyocera (and Utax) printers have a bug in their PostScript
	    * interpreter making them crashing on PostScript input data
	    * generated by Ghostscript's "ps2write" output device.
	    *
	    * The problem can be simply worked around by preceding the
	    * PostScript code with some extra bits.
	    *
	    * See https://bugs.launchpad.net/bugs/951627
	    *
	    * In addition, at least some of Kyocera's PostScript printers are
	    * very slow on rendering images which request interpolation. So we
	    * also add some code to eliminate interpolation requests.
	    *
	    * See https://bugs.launchpad.net/bugs/1026974
	    */

	    if (!strncasecmp(make_model, "Kyocera", 7) ||
		!strncasecmp(make_model, "Utax", 4))
	    {
	      fprintf(stderr, "DEBUG: Inserted workaround PostScript code for Kyocera and Utax printers\n");
	      puts("% ===== Workaround insertion by pdftops CUPS filter =====");
	      puts("% Kyocera's/Utax's PostScript interpreter crashes on early name binding,");
	      puts("% so eliminate all \"bind\"s by redefining \"bind\" to no-op");
	      puts("/bind {} bind def");
	      puts("% Some Kyocera and Utax printers have an unacceptably slow implementation");
	      puts("% of image interpolation.");
	      puts("/image");
	      puts("{");
	      puts("  dup /Interpolate known");
	      puts("  {");
	      puts("    dup /Interpolate undef");
	      puts("  } if");
	      puts("  systemdict /image get exec");
	      puts("} def");
	      puts("% =====");
	    }

	   /*
	    * Brother printers have a bug in their PostScript interpreter
	    * making them printing one blank page if PostScript input data
	    * generated by Ghostscript's "ps2write" output device is used.
	    *
	    * The problem can be simply worked around by preceding the PostScript
	    * code with some extra bits.
	    *
	    * See https://bugs.launchpad.net/bugs/950713
	    */

	    else if (!strncasecmp(make_model, "Brother", 7))
	    {
	      fprintf(stderr, "DEBUG: Inserted workaround PostScript code for Brother printers\n");
	      puts("% ===== Workaround insertion by pdftops CUPS filter =====");
	      puts("% Brother's PostScript interpreter spits out the current page");
	      puts("% and aborts the job on the \"currenthalftone\" operator, so redefine");
	      puts("% it to null");
	      puts("/currenthalftone {//null} bind def");
	      puts("/orig.sethalftone systemdict /sethalftone get def");
	      puts("/sethalftone {dup //null eq not {//orig.sethalftone}{pop} ifelse} bind def");
	      puts("% =====");
	    }
	  }

	  if (strncmp(buffer, "%%BeginProlog", 13))
	  {
	    /* Close newly created Prolog section */
	    if (strncmp(buffer, "%%EndProlog", 11))
	      puts("%%EndProlog");
	    printf("%s", buffer);
	  }

	  if (!ppd)
	  {
	   /*
	    * Copy everything until the setup section
	    */
	    while (bytes > 0 &&
		   strncmp(buffer, "%%BeginSetup", 12) &&
		   strncmp(buffer, "%%EndSetup", 10) &&
		   strncmp(buffer, "%%Page:", 7))
	    {
	      bytes = cupsFileGetLine(fp, buffer, sizeof(buffer));
	      if (strncmp(buffer, "%%Page:", 7) &&
		  strncmp(buffer, "%%EndSetup", 10))
		printf("%s", buffer);
	    }
	  
	    if (bytes > 0)
	    {
	     /*
	      * Insert option PostScript code in Setup section
	      */
	      if (strncmp(buffer, "%%BeginSetup", 12))
	      {
		/* No Setup section, create one */
		fprintf(stderr, "DEBUG: Adding Setup section for option PostScript code\n");
		puts("%%BeginSetup");
	      }

	     /*
	      * Duplex
	      */
	      duplex = 0;
	      tumble = 0;
	      if ((val = cupsGetOption("sides", num_options, options)) != NULL ||
		  (val = cupsGetOption("Duplex", num_options, options)) != NULL)
	      {
		if (!strcasecmp(val, "On") ||
			 !strcasecmp(val, "True") || !strcasecmp(val, "Yes") ||
			 !strncasecmp(val, "two-sided", 9) ||
			 !strncasecmp(val, "TwoSided", 8) ||
			 !strncasecmp(val, "Duplex", 6))
		{
		  duplex = 1;
		  if (!strncasecmp(val, "DuplexTumble", 12))
		    tumble = 1;
		}
	      }

	      if ((val = cupsGetOption("sides", num_options, options)) != NULL ||
		  (val = cupsGetOption("Tumble", num_options, options)) != NULL)
	      {
		if (!strcasecmp(val, "None") || !strcasecmp(val, "Off") ||
		    !strcasecmp(val, "False") || !strcasecmp(val, "No") ||
		    !strcasecmp(val, "one-sided") || !strcasecmp(val, "OneSided") ||
		    !strcasecmp(val, "two-sided-long-edge") ||
		    !strcasecmp(val, "TwoSidedLongEdge") ||
		    !strcasecmp(val, "DuplexNoTumble"))
		  tumble = 0;
		else if (!strcasecmp(val, "On") ||
			 !strcasecmp(val, "True") || !strcasecmp(val, "Yes") ||
			 !strcasecmp(val, "two-sided-short-edge") ||
			 !strcasecmp(val, "TwoSidedShortEdge") ||
			 !strcasecmp(val, "DuplexTumble"))
		  tumble = 1;
	      }

	      if (duplex)
	      {
		if (tumble)
		  puts("<</Duplex true /Tumble true>> setpagedevice");
		else
		  puts("<</Duplex true /Tumble false>> setpagedevice");
	      }
	      else
		puts("<</Duplex false>> setpagedevice");

	     /*
	      * Resolution
	      */
	      if ((xres > 0) && (yres > 0))
		printf("<</HWResolution[%d %d]>> setpagedevice\n", xres, yres);

	     /*
	      * InputSlot/MediaSource
	      */
	      if ((val = cupsGetOption("media-position", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("MediaPosition", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("media-source", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("MediaSource", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("InputSlot", num_options,
				       options)) != NULL)
	      {
		if (!strncasecmp(val, "Auto", 4) ||
		    !strncasecmp(val, "Default", 7))
		  puts("<</ManualFeed false /MediaPosition 7>> setpagedevice");
		else if (!strcasecmp(val, "Main"))
		  puts("<</MediaPosition 0 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Alternate"))
		  puts("<</MediaPosition 1 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Manual"))
		  puts("<</MediaPosition 3 /ManualFeed true>> setpagedevice");
		else if (!strcasecmp(val, "Top"))
		  puts("<</MediaPosition 0 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Bottom"))
		  puts("<</MediaPosition 1 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "ByPassTray"))
		  puts("<</MediaPosition 3 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Tray1"))
		  puts("<</MediaPosition 3 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Tray2"))
		  puts("<</MediaPosition 0 /ManualFeed false>> setpagedevice");
		else if (!strcasecmp(val, "Tray3"))
		  puts("<</MediaPosition 1 /ManualFeed false>> setpagedevice");
	      }

	     /*
	      * ColorModel
	      */
	      if ((val = cupsGetOption("pwg-raster-document-type", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("PwgRasterDocumentType", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("print-color-mode", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("PrintColorMode", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("color-space", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("ColorSpace", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("color-model", num_options,
				       options)) != NULL ||
		  (val = cupsGetOption("ColorModel", num_options,
				       options)) != NULL)
	      {
		if (!strncasecmp(val, "Black", 5))
		  puts("<</ProcessColorModel /DeviceGray>> setpagedevice");
		else if (!strncasecmp(val, "Cmyk", 4))
		  puts("<</ProcessColorModel /DeviceCMYK>> setpagedevice");
		else if (!strncasecmp(val, "Cmy", 3))
		  puts("<</ProcessColorModel /DeviceCMY>> setpagedevice");
		else if (!strncasecmp(val, "Rgb", 3))
		  puts("<</ProcessColorModel /DeviceRGB>> setpagedevice");
		else if (!strncasecmp(val, "Gray", 4))
		  puts("<</ProcessColorModel /DeviceGray>> setpagedevice");
		else if (!strncasecmp(val, "Color", 5))
		  puts("<</ProcessColorModel /DeviceRGB>> setpagedevice");
	      }

	      if (strncmp(buffer, "%%BeginSetup", 12))
	      {
		/* Close newly created Setup section */
		if (strncmp(buffer, "%%EndSetup", 10))
		  puts("%%EndSetup");
		printf("%s", buffer);
	      }
	    }
	  }

	 /*
	  * Copy the rest of the file
	  */
	  while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
	    fwrite(buffer, 1, bytes, stdout);
	}
      }

      exit(0);
    }
    else if (post_proc_pid < 0)
    {
     /*
      * Unable to fork!
      */

      perror("DEBUG: Unable to execute post-processing process");

      exit_status = 1;
      goto error;
    }

    fprintf(stderr, "DEBUG: Started post-processing (PID %d)\n", post_proc_pid);
  }

  if ((pstops_pid = fork()) == 0)
  {
   /*
    * Child comes here...
    */

    dup2(pstops_pipe[0], 0);
    close(pstops_pipe[0]);
    close(pstops_pipe[1]);
    if (need_rastertops)
    {
      close(rastertops_pipe[0]);
      close(rastertops_pipe[1]);
    }
    if (need_post_proc)
    {
      close(post_proc_pipe[0]);
      close(post_proc_pipe[1]);
    }

    execvp(pstops_path, pstops_argv);
    perror("DEBUG: Unable to execute pstops program");

    exit(1);
  }
  else if (pstops_pid < 0)
  {
   /*
    * Unable to fork!
    */

    perror("DEBUG: Unable to execute pstops program");

    exit_status = 1;
    goto error;
  }

  fprintf(stderr, "DEBUG: Started filter pstops (PID %d)\n", pstops_pid);

  close(pstops_pipe[0]);
  close(pstops_pipe[1]);
  if (need_post_proc)
  {
    close(post_proc_pipe[0]);
    close(post_proc_pipe[1]);
  }
  if (need_rastertops)
  {
    close(rastertops_pipe[0]);
    close(rastertops_pipe[1]);
  }

 /*
  * Wait for the child processes to exit...
  */

  wait_children = 2 + need_rastertops + need_post_proc;

  while (wait_children > 0)
  {
   /*
    * Wait until we get a valid process ID or the job is canceled...
    */

    while ((wait_pid = wait(&wait_status)) < 0 && errno == EINTR)
    {
      if (job_canceled)
      {
	kill(pdf_pid, SIGTERM);
	if (need_rastertops)
	  kill(rastertops_pid, SIGTERM);
	if (need_post_proc)
	  kill(post_proc_pid, SIGTERM);
	kill(pstops_pid, SIGTERM);

	job_canceled = 0;
      }
    }

    if (wait_pid < 0)
      break;

    wait_children --;

   /*
    * Report child status...
    */

    if (wait_status)
    {
      if (WIFEXITED(wait_status))
      {
	exit_status = WEXITSTATUS(wait_status);

	fprintf(stderr, "DEBUG: PID %d (%s) stopped with status %d!\n",
		wait_pid,
		wait_pid == pdf_pid ?
		(renderer == PDFTOPS ? "pdftops" :
                (renderer == PDFTOCAIRO ? "pdftocairo" :
		(renderer == GS ? "gs" :
		(renderer == ACROREAD ? "acroread" :
		(renderer == MUPDF ? "mupdftoraster" :
		 "Unknown renderer"))))) :
		(wait_pid == pstops_pid ? "pstops" :
		(wait_pid == rastertops_pid ? "rastertops" :
		(wait_pid == post_proc_pid ? "Post-processing" :
		 "Unknown process"))),
		exit_status);
      }
      else if (WTERMSIG(wait_status) == SIGTERM)
      {
	fprintf(stderr,
		"DEBUG: PID %d (%s) was terminated normally with signal %d!\n",
		wait_pid,
		wait_pid == pdf_pid ?
		(renderer == PDFTOPS ? "pdftops" :
                (renderer == PDFTOCAIRO ? "pdftocairo" :
		(renderer == GS ? "gs" :
		(renderer == ACROREAD ? "acroread" :
		(renderer == MUPDF ? "mupdftoraster" :
		 "Unknown renderer"))))) :
		(wait_pid == pstops_pid ? "pstops" :
		(wait_pid == rastertops_pid ? "rastertops" :
		(wait_pid == post_proc_pid ? "Post-processing" :
		 "Unknown process"))),
		exit_status);
      }
      else
      {
	exit_status = WTERMSIG(wait_status);

	fprintf(stderr, "DEBUG: PID %d (%s) crashed on signal %d!\n",
		wait_pid,
		wait_pid == pdf_pid ?
		(renderer == PDFTOPS ? "pdftops" :
                (renderer == PDFTOCAIRO ? "pdftocairo" :
		(renderer == GS ? "gs" :
		(renderer == ACROREAD ? "acroread" :
		(renderer == MUPDF ? "mupdftoraster" :
		 "Unknown renderer"))))) :
		(wait_pid == pstops_pid ? "pstops" :
		(wait_pid == rastertops_pid ? "rastertops" :
		(wait_pid == post_proc_pid ? "Post-processing" :
		 "Unknown process"))),
		exit_status);
      }
    }
    else
    {
      fprintf(stderr, "DEBUG: PID %d (%s) exited with no errors.\n",
	      wait_pid,
	      wait_pid == pdf_pid ?
	      (renderer == PDFTOPS ? "pdftops" :
	      (renderer == PDFTOCAIRO ? "pdftocairo" :
	      (renderer == GS ? "gs" :
	      (renderer == ACROREAD ? "acroread" :
	      (renderer == MUPDF ? "mupdftoraster" :
	       "Unknown renderer"))))) :
	      (wait_pid == pstops_pid ? "pstops" :
	      (wait_pid == rastertops_pid ? "rastertops" :
	      (wait_pid == post_proc_pid ? "Post-processing" :
	       "Unknown process"))));
    }
  }

 /*
  * Cleanup and exit...
  */

  error:

  if (tempfile[0])
    unlink(tempfile);

  return (exit_status);
}


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

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

  job_canceled = 1;
}

