text: /*
   * Generic Adobe PostScript printer command for ippeveprinter/CUPS.
   *
   * Copyright © 2019 by Apple Inc.
   *
   * Licensed under Apache License v2.0.  See the file "LICENSE" for more
   * information.
   */
  
  /*
   * Include necessary headers...
   */
  
  #include "ippevecommon.h"
  #if !CUPS_LITE
  #  include <cups/ppd-private.h>
  #endif /* !CUPS_LITE */
  #include <limits.h>
  #include <sys/wait.h>
  
  #ifdef __APPLE__
  #  define PDFTOPS CUPS_SERVERBIN "/filter/cgpdftops"
  #else
  #  define PDFTOPS CUPS_SERVERBIN "/filter/pdftops"
  #endif /* __APPLE__ */
  
  
  /*
   * Local globals...
   */
  
  #if !CUPS_LITE
  static ppd_file_t	*ppd = NULL;	/* PPD file data */
  static _ppd_cache_t	*ppd_cache = NULL;
  					/* IPP to PPD cache data */
  #endif /* !CUPS_LITE */
  
  
  /*
   * Local functions...
   */
  
  static void	ascii85(const unsigned char *data, int length, int eod);
  static void	dsc_header(int num_pages);
  static void	dsc_page(int page);
  static void	dsc_trailer(int num_pages);
  static int	get_options(cups_option_t **options);
  static int	jpeg_to_ps(const char *filename, int copies);
  static int	pdf_to_ps(const char *filename, int copies, int num_options, cups_option_t *options);
  static int	ps_to_ps(const char *filename, int copies);
  static int	raster_to_ps(const char *filename);
  
  
  /*
   * 'main()' - Main entry for PostScript printer command.
   */
  
  int					/* O - Exit status */
  main(int  argc,				/* I - Number of command-line arguments */
       char *argv[])			/* I - Command-line arguments */
  {
    const char	*content_type,		/* Content type to print */
  		*ipp_copies;		/* IPP_COPIES value */
    int		copies;			/* Number of copies */
    int		num_options;		/* Number of options */
    cups_option_t	*options;		/* Options */
  
  
   /*
    * Get print options...
    */
  
    num_options = get_options(&options);
    if ((ipp_copies = getenv("IPP_COPIES")) != NULL)
      copies = atoi(ipp_copies);
    else
      copies = 1;
  
   /*
    * Print it...
    */
  
    if (argc > 2)
    {
      fputs("ERROR: Too many arguments supplied, aborting.\n", stderr);
      return (1);
    }
    else if ((content_type = getenv("CONTENT_TYPE")) == NULL)
    {
      fputs("ERROR: CONTENT_TYPE environment variable not set, aborting.\n", stderr);
      return (1);
    }
    else if (!strcasecmp(content_type, "application/pdf"))
    {
      return (pdf_to_ps(argv[1], copies, num_options, options));
    }
    else if (!strcasecmp(content_type, "application/postscript"))
    {
      return (ps_to_ps(argv[1], copies));
    }
    else if (!strcasecmp(content_type, "image/jpeg"))
    {
      return (jpeg_to_ps(argv[1], copies));
    }
    else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf"))
    {
      return (raster_to_ps(argv[1]));
    }
    else
    {
      fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type);
      return (1);
    }
  }
  
  
  /*
   * 'ascii85()' - Write binary data using a Base85 encoding...
   */
  
  static void
  ascii85(const unsigned char *data,	/* I - Data to write */
          int                 length,	/* I - Number of bytes to write */
          int                 eod)	/* I - 1 if this is the end, 0 otherwise */
  {
    unsigned	b = 0;			/* Current 32-bit word */
    unsigned char	c[5];			/* Base-85 encoded characters */
    static int	col = 0;		/* Column */
    static unsigned char leftdata[4];	/* Leftover data at the end */
    static int	leftcount = 0;		/* Size of leftover data */
  
  
    length += leftcount;
  
    while (length > 3)
    {
      switch (leftcount)
      {
        case 0 :
            b = (unsigned)((((((data[0] << 8) | data[1]) << 8) | data[2]) << 8) | data[3]);
  	  break;
        case 1 :
            b = (unsigned)((((((leftdata[0] << 8) | data[0]) << 8) | data[1]) << 8) | data[2]);
  	  break;
        case 2 :
            b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | data[0]) << 8) | data[1]);
  	  break;
        case 3 :
            b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | data[0]);
  	  break;
      }
  
      if (col >= 76)
      {
        col = 0;
        putchar('\n');
      }
  
      if (b == 0)
      {
        putchar('z');
        col ++;
      }
      else
      {
        c[4] = (b % 85) + '!';
        b /= 85;
        c[3] = (b % 85) + '!';
        b /= 85;
        c[2] = (b % 85) + '!';
        b /= 85;
        c[1] = (b % 85) + '!';
        b /= 85;
        c[0] = (unsigned char)(b + '!');
  
        fwrite(c, 1, 5, stdout);
        col += 5;
      }
  
      data      += 4 - leftcount;
      length    -= 4 - leftcount;
      leftcount = 0;
    }
  
    if (length > 0)
    {
      // Copy any remainder into the leftdata array...
      if (length > leftcount)
        memcpy(leftdata + leftcount, data, (size_t)(length - leftcount));
  
      memset(leftdata + length, 0, (size_t)(4 - length));
  
      leftcount = length;
    }
  
    if (eod)
    {
      // Do the end-of-data dance...
      if (col >= 76)
      {
        col = 0;
        putchar('\n');
      }
  
      if (leftcount > 0)
      {
        // Write the remaining bytes as needed...
        b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | leftdata[3]);
  
        c[4] = (b % 85) + '!';
        b /= 85;
        c[3] = (b % 85) + '!';
        b /= 85;
        c[2] = (b % 85) + '!';
        b /= 85;
        c[1] = (b % 85) + '!';
        b /= 85;
        c[0] = (unsigned char)(b + '!');
  
        fwrite(c, (size_t)(leftcount + 1), 1, stdout);
  
        leftcount = 0;
      }
  
      puts("~>");
      col = 0;
    }
  }
  
  
  /*
   * 'dsc_header()' - Write out a standard Document Structuring Conventions
   *                  PostScript header.
   */
  
  static void
  dsc_header(int num_pages)		/* I - Number of pages or 0 if not known */
  {
    const char	*job_name = getenv("IPP_JOB_NAME");
  					/* job-name value */
  
  
  #if !CUPS_LITE
    const char	*job_id = getenv("IPP_JOB_ID");
  					/* job-id value */
  
    ppdEmitJCL(ppd, stdout, job_id ? atoi(job_id) : 0, cupsUser(), job_name ? job_name : "Unknown");
  #endif /* !CUPS_LITE */
  
    puts("%!PS-Adobe-3.0");
    puts("%%LanguageLevel: 2");
    printf("%%%%Creator: ippeveps/%d.%d.%d\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, CUPS_VERSION_PATCH);
    if (job_name)
    {
      fputs("%%Title: ", stdout);
      while (*job_name)
      {
        if (*job_name >= 0x20 && *job_name < 0x7f)
          putchar(*job_name);
        else
          putchar('?');
  
        job_name ++;
      }
      putchar('\n');
    }
    if (num_pages > 0)
      printf("%%%%Pages: %d\n", num_pages);
    else
      puts("%%Pages: (atend)");
    puts("%%EndComments");
  
  #if !CUPS_LITE
    if (ppd)
    {
      puts("%%BeginProlog");
      if (ppd->patches)
      {
        puts("%%BeginFeature: *JobPatchFile 1");
        puts(ppd->patches);
        puts("%%EndFeature");
      }
      ppdEmit(ppd, stdout, PPD_ORDER_PROLOG);
      puts("%%EndProlog");
  
      puts("%%BeginSetup");
      ppdEmit(ppd, stdout, PPD_ORDER_DOCUMENT);
      ppdEmit(ppd, stdout, PPD_ORDER_ANY);
      puts("%%EndSetup");
    }
  #endif /* !CUPS_LITE */
  }
  
  
  /*
   * 'dsc_page()' - Mark the start of a page.
   */
  
  static void
  dsc_page(int page)			/* I - Page numebr (1-based) */
  {
    printf("%%%%Page: (%d) %d\n", page, page);
  
    fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page);
  
  #if !CUPS_LITE
    if (ppd)
    {
      puts("%%BeginPageSetup");
      ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
      puts("%%EndPageSetup");
    }
  #endif /* !CUPS_LITE */
  }
  
  
  /*
   * 'dsc_trailer()' - Mark the end of the document.
   */
  
  static void
  dsc_trailer(int num_pages)		/* I - Number of pages */
  {
    if (num_pages > 0)
    {
      puts("%%Trailer");
      printf("%%%%Pages: %d\n", num_pages);
      puts("%%EOF");
    }
  
  #if !CUPS_LITE
    if (ppd && ppd->jcl_end)
      ppdEmitJCLEnd(ppd, stdout);
    else
  #endif /* !CUPS_LITE */
      putchar(0x04);
  }
  
  
  /*
   * 'get_options()' - Get the PPD options corresponding to the IPP Job Template
   *                   attributes.
   */
  
  static int				/* O - Number of options */
  get_options(cups_option_t **options)	/* O - Options */
  {
    int		num_options = 0;	/* Number of options */
    const char	*value;			/* Option value */
    pwg_media_t	*media = NULL;		/* Media mapping */
    int		num_media_col = 0;	/* Number of media-col values */
    cups_option_t	*media_col = NULL;	/* media-col values */
  #if !CUPS_LITE
    const char	*choice;		/* PPD choice */
  #endif /* !CUPS_LITE */
  
  
   /*
    * No options to start...
    */
  
    *options = NULL;
  
   /*
    * Media...
    */
  
    if ((value = getenv("IPP_MEDIA")) == NULL)
      if ((value = getenv("IPP_MEDIA_COL")) == NULL)
        if ((value = getenv("IPP_MEDIA_DEFAULT")) == NULL)
          value = getenv("IPP_MEDIA_COL_DEFAULT");
  
    if (value)
    {
      if (*value == '{')
      {
       /*
        * media-col value...
        */
  
        num_media_col = cupsParseOptions(value, 0, &media_col);
      }
      else
      {
       /*
        * media value - map to media-col.media-size-name...
        */
  
        num_media_col = cupsAddOption("media-size-name", value, 0, &media_col);
      }
    }
  
    if ((value = cupsGetOption("media-size-name", num_media_col, media_col)) != NULL)
    {
      media = pwgMediaForPWG(value);
    }
    else if ((value = cupsGetOption("media-size", num_media_col, media_col)) != NULL)
    {
      int		num_media_size;		/* Number of media-size values */
      cups_option_t *media_size;		/* media-size values */
      const char	*x_dimension,		/* x-dimension value */
  		*y_dimension;		/* y-dimension value */
  
      num_media_size = cupsParseOptions(value, 0, &media_size);
  
      if ((x_dimension = cupsGetOption("x-dimension", num_media_size, media_size)) != NULL && (y_dimension = cupsGetOption("y-dimension", num_media_size, media_size)) != NULL)
        media = pwgMediaForSize(atoi(x_dimension), atoi(y_dimension));
  
      cupsFreeOptions(num_media_size, media_size);
    }
  
    if (media)
      num_options = cupsAddOption("PageSize", media->ppd, num_options, options);
  
  #if !CUPS_LITE
   /*
    * Load PPD file and the corresponding IPP <-> PPD cache data...
    */
  
    if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL)
    {
      ppd_cache = _ppdCacheCreateWithPPD(ppd);
  
      /* TODO: Fix me - values are names, not numbers... Also need to support finishings-col */
      if ((value = getenv("IPP_FINISHINGS")) == NULL)
        value = getenv("IPP_FINISHINGS_DEFAULT");
  
      if (value)
      {
        char	*ptr;			/* Pointer into value */
        int	fin;			/* Current value */
  
        for (fin = strtol(value, &ptr, 10); fin > 0; fin = strtol(ptr + 1, &ptr, 10))
        {
  	num_options = _ppdCacheGetFinishingOptions(ppd_cache, NULL, (ipp_finishings_t)fin, num_options, options);
  
  	if (*ptr != ',')
  	  break;
        }
      }
  
      if ((value = cupsGetOption("media-source", num_media_col, media_col)) != NULL)
      {
        if ((choice = _ppdCacheGetInputSlot(ppd_cache, NULL, value)) != NULL)
  	num_options = cupsAddOption("InputSlot", choice, num_options, options);
      }
  
      if ((value = cupsGetOption("media-type", num_media_col, media_col)) != NULL)
      {
        if ((choice = _ppdCacheGetMediaType(ppd_cache, NULL, value)) != NULL)
  	num_options = cupsAddOption("MediaType", choice, num_options, options);
      }
  
      if ((value = getenv("IPP_OUTPUT_BIN")) == NULL)
        value = getenv("IPP_OUTPUT_BIN_DEFAULT");
  
      if (value)
      {
        if ((choice = _ppdCacheGetOutputBin(ppd_cache, value)) != NULL)
  	num_options = cupsAddOption("OutputBin", choice, num_options, options);
      }
  
      if ((value = getenv("IPP_SIDES")) == NULL)
        value = getenv("IPP_SIDES_DEFAULT");
  
      if (value && ppd_cache->sides_option)
      {
        if (!strcmp(value, "one-sided") && ppd_cache->sides_1sided)
  	num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_1sided, num_options, options);
        else if (!strcmp(value, "two-sided-long-edge") && ppd_cache->sides_2sided_long)
  	num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_long, num_options, options);
        else if (!strcmp(value, "two-sided-short-edge") && ppd_cache->sides_2sided_short)
  	num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_short, num_options, options);
      }
  
      if ((value = getenv("IPP_PRINT_QUALITY")) == NULL)
        value = getenv("IPP_PRINT_QUALITY_DEFAULT");
  
      if (value)
      {
        int		i;		/* Looping var */
        int		pq;		/* Print quality (0-2) */
        int		pcm = 1;	/* Print color model (0 = mono, 1 = color) */
        int		num_presets;	/* Number of presets */
        cups_option_t	*presets;	/* Presets */
  
        if (!strcmp(value, "draft"))
          pq = 0;
        else if (!strcmp(value, "high"))
          pq = 2;
        else
          pq = 1;
  
        if ((value = getenv("IPP_PRINT_COLOR_MODE")) == NULL)
  	value = getenv("IPP_PRINT_COLOR_MODE_DEFAULT");
  
        if (value && !strcmp(value, "monochrome"))
  	pcm = 0;
  
        num_presets = ppd_cache->num_presets[pcm][pq];
        presets     = ppd_cache->presets[pcm][pq];
  
        for (i = 0; i < num_presets; i ++)
  	num_options = cupsAddOption(presets[i].name, presets[i].value, num_options, options);
      }
  
     /*
      * Mark the PPD with the options...
      */
  
      ppdMarkDefaults(ppd);
      cupsMarkOptions(ppd, num_options, *options);
    }
  #endif /* !CUPS_LITE */
  
    cupsFreeOptions(num_media_col, media_col);
  
    return (num_options);
  }
  
  
  /*
   * 'jpeg_to_ps()' - Convert a JPEG file to PostScript.
   */
  
  static int				/* O - Exit status */
  jpeg_to_ps(const char    *filename,	/* I - Filename */
             int           copies)	/* I - Number of copies */
  {
    int		fd;			/* JPEG file descriptor */
    int		copy;			/* Current copy */
    int		width = 0,		/* Width */
  		height = 0,		/* Height */
  		depth = 0,		/* Number of colors */
  		length;			/* Length of marker */
    unsigned char	buffer[65536],		/* Copy buffer */
  		*bufptr,		/* Pointer info buffer */
  		*bufend;		/* End of buffer */
    ssize_t	bytes;			/* Bytes in buffer */
    const char	*decode;		/* Decode array */
    float		page_left,		/* Left margin */
  		page_top,		/* Top margin */
  		page_width,		/* Page width in points */
  		page_height,		/* Page heigth in points */
  		x_factor,		/* X image scaling factor */
  		y_factor,		/* Y image scaling factor */
  		page_scaling;		/* Image scaling factor */
  #if !CUPS_LITE
    ppd_size_t	*page_size;		/* Current page size */
  #endif /* !CUPS_LITE */
  
  
   /*
    * Open the input file...
    */
  
    if (filename)
    {
      if ((fd = open(filename, O_RDONLY)) < 0)
      {
        fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
        return (1);
      }
    }
    else
    {
      fd     = 0;
      copies = 1;
    }
  
   /*
    * Read the JPEG dimensions...
    */
  
    bytes = read(fd, buffer, sizeof(buffer));
  
    if (bytes < 3 || memcmp(buffer, "\377\330\377", 3))
    {
      fputs("ERROR: Not a JPEG image.\n", stderr);
  
      if (fd > 0)
        close(fd);
  
      return (1);
    }
  
    for (bufptr = buffer + 2, bufend = buffer + bytes; bufptr < bufend;)
    {
     /*
      * Scan the file for a SOFn marker, then we can get the dimensions...
      */
  
      if (*bufptr == 0xff)
      {
        bufptr ++;
  
        if (bufptr >= bufend)
        {
         /*
  	* If we are at the end of the current buffer, re-fill and continue...
  	*/
  
  	if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
  	  break;
  
  	bufptr = buffer;
  	bufend = buffer + bytes;
        }
  
        if (*bufptr == 0xff)
  	continue;
  
        if ((bufptr + 16) >= bufend)
        {
         /*
  	* Read more of the marker...
  	*/
  
  	bytes = bufend - bufptr;
  
  	memmove(buffer, bufptr, (size_t)bytes);
  	bufptr = buffer;
  	bufend = buffer + bytes;
  
  	if ((bytes = read(fd, bufend, sizeof(buffer) - (size_t)bytes)) <= 0)
  	  break;
  
  	bufend += bytes;
        }
  
        length = (size_t)((bufptr[1] << 8) | bufptr[2]);
  
        if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) || (*bufptr >= 0xc5 && *bufptr <= 0xc7) || (*bufptr >= 0xc9 && *bufptr <= 0xcb) || (*bufptr >= 0xcd && *bufptr <= 0xcf))
        {
         /*
  	* SOFn marker, look for dimensions...
  	*/
  
  	width  = (bufptr[6] << 8) | bufptr[7];
  	height = (bufptr[4] << 8) | bufptr[5];
  	depth  = bufptr[8];
  	break;
        }
  
       /*
        * Skip past this marker...
        */
  
        bufptr ++;
        bytes = bufend - bufptr;
  
        while (length >= bytes)
        {
  	length -= bytes;
  
  	if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
  	  break;
  
  	bufptr = buffer;
  	bufend = buffer + bytes;
        }
  
        if (length > bytes)
  	break;
  
        bufptr += length;
      }
    }
  
    fprintf(stderr, "DEBUG: JPEG dimensions are %dx%dx%d\n", width, height, depth);
  
    if (width <= 0 || height <= 0 || depth <= 0)
    {
      fputs("ERROR: No valid image data in JPEG file.\n", stderr);
  
      if (fd > 0)
        close(fd);
  
      return (1);
    }
  
    fputs("ATTR: job-impressions=1\n", stderr);
  
   /*
    * Figure out the dimensions/scaling of the final image...
    */
  
  #if CUPS_LITE
    page_left   = 18.0f;
    page_top    = 756.0f;
    page_width  = 576.0f;
    page_height = 720.0f;
  
  #else
    if ((page_size = ppdPageSize(ppd, NULL)) != NULL)
    {
      page_left   = page_size->left;
      page_top    = page_size->top;
      page_width  = page_size->right - page_left;
      page_height = page_top - page_size->bottom;
    }
    else
    {
      page_left   = 18.0f;
      page_top    = 756.0f;
      page_width  = 576.0f;
      page_height = 720.0f;
    }
  #endif /* CUPS_LITE */
  
    fprintf(stderr, "DEBUG: page_left=%.2f, page_top=%.2f, page_width=%.2f, page_height=%.2f\n", page_left, page_top, page_width, page_height);
  
    /* TODO: Support orientation/rotation, different print-scaling modes */
    x_factor = page_width / width;
    y_factor = page_height / height;
  
    if (x_factor > y_factor && (height * x_factor) <= page_height)
      page_scaling = x_factor;
    else
      page_scaling = y_factor;
  
    fprintf(stderr, "DEBUG: Scaled dimensions are %.2fx%.2f\n", width * page_scaling, height * page_scaling);
  
   /*
    * Write pages...
    */
  
    dsc_header(copies);
  
    for (copy = 1; copy <= copies; copy ++)
    {
      dsc_page(copy);
  
      if (depth == 1)
      {
        puts("/DeviceGray setcolorspace");
        decode = "0 1";
      }
      else if (depth == 3)
      {
        puts("/DeviceRGB setcolorspace");
        decode = "0 1 0 1 0 1";
      }
      else
      {
        puts("/DeviceCMYK setcolorspace");
        decode = "0 1 0 1 0 1 0 1";
      }
  
      printf("gsave %.3f %.3f translate %.3f %.3f scale\n", page_left + 0.5f * (page_width - width * page_scaling), page_top - 0.5f * (page_height - height * page_scaling), page_scaling, page_scaling);
      printf("<</ImageType 1/Width %d/Height %d/BitsPerComponent 8/ImageMatrix[1 0 0 -1 0 1]/Decode[%s]/DataSource currentfile/ASCII85Decode filter/DCTDecode filter/Interpolate true>>image\n", width, height, decode);
  
      if (fd > 0)
        lseek(fd, 0, SEEK_SET);
  
      while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
        ascii85(buffer, (int)bytes, 0);
  
      ascii85(buffer, 0, 1);
  
      puts("grestore showpage");
    }
  
    dsc_trailer(0);
  
    return (0);
  }
  
  
  /*
   * 'pdf_to_ps()' - Convert a PDF file to PostScript.
   */
  
  static int				/* O - Exit status */
  pdf_to_ps(const char    *filename,	/* I - Filename */
  	  int           copies,		/* I - Number of copies */
  	  int           num_options,	/* I - Number of options */
  	  cups_option_t *options)	/* I - options */
  {
    int		status;			/* Exit status */
    char		tempfile[1024];		/* Temporary file */
    int		tempfd;			/* Temporary file descriptor */
    int		pid;			/* Process ID */
    const char	*pdf_argv[8];		/* Command-line arguments */
    char		pdf_options[1024];	/* Options */
    const char	*value;			/* Option value */
    const char	*job_id,		/* job-id value */
  		*job_name;		/* job-name value */
  
  
   /*
    * Create a temporary file for the PostScript version...
    */
  
    if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
    {
      fprintf(stderr, "ERROR: Unable to create temporary file: %s\n", strerror(errno));
      return (1);
    }
  
   /*
    * Run cgpdftops or pdftops in the filter directory...
    */
  
    if ((value = cupsGetOption("PageSize", num_options, options)) != NULL)
      snprintf(pdf_options, sizeof(pdf_options), "PageSize=%s", value);
    else
      pdf_options[0] = '\0';
  
    if ((job_id = getenv("IPP_JOB_ID")) == NULL)
      job_id = "42";
    if ((job_name = getenv("IPP_JOB_NAME")) == NULL)
      job_name = "untitled";
  
    pdf_argv[0] = "printer";
    pdf_argv[1] = job_id;
    pdf_argv[2] = cupsUser();
    pdf_argv[3] = job_name;
    pdf_argv[4] = "1";
    pdf_argv[5] = pdf_options;
    pdf_argv[6] = filename;
    pdf_argv[7] = NULL;
  
    if ((pid = fork()) == 0)
    {
     /*
      * Child comes here...
      */
  
      close(1);
      dup2(tempfd, 1);
      close(tempfd);
  
      execv(PDFTOPS, (char * const *)pdf_argv);
      exit(errno);
    }
    else if (pid < 0)
    {
     /*
      * Unable to fork process...
      */
  
      perror("ERROR: Unable to start PDF filter");
  
      close(tempfd);
      unlink(tempfile);
  
      return (1);
    }
    else
    {
     /*
      * Wait for the filter to complete...
      */
  
      close(tempfd);
  
  #  ifdef HAVE_WAITPID
      while (waitpid(pid, &status, 0) < 0);
  #  else
      while (wait(&status) < 0);
  #  endif /* HAVE_WAITPID */
  
      if (status)
      {
        if (WIFEXITED(status))
  	fprintf(stderr, "ERROR: " PDFTOPS " exited with status %d.\n", WEXITSTATUS(status));
        else
  	fprintf(stderr, "ERROR: " PDFTOPS " terminated with signal %d.\n", WTERMSIG(status));
  
        unlink(tempfile);
        return (1);
      }
    }
  
   /*
    * Copy the PostScript output from the command...
    */
  
    status = ps_to_ps(tempfile, copies);
  
    unlink(tempfile);
  
    return (status);
  }
  
  
  /*
   * 'ps_to_ps()' - Copy PostScript to the standard output.
   */
  
  static int				/* O - Exit status */
  ps_to_ps(const char    *filename,	/* I - Filename */
  	 int           copies)		/* I - Number of copies */
  {
    FILE		*fp;				/* File to read from */
    int		copy,				/* Current copy */
  		page,				/* Current page number */
  		num_pages = 0,			/* Total number of pages */
  		first_page,			/* First page */
  		last_page;			/* Last page */
    const char	*page_ranges;			/* page-ranges option */
    long		first_pos = -1;			/* Offset for first page */
    char		line[1024];			/* Line from file */
  
  
   /*
    * Open the print file...
    */
  
    if (filename)
    {
      if ((fp = fopen(filename, "rb")) == NULL)
      {
        fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
        return (1);
      }
    }
    else
    {
      copies = 1;
      fp     = stdin;
    }
  
   /*
    * Check page ranges...
    */
  
    if ((page_ranges = getenv("IPP_PAGE_RANGES")) != NULL)
    {
      if (sscanf(page_ranges, "%d-%d", &first_page, &last_page) != 2)
      {
        first_page = 1;
        last_page  = INT_MAX;
      }
    }
    else
    {
      first_page = 1;
      last_page  = INT_MAX;
    }
  
   /*
    * Write the PostScript header for the document...
    */
  
    dsc_header(0);
  
    first_pos = 0;
  
    while (fgets(line, sizeof(line), fp))
    {
      if (!strncmp(line, "%%Page:", 7))
        break;
  
      first_pos = ftell(fp);
  
      if (line[0] != '%')
        fputs(line, stdout);
    }
  
    if (!strncmp(line, "%%Page:", 7))
    {
      for (copy = 0; copy < copies; copy ++)
      {
        int copy_page = 0;		/* Do we copy the page data? */
  
        if (fp != stdin)
          fseek(fp, first_pos, SEEK_SET);
  
        page = 0;
        while (fgets(line, sizeof(line), fp))
        {
          if (!strncmp(line, "%%Page:", 7))
          {
            page ++;
            copy_page = page >= first_page && page <= last_page;
  
            if (copy_page)
            {
  	    num_pages ++;
  	    dsc_page(num_pages);
  	  }
  	}
  	else if (copy_page)
  	  fputs(line, stdout);
        }
      }
    }
  
    dsc_trailer(num_pages);
  
    fprintf(stderr, "ATTR: job-impressions=%d\n", num_pages / copies);
  
    if (fp != stdin)
      fclose(fp);
  
    return (0);
  }
  
  
  /*
   * 'raster_to_ps()' - Convert PWG Raster/Apple Raster to PostScript.
   *
   * The current implementation locally-decodes the raster data and then writes
   * whole, non-blank lines as 1-line high images with base-85 encoding, resulting
   * in between 10 and 20 times larger output.  A alternate implementation (if it
   * is deemed necessary) would be to implement a PostScript decode procedure that
   * handles the modified packbits decompression so that we just have the base-85
   * encoding overhead (25%).  Furthermore, Level 3 PostScript printers also
   * support Flate compression.
   *
   * That said, the most efficient path with the highest quality is for Clients
   * to supply PDF files and us to use the existing PDF to PostScript conversion
   * filters.
   */
  
  static int				/* O - Exit status */
  raster_to_ps(const char *filename)	/* I - Filename */
  {
    int			fd;		/* Input file */
    cups_raster_t		*ras;		/* Raster stream */
    cups_page_header2_t	header;		/* Page header */
    int			page = 0;	/* Current page */
    unsigned		y;		/* Current line */
    unsigned char		*line;		/* Line buffer */
    unsigned char		white;		/* White color */
    const char		*decode;	/* Image decode array */
  
  
   /*
    * Open the input file...
    */
  
    if (filename)
    {
      if ((fd = open(filename, O_RDONLY)) < 0)
      {
        fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
        return (1);
      }
    }
    else
    {
      fd = 0;
    }
  
   /*
    * Open the raster stream and send pages...
    */
  
    if ((ras = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL)
    {
      fputs("ERROR: Unable to read raster data, aborting.\n", stderr);
      return (1);
    }
  
    dsc_header(0);
  
    while (cupsRasterReadHeader2(ras, &header))
    {
      page ++;
  
      fprintf(stderr, "DEBUG: Page %d: %ux%ux%u\n", page, header.cupsWidth, header.cupsHeight, header.cupsBitsPerPixel);
  
      if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K && header.cupsColorSpace != CUPS_CSPACE_RGB && header.cupsColorSpace != CUPS_CSPACE_SRGB)
      {
        fputs("ERROR: Unsupported color space, aborting.\n", stderr);
        break;
      }
      else if (header.cupsBitsPerColor != 1 && header.cupsBitsPerColor != 8)
      {
        fputs("ERROR: Unsupported bit depth, aborting.\n", stderr);
        break;
      }
  
      line = malloc(header.cupsBytesPerLine);
  
      dsc_page(page);
  
      puts("gsave");
      printf("%.6f %.6f scale\n", 72.0f / header.HWResolution[0], 72.0f / header.HWResolution[1]);
  
      switch (header.cupsColorSpace)
      {
        case CUPS_CSPACE_W :
        case CUPS_CSPACE_SW :
            decode = "0 1";
            puts("/DeviceGray setcolorspace");
            white = 255;
            break;
  
        case CUPS_CSPACE_K :
            decode = "0 1";
            puts("/DeviceGray setcolorspace");
            white = 0;
            break;
  
        default :
            decode = "0 1 0 1 0 1";
            puts("/DeviceRGB setcolorspace");
            white = 255;
            break;
      }
  
      printf("gsave /L{grestore gsave 0 exch translate <</ImageType 1/Width %u/Height 1/BitsPerComponent %u/ImageMatrix[1 0 0 -1 0 1]/DataSource currentfile/ASCII85Decode filter/Decode[%s]>>image}bind def\n", header.cupsWidth, header.cupsBitsPerColor, decode);
  
      for (y = header.cupsHeight; y > 0; y --)
      {
        if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine))
        {
          if (line[0] != white || memcmp(line, line + 1, header.cupsBytesPerLine - 1))
          {
            printf("%d L\n", y - 1);
            ascii85(line, (int)header.cupsBytesPerLine, 1);
          }
        }
        else
          break;
      }
  
      fprintf(stderr, "DEBUG: y=%d at end...\n", y);
  
      puts("grestore grestore");
      puts("showpage");
  
      free(line);
    }
  
    cupsRasterClose(ras);
  
    dsc_trailer(page);
  
    fprintf(stderr, "ATTR: job-impressions=%d\n", page);
  
    return (0);
  }
  
  
  