/*
 * "$Id: pstops.c,v 1.14 2002/07/02 19:22:42 gelphman Exp $"
 *
 *   PostScript filter for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 1993-2002 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products and are protected by Federal
 *   copyright law.  Distribution and use rights are outlined in the file
 *   "LICENSE.txt" which should have been included with this file.  If this
 *   file is missing or damaged please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636-3111 USA
 *
 *       Voice: (301) 373-9603
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   main()        - Main entry...
 *   check_range() - Check to see if the current page is selected for
 *   copy_bytes()  - Copy bytes from the input file to stdout...
 *   do_prolog()   - Send the necessary document prolog commands...
 *   do_setup()    - Send the necessary document setup commands...
 *   end_nup()     - End processing for N-up printing...
 *   psgets()      - Get a line from a file.
 *   start_nup()   - Start processing for N-up printing...
 */

/*
 * Include necessary headers...
 */

#include "common.h"


/*
 * Constants...
 */

#define MAX_PAGES	10000

#define BORDER_NONE	0	/* No border or hairline border */
#define BORDER_THICK	1	/* Think border */
#define BORDER_SINGLE	2	/* Single-line hairline border */
#define BORDER_SINGLE2	3	/* Single-line thick border */
#define BORDER_DOUBLE	4	/* Double-line hairline border */
#define BORDER_DOUBLE2	5	/* Double-line thick border */

#define LAYOUT_LRBT	0	/* Left to right, bottom to top */
#define LAYOUT_LRTB	1	/* Left to right, top to bottom */
#define LAYOUT_RLBT	2	/* Right to left, bottom to top */
#define LAYOUT_RLTB	3	/* Right to left, top to bottom */
#define LAYOUT_BTLR	4	/* Bottom to top, left to right */
#define LAYOUT_TBLR	5	/* Top to bottom, left to right */
#define LAYOUT_BTRL	6	/* Bottom to top, right to left */
#define LAYOUT_TBRL	7	/* Top to bottom, right to left */

#define LAYOUT_NEGATEY	1
#define LAYOUT_NEGATEX	2
#define LAYOUT_VERTICAL	4


/*
 * Globals...
 */

int		NumPages = 0;		/* Number of pages in file */
long		Pages[MAX_PAGES];	/* Offsets to each page */
char		PageLabels[MAX_PAGES][64];
					/* Page labels */
const char	*PageRanges = NULL;	/* Range of pages selected */
const char	*PageSet = NULL;	/* All, Even, Odd pages */
int		Order = 0,		/* 0 = normal, 1 = reverse pages */
		Flip = 0,		/* Flip/mirror pages */
		NUp = 1,		/* Number of pages on each sheet (1, 2, 4) */
		Collate = 0,		/* Collate copies? */
		Copies = 1,		/* Number of copies */
		UseESPsp = 0,		/* Use ESPshowpage? */
		Border = BORDER_NONE,	/* Border around pages */
		Layout = LAYOUT_LRTB,	/* Layout of N-up pages */
		NormalLandscape = 0;	/* Normal rotation for landscape? */


/*
 * Local functions...
 */

static int	check_range(int page);
static void	copy_bytes(FILE *fp, size_t length);
static void	do_prolog(ppd_file_t *ppd);
static void 	do_setup(ppd_file_t *ppd, int copies,  int collate,
		         int slowcollate, float g, float b);
static void	end_nup(int number);
#define		is_first_page(p)	(NUp == 1 || (((p)+1) % NUp) == 1)
#define		is_last_page(p)		(NUp > 1 && (((p)+1) % NUp) == 0)
#define 	is_not_last_page(p)	(NUp > 1 && ((p) % NUp) != 0)
static char	*psgets(char *buf, size_t len, FILE *fp);
static void	start_nup(int number, int show_border);


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

