/*
#  poster.c  -  a generic PS scaling program
#  This program scales a PostScript page to a given size (a poster).
#  The output can be tiled on multiple sheets, and output
#  media size can be chosen independently.
#  Each tile (sheet) of a will bear cropmarks and slightly overlapping
#  image for easier poster assembly.
#
#  Compile this program with:
#        cc -O -o poster poster.c -lm
#  or something alike.
#
#  Maybe you want to change the `DefaultMedia' and `DefaultImage'
#  settings in the few lines below, to reflect your local situation.
#  Names can to be chosen from the `mediatable' further down.
#
#  The `Gv_gs_orientbug 1' disables a feature of this program to
#  ask for landscape previewing of rotated images.
#  Our currently installed combination of ghostview 1.5 with ghostscript 3.33
#  cannot properly do a landscape viewing of the `poster' output.
#  The problem does not exist in combination with an older ghostscript 2.x,
#  and has the attention of the ghostview authors.
#  (The problem is in the evaluation of the `setpagedevice' call.)
#  If you have a different previewing environment,
#  you might want to set `Gv_gs_orientbug 0'
#
#  Jos van Eijndhoven, Eindhoven University of Technology
#  email: J.T.J.v.Eijndhoven@ele.tue.nl
#  September 7,  1995
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public
#  License version 2 as published by the Free Software Foundation.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU Library General Public License
#  along with this library; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
*/

#define Gv_gs_orientbug 1
#define DefaultMedia  "A4"
#define DefaultImage  "A4"
#define DefaultCutMargin "5%"
#define DefaultWhiteMargin "0"
#define BUFSIZE 1024

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <math.h>


extern char *optarg;        /* silently set by getopt() */
extern int optind, opterr;  /* silently set by getopt() */

static void usage();
static void dsc_head1();
static int dsc_infile( double ps_bb[4]);
static void dsc_head2( void);
static void printposter( void);
static void printprolog();
static void tile ( int row, int col, int pagetoprint);
static void printfile( int pagetoprint);
static void postersize( char *scalespec, char *posterspec);
static void box_convert( char *boxspec, double psbox[4]);
static void boxerr( char *spec);
static void margin_convert( char *spec, double margin[2]);
static int mystrncasecmp( const char *s1, const char *s2, int n);
static char* tmpinfile( void );
static int parsepagespec( int **page_array, int *len, int *size, char *pagespec );
static void do_fake_mode( void );

int verbose;
char *myname;
char *infile;
int rotate, nrows, ncols;
int manualfeed = 0;
unsigned int clipFacilValue;
int tail_cntl_D = 0;
#define Xl 0
#define Yb 1
#define Xr 2
#define Yt 3
#define X 0
#define Y 1
double posterbb[4];	/* final image in ps units */
double imagebb[4];	/* original image in ps units */
double mediasize[4];	/* [34] = size of media to print on, [01] not used! */
double cutmargin[2];
double whitemargin[2];
double scale;		/* linear scaling factor */
int number_pages = 1;
int *pages = NULL;
int pages_size = 0;
int pages_length = 0;
int fake_mode = 0;

/* defaults: */
char *imagespec = NULL;
char *posterspec = NULL;
char *mediaspec = NULL;
char *cutmarginspec = NULL;
char *whitemarginspec = NULL;
char *scalespec = NULL;
char *filespec = NULL;
char *pagespec = NULL;
char *clipfacil = "0xFF";
char *overlap = "6";

/* media sizes in ps units (1/72 inch) */
static char *mediatable[][2] =
{	{ "Letter",   "612,792"},
	{ "Legal",    "612,1008"},
	{ "Tabloid",  "792,1224"},
	{ "Ledger",   "792,1224"},
	{ "Executive","540,720"},
	{ "Monarch",  "279,540"},
	{ "Statement","396,612"},
	{ "Folio",    "612,936"},
	{ "Quarto",   "610,780"},
	{ "A0",       "2384,3370"},
	{ "A1",       "1684,2384"},
	{ "A2",       "1191,1684"},
	{ "A3",       "842,1191"},
	{ "A4",       "595,842"},
	{ "A5",       "420,595"},
	{ "A6",       "298,420"},
	{ "A7",       "210,298"},
	{ "A8",       "147,210"},
	{ "A9",       "105,147"},
	{ "A10",      "74,105"},
	{ "B0",       "2835,4008"},
	{ "B1",       "2004,2835"},
	{ "B2",       "1417,2004"},
	{ "B3",       "1001,1417"},
	{ "B4",       "709,1001"},
	{ "B5",       "499,709"},
	{ "B6",       "354,499"},
	{ "B7",       "249,354"},
	{ "B8",       "176,249"},
	{ "B9",       "125,176"},
	{ "B10",      "88,125"},
	{ "C4",       "649,918"},
	{ "C5",       "459,649"},
	{ "C5E",      "459,649"},
	{ "EnvC5",    "459,649"},
	{ "C6",       "323,459"},
	{ "JB0",      "2920,4127"},
	{ "JB1",      "2064,2920"},
	{ "JB2",      "1460,2064"},
	{ "JB3",      "1032,1460"},
	{ "JB4",      "729,1032"},
	{ "JB5",      "516,729"},
	{ "JB6",      "363,516"},
	{ "JB7",      "258,363"},
	{ "JB8",      "181,258"},
	{ "JB9",      "128,181"},
	{ "JB10",     "91,128"},
	{ "Dl",       "281,595"},
	{ "DL",       "281,595"},
	{ "DLE",      "312,624"},
	{ "EnvDL",    "312,624"},
	{ "Comm10",   "298,684"},
	{ "COM10",    "298,684"},
	{ "Env10",    "298,684"},

	/* as fall-back: linear units of measurement: */
	{ "p",        "1,1"},
	{ "i",        "72,72"},
	{ "ft",       "864,864"},
	{ "mm",       "2.83465,2.83465"},
	{ "cm",       "28.3465,28.3465"},
	{ "m",        "2834.65,2834.65"},
	{ NULL, NULL}
};


