/* lpps.c */
/* Copyright 1995 by Steve Kirkendall */

char id_lpps[] = "$Id: lpps.c,v 2.13 1997/10/05 19:06:12 steve Exp $";


/* This file contains a driver for Adobe PostScript.  The driver supports
 * printing text either 1-up or 2-up.
 */


#include "elvis.h"
#ifdef FEATURE_LPR

#if USE_PROTOTYPES
static void out(char *str, char *param);
static void psmove(int newrow, int newcol);
static void psstring(_char_ newfont);
static void pagestart(void);
static void pageend(BOOLEAN final);
static void before(int minorno, void (*draw)(_CHAR_));
static void fontch(_char_ font, _CHAR_ ch);
static void page(int linesleft);
static void after(int linesleft);
#endif

/* These variables store the current state of the driver */
static int	elvrow;	/* Row where elvis thinks cursor is */
static int	elvcol;	/* Column where elvis thinks cursor is */
static char	elvfont;/* Current elvis font - one of n/b/i/u/g */
static int	psrow;	/* Row where cursor is located */
static int	pscol;	/* Column where cursor is located */
static char	psfont;	/* Current PS font - one of n/b/i */
static BOOLEAN	instr;	/* Are we in the middle of outputting a string? */
static int	width;	/* width of current string, measured in characters */
static BOOLEAN	twoup;	/* Print two logical pages on each piece of paper? */
static BOOLEAN	even;	/* True during printing of second logical page */
static long	pagenum;/* Physical page number */


/* This is a pointer to the draw() function to use for outputing individual
 * characters.  It is set by the before() function, and remains valid until
 * the after() function returns.
 */
static void (*prtchar) P_((_CHAR_ ch));


/* Output a constant string to the printer, optionally followed by another
 * string, and then an implied newline.  This is used mostly for outputting
 * the header.
 */
static void out(str, param)
	char	*str;	/* string to be output */
	char	*param;	/* optional second string */
{
	while (*str)
	{
		(*prtchar)((_CHAR_)*str++);
	}
	while (param && *param)
	{
		(*prtchar)((_CHAR_)*param++);
	}
	(*prtchar)((_CHAR_)'\n');
}


/* This function moves the PostScript cursor to a given row/column, if it
 * isn't there already.
 */
static void psmove(newrow, newcol)
	int	newrow;	/* new row number, with 0 being the top of the page */
	int	newcol;	/* column number, with 0 being the leftmost column */
{
	char	buf[50];
	int	psx, psy;	/* PostScript coordinates for position */

	/* If the PS cursor is already there, then do nothing */
	if (newrow == psrow && newcol == pscol)
		return;

	/* Convert row/column position to PostScript coordinates.  Here, we
	 * assume that each character is 1/10 of an inch wide and 1/6 of an
	 * inch high.  We also assume that the font's baseline is about 1/6
	 * of the way up the character cell.
	 */
	psx = newcol * 72;
	psy = (o_lplines - newrow) * 120 - 100;

	/* Output a "move" command */
	sprintf(buf, "%d.%d %d.%d moveto", psx/10, psx%10, psy/10, psy%10);
	out(buf, NULL);
	psrow = newrow;
	pscol = newcol;
}