int				/* O - Exit status */
main(int  argc,			/* I - Number of command-line arguments */
     char *argv[])		/* I - Command-line arguments */
{
  FILE		*fp;		/* Print file */
  ppd_file_t	*ppd;		/* PPD file */
  int		num_options;	/* Number of print options */
  cups_option_t	*options;	/* Print options */
  const char	*val;		/* Option value */
  char		tempfile[255];	/* Temporary file name */
  FILE		*temp;		/* Temporary file */
  int		tempfd;		/* Temporary file descriptor */
  int		number;		/* Page number */
  int		slowcollate;	/* 1 if we need to collate manually */
  int		sloworder;	/* 1 if we need to order manually */
  int		slowduplex;	/* 1 if we need an even number of pages */
  char		line[8192];	/* Line buffer */
  float		g;		/* Gamma correction value */
  float		b;		/* Brightness factor */
  int		level;		/* Nesting level for embedded files */
  int		nbytes,		/* Number of bytes read */
		tbytes;		/* Total bytes to read for binary data */
  int		page;		/* Current page sequence number */
  int		real_page;	/* "Real" page number in document */
  int		page_count;	/* Page count for NUp */
  int		basepage;	/* Base page number */
  int		subpage;	/* Sub-page number */
  int		copy;		/* Current copy */
  int		saweof;		/* Did we see a %%EOF tag? */
  int		sent_espsp,	/* Did we send the ESPshowpage commands? */
		sent_prolog,	/* Did we send the prolog commands? */
		sent_setup;	/* Did we send the setup commands? */


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

  setbuf(stderr, NULL);

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

  if (argc < 6 || argc > 7)
  {
    fputs("ERROR: pstops job-id user title copies options [file]\n", stderr);
    return (1);
  }

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

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

    if ((fp = fopen(argv[6], "rb")) == NULL)
    {
      fprintf(stderr, "ERROR: unable to open print file \"%s\" - %s\n",
              argv[6], strerror(errno));
      return (1);
    }
  }

 /*
  * Process command-line options and write the prolog...
  */

  g = 1.0;
  b = 1.0;

  Copies = atoi(argv[4]);

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

  ppd = SetCommonOptions(num_options, options, 1);

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

  if ((val = cupsGetOption("page-ranges", num_options, options)) != NULL)
    PageRanges = val;

  if ((val = cupsGetOption("page-set", num_options, options)) != NULL)
    PageSet = val;

  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.
    */

    Collate = strcasecmp(val, "separate-documents-uncollated-copies") != 0;
  }

  if ((val = cupsGetOption("Collate", num_options, options)) != NULL &&
      strcasecmp(val, "True") == 0)
    Collate = 1;

  if ((val = cupsGetOption("OutputOrder", num_options, options)) != NULL &&
      strcasecmp(val, "Reverse") == 0)
    Order = 1;

  if ((val = cupsGetOption("number-up", num_options, options)) != NULL)
    NUp = atoi(val);

  if ((val = cupsGetOption("page-border", num_options, options)) != NULL)
  {
    if (strcasecmp(val, "none") == 0)
      Border = BORDER_NONE;
    else if (strcasecmp(val, "single") == 0)
      Border = BORDER_SINGLE;
    else if (strcasecmp(val, "single-thick") == 0)
      Border = BORDER_SINGLE2;
    else if (strcasecmp(val, "double") == 0)
      Border = BORDER_DOUBLE;
    else if (strcasecmp(val, "double-thick") == 0)
      Border = BORDER_DOUBLE2;
  }

  if ((val = cupsGetOption("number-up-layout", num_options, options)) != NULL)
  {
    if (strcasecmp(val, "lrtb") == 0)
      Layout = LAYOUT_LRTB;
    else if (strcasecmp(val, "lrbt") == 0)
      Layout = LAYOUT_LRBT;
    else if (strcasecmp(val, "rltb") == 0)
      Layout = LAYOUT_RLTB;
    else if (strcasecmp(val, "rlbt") == 0)
      Layout = LAYOUT_RLBT;
    else if (strcasecmp(val, "tblr") == 0)
      Layout = LAYOUT_TBLR;
    else if (strcasecmp(val, "tbrl") == 0)
      Layout = LAYOUT_TBRL;
    else if (strcasecmp(val, "btlr") == 0)
      Layout = LAYOUT_BTLR;
    else if (strcasecmp(val, "btrl") == 0)
      Layout = LAYOUT_BTRL;
  }

  if ((val = cupsGetOption("gamma", num_options, options)) != NULL)
    g = atoi(val) * 0.001f;

  if ((val = cupsGetOption("brightness", num_options, options)) != NULL)
    b = atoi(val) * 0.01f;

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

  if (ppd && ppd->manual_copies && Duplex && 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.  Also, set the "slowduplex" option to make sure
    * that we output an even number of pages...
    */

    Collate    = 1;
    slowduplex = 1;
  }
  else
    slowduplex = 0;

  if (ppdFindOption(ppd, "Collate") == NULL && Collate && Copies > 1)
    slowcollate = 1;
  else
    slowcollate = 0;

  if (ppdFindOption(ppd, "OutputOrder") == NULL && Order)
    sloworder = 1;
  else
    sloworder = 0;

 /*
  * If we need to filter slowly, then create a temporary file for page data...
  *
  * If the temp file can't be created, then we'll ignore the collating/output
  * order options...
  */

  if (sloworder || slowcollate)
  {
    tempfd = cupsTempFd(tempfile, sizeof(tempfile));
    temp   = fdopen(tempfd, "wb+");

    if (temp == NULL)
      slowcollate = sloworder = 0;
  }
  else
    temp = NULL;

 /*
  * 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...
  */

  ppdEmitJCL(ppd, stdout, atoi(argv[1]), argv[2], argv[3]);

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

  if (psgets(line, sizeof(line), fp) == NULL)
  {
    fputs("ERROR: Empty print file!\n", stderr);
    ppdClose(ppd);
    return (1);
  }

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

  fputs(line, stdout);

  saweof      = 0;
  sent_espsp  = 0;
  sent_prolog = 0;
  sent_setup  = 0;

  if (Copies != 1 && (!Collate || !slowcollate))
    printf("%%%%Requirements: numcopies(%d)%s%s\n", Copies,
           Collate ? " collate" : "",
	   Duplex ? " duplex" : "");
  else if (Duplex)
    puts("%%%%Requirements: duplex\n");

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

  if (Copies != 1 && (!Collate || !slowcollate))
    printf("%%RBINumCopies: %d\n", Copies);
  else
    printf("%%RBINumCopies: %d\n", 1);

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

  val = cupsGetOption("page-label", num_options, options);

  if (val != NULL || getenv("CLASSIFICATION") != NULL || NUp > 1 ||
      Border || strstr(line, "EPS") != NULL)
  {
   /*
    * Yes, use ESPshowpage...
    */

    UseESPsp = 1;
  }

  if (strncmp(line, "%!PS-Adobe-", 11) == 0)
  {
   /*
    * OK, we have DSC comments; read until we find a %%Page comment...
    */

    puts("%%Pages: (atend)");

    level = 0;

    while (psgets(line, sizeof(line), fp) != NULL)
    {
      if (strncmp(line, "%%", 2) == 0)
        fprintf(stderr, "DEBUG: %d %s", level, line);
      else if (line[0] != '%' && line[0] && !sent_espsp && UseESPsp)
      {
       /*
        * Send ESPshowpage stuff...
	*/

        sent_espsp = 1;

	puts("userdict/ESPshowpage/showpage load put\n"
	     "userdict/showpage{}put");
      }

      if (strncmp(line, "%%BeginDocument:", 16) == 0 ||
          strncmp(line, "%%BeginDocument ", 16) == 0)	/* Adobe Acrobat BUG */
      {
	fputs(line, stdout);
        level ++;
      }
      else if (strncmp(line, "%%EndDocument", 13) == 0 && level > 0)
      {
	fputs(line, stdout);
        level --;
      }
      else if (strncmp(line, "%ESPRotation:", 13) == 0 && level == 0)
      {
       /*
        * Reset orientation of document?
	*/

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

        if (orient != Orientation)
	{
	  Orientation = (4 - Orientation + orient) & 3;
	  UpdatePageVars();
	  Orientation = orient;
	}
      }
      else if (strncmp(line, "%%BeginProlog", 13) == 0 && level == 0)
      {
       /*
        * Write the existing comment line, and then follow with patches
	* and prolog commands...
	*/

        fputs(line, stdout);

	if (!sent_prolog)
	{
	  sent_prolog = 1;
          do_prolog(ppd);
	}
      }
      else if (strncmp(line, "%%BeginSetup", 12) == 0 && level == 0)
      {
       /*
        * Write the existing comment line, and then follow with document
	* setup commands...
	*/

        fputs(line, stdout);

	if (!sent_setup)
	{
	  sent_setup = 1;
          do_setup(ppd, Copies, Collate, slowcollate, g, b);
	}
      }
      else if (strncmp(line, "%%Page:", 7) == 0 && level == 0)
        break;
      else if (strncmp(line, "%%BeginBinary:", 14) == 0 ||
               (strncmp(line, "%%BeginData:", 12) == 0 &&
	        strstr(line, "ASCII") == NULL && strstr(line, "Hex") == NULL))
      {
       /*
        * Copy binary data...
	*/

        tbytes = atoi(strchr(line, ':') + 1);
	fputs(line, stdout);

	while (tbytes > 0)
	{
	  if (tbytes > sizeof(line))
	    nbytes = fread(line, 1, sizeof(line), fp);
	  else
	    nbytes = fread(line, 1, tbytes, fp);

          if (nbytes < 1)
	  {
	    perror("ERROR: Early end-of-file while reading binary data");
	    return (1);
	  }

	  fwrite(line, 1, nbytes, stdout);
	  tbytes -= nbytes;
	}
      }
      else if (strncmp(line, "%%Pages:", 8) != 0)
        fputs(line, stdout);
    }

   /*
    * Make sure we have the prolog and setup commands written...
    */

    if (!sent_prolog)
    {
      puts("%%BeginProlog");

      sent_prolog = 1;
      do_prolog(ppd);

      puts("%%EndProlog");
    }

    if (!sent_setup)
    {
      puts("%%BeginSetup");

      sent_setup = 1;
      do_setup(ppd, Copies, Collate, slowcollate, g, b);

      puts("%%EndSetup");
    }

    if (!sent_espsp && UseESPsp)
    {
     /*
      * Send ESPshowpage stuff...
      */

      sent_espsp = 1;

      puts("userdict/ESPshowpage/showpage load put\n"
	   "userdict/showpage{}put");
    }

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

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

      if (Orientation & 1)
	WriteLabelProlog(val, PageBottom, PageWidth - PageLength + PageTop,
                	 PageLength);
      else
	WriteLabelProlog(val, PageLeft, PageRight, PageLength);
    }
    else
      WriteLabelProlog(val, PageBottom, PageTop, PageWidth);

   /*
    * Then read all of the pages, filtering as needed...
    */

    for (page = 1, real_page = 1;;)
    {
      if (strncmp(line, "%%", 2) == 0)
        fprintf(stderr, "DEBUG: %d %s", level, line);

      if (strncmp(line, "%%BeginDocument:", 16) == 0 ||
          strncmp(line, "%%BeginDocument ", 16) == 0)	/* Adobe Acrobat BUG */
        level ++;
      else if (strncmp(line, "%%EndDocument", 13) == 0 && level > 0)
        level --;
      else if (strcmp(line, "\004") == 0)
        break;
      else if (strncmp(line, "%%EOF", 5) == 0 && level == 0)
      {
        fputs("DEBUG: Saw EOF!\n", stderr);
        saweof = 1;
	break;
      }
      else if (strncmp(line, "%%Page:", 7) == 0 && level == 0)
      {
	if (!check_range(real_page))
	{
	  while (psgets(line, sizeof(line), fp) != NULL)
	    if (strncmp(line, "%%BeginDocument:", 16) == 0 ||
        	strncmp(line, "%%BeginDocument ", 16) == 0)	/* Adobe Acrobat BUG */
              level ++;
	    else if (strcmp(line, "%%EndDocument") == 0 && level > 0)
              level --;
	    else if (strncmp(line, "%%Page:", 7) == 0 && level == 0)
	    {
	      real_page ++;
	      break;
	    }

          continue;
        }

        if (!sloworder && NumPages > 0)
	  end_nup(NumPages - 1);

	if (slowcollate || sloworder)
	  Pages[NumPages] = ftell(temp);

        if (!sloworder)
	{
	  if (is_first_page(NumPages))
	  {
	    if (ppd == NULL || ppd->num_filters == 0)
	      fprintf(stderr, "PAGE: %d %d\n", page, Copies);

            printf("%%%%Page: %d %d\n", page, page);
	    page ++;
	    ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
	  }

	  start_nup(NumPages, 1);
	}

	NumPages ++;
	real_page ++;
      }
      else if (strncmp(line, "%%BeginBinary:", 14) == 0 ||
               (strncmp(line, "%%BeginData:", 12) == 0 &&
	        strstr(line, "ASCII") == NULL && strstr(line, "Hex") == NULL))
      {
       /*
        * Copy binary data...
	*/

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

	if (!sloworder)
	  fputs(line, stdout);
	if (slowcollate || sloworder)
	  fputs(line, temp);

	while (tbytes > 0)
	{
	  if (tbytes > sizeof(line))
	    nbytes = fread(line, 1, sizeof(line), fp);
	  else
	    nbytes = fread(line, 1, tbytes, fp);

          if (nbytes < 1)
	  {
	    perror("ERROR: Early end-of-file while reading binary data");
	    return (1);
	  }

          if (!sloworder)
	    fwrite(line, 1, nbytes, stdout);

          if (slowcollate || sloworder)
	    fwrite(line, 1, nbytes, temp);

	  tbytes -= nbytes;
	}
      }
      else if (strncmp(line, "%%Trailer", 9) == 0 && level == 0)
      {
        fputs("DEBUG: Saw Trailer!\n", stderr);
        break;
      }
      else
      {
        if (!sloworder)
          fputs(line, stdout);

	if (slowcollate || sloworder)
	  fputs(line, temp);
      }

      if (psgets(line, sizeof(line), fp) == NULL)
        break;
    }

    if (!sloworder)
    {
      end_nup(NumPages - 1);

      if (is_not_last_page(NumPages))
      {
	start_nup(NUp - 1, 0);
        end_nup(NUp - 1);
      }

      if (slowduplex && !(page & 1))
      {
       /*
        * Make sure we have an even number of pages...
	*/

	if (ppd == NULL || ppd->num_filters == 0)
	  fprintf(stderr, "PAGE: %d %d\n", page, Copies);

        printf("%%%%Page: %d %d\n", page, page);
	page ++;
	ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

	start_nup(NUp - 1, 0);
	puts("showpage");
        end_nup(NUp - 1);
      }
    }

    if (slowcollate || sloworder)
    {
      Pages[NumPages] = ftell(temp);

      if (!sloworder)
      {
        while (Copies > 1)
	{
	  rewind(temp);

	  for (number = 0; number < NumPages; number ++)
	  {
	    if (is_first_page(number))
	    {
	      if (ppd == NULL || ppd->num_filters == 0)
		fprintf(stderr, "PAGE: %d 1\n", page);

              printf("%%%%Page: %d %d\n", page, page);
	      page ++;
	      ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
	    }

	    start_nup(number, 1);
	    copy_bytes(temp, Pages[number + 1] - Pages[number]);
	    end_nup(number);
	  }

          if (is_not_last_page(NumPages))
	  {
	    start_nup(NUp - 1, 0);
            end_nup(NUp - 1);
	  }

	  if (slowduplex && !(page & 1))
	  {
	   /*
            * Make sure we have an even number of pages...
	    */

	    if (ppd == NULL || ppd->num_filters == 0)
	      fprintf(stderr, "PAGE: %d 1\n", page);

            printf("%%%%Page: %d %d\n", page, page);
	    page ++;
	    ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

	    start_nup(NUp - 1, 0);
	    puts("showpage");
            end_nup(NUp - 1);
	  }

	  Copies --;
	}
      }
      else
      {
        page_count = (NumPages + NUp - 1) / NUp;
	copy       = 0;

        do
	{
	  if (slowduplex && (page_count & 1))
	  {
            basepage = page_count - 1;
	  }
	  else
	    basepage = page_count - 1 - slowduplex;

	  for (; basepage >= 0; basepage -= 1 + slowduplex)
	  {
	    if (ppd == NULL || ppd->num_filters == 0)
	      fprintf(stderr, "PAGE: %d %d\n", page,
	              slowcollate ? 1 : Copies);

            printf("%%%%Page: %d %d\n", page, page);
	    page ++;

	    ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

	    for (subpage = 0, number = basepage * NUp;
	         subpage < NUp && number < NumPages;
		 subpage ++, number ++)
	    {
	      start_nup(number, 1);
	      fseek(temp, Pages[number], SEEK_SET);
	      copy_bytes(temp, Pages[number + 1] - Pages[number]);
	      end_nup(number);
	    }

            if (is_not_last_page(number))
	    {
	      start_nup(NUp - 1, 0);
              end_nup(NUp - 1);
	    }

            if (slowduplex)
	    {
              if (number < NumPages)
	      {
		if (ppd == NULL || ppd->num_filters == 0)
		  fprintf(stderr, "PAGE: %d %d\n", page,
	        	  slowcollate ? 1 : Copies);

        	printf("%%%%Page: %d %d\n", page, page);
		page ++;

		ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

		for (subpage = 0, number = (basepage + 1) * NUp;
	             subpage < NUp && number < NumPages;
		     subpage ++, number ++)
		{
		  start_nup(number, 1);
		  fseek(temp, Pages[number], SEEK_SET);
		  copy_bytes(temp, Pages[number + 1] - Pages[number]);
		  end_nup(number);
		}

        	if (is_not_last_page(number))
		{
		  start_nup(NUp - 1, 0);
        	  end_nup(NUp - 1);
		}
              }
	      else
	      {
	       /*
        	* Make sure we have an even number of pages...
		*/

		if (ppd == NULL || ppd->num_filters == 0)
		  fprintf(stderr, "PAGE: %d %d\n", page, slowcollate ? 1 : Copies);

        	printf("%%%%Page: %d %d\n", page, page);
		page ++;
		ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

		start_nup(NUp - 1, 0);
		puts("showpage");
        	end_nup(NUp - 1);

        	basepage = page_count - 1;
	      }
	    }
	  }

	  copy ++;
	}
	while (copy < Copies && slowcollate);
      }
    }

   /*
    * Copy the trailer, if any...
    */

    puts("%%Trailer");
    printf("%%%%Pages: %d\n", page - 1);

    while (psgets(line, sizeof(line), fp) != NULL)
    {
      if (strcmp(line, "\004") != 0 &&
          strncmp(line, "%%Pages:", 8) != 0)
        fputs(line, stdout);

      if (strncmp(line, "%%EOF", 5) == 0)
      {
        fputs("DEBUG: Saw EOF!\n", stderr);
        saweof = 1;
	break;
      }
    }
  }
  else
  {
   /*
    * No DSC comments - write any page commands and then the rest of the file...
    */

    if (slowcollate && Copies > 1)
      printf("%%%%Pages: %d\n", Copies);
    else
      puts("%%Pages: 1");

    if (UseESPsp)
      puts("userdict/ESPshowpage/showpage load put\n"
	   "userdict/showpage{}put");

    puts("%%BeginProlog");
    do_prolog(ppd);
    puts("%%EndProlog");

    puts("%%BeginSetup");
    do_setup(ppd, Copies, Collate, slowcollate, g, b);
    puts("%%EndSetup");

    if (ppd == NULL || ppd->num_filters == 0)
      fprintf(stderr, "PAGE: 1 %d\n", slowcollate ? 1 : Copies);

    ppdEmit(ppd, stdout, PPD_ORDER_PAGE);

    saweof = 1;

    while ((nbytes = fread(line, 1, sizeof(line), fp)) > 0)
    {
      fwrite(line, 1, nbytes, stdout);

      if (slowcollate)
	fwrite(line, 1, nbytes, temp);
    }

    if (UseESPsp)
      puts("ESPshowpage");

    if (slowcollate)
    {
      while (Copies > 1)
      {
	if (ppd == NULL || ppd->num_filters == 0)
	  fputs("PAGE: 1 1\n", stderr);

        ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
	rewind(temp);
	copy_bytes(temp, 0);
	Copies --;

        if (UseESPsp)
          puts("ESPshowpage");
      }
    }
  }

 /*
  * Send %%EOF if needed...
  */

  if (!saweof)
    puts("%%EOF");

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

  if (ppd != NULL)
  {
    if (ppd->jcl_end)
      fputs(ppd->jcl_end, stdout);
#if 0	// we don't generate control-D
    else if (ppd->num_filters == 0)
      putchar(0x04);
#endif
  }

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

  if (slowcollate || sloworder)
  {
    fclose(temp);
    unlink(tempfile);
  }

  ppdClose(ppd);

  if (fp != stdin)
    fclose(fp);

  return (0);
}


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

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


  if (PageSet != NULL)
  {
   /*
    * See if we only print even or odd pages...
    */

    if (strcasecmp(PageSet, "even") == 0 && ((page - 1) % (NUp << 1)) <  NUp)
      return (0);
    if (strcasecmp(PageSet, "odd")  == 0 && ((page - 1) % (NUp << 1)) >= NUp)
      return (0);
  }

  if (PageRanges == NULL)
    return (1);		/* No range, print all pages... */

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

      if (*range == '-')
      {
        range ++;
	if (!isdigit(*range))
	  upper = 65535;
	else
	  upper = 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(FILE   *fp,		/* I - File to read from */
           size_t length)	/* I - Length of page data */
{
  char		buffer[8192];	/* Data buffer */
  size_t	nbytes,		/* Number of bytes read */
		nleft;		/* Number of bytes left/remaining */


  nleft = length;

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

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

    nleft -= nbytes;

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


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

static void
do_prolog(ppd_file_t *ppd)		/* I - PPD file */
{
 /*
  * Send the document prolog commands...
  */

  if (ppd != NULL && ppd->patches != NULL)
  {
    puts("%%BeginFeature: *JobPatchFile 1");
    puts(ppd->patches);
    puts("%%EndFeature");
  }

  ppdEmit(ppd, stdout, PPD_ORDER_PROLOG);
}


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

static void
do_setup(ppd_file_t *ppd,		/* I - PPD file */
         int        copies,		/* I - Number of copies */
	 int        collate,		/* I - Collate output? */
	 int        slowcollate,	/* I - Slow collate */
	 float      g,			/* I - Gamma value */
	 float      b)			/* I - Brightness value */
{
 /*
  * Send all the printer-specific setup commands...
  */

  ppdEmit(ppd, stdout, PPD_ORDER_DOCUMENT);
  ppdEmit(ppd, stdout, PPD_ORDER_ANY);

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

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

 /*
  * Changes to the transfer function must be made AFTER any
  * setpagedevice code...
  */

  if (g != 1.0 || b != 1.0)
    printf("{ neg 1 add dup 0 lt { pop 1 } { %.3f exp neg 1 add } "
	   "ifelse %.3f mul } bind settransfer\n", g, b);

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

  WriteCommon();
}


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

static void
end_nup(int number)	/* I - Page number */
{
  puts("");

  if (Flip || Orientation || NUp > 1)
    puts("userdict /ESPsave get restore");

  switch (NUp)
  {
    case 1 :
	if (UseESPsp)
	{
	  WriteLabels(Orientation);
          puts("ESPshowpage");
	}
	break;

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

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

	    WriteLabels(NormalLandscape ? 1 : 3);
	  }
	  else
	  {
	   /*
	    * Rotate the labels to landscape...
	    */

	    WriteLabels(NormalLandscape ? 3 : 1);
	  }

          puts("ESPshowpage");
	}
        break;

    default :
	if (is_last_page(number) && UseESPsp)
	{
	  WriteLabels(Orientation);
          puts("ESPshowpage");
	}
        break;
  }

  fflush(stdout);
}


