/* ======== PSpage - a page manipulation program ======== */
/*
 * Copyright 1991, 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * This file may be freely copied and redistributed as long as:
 *   1) This entire notice continues to be included in the file, 
 *   2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * PostScript, Display PostScript, and Adobe are registered trademarks of
 * Adobe Systems Incorporated.
 * 
 * ************************************************************************
 * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT
 * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS
 * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR 
 * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY 
 * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION, 
 * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * ************************************************************************
 */

/*
 * SUMMARY:
 * This program will manipulate a properly formatted PostScript document,
 * that is, one that complies with the Adobe Document Structuring Conventions.
 * Given the simplicity of this program, it is really only interested in the
 * following comments;
 * 	%!PS-Adobe-X.x
 * 	%%EndSetup
 * 	%%Page:
 * 	%%Trailer
 * 
 * OPTIONS:
 * p - allows a range of pages to be specified in the form
 *     of a list separated by commas.  E.g. 4,5-13,22
 *     Ranges can be specified forwards, e.g. 5-13, or backwards, e.g. 13-5
 * r - will cause PSpage to page reverse the document.
 * 
 * DEFAULTS:
 * If options are not specified, all pages are output without page reversal.
 * 
 * KNOWN BUGS:
 * For page reversal: PSpage does not check the %%Pages: comment to see if
 * the second argument is a 0, which indicates that page order should not
 * be changed.
 * 
 * PSpage does not handle included documents, that would be better serviced
 * by a proper state machine implementation.
 * 
 * Specifying an open range (e.g. 5-) to indicate that the current page until
 * the end of the document is desired but is not supported.
 * 
 * THINGS TO TWEAK:
 * ** MAXPAGES defines the maximum number of pages that can be reversed
 * ** MAXRANGES defines the maximum number of ranges that can be specified
 * ** In the #include section, the file <stdarg.h> may be <varargs.h> or 
 * <sys/varagrs.h> on your system.
 *
 * CHANGE HISTORY:
 *   90-June-01	orthlieb	created
 *   91-June-05	mackay		incorporated comments; made lines < 80 chars;
 *   91-Sept-26 mackay		added comment on #include <stdarg.h>
 */


/* ----------------------- includes ----------------------*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
/* ------------------------ defines ----------------------*/
#define TRUE 1
#define FALSE 0
#define VERBOSE TRUE
#define TERSE FALSE
#define error printf

/* MAXPAGES defines the maximum number of pages that can be reversed */
#define MAXPAGES 100

/* MAXRANGES defines the maximum number of ranges that can be specified */
#define MAXRANGES 25

/* ----------------------- typedefs --------------------- */
typedef char bool;

/* ------------------------ globals ----------------------*/
long pageloc[MAXPAGES];
int  numpages;
bool pagereverse = FALSE;    /* Default: No page reversal */
char *pageranges[MAXRANGES];
int numranges = 0;
FILE *infile, *outfile;

/* ------------------------ externs ----------------------*/
extern void find_page_locations(FILE *thefile);
extern void output_document(FILE *infile, FILE *outfile);
extern void check_args(int numargs, char **args);

/* ----------------------- mainline ----------------------*/
void main (int argc, char **argv)
{

  /* Setup page ranges */
  numranges = 1;
  pageranges[0] = "*";
  pageranges[1] = NULL;

  check_args(argc, argv);
  find_page_locations(infile);
  rewind(infile);
  output_document(infile, outfile);
}

void print_instructions(bool howlong)
{
   error("PSpage [-p <pages>] [-r] <infile> <outfile>\n");
   if (howlong == TERSE) return;
   error("This program will manipulate a properly formatted PostScript\n");
   error("document, that is, one that complies with the Adobe Document\n");
   error("Structuring Conventions. \n\n");
   error("Options:\n");
   error("p - allows a range of pages to be specified in the form\n");
   error("    of a list separated by commas.  E.g. 4,5-13,22\n");
   error("    Ranges can be specified forwards, e.g. 5-13,\n");
   error("    or backwards, e.g. 13-5\n");
   error("r - will cause PSpage to page reverse the document.\n\n");
   error("Defaults:\n");
   error("If options are not specified, all pages are output");
   error("without page reversal\n\n");
   error("Known Bugs:\n");
   error("For page reversal: PSpage does not check the %%Pages: comment\n");
   error("to see if the second argument is a 0, which indicates that page\n");
   error("order should not be changed.\n");
   error("PSpage does not handle included documents, that would be better\n");
   error("serviced by a proper state machine implementation.\n");
}