int main( int argc, char *argv[])
{
	int opt;
	double ps_bb[4];
	int got_bb;
	int need_remove = 0;

	myname = argv[0];

	while ((opt = getopt( argc, argv, "vfi:c:w:C:O:m:p:s:o:P:F")) != EOF)
	{	switch( opt)
		{ case 'v': verbose++; break;
		  case 'f': manualfeed = 1; break;
		  case 'i': imagespec = optarg; break;
		  case 'c': cutmarginspec = optarg; break;
		  case 'w': whitemarginspec = optarg; break;
		  case 'C': clipfacil = optarg; break;
		  case 'O': overlap = optarg; break;
		  case 'm': mediaspec = optarg; break;
		  case 'p': posterspec = optarg; break;
		  case 's': scalespec = optarg; break;
		  case 'o': filespec = optarg; break;
		  case 'P': pagespec = optarg; break;
		  case 'F': fake_mode = 1; break;
		  default:	usage(); break;
		}
	}

	/*** check command line arguments ***/
	if (scalespec && posterspec)
	{	fprintf( stderr, "Please don't specify both -s and -p, ignoring -s!\n");
		scalespec = NULL;
	}

	if (optind < argc)
		infile = argv[ optind];
	else if ( !fake_mode )
	{
		infile = tmpinfile();
		need_remove = 1;
	}
	/*
	{	fprintf( stderr, "Filename argument missing!\n");
		usage();
	}
	*/

	/*** decide on media size ***/
	if (!mediaspec)
	{	mediaspec = DefaultMedia;
		if (verbose)
			fprintf( stderr,
				"Using default media of %s\n",
				mediaspec);
	}
	box_convert( mediaspec, mediasize);
	if (mediasize[3] < mediasize[2])
	{	fprintf( stderr, "Media should always be specified in portrait format!\n");
		exit(1);
	}
	if (mediasize[2]-mediasize[0] <= 10.0 || mediasize[3]-mediasize[1] <= 10.0)
	{	fprintf( stderr, "Media size is ridiculous!\n");
		exit(1);
	}

	/*** defaulting poster size ? **/
	if (!scalespec && !posterspec)
	{	/* inherit postersize from given media size */
		posterspec = mediaspec;
		if (verbose)
			fprintf( stderr,
				"Defaulting poster size to media size of %s\n",
				mediaspec);
	}

	/*** decide the cutmargin size, after knowing media size ***/
	if (!cutmarginspec)
	{	if (posterspec && (!strcmp( posterspec, mediaspec)))
			/* zero cutmargin if printing to 1 sheet */
			cutmarginspec = "0%";
		else
			cutmarginspec = DefaultCutMargin;
		if (verbose)
			fprintf( stderr,
				"Using default cutmargin of %s\n",
				cutmarginspec);
	}
	margin_convert( cutmarginspec, cutmargin);

	clipFacilValue = atoi (clipfacil);

	/*** decide the whitemargin size, after knowing media size ***/
	if (!whitemarginspec)
	{	whitemarginspec = DefaultWhiteMargin;
		if (verbose)
			fprintf( stderr,
				"Using default whitemargin of %s\n",
				whitemarginspec);
	}
	margin_convert( whitemarginspec, whitemargin);

	/*** parse page specification ***/
	if ( pagespec && parsepagespec( &pages, &pages_length, &pages_size, pagespec ) == 0 )
	{
		fprintf( stderr, "Wrong page set specification: %s\n", pagespec );
		exit( 1 );
	}

	if ( fake_mode )
	{
		do_fake_mode();
		exit( 0 );
	}

	/******************* now start doing things **************************/
	/* open output file */
	if (filespec)
	{	if (!freopen( filespec, "w", stdout))
		{	fprintf( stderr, "Cannot open '%s' for writing!\n",
				 filespec);
			exit(1);
		} else if (verbose)
			fprintf( stderr, "Opened '%s' for writing\n",
				 filespec);
	}

	/******* I might need to read some input to find picture size ********/
	/* start DSC header on output */
	dsc_head1();

	/* pass input DSC lines to output, get BoundingBox spec if there */
	got_bb = dsc_infile( ps_bb);

	/**** decide the input image bounding box ****/
	if (!got_bb && !imagespec)
	{	imagespec = DefaultImage;
		if (verbose)
			fprintf( stderr,
				"Using default input image of %s\n",
				imagespec);
	}
	if (imagespec)
		box_convert( imagespec, imagebb);
	else
	{	int i;
		for (i=0; i<4; i++)
			imagebb[i] = ps_bb[i];
	}

	if (verbose > 1)
		fprintf( stderr, "   Input image is: [%g,%g,%g,%g]\n",
			imagebb[0], imagebb[1], imagebb[2], imagebb[3]);

	if (imagebb[2]-imagebb[0] <= 0.0 || imagebb[3]-imagebb[1] <= 0.0)
	{	fprintf( stderr, "Input image should have positive size!\n");
		exit(1);
	}


	/*** decide on the scale factor and poster size ***/
	postersize( scalespec, posterspec);

	if (verbose > 1)
		fprintf( stderr, "   Output image is: [%g,%g,%g,%g]\n",
			posterbb[0], posterbb[1], posterbb[2], posterbb[3]);

	
	dsc_head2();

	printposter();

	if ( need_remove )
		remove( infile );

	exit (0);
}