/* This function starts and/or ends a string.  It also handles font changes */
static void psstring(newfont)
	_char_	newfont;	/* font character of font to load */
{
	char	newps;		/* new postscript font */
	int	oldcol;

	/* If fonts haven't changed, we don't need to do anything */
	if (newfont == elvfont && instr)
		return;

	/* If we were in a string before, we need to draw it now. */
	if (instr)
	{
		/* end the string */
		(*prtchar)(')');

		/* drawing underlined strings requires extra work */
		if (elvfont == 'u')
		{
			/* Output the string, and also find its width */
			out(" dup show stringwidth pop", NULL);

			/* Draw a horizontal line as wide as the string */
			out(" newpath", NULL);
			oldcol = pscol;
			pscol = -1;
			psmove(elvrow, oldcol);
			out(" 0 -1 rmoveto dup 0 rlineto neg 1 rmoveto stroke", NULL);

			/* That leaves the PS cursor position undefined */
			pscol = psrow = -1;
		}
		else
		{
			/* output the string */
			out(" show", NULL);
			pscol += width;
		}

		/* we are no longer in a string */
		instr = False;
		width = 0;
	}

	/* if no new font is specified, we're done */
	if (!newfont)
		return;

	/* Move the cursor to where the new string will be output */
	psmove(elvrow, elvcol);

	/* If necessary, switch postscript fonts */
	if (newfont == 'b' || newfont == 'e')
		newps = 'b';
	else if (newfont == 'i')
		newps = 'i';
	else
		newps = 'n';
	if (newps != psfont)
	{
		switch (newps)
		{
		  case 'n':	out("ElvisN", " setfont");	break;
		  case 'b':	out("ElvisB", " setfont");	break;
		  case 'i':	out("ElvisI", " setfont");	break;
		}
		psfont = newps;
	}
	elvfont = newfont;

	/* start outputing a new string */
	(*prtchar)('(');

	/* we are now in a string */
	instr = True;
}


/* Prepare for the next logical page.  For one-up printing, this is fairly
 * simple.  For two-up, it depends on whether this is the first logical page
 * on this sheet of paper, or the second logical page.
 */
static void pagestart()
{
	char	buf[12];

	/* reset variables */
	elvrow = elvcol = 0;
	elvfont = psfont = '\0';
	psrow = psfont = -1; /* to force movement the first time */
	instr = False;
	width = 0;

	/* output a comment, if at the top of a physical page */
	if (!twoup || !even)
	{
		sprintf(buf, "%ld", pagenum++);
		out("%%Page: page ", buf);
	}

	/* change graphic context, depending on printing style */
	if (!twoup)
	{
		out("gsave ElvisPage", NULL);
	}
	else if (!even)
	{
		out("gsave ElvisLeftPage", NULL);
	}
	else
	{
		out("gsave ElvisRightPage", NULL);
	}
}


/* This function ends a logical page */
static void pageend(final)
	BOOLEAN	final;	/* is this the last page of the print job? */
{
	/* end the current string, if any */
	psstring('\0');

	out("grestore", NULL);
	if (!twoup || even || final)
	{
		out("showpage", NULL);
	}
	even = (BOOLEAN)!even;
}



/* This is the before() function.  It sets the prtchar variable, and outputs
 * the PostScript header.
 */
static void before(minorno, draw)
	int	minorno;		/* ignored */
	void	(*draw) P_((_CHAR_));	/* function for sending single char to printer */
{
	char	*pathname;
	FILE	*fp;
	int	ch;
	char	paper[20];
	int	i, j;

	/* Set the ptype and out function */
	twoup = (BOOLEAN)(minorno == 2);
	prtchar = draw;

	/* Output the basic header */
	out("%!PS-Adobe-2.0", NULL);
	out("%%Creator: Elvis 2.0", NULL);
	out("%%EndComments", "\n");

	out("%%BeginProlog", NULL);

	/* output the paper size.  This can be used by the lib/elvis.ps file
	 * to adjust its size and positions.
	 */
	if (!o_lppaper || !*o_lppaper)
	{
		strcpy(paper, "(letter) def");
	}
	else
	{
		paper[0] = '(';
		for (i = 0, j = 1; o_lppaper[i]; i++)
		{
			if (isupper(o_lppaper[i]))
				paper[j++] = tolower(o_lppaper[i]);
			else if (isalnum(o_lppaper[i]))
				paper[j++] = o_lppaper[i];
		}
		paper[j] = '\0';
		strcat(paper, ") def");
	}
	out("/ElvisPaper ", paper);
	sprintf(paper, "%ld def", o_lplines);
	out("/ElvisLines ", paper);
	sprintf(paper, "%ld def", o_lpcolumns);
	out("/ElvisColumns ", paper);

	/* Define the fonts that we'll be using.  Note that we don't define
	 * fonts for 'g' and 'u' because they're done using graphics and the
	 * 'n' font.
	 *
	 * Also define some page positioning macros.
	 */
	pathname = iopath(tochar8(o_elvispath), "elvis.ps", False);
	if (pathname && (fp = fopen(pathname, "r")) != NULL)
	{
		/* Copy definitions from the "lib/elvis.ps" file */
		while ((ch = getc(fp)) != EOF)
		{
			(*prtchar)((_CHAR_)ch);
		}
		fclose(fp);
	}
	else
	{
		/* Use default definitions. These are not sensitive to the
		 * lppaper option.
		 */
		out("/ElvisN /Courier findfont 12 scalefont def", NULL);
		out("/ElvisB /Courier-Bold findfont 12 scalefont def", NULL);
		out("/ElvisI /Courier-Oblique findfont 12 scalefont def", NULL);
		out("/ElvisPage { 12 36 translate } def", NULL);
		out("/ElvisLeftPage { 12 750 translate -90 rotate 0.58 0.75 scale } def", NULL);
		out("/ElvisRightPage { newpath 12 394 moveto 576 0 rlineto stroke 12 366 translate -90 rotate 0.58 0.75 scale } def", NULL);
	}
	out("%%EndProlog", "\n");

	/* Prepare for first page */
	even = False;
	pagenum = 1;
	pagestart();
}