void check_args(int numargs, char **arg)
{
   int  mark;
   char *cmd;

   /* Check to see if long instructions are desired */
   if (arg[1][0] == '?') {
      print_instructions(VERBOSE);
      exit(0);
   }
   if (numargs < 3) {
      error("PSpage ERROR: Not enough arguments\n");
      print_instructions(TERSE);
      exit(0);
   }

   mark = 1;
   while(mark < numargs) {
      /* If we've found a command */
      if (arg[mark][0] == '-') {
	 for (cmd = &arg[mark][1]; *cmd != NULL; cmd++) {
            /* Scan the arguments */
	    switch (*cmd) {
   	       case 'p': /* Page number list */
	          error("PSpage STATUS: using page range option.\n");
                  /* The next argument must be a list of page ranges */
                  /* Breakout the page ranges into individual tokens */
                  numranges = 0;
                  pageranges[numranges++] = strtok(arg[mark+1],",");
                  while ((pageranges[numranges++] = strtok(NULL,",")) != NULL);
                  numranges--; /* Reset to reflect actual number of ranges */
                  mark++;   /* move to next token */
               break;
               case 'r': /* Page reversal desired */
	          error("PSpage STATUS: performing page reversal.\n");
                  pagereverse = TRUE;
	       break;
               default:
                  error("PSpage ERROR: invalid option\n");
	          print_instructions(TERSE);
	          exit(-1);
               break;
            }
         }
      }
      else {
         if (infile == NULL) {
	    if ((infile = fopen(arg[mark],"rb")) == NULL) {
	       error("PSpage ERROR: cannot open input file %s.\n",arg[mark]);
	       exit(-1);
	    }
	 }
         else {
	    if ((outfile = fopen(arg[mark],"wb")) == NULL) {
	       error("PSpage ERROR: cannot open output file %s.\n",arg[mark]);
	       exit(-1);
	    }
	 }
      }
      mark++;
   }
}

int check_comment(char *str, int numargs, ...)
{
  va_list ap;
  char    *comment;

  va_start(ap, numargs);

  while (numargs-- > 0) {
    comment = va_arg(ap, char *);
    if (strncmp(str, comment, strlen(comment)) == 0) {
      va_end(ap);
      return TRUE;
    }
  }
  va_end(ap);
  return FALSE;
}

void find_page_locations(FILE *thefile)
{
  int  i;
  bool endoffile = FALSE;
  long oldpos = 0;
  char buf[256];
  char *junk;

  while (endoffile == FALSE) {
    /* Save our current position */
    oldpos = ftell(thefile);

    /* Get a line from the file */
    if (fgets(buf, 256, thefile) == NULL) {
      /* No %%Trailer, No %%EOF */
      if (!pageloc[numpages]) pageloc[numpages] = oldpos;
      endoffile = TRUE;
    }
    else {
      /* Check to see if we've found a comment */
      if (buf[0] == '%' && buf[1] == '%') {
	/* Check for the Page comment, record the location */
	if (check_comment(buf, 1, "%%Page:") == TRUE) {
	  /* Note the location of this page */
	  pageloc[numpages++] = oldpos;
	}
	/* Check for the trailer or EOF to see if it's time to leave */
	if (check_comment(buf, 2, "%%Trailer", "%%EOF") == TRUE) {
	  /* End of the last page */
	  pageloc[numpages] = oldpos;
	  endoffile = TRUE; /* No use searching any more */
	}
      }
    }
  }
  if (numpages == 0) {
    error("PSpage ERROR: No %%%%Page: comments in this document.\n");
    exit(-2);
  } else {
    error("PSpage STATUS: This document has %d pages.\n", numpages);
  }
}

void find_page_parms(char *range, int *start, int *end)
{
   char *pdash = NULL;
   int  temp;

   if (range[0] == '*') {
     *start = 1;
     *end = numpages;
   }
   /* Single number */
   else if ((pdash = strchr(range,'-')) == NULL) {
      *start = *end = atoi(range);
   }
   else {
   /* Range of numbers */
      *pdash++ = NULL;
      *start = atoi(range); *end = atoi(pdash);
   }

   if (*start < 1 | *end < 1 | *start > numpages | *end > numpages) {
      error("PSpage ERROR: page range is invalid [%d-%d].\n", start, end);
      exit(-3);
   }
   (*start)--; (*end)--;

   /* Pull a little sneaky to help with page reversal */
   if (pagereverse == TRUE) {
      temp = *start; *start = *end; *end = temp;
   }
}

void print_page(FILE *infile, FILE *outfile, int page)
{
   long i;

   if (fseek(infile, pageloc[page], SEEK_SET) != 0) {
      error("PSpage ERROR: Seek at position %d, page %d\n", 
        pageloc[page], page+1);
      exit(-2);
   }
   for (i = pageloc[page]; i < pageloc[page+1]; i++)
      fputc(fgetc(infile), outfile);
}

void output_document(FILE *infile, FILE *outfile)
{
   char c;
   int  pagestart, pageend, pageincr, rangeindex, rangeincr;
   long i;
   register int r, p;

   /* Print out the top of the file */
   for (i = 0; i < pageloc[0]; i++)
      fputc(fgetc(infile),outfile);

   /* Now start the page manipulations */
   /* Flip the order of ranges for page reversal */
   rangeincr = 1; rangeindex = 0;
   if (pagereverse == TRUE) {
      rangeincr = -1;
      rangeindex = numranges - 1;
   }

   for (r = rangeindex; r < numranges && r >= 0; r += rangeincr) {
      find_page_parms(pageranges[r], &pagestart, &pageend);
      /* Print out each individual page */
      if (pagestart > pageend)
	 for (p = pagestart; p >= pageend;p--)
            print_page(infile, outfile, p);
      else
	 for (p = pagestart; p <= pageend; p++)
            print_page(infile, outfile, p);
   }

   /* Now print out the end of the file */
   if (fseek(infile, pageloc[numpages], SEEK_SET) != 0) {
     error("PSpage ERROR: Seek at position %d while writing trailer %d\n",
  	   pageloc[numpages]);
     exit(-2);
   }
   while ((c = fgetc(infile)) != EOF)
     fputc(c,outfile);
}