static void addpages( int **page_array, int p1, int p2, int n, int *size )
{
	int i;

	while ( p2-p1+1+n > *size )
	{
		*size += 100;
		*page_array = ( int* )realloc( *page_array, sizeof( int ) * ( *size ) );
	}
	for ( i=n+p2-p1; i>=n; i-- )
		( *page_array )[ i ] = i+p1-n;
}

static void do_fake_mode( void )
{
	if ( !imagespec )
		imagespec = posterspec;
	box_convert( imagespec, imagebb );

	postersize( scalespec, posterspec );

	fprintf( stderr, "%d %d %d %g %g %g %g %g %g %g %g %g\n", nrows, ncols, rotate, mediasize[ 2 ], mediasize[ 3 ],
			cutmargin[ 0 ], cutmargin[ 1 ], posterbb[ 0 ], posterbb[ 1 ], posterbb[ 2 ], posterbb[ 3 ], scale );

	exit( 0 );
}

static int parsepagespec( int **page_array, int *len, int *size, char *pagespec )
{
	char *c = pagespec, *r = NULL;
	int p1, p2;

	if ( *page_array != NULL )
		return 1;

	*size = 100;
	*len = 0;
	*page_array = ( int* )malloc( sizeof( int ) * ( *size ) );

	while ( *c != '\0' )
	{
		p1 = p2 = 0;
		p1 = ( int )strtol( c, &r, 10 );
		if ( *r == '\0' || *r == ',' )
			p2 = p1;
		else if ( *r == '-' )
		{
			c = r+1;
			p2 = ( int )strtol( c, &r, 10 );
			if ( *r != '\0' && *r != ',' )
				goto error;
		}
		else
			goto error;
		if ( p1 > 0 && p2 > 0 && p2 >= p1 )
		{
			addpages( page_array, p1, p2, *len, size );
			*len += ( p2-p1+1 );
		}
		else
			goto error;
		if ( *r == '\0' )
			break;
		c = r+1;
	}

	return 1;

error:
	free( *page_array );
	*size = 0;
	return 0;
}

static char* tmpinfile()
{
	char filename[ 256 ] = {0};
	int fd = -1;

	if ( getenv( "TEMP" ) == NULL )
		strcat( filename, "/tmp" );
	else
		strcat( filename, getenv( "TEMP" ) );
	strcat( filename, "/poster.XXXXXX" );

	fd = mkstemp( filename );
	if ( fd == -1 )
	{
		fprintf( stderr, "Cannot create temporary file.\n" );
		exit( 1 );
	}
	else
	{
		char buffer[ 4096 ];
		int n;

		do
		{
			n = fread( buffer, sizeof( char ), 4096, stdin );
			if ( n > 0 )
				write( fd, buffer, n );
		} while ( n == 4096 );

		close( fd );
		return strdup( filename );
	}
}