/* This function adds a character to the output, simulating the behavior of
 * control characters.  (The only possible control character is '\n'.)
 */
static void fontch(font, ch)
	_char_	font;	/* font of the next character from text image */
	_CHAR_	ch;	/* the next character */
{
	char	buf[10];

	if (ch == '\n')
	{
		/* end the current string, if any */
		psstring('\0');
		(*prtchar)('\n');

		/* move to the start of the next line */
		elvcol = 0;
		elvrow++;
	}
	else if (font == 'g' && strchr("123456789|-", (_char_)ch))
	{
		/* End any current string. */
		psstring('\0');

		/* Move to the center of the character cell */
		out("newpath", NULL);
		pscol = -1;
		psmove(elvrow, elvcol);
		out("3.6 4.0 rmoveto", NULL);

		/* Maybe draw an uptick */
		if (strchr("123456|", (_char_)ch))
			out(" 0 6.0 rlineto 0 -6.0 rmoveto", NULL);

		/* Maybe draw a downtick */
		if (strchr("456789|", (_char_)ch))
			out(" 0 -6.0 rlineto 0 6.0 rmoveto", NULL);

		/* Maybe draw a lefttick */
		if (strchr("235689-", (_char_)ch))
			out(" -3.6 0 rlineto 3.6 0 rmoveto", NULL);

		/* Maybe draw a righttick */
		if (strchr("124578-", (_char_)ch))
			out(" 3.6 0 rlineto -3.6 0 rmoveto", NULL);

		/* Draw those lines, and move to next character cell */
		out(" stroke", NULL);
		elvcol++;
		pscol = -1;
	}
	else
	{
		/* start a new string, if necessary */
		psstring(font);

		/* Add the character to the current string */
		if (ch == '(' || ch == ')' || ch == '\\')
		{
			(*prtchar)('\\');
			(*prtchar)(ch);
		}
		else if (ch >= 127)
		{
			sprintf(buf, "%03o", ch);
			(*prtchar)('\\');
			(*prtchar)((_CHAR_)buf[0]);
			(*prtchar)((_CHAR_)buf[1]);
			(*prtchar)((_CHAR_)buf[2]);
		}
		else
		{
			(*prtchar)(ch);
		}
		elvcol++;
	}
}


/* This function is called after every page except the last one */
static void page(linesleft)
	int	linesleft;	/* lines remaining on page */
{
	/* end the page */
	pageend(False);

	/* start the next page */
	pagestart();
}


/* This function is called at the end of the print job.  It can output a
 * final formfeed, restore fonts, or whatever.
 */
static void after(linesleft)
	int	linesleft;	/* lines remaining on final page */
{
	/* print the last page */
	pageend(True);

	/* output a trailer */
	out("%%Trailer", NULL);
}


/* These describe the printer types supported by these functions */
LPTYPE lpps =	{"ps", 1, True, before, fontch, page, after};
LPTYPE lpps2 =	{"ps2", 2, True, before, fontch, page, after};

#endif /* FEATURE_LPR */