/*
 * 'psgets()' - Get a line from a file.
 *
 * Note:
 *
 *   This function differs from the gets() function in that it
 *   handles any combination of CR, LF, or CR LF to end input
 *   lines.
 */

static char *		/* O - String or NULL if EOF */
psgets(char   *buf,	/* I - Buffer to read into */
       size_t len,	/* I - Length of buffer */
       FILE   *fp)	/* I - File to read from */
{
  char	*bufptr;	/* Pointer into buffer */
  int	ch;		/* Character from file */


  len --;
  bufptr = buf;
  ch     = EOF;

  while ((bufptr - buf) < len)
  {
    if ((ch = getc(fp)) == EOF)
      break;

    if (ch == 0x0d)
    {
     /*
      * Got a CR; see if there is a LF as well...
      */

      ch = getc(fp);
      if (ch != EOF && ch != 0x0a)
        ungetc(ch, fp);	/* Nope, save it for later... */

      ch = 0x0a;
      break;
    }
    else if (ch == 0x0a)
      break;
    else
      *bufptr++ = ch;
  }

 /*
  * Add a trailing newline if it is there...
  */

  if (ch == '\n')
    *bufptr++ = '\n';

 /*
  * Nul-terminate the string and return it (or NULL for EOF).
  */

  *bufptr = '\0';

  if (ch == EOF && bufptr == buf)
    return (NULL);
  else
    return (buf);
}


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