static void usage()
{
	fprintf( stderr, "Usage: %s <options> infile\n\n", myname);
	fprintf( stderr, "options are:\n");
	fprintf( stderr, "   -v:         be verbose\n");
	fprintf( stderr, "   -f:         ask manual feed on plotting/printing device\n");
	fprintf( stderr, "   -i<box>:    specify input image size\n");
	fprintf( stderr, "   -c<margin>: horizontal and vertical cutmargin\n");
	fprintf( stderr, "   -w<margin>: horizontal and vertical additional white margin\n");
	fprintf( stderr, "   -C<number>: clipping facilities (grid label and cutmarks)\n");
	fprintf( stderr, "               bit 1 (value = 1): cutmark lines\n");
	fprintf( stderr, "               bit 2 (value = 2): cutmark arrow heads\n");
	fprintf( stderr, "               bit 3 (value = 4): grid label\n");
	fprintf( stderr, "   -O<number>: content overlap\n");
	fprintf( stderr, "   -m<box>:    media paper size\n");
	fprintf( stderr, "   -p<box>:    output poster size\n");
	fprintf( stderr, "   -s<number>: linear scale factor for poster\n");
	fprintf( stderr, "   -P<pages>:  comma-separated list of tile pages to print\n" );
	fprintf( stderr, "   -o<file>:   output redirection to named file\n\n");
	fprintf( stderr, "   At least one of -s -p -m is mandatory, and don't give both -s and -p\n"); 
	fprintf( stderr, "   <box> is like 'A4', '3x3letter', '10x25cm', '200x200+10,10p'\n");
	fprintf( stderr, "   <margin> is either a simple <box> or <number>%%. Page lists are comma-\n" );
	fprintf( stderr, "   separated, each element being a single page or a page range.\n" );
	fprintf( stderr, "   (ex: 1,2-5,8,10-34)\n\n");

	fprintf( stderr, "   Defaults are: '-m%s', '-c%s', '-i<box>' read from input file.\n",
		DefaultMedia, DefaultCutMargin);
	fprintf( stderr, "                 and output written to stdout.\n");
	fprintf( stderr, "\n");
	exit(1);
}

#define exch( x, y)	{double h; h=x; x=y; y=h;}

static void postersize( char *scalespec, char *posterspec)
{	/* exactly one the arguments is NULL ! */
	/* media and image sizes are fixed already */

	int nx0, ny0, nx1, ny1;
	double sizex, sizey;    /* size of the scaled image in ps units */
	double drawablex, drawabley; /* effective drawable size of media */
	double mediax, mediay;
	double tmpposter[4];
	
	/* available drawing area per sheet: */
	drawablex = mediasize[2] - 2.0*cutmargin[0];
	drawabley = mediasize[3] - 2.0*cutmargin[1];

	/*** decide on number of pages  ***/
	if (scalespec)
	{	/* user specified scale factor */
		scale = atof( scalespec);
		if (scale < 0.01 || scale > 1.0e6)
		{	fprintf( stderr, "Illegal scale value %s!\n", scalespec);
			exit(1);
		}
		sizex = (imagebb[2] - imagebb[0]) * scale + 2*whitemargin[0];
		sizey = (imagebb[3] - imagebb[1]) * scale + 2*whitemargin[1];

		/* without rotation */
		nx0 = ceil( sizex / drawablex);
		ny0 = ceil( sizey / drawabley);

		/* with rotation */
		nx1 = ceil( sizex / drawabley);
		ny1 = ceil( sizey / drawablex);

	} else
	{	/* user specified output size */
		box_convert( posterspec, tmpposter);
		if (tmpposter[0]!=0.0 || tmpposter[1]!=0.0)
		{	fprintf( stderr, "Poster lower-left coordinates are assumed 0!\n");
			tmpposter[0] = tmpposter[1] = 0.0;
		}
		if (tmpposter[2]-tmpposter[0] <= 0.0 || tmpposter[3]-tmpposter[1] <= 0.0)
		{	fprintf( stderr, "Poster should have positive size!\n");
			exit(1);
		}

		if ((tmpposter[3]-tmpposter[1]) < (tmpposter[2]-tmpposter[0]))
		{	/* hmmm... landscape spec, change to portrait for now */
			exch( tmpposter[0], tmpposter[1]);
			exch( tmpposter[2], tmpposter[3]);
		}
			

		/* Should we tilt the poster to landscape style? */
		if ((imagebb[3] - imagebb[1]) < (imagebb[2] - imagebb[0]))
		{	/* image has landscape format ==> make landscape poster */
			exch( tmpposter[0], tmpposter[1]);
			exch( tmpposter[2], tmpposter[3]);
		}

		/* without rotation */ /* assuming tmpposter[0],[1] = 0,0 */
		nx0 = ceil( 0.95 * tmpposter[2] / mediasize[2]);
		ny0 = ceil( 0.95 * tmpposter[3] / mediasize[3]);

		/* with rotation */
		nx1 = ceil( 0.95 * tmpposter[2] / mediasize[3]);
		ny1 = ceil( 0.95 * tmpposter[3] / mediasize[2]);
		/* (rotation is considered as media versus image, which is totally */
		/*  independent of the portrait or landscape style of the final poster) */
	}

	/* decide for rotation to get the minimum page count */
	rotate = nx0*ny0 > nx1*ny1;

	ncols = rotate ? nx1 : nx0;
	nrows = rotate ? ny1 : ny0;

	if (verbose)
		fprintf( stderr,
			"Deciding for %d column%s and %d row%s of %s pages.\n",
			ncols, (ncols==1)?"":"s", nrows, (nrows==1)?"":"s",
			rotate?"landscape":"portrait");

	if (nrows * ncols > 400)
	{	fprintf( stderr, "However %dx%d pages seems ridiculous to me!\n",
			ncols, nrows);
		exit(1);
	}

	mediax = ncols * (rotate ? drawabley : drawablex);
	mediay = nrows * (rotate ? drawablex : drawabley);

	if (!scalespec)  /* no scaling number given by user */
	{	double scalex, scaley;
		scalex = (mediax - 2*whitemargin[0]) / (imagebb[2] - imagebb[0]);
		scaley = (mediay - 2*whitemargin[1]) / (imagebb[3] - imagebb[1]);
		//scalex = (tmpposter[ 2 ]-tmpposter[ 0 ]) / (imagebb[2] - imagebb[0]);
		//scaley = (tmpposter[ 3 ]-tmpposter[ 1 ]) / (imagebb[3] - imagebb[1]);
		scale = (scalex < scaley) ? scalex : scaley;

		if (verbose)
			fprintf( stderr,
				"Deciding for a scale factor of %g\n", scale);
		sizex = scale * (imagebb[2] - imagebb[0]);
		sizey = scale * (imagebb[3] - imagebb[1]);
	}

	/* set poster size as if it were a continuous surface without margins */
	posterbb[0] = (mediax - sizex) / 2.0; /* center picture on paper */
	posterbb[1] = (mediay - sizey) / 2.0; /* center picture on paper */
	posterbb[2] = posterbb[0] + sizex;
	posterbb[3] = posterbb[1] + sizey;

}

static void margin_convert( char *spec, double margin[2])
{	double x;
	int i, n;

	if (1==sscanf( spec, "%lf%n", &x, &n) && x==0.0 && n==strlen(spec))
	{	/* margin spec of 0, dont bother about a otherwise mandatory unit */
		margin[0] = margin[1] = 0.0;
	} else if (spec[ strlen( spec) - 1] == '%')
	{	/* margin relative to media size */
		if (1 != sscanf( spec, "%lf%%", &x))
		{	fprintf( stderr, "Illegal margin specification!\n");
			exit( 1);
		}
		margin[0] = 0.01 * x * mediasize[2];
		margin[1] = 0.01 * x * mediasize[3];
	} else
	{	/* absolute margin value */
		double marg[4];
		box_convert( spec, marg);
		margin[0] = marg[2];
		margin[1] = marg[3];
	}

	for (i=0; i<2; i++)
	{	if (margin[i] < 0 || 2.0*margin[i] >= mediasize[i+2])
		{	fprintf( stderr, "Margin value '%s' out of range!\n",
				spec);
			exit(1);
		}
	}
}

static void box_convert( char *boxspec, double psbox[4])
{	/* convert user textual box spec into numbers in ps units */
	/* box = [fixed x fixed][+ fixed , fixed] unit */
	/* fixed = digits [ . digits] */
	/* unit = medianame | i | cm | mm | m | p */

	double mx, my, ox, oy, ux, uy;
	int n, r, i, l, inx;
	char *spec;

	mx = my = 1.0;
	ox = oy = 0.0;

	spec = boxspec;
	/* read 'fixed x fixed' */
	if (isdigit( spec[0]))
	{	r = sscanf( spec, "%lfx%lf%n", &mx, &my, &n);
		if (r != 2)
		{	r = sscanf( spec, "%lf*%lf%n", &mx, &my, &n);
			if (r != 2) boxerr( boxspec);
		}
		spec += n;
	}

	/* read '+ fixed , fixed' */
	if (1 < (r = sscanf( spec, "+%lf,%lf%n", &ox, &oy, &n)))
	{	if (r != 2) boxerr( boxspec);
		spec += n;
	}

	/* read unit */
	l = strlen( spec);
	for (n=i=0; mediatable[i][0]; i++)
	{	if (!mystrncasecmp( mediatable[i][0], spec, l))
		{	/* found */
			n++;
			inx = i;
			if (l == strlen( mediatable[i][0]))
			{	/* match is exact */
				n = 1;
				break;
			}
		}
	}
	if (!n) boxerr( boxspec);
	if (n>1)
	{	fprintf( stderr, "Your box spec '%s' is not unique! (give more chars)\n",
			spec);
		exit(1);
	}
	sscanf( mediatable[inx][1], "%lf,%lf", &ux, &uy);

	psbox[0] = ox * ux;
	psbox[1] = oy * uy;
	psbox[2] = mx * ux;
	psbox[3] = my * uy;

	if (verbose > 1)
		fprintf( stderr, "   Box_convert: '%s' into [%g,%g,%g,%g]\n",
			boxspec, psbox[0], psbox[1], psbox[2], psbox[3]);

	for (i=0; i<2; i++)
	{	if (psbox[i] < 0.0 || psbox[i+2] < psbox[i])
		{	fprintf( stderr, "Your specification `%s' leads to "
				"negative values!\n", boxspec);
			exit(1);
		}
	}
}