static void
start_nup(int number,		/* I - Page number */
          int show_border)	/* I - Show the page border? */
{
  int	pos;			/* Position on page */
  int	x, y;			/* Relative position of subpage */
  float	w, l,			/* Width and length of subpage */
	tx, ty;			/* Translation values for subpage */
  float	pw, pl;			/* Printable width and length of full page */


  if (Flip || Orientation || NUp > 1)
    puts("userdict/ESPsave save put");

  if (Flip)
    printf("%.1f 0.0 translate -1 1 scale\n", PageWidth);

  pos = number % NUp;
  pw  = PageRight - PageLeft;
  pl  = PageTop - PageBottom;

  fprintf(stderr, "DEBUG: pw = %.1f, pl = %.1f\n", pw, pl);
  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);

#if 0	// debugging only!
    if(Orientation & 1)
    {
        if(Orientation == 1)
            printf("%.1f %.1f %.1f %.1f ESPrs\n", 
                PageBottom, PageLeft, 
                PageTop - PageBottom, PageRight - PageLeft);
        else//reverse
            printf("%.1f %.1f %.1f %.1f ESPrs\n", 
                PageLength - PageTop, PageWidth - PageRight, 
                PageTop - PageBottom, PageRight - PageLeft);
    }else{
        if(Orientation == 0)
	    printf("%.1f %.1f %.1f %.1f ESPrs\n", 
		PageLeft, PageBottom, 
		PageRight - PageLeft, PageTop - PageBottom);
	else
	    printf("%.1f %.1f %.1f %.1f ESPrs\n", 
		PageWidth - PageRight, PageLength - PageTop, 
		PageRight - PageLeft, PageTop - PageBottom);
    }