static void boxerr( char *spec)
{	int i;

	fprintf( stderr, "I don't understand your box specification `%s'!\n",
		spec);

	fprintf( stderr, "The proper format is: ([text] meaning optional text)\n");
	fprintf( stderr, "  [multiplier][offset]unit\n");
	fprintf( stderr, "  with multiplier:  numberxnumber\n");
	fprintf( stderr, "  with offset:      +number,number\n");
	fprintf( stderr, "  with unit one of:");

	for (i=0; mediatable[i][0]; i++)
		fprintf( stderr, "%c%-10s", (i%7)?' ':'\n', mediatable[i][0]);
	fprintf( stderr, "\nYou can use a shorthand for these unit names,\n"
		"provided it resolves unique.\n");
	exit( 1);
}

/*********************************************/
/* output first part of DSC header           */
/*********************************************/
static void dsc_head1()
{
	printf ("%%!PS-Adobe-3.0\n");
	printf ("%%%%Creator: %s\n", myname);
}

/*********************************************/
/* pass some DSC info from the infile in the new DSC header */
/* such as document fonts and */
/* extract BoundingBox info from the PS file */
/*********************************************/
static int dsc_infile( double ps_bb[4])
{
	char *c, buf[BUFSIZE];
	int gotall, atend, level, dsc_cont, inbody, got_bb, n;

	if (freopen (infile, "r", stdin) == NULL) {
		fprintf (stderr, "%s: fail to open file '%s'!\n",
			myname, infile);
		exit (1);
	}

	got_bb = 0;
	dsc_cont = inbody = gotall = level = atend = 0;
	while (!gotall && (fgets(buf, BUFSIZE, stdin) != NULL))
	/*while (!gotall && (gets(buf) != NULL))*/
	{	
		n = strlen( buf );
		if ( buf[ n-1 ] == '\n' )
			buf[ n-1 ] = 0;

		if (buf[0] != '%')
		{	dsc_cont = 0;
			if (!inbody) inbody = 1;
			if (!atend) gotall = 1;
			continue;
		}

		if (!strncmp( buf, "%%+",3) && dsc_cont)
		{	puts( buf);
			continue;
		}

		dsc_cont = 0;
		if      (!strncmp( buf, "%%EndComments", 13))
		{	inbody = 1;
			if (!atend) gotall = 1;
		}
		else if (!strncmp( buf, "%%BeginDocument", 15) ||
		         !strncmp( buf, "%%BeginData", 11)) level++;
		else if (!strncmp( buf, "%%EndDocument", 13) ||
		         !strncmp( buf, "%%EndData", 9)) level--;
		else if (!strncmp( buf, "%%Trailer", 9) && level == 0)
			inbody = 2;
		else if (!strncmp( buf, "%%BoundingBox:", 14) &&
			 inbody!=1 && !level)
		{	for (c=buf+14; *c==' ' || *c=='\t'; c++);
			if (!strncmp( c, "(atend)", 7)) atend = 1;
			else
			{	sscanf( c, "%lf %lf %lf %lf",
				       ps_bb, ps_bb+1, ps_bb+2, ps_bb+3);
				got_bb = 1;
			}
		}
		else if (!strncmp( buf, "%%Document", 10) &&
			 inbody!=1 && !level)  /* several kinds of doc props */
		{	for (c=buf+10; *c && *c!=' ' && *c!='\t'; c++);
			for (; *c==' ' || *c=='\t'; c++);
			if (!strncmp( c, "(atend)", 7)) atend = 1;
			else
			{	/* pass this DSC to output */
				puts( buf);
				dsc_cont = 1;
			}
		}
		else if ( !strncmp( buf, "%%Pages:", 8 ) )
		{
			int n;
			if ( sscanf( buf+8, "%d", &n ) == 1 )
				number_pages = n;
		}
	}
	return got_bb;
}

/*********************************************/
/* output last part of DSC header            */
/*********************************************/
static void dsc_head2()
{
	if ( pages == NULL )
		printf ("%%%%Pages: %d\n", nrows*ncols*number_pages);
	else
		printf ("%%%%Pages: %d\n", pages_length*number_pages);

#ifndef Gv_gs_orientbug
	printf ("%%%%Orientation: %s\n", rotate?"Landscape":"Portrait");
#endif
	printf ("%%%%DocumentMedia: %s %d %d 0 white ()\n",
		mediaspec, (int)(mediasize[2]), (int)(mediasize[3]));
	printf ("%%%%BoundingBox: 0 0 %d %d\n", (int)(mediasize[2]), (int)(mediasize[3]));
	printf ("%%%%EndComments\n\n");

	printf ("%% Print poster %s in %dx%d tiles with %.3g magnification\n", 
		infile, nrows, ncols, scale);
}

/*********************************************/
/* output the poster, create tiles if needed */
/*********************************************/
static void printposter()
{
	int row, col, page;

	printprolog();
	for ( page = 0; page < number_pages; page++ )
	{
		printf( "/print_content_of_complete_page_%d\n", page);
		printf( "{\n");
		printfile (page);
		printf( "} bind def\n\n");
	
		if ( pages == NULL )
		{
			for (row = 1; row <= nrows; row++)
				for (col = 1; col <= ncols; col++)
					tile( row, col, page);
		}
		else
		{
			for ( row = 0; row < pages_length; row++ )
			{
				int p = pages[ row ]-1;
				if ( p+1 > nrows*ncols )
					fprintf( stderr, "Warning: page index out of range: %d\n", p+1 );
				tile( p/ncols+1, p%ncols+1, page );
			}
		}
	}
	printf ("%%%%EOF\n");

	if (tail_cntl_D)
	{	printf("%c", 0x4);
	}
}

/*******************************************************/
/* output PS prolog of the scaling and tiling routines */
/*******************************************************/
static void printprolog()
{
	printf( "%%%%BeginProlog\n");

	printf( "/cutmark	%% - cutmark -\n"
		"{ %% def\n"
		"	%% define clipping area\n"
		"	0.23 setlinewidth 0 setgray\n"
		"	clipmargin\n"
		"	dup 0 moveto\n"
		"	dup neg leftmargin add 0 rlineto stroke\n");
	if (clipFacilValue & 0x02)
	{  printf(
		"	%% draw sheet alignment mark\n"
		"	dup dup neg moveto\n"
		"	dup 0 rlineto\n"
		"	dup dup lineto\n"
		"	0 rlineto\n"
		"	closepath fill\n");
	}
	else
	{  printf("  pop\n");
	}
	printf( "} bind def\n\n");

	printf( "%% usage: 	row col tileprolog ps-code tilepilog\n"
		"%% these procedures output the tile specified by row & col\n"
		"/tileprolog\n"
		"{ 	%%def\n"
		"	gsave\n"
		"       leftmargin botmargin translate\n"
	        "	do_turn {exch} if\n"
		"	/colcount exch def\n"
		"	/rowcount exch def\n"
		"	%% clip page contents\n"
		"	clipmargin neg dup moveto\n"
		"	pagewidth clipmargin 2 mul add 0 rlineto\n"
		"	0 pageheight clipmargin 2 mul add rlineto\n"
		"	pagewidth clipmargin 2 mul add neg 0 rlineto\n"
		"	closepath clip\n"
		"	%% set page contents transformation\n"
		"	do_turn\n"
	        "	{	pagewidth 0 translate\n"
		"		90 rotate\n"
	        "	} if\n"
		"	pagewidth colcount 1 sub mul neg\n"
		"	pageheight rowcount 1 sub mul neg\n"
	        "	do_turn {exch} if\n"
		"	translate\n"
	        "	posterxl posteryb translate\n"
		"	sfactor dup scale\n"
	        "	imagexl neg imageyb neg translate\n"
		"	tiledict begin\n"
	        "	0 setgray 0 setlinecap 1 setlinewidth\n"
	        "	0 setlinejoin 10 setmiterlimit [] 0 setdash newpath\n"
	        "} bind def\n\n");

	printf( "/tileepilog\n"
	        "{	end %% of tiledict\n"
	        "	grestore\n");
	if (clipFacilValue & 0x01)
	{  printf(
	           "	%% print the cutmarks\n"
	           "	gsave\n"
		        "   leftmargin botmargin translate\n"
	           "	pagewidth pageheight translate cutmark 90 rotate cutmark\n"
	           "	0 pagewidth translate cutmark 90 rotate cutmark\n"
	           "	0 pageheight translate cutmark 90 rotate cutmark\n"
	           "	0 pagewidth translate cutmark 90 rotate cutmark\n"
	           "	grestore\n");
	}
	if (clipFacilValue & 0x04)
	{  printf(
	           "	%% print the page label\n"
	           "	0 setgray\n"
	           "	leftmargin clipmargin 3 mul add clipmargin labelsize add neg botmargin add moveto\n"
	           "	(Grid \\( ) show\n"
	           "	rowcount strg cvs show\n"
	           "	( , ) show\n"
	           "	colcount strg cvs show\n"
	           "	( \\)) show\n");
	}
	printf( "	showpage\n"
                "} bind def\n\n");

	printf( "%%%%EndProlog\n\n");
	printf( "%%%%BeginSetup\n");
	printf( "%% Try to inform the printer about the desired media size:\n"
	        "/setpagedevice where 	%% level-2 page commands available...\n"
	        "{	pop		%% ignore where found\n"
	        "	3 dict dup /PageSize [ %d %d ] put\n"
	        "	dup /Duplex false put\n%s"
	        "	setpagedevice\n"
                "} if\n",
	       (int)(mediasize[2]), (int)(mediasize[3]),
	       manualfeed?"       dup /ManualFeed true put\n":"");

	printf( "/sfactor %.10f def\n"
	        "/leftmargin %d def\n"
	        "/botmargin %d def\n"
	        "/pagewidth %d def\n"
	        "/pageheight %d def\n"
	        "/imagexl %d def\n"
	        "/imageyb %d def\n"
	        "/posterxl %d def\n"
	        "/posteryb %d def\n"
	        "/do_turn %s def\n"
	        "/strg 10 string def\n"
	        "/clipmargin %d def\n"
	        "/labelsize 9 def\n"
	        "/tiledict 250 dict def\n"
	        "tiledict begin\n"
	        "%% delay users showpage until cropmark is printed.\n"
	        "/showpage {} def\n"
	        "end\n",
	        scale, (int)(cutmargin[0]), (int)(cutmargin[1]),
	        (int)(mediasize[2]-2.0*cutmargin[0]), (int)(mediasize[3]-2.0*cutmargin[1]),
	        (int)imagebb[0], (int)imagebb[1], (int)posterbb[0], (int)posterbb[1],
	        rotate?"true":"false", atoi (overlap));

	printf( "/Helvetica findfont labelsize scalefont setfont\n");

	printf( "%%%%EndSetup\n\n");
}