#endif

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

  if (Duplex && NUp > 1 && ((number / NUp) & 1))
    printf("%.1f %.1f translate\n", PageWidth - PageRight, PageBottom);
  else if (NUp > 1)
    printf("%.1f %.1f translate\n", PageLeft, PageBottom);

  switch (NUp)
  {
    default :
        w = PageWidth;
	l = PageLength;
	break;

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

          if (Layout & LAYOUT_NEGATEY)
	    x = 1 - x;

          w = pl;
          l = w * PageLength / PageWidth;

          if (l > (pw * 0.5))
          {
            l = pw * 0.5;
            w = l * PageWidth / PageLength;
          }

          tx = 0.5 * (pw * 0.5 - l);
          ty = 0.5 * (pl - w);

          if (NormalLandscape)
            printf("0.0 %.1f translate -90 rotate\n", pl);
	  else
	    printf("%.1f 0.0 translate 90 rotate\n", pw);

          printf("%.1f %.1f translate %.3f %.3f scale\n",
                 ty, tx + l * x, w / PageWidth, l / PageLength);
        }
	else
	{
          x = pos & 1;

          if (Layout & LAYOUT_NEGATEX)
	    x = 1 - x;

          l = pw;
          w = l * PageWidth / PageLength;

          if (w > (pl * 0.5))
          {
            w = pl * 0.5;
            l = w * PageLength / PageWidth;
          }

          tx = 0.5 * (pl * 0.5 - w);
          ty = 0.5 * (pw - l);

          if (NormalLandscape)
	    printf("%.1f 0.0 translate 90 rotate\n", pw);
	  else
            printf("0.0 %.1f translate -90 rotate\n", pl);

          printf("%.1f %.1f translate %.3f %.3f scale\n",
                 tx + w * x, ty, w / PageWidth, l / PageLength);
        }
        break;

    case 4 :
        if (Layout & LAYOUT_VERTICAL)
	{
	  x = (pos / 2) & 1;
          y = pos & 1;
        }
	else
	{
          x = pos & 1;
	  y = (pos / 2) & 1;
        }

        if (Layout & LAYOUT_NEGATEX)
	  x = 1 - x;

	if (Layout & LAYOUT_NEGATEY)
	  y = 1 - y;

        w = pw * 0.5;
	l = w * PageLength / PageWidth;

	if (l > (pl * 0.5))
	{
	  l = pl * 0.5;
	  w = l * PageWidth / PageLength;
	}

        tx = 0.5 * (pw * 0.5 - w);
        ty = 0.5 * (pl * 0.5 - l);

	printf("%.1f %.1f translate %.3f %.3f scale\n", tx + x * w, ty + y * l,
	       w / PageWidth, l / PageLength);
        break;

    case 6 :
        if (Orientation & 1)
	{
	  if (Layout & LAYOUT_VERTICAL)
	  {
	    x = pos / 3;
	    y = pos % 3;

            if (Layout & LAYOUT_NEGATEX)
	      x = 1 - x;

            if (Layout & LAYOUT_NEGATEY)
	      y = 2 - y;
	  }
	  else
	  {
	    x = pos & 1;
	    y = pos / 2;

            if (Layout & LAYOUT_NEGATEX)
	      x = 1 - x;

            if (Layout & LAYOUT_NEGATEY)
	      y = 2 - y;
	  }

          w = pl * 0.5;
          l = w * PageLength / PageWidth;

          if (l > (pw * 0.333))
          {
            l = pw * 0.333;
            w = l * PageWidth / PageLength;
          }

          tx = 0.5 * (pl - 2 * w);
          ty = 0.5 * (pw - 3 * l);

          if (NormalLandscape)
            printf("0.0 %.1f translate -90 rotate\n", pl);
	  else
	    printf("%.1f 0.0 translate 90 rotate\n", pw);

          printf("%.1f %.1f translate %.3f %.3f scale\n",
                 tx + x * w, ty + y * l, w / PageWidth, l / PageLength);
        }
	else
	{
	  if (Layout & LAYOUT_VERTICAL)
	  {
	    x = pos / 2;
	    y = pos & 1;

            if (Layout & LAYOUT_NEGATEX)
	      x = 2 - x;

            if (Layout & LAYOUT_NEGATEY)
	      y = 1 - y;
	  }
	  else
	  {
	    x = pos % 3;
	    y = pos / 3;

            if (Layout & LAYOUT_NEGATEX)
	      x = 2 - x;

            if (Layout & LAYOUT_NEGATEY)
	      y = 1 - y;
	  }

          l = pw * 0.5;
          w = l * PageWidth / PageLength;

          if (w > (pl * 0.333))
          {
            w = pl * 0.333;
            l = w * PageLength / PageWidth;
          }

          tx = 0.5 * (pl - 3 * w);
          ty = 0.5 * (pw - 2 * l);

          if (NormalLandscape)
	    printf("%.1f 0.0 translate 90 rotate\n", pw);
	  else
            printf("0.0 %.1f translate -90 rotate\n", pl);

          printf("%.1f %.1f translate %.3f %.3f scale\n",
                 tx + w * x, ty + l * y, w / PageWidth, l / PageLength);
        }
        break;

    case 9 :
        if (Layout & LAYOUT_VERTICAL)
	{
	  x = (pos / 3) % 3;
          y = pos % 3;
        }
	else
	{
          x = pos % 3;
	  y = (pos / 3) % 3;
        }

        if (Layout & LAYOUT_NEGATEX)
	  x = 2 - x;

	if (Layout & LAYOUT_NEGATEY)
	  y = 2 - y;

        w = pw * 0.333;
	l = w * PageLength / PageWidth;

	if (l > (pl * 0.333))
	{
	  l = pl * 0.333;
	  w = l * PageWidth / PageLength;
	}

        tx = 0.5 * (pw * 0.333 - w);
        ty = 0.5 * (pl * 0.333 - l);

	printf("%.1f %.1f translate %.3f %.3f scale\n", tx + x * w, ty + y * l,
	       w / PageWidth, l / PageLength);
        break;

    case 16 :
        if (Layout & LAYOUT_VERTICAL)
	{
	  x = (pos / 4) & 3;
          y = pos & 3;
        }
	else
	{
          x = pos & 3;
	  y = (pos / 4) & 3;
        }

        if (Layout & LAYOUT_NEGATEX)
	  x = 3 - x;

	if (Layout & LAYOUT_NEGATEY)
	  y = 3 - y;

        w = pw * 0.25;
	l = w * PageLength / PageWidth;

	if (l > (pl * 0.25))
	{
	  l = pl * 0.25;
	  w = l * PageWidth / PageLength;
	}

        tx = 0.5 * (pw * 0.25 - w);
        ty = 0.5 * (pl * 0.25 - l);

	printf("%.1f %.1f translate %.3f %.3f scale\n", tx + x * w, ty + y * l,
	       w / PageWidth, l / PageLength);
        break;
  }

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

  if (Border && show_border)
  {
    int		rects;		/* Number of border rectangles */
    float	fscale,		/* Scaling value for points */
		margin;		/* Current margin for borders */


    rects  = (Border & BORDER_DOUBLE) ? 2 : 1;
    fscale = PageWidth / w;
    margin = 2.25 * fscale;

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

    puts("gsave");
    printf("%.3f setlinewidth 0 setgray newpath\n",
           (Border & BORDER_THICK) ? 0.5 * fscale : 0.24 * fscale);

   /*
    * Draw border boxes...
    */

    for (; rects > 0; rects --, margin += 2 * fscale)
      if (NUp > 1)
	printf("%.1f %.1f %.1f %.1f ESPrs\n",
	       margin,
	       margin,
	       PageWidth - 2 * margin,
	       PageLength - 2 * margin);
      else
	printf("%.1f %.1f %.1f %.1f ESPrs\n",
               PageLeft + margin,
	       PageBottom + margin,
	       PageRight - PageLeft - 2 * margin,
	       PageTop - PageBottom - 2 * margin);

   /*
    * Restore pen settings...
    */

    puts("grestore");
  }

  if (NUp > 1)
  {
   /*
    * Clip the page that follows to the bounding box of the page...
    */

    printf("0 0 %.1f %.1f ESPrc\n", PageWidth, PageLength);
  }
}


/*
 * End of "$Id: pstops.c,v 1.14 2002/07/02 19:22:42 gelphman Exp $".
 */