/*****************************/
/* output one tile at a time */
/*****************************/
static void tile ( int row, int col, int pagetoprint)
{
	static int page=1;

	if (verbose) fprintf( stderr, "print page %d\n", page);

	printf ("\n%%%%Page: (%d,%d) %d\n", pagetoprint+1, ((row-1)*ncols+col), page);
	printf ("%d %d tileprolog\n", row, col);
	printf ("%%%%BeginDocument: %s\n", infile);
	printf( "print_content_of_complete_page_%d\n", pagetoprint);
	printf ("%%%%EndDocument\n");
	printf ("tileepilog\n");

	page++;
}

/******************************/
/* copy the PS file to output */
/******************************/
static void printfile (int pagetoprint)
{
	/* use a double line buffer, so that when I print */
	/* a line, I know whether it is the last or not */
	/* I surely dont want to print a 'cntl_D' on the last line */
	/* The double buffer removes the need to scan each line for each page*/
	/* Furthermore allows cntl_D within binary transmissions */

	char buf[2][BUFSIZE];
	int bp;
	char *c;
	int currentpage = -1;

	if (freopen (infile, "r", stdin) == NULL) {
		fprintf (stderr, "%s: fail to open file '%s'!\n",
			myname, infile);
		printf ("/systemdict /showpage get exec\n");
		printf ("%%%%EOF\n");
		exit (1);
	}

	/* fill first buffer for the first time */
	fgets( buf[bp=0], BUFSIZE, stdin);

	/* read subsequent lines by rotating the buffers */
	while (fgets(buf[1-bp], BUFSIZE, stdin))
	{	
		if ( !strncmp( buf[ bp ], "%%Page:", 7 ) )
			currentpage++;

		/* print line from the previous fgets */
		/* do not print postscript comment lines: those (DSC) lines */
		/* sometimes disturb proper previewing of the result with ghostview */
		if (buf[bp][0] != '%' && ( currentpage == -1 || currentpage == pagetoprint ))
			fputs( buf[bp], stdout);
		bp = 1-bp;
	}

	/* print buf from last successfull fgets, after removing cntlD */
	for (c=buf[bp]; *c && *c != '\04'; c++);
	if (*c == '\04')
	{	tail_cntl_D = 1;
		*c = '\0';
	}
	if (buf[bp][0] != '%' && strlen( buf[bp]))
		fputs( buf[bp], stdout);
}

static int mystrncasecmp( const char *s1, const char *s2, int n)
{	/* compare case-insensitive s1 and s2 for at most n chars */
	/* return 0 if equal. */
	/* Although standard available on some machines, */
	/* this function seems not everywhere around... */

	char c1, c2;
	int i;

	if (!s1) s1 = "";
	if (!s2) s2 = "";

	for (i=0; i<n && *s1 && *s2; i++, s1++, s2++)
	{	c1 = tolower( *s1);
		c2 = tolower( *s2);
		if (c1 != c2) break;
	}

	return (i < n && (*s1 || *s2));
}
