#ifndef lint
static char *sccsid = "@(#)djet.c	1.34 (UKC) 8/1/88";
#endif  lint
/*
 *	djet
 *	Ditroff driver for Laserjet printer
 *	Martin Guy. UKC. July 1985
 *	"Adapted" from dsan.c
 *
 *	Notes on Laserjet Printer
 *
 *	Fine resolution cursor positioning is expressed in 720th of an inch
 *		(why, when its printing resolution is 300 dots per inch?)
 *		Do not depend upon res having this value as it won't.
 *
 *	Escape codes:
 *		esc E		Reset printer (see below)
 *		esc &s0C	Enable automatic line-wrapping
 *		esc &a#H	Set cursor horizontal position to # 720ths of an inch from left page boundary
 *		esc &a#V	Set cursor vertical position to # 720ths of an inch from top margin
 *					+# and -# specify relative motions.
 *
 *	If the first two characters after the <esc> of consecutive sequences
 *	are the same, they may be combined by decapitalising the final character
 *	of each sequence save the last and omitting the <esc>xy from all but the
 *	first. EG <esc>&a123H<esc>&a456V == <esc>&a123h456V
 *
 *	The reset state (after <esc>E) is:
 *		top margin	1/2", 3 lines @ 6 LPI
 *		textlength	Pagelength - 1 inch
 *		left margin	Column 0
 *		right margin	Rightmost printable limit (77chars@10cpi)
 *		Perf skip	Enabled
 *		Line wrapping	Disabled
 *
 *	TROFF defaults:
 *		page length = 11i
 *		line length = 6.5i
 *		page offset = 26/27ths of an inch
 *
 *	Printable surface for an A4 page is quoted as 7.8" x 11.3"
 *	An A4 page is in fact 8.25" x 11.65"
 *
 *	See also: djet.doc
 */


/* Conditional Compilation Options
 *	DEBUG	Include bits for -d option. Produces eNORMOUS crap in log file
 *
 *	values of dbg (number following -d option):
 *	1	description of everything the prog puts out as it goes along. BIG.
 *	4	print analysis of DELIVERY string used in tail banner
 *	8	list of pages to output (-o option)
 *
 *	PACKF		produce filter for font pack F (Var-width text pack)
 *	PACKJ		produce filter for font pack J (Maths pack)
 *	SIZEMUNGE	Include nasty hack to enable	8 point in PACKF,
 *							7 point in PACKJ.
 *	BSD4_2		Rip the characters out of the stdout buffer if the
 *			job is aborted.
 */

#ifdef PACKF
# ifdef PACKJ
   Only one PACK may be defined
# else
   static char *version = "@(#) for PACKF";
#  define SIZEMUNGE
# endif
#else
# ifdef PACKJ
   static char *version = "@(#) for PACKJ";
#  define SIZEMUNGE
# else
   static char *version = "@(#) for default font";
# endif
#endif

#include	<stdio.h>
#include	<signal.h>	/* for SIG* in main() */
#include	<ctype.h>	/* for isdigit() in outlist() and conv(), isprint() in put1() */
#include	<sys/types.h>	/* for succ. */
#include	<sys/time.h>	/* for banner() and error() */
#include	"dev.h"

typedef struct charac {
	u_short	width;
	u_short	height;
	short	xoffs;
	short	yoffs;
	u_long	raster_index;
	u_long	effective_width;
} charac;
#define	NFONT	10
#define X_OFFS	10	/* to cancel the x-offset of LaserJet characters */

#define uppercase(c) ((c)&~32)
#define lowercase(c) ((c)|32)

int	output	= 0;	/* 0 => skip, 1 => print it */
int	nolist	= 0;	/* output page list if > 0 */
int	olist[20];	/* pairs of page numbers */

struct dev dev;
struct Font *fontbase[NFONT+1];
short	*pstab;		/* USED IN draw.o */
int	unitwidth = 10;
int	nsizes	= 1;
int	nfonts;
int	smnt;		/* index of first special font */
int	nchtab;
char	*chname;
short	*chtab;
char	*fitab[NFONT+1];/* font index table: position of char i on this font */
			/* zero if not there */
unsigned char *widthtab[NFONT+1];	/* Widths of the characters */
char	*descend[NFONT+1];	/* descender field used for synthesising */
char	*codetab[NFONT+1];	/* device codes */
short	boxrulewid;		/* See djet.doc, put1s(), xychar() */
int	hpos = 0,		/* current internal x,y pos in res units */
	vpos = 0;		/* USED IN draw.o */
int	hoffset = 0;	/* used to reposition page (-me gets it wrong) */

#define hgoto(n) hpos = (n)
#define vgoto(n) vpos = (n)
#define hmot(n)  hpos += (n)
#define vmot(n)  vpos += (n)

#define	BMASK	0377
#ifdef DEBUG
int	dbg	= 0;
#endif

static	short	get16();
static	long	get32();

extern	char	*calloc();
charac	**chartable[NFONT+1];	/* each font has a 2 tables of pointers */
u_long	**rastable[NFONT+1];	/* indexed by the point size. see bitmap() */

int	res	= 720;		/* resolution assumed when generated */
char	*fontdir	= "/usr/lib/font";
char	*username = "someone";	/* for banner() and account() */
char	*jobnumber = "ABC123";
char	*jobname = "something";
char	*host = "somewhere";
int	printbanner	= 0;	/* print a banner, if we are told to do so */
int	acctpages	= 0;
char	*acctfile = NULL;
#ifdef PACKF
char	devname[]	= "jetF";
#else
# ifdef PACKJ
char	devname[]	= "jetJ";
# else
char	devname[]	= "jet";
# endif
#endif
char *progname;

#ifdef DRAW
extern int DX, DY, maxdots;	/* USED IN draw.o */
#endif

int abortjob();	/* must declare to use in signal() call */

main(argc, argv)
char *argv[];
{
	register i;

	progname = argv[0];
	for (i=1; i<argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			case 'x':		/* width of printer in pixels */
			case 'y':		/* length of printer in pixels */
			case 'F':		/* print formfeed at start */
				break;

			case 'n':
				username = argv[++i];
				break;
			case 'f':
				jobname = argv[++i];
				break;
			case 'j':
				jobnumber = argv[++i];
				break;
			case 'h':
				host = argv[++i];
				break;

			case 'b':		/* print banner at start */
			case 'e':		/* print banner at end */
				/*
				 * If either banner is asked for,
				 * then we will oblige - we always
				 * put it at the end of the job.
				 */
				printbanner = 1;
				break;
#ifdef DEBUG
			case 'd':
				dbg = atoi(&argv[i][2]);
				if (dbg == 0) dbg = -1;	/* I hope they know what they just asked for! */
				break;
#endif
			default:
				error("arg '%s' ?", argv[i]);
			}
		} else {
			/* acctfile, if any, should be last argument */
			if (i != argc-1) {
				error("arg '%s' ?", argv[i]);
			} else	acctfile = argv[i];
		}
	}

	(void) signal(SIGINT, abortjob);
	(void) signal(SIGHUP, abortjob);

#ifdef DEBUG
	if (dbg) error("DJET DEBUGGING STARTS");    /* not really an error */
#endif
	conv(stdin);
	t_wrapup();
	done();
}

/*
 *	Process list of page numbers to be printed
 */
outlist(s)
char *s;
{
	int n1, n2;

	nolist = 0;
	while (*s) {
		n1 = 0;
		if (isdigit(*s))
			do
				n1 = 10 * n1 + *s++ - '0';
			while (isdigit(*s));
		else
			n1 = -9999;
		n2 = n1;
		if (*s == '-') {
			s++;
			n2 = 0;
			if (isdigit(*s))
				do
					n2 = 10 * n2 + *s++ - '0';
				while (isdigit(*s));
			else
				n2 = 9999;
		}
		olist[nolist++] = n1;
		olist[nolist++] = n2;
		if (*s != '\0')
			s++;
	}
	olist[nolist] = 0;
#ifdef DEBUG
	if (dbg&8) {
		register i;
		fputs("Output Page list\nFrom To\n", stderr);
		for (i=0; i<nolist; i += 2)
			fprintf(stderr,"%3d  %3d\n", olist[i], olist[i+1]);
	}
#endif
}

in_olist(n)
int n;
{
	int i;

	if (nolist == 0)
		return(1);	/* everything is included */
	for (i = 0; i < nolist; i += 2)
		if (n >= olist[i] && n <= olist[i+1])
			return(1);
	return(0);
}

conv(fp)
register FILE *fp;
{
	register int c, k;
	int m, n, m1, n1;
	char str[100], buf[300];

	while ((c = getc(fp)) != EOF) {
		switch (c) {
		case '\n':	/* when input is text */
		case ' ':
		case 0:
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			/* two motion digits plus a character */
			hmot((c-'0')*10 + getc(fp)-'0');
			put1(getc(fp));
			break;
		case 'c':	/* single ascii character */
			put1(getc(fp));
			break;
		case 'C':
			(void) fscanf(fp, "%s", str);
			put1s(str);
			break;
		case 'D':	/* draw function */
			(void) fgets(buf, sizeof(buf), fp);
#ifdef DRAW
			switch (buf[0]) {
			case 'l':	/* draw a line */
				(void) sscanf(buf+1, "%d %d", &n, &m);
				drawline(n, m, ".");
				break;
			case 'c':	/* circle */
				(void) sscanf(buf+1, "%d", &n);
				drawcirc(n);
				break;
			case 'e':	/* ellipse */
				(void) sscanf(buf+1, "%d %d", &m, &n);
				drawellip(m, n);
				break;
			case 'a':	/* arc */
				(void) sscanf(buf+1, "%d %d %d %d", &n, &m, &n1, &m1);
				drawarc(n, m, n1, m1);
				break;
			case '~':	/* wiggly line */
				drawwig(buf+1);
				break;
			default:
				error("Unknown drawing function %s", buf);
				done();
			}
#else
			error("Drawing functions not supported.");
#endif
			break;
		case 's':
			(void) fscanf(fp, "%d", &n);	/* ignore fractional sizes */
			setsize(t_size(n));
			break;
		case 'f':
			(void) fscanf(fp, "%s", str);
			setfont(t_font(str));
			break;
		case 'H':	/* absolute horizontal motion */
			k = 0;
			while (isdigit(c = getc(fp))) {
				k = 10 * k + c - '0';
			}
			(void) ungetc(c, fp);
			hgoto(k);
			break;
		case 'h':	/* relative horizontal motion */
			k = 0;
			while (isdigit(c = getc(fp))) {
				k = 10 * k + c - '0';
			}
			(void) ungetc(c, fp);
			hmot(k);
			break;
		case 'w':	/* word space */
			break;
		case 'V':
			(void) fscanf(fp, "%d", &n);
			vgoto(n);
			break;
		case 'v':
			(void) fscanf(fp, "%d", &n);
			vmot(n);
			break;
		case 'p':	/* new page */
			(void) fscanf(fp, "%d", &n);
			t_page(n);
			break;
		case 'n':	/* end of line */
			t_newline();
			/* Drop through; reuse line gobbler */
		case '#':	/* comment */
			while (getc(fp) != '\n')
				;
			break;
		case 'x':	/* device control */
			devcntrl(fp);
			break;
		default:
			error("unknown input character %o %c", c, c);
			done();
		}
	}
}

/*
 *	Interpret device control functions
 */
devcntrl(fp)
FILE *fp;
{
        char str[20], str1[50], buf[50];
	int c, n;

	(void) fscanf(fp, "%s", str);
	switch (str[0]) {	/* crude for now */
	case 'i':	/* initialize */
		fileinit();
		t_init(0);
		break;
	case 'T':	/* device name */
		(void) fscanf(fp, "%s", str);
		if (strcmp(devname,str) != 0) {
			error("Input file prepared for %s instead of for %s", str, devname);
			_exit(0); /* DON'T flush stdout buffers */
		}
		break;
	case 't':	/* trailer */
		t_trailer();
		break;
	case 'p':	/* pause -- can restart */
		t_reset('p');
		break;
	case 's':	/* stop */
		t_reset('s');
		break;
	case 'r':	/* resolution assumed when prepared */
		(void) fscanf(fp, "%d", &res);
		break;
	case 'f':	/* font used */
		(void) fscanf(fp, "%d %s", &n, str);
		(void) fgets(buf, sizeof buf, fp);	/* in case there's a filename */
		(void) ungetc('\n', fp);	/* fgets goes too far */
		str1[0] = 0;	/* in case there's nothing to come in */
		(void) sscanf(buf, "%s", str1);
		loadfont(n, str, str1);
		break;
	case 'H':	/* char height */
		(void) fscanf(fp, "%d", &n);
		t_charht(n);
		break;
	case 'S':	/* slant */
		(void) fscanf(fp, "%d", &n);
		t_slant(n);
		break;
	}
	while ((c = getc(fp)) != '\n')	/* skip rest of input line */
		if (c == EOF)
			break;
}

/*
 *	Read in font and code files etc.
 */
fileinit()
{
	int i, j, fin;
	char *malloc(), *filebase;
	char temp[60];

	/* open table for device,
		 * read in resolution, size info, font info, etc.
		 * and set params
	 */
	(void) sprintf(temp, "%s/dev%s/DESC.out", fontdir, devname);
	if ((fin = open(temp, 0)) < 0) {
		error("can't open tables for %s", temp);
		done();
	}
	(void) read(fin, (char *)&dev, sizeof(struct dev));
	nfonts = dev.nfonts;
	nsizes = dev.nsizes;
	nchtab = dev.nchtab;
	unitwidth = dev.unitwidth;
	boxrulewid = dev.spare1;
	filebase = malloc((unsigned)dev.filesize);	/* enough room for whole file */
	(void) read(fin, filebase, dev.filesize);	/* all at once */
	pstab = (short *) filebase;
	chtab = pstab + nsizes + 1;
	chname = (char *) (chtab + dev.nchtab);
	for (i = 0; i <= nfonts; i++) {
		fontbase[i] = NULL;
		descend[i] = NULL;
		widthtab[i] = NULL; /* seperate because of different types */
		codetab[i] = fitab[i] = NULL;
	}
	(void) close(fin);

	/*
	 *	For each font, at each point size, we have a pointer to charac
	 *	structures, and a pointer to rasters. Initialise to NULL here
	 */
	for (i = 0; i < NFONT +1; i++) {
		chartable[i] = (charac **) calloc((unsigned) nsizes,
			sizeof(charac **));
		if (chartable[i] == NULL) {
			error("can't allocate space for character table\n");
			done();
		}

		rastable[i] = (u_long **) calloc((unsigned) nsizes,
			sizeof(u_long **));
		if (rastable[i] == NULL) {
			error("can't allocate space for raster table\n");
			done();
		}
		for (j = 0; j < nsizes; j++)
			chartable[i][j] = NULL;
	}
}

/*
 *	Load font info for font s on position n (0,...)
 */
loadfont(n, s, s1)
int n;
char *s, *s1;
{
	char temp[60];
	int fin, nw;

	if (n < 0 || n > NFONT) {
		error("illegal fp command %d %s", n, s);
		done();
	}
	if (fontbase[n] != NULL && strcmp(s, fontbase[n]->namefont) == 0)
		return;
	if (s1 == NULL || s1[0] == '\0')
		(void) sprintf(temp, "%s/dev%s/%s.out", fontdir, devname, s);
	else
		(void) sprintf(temp, "%s/%s.out", s1, s);
	if ((fin = open(temp, 0)) < 0) {
		error("can't open font table %s", temp);
		return;
	}
	if (fontbase[n] != NULL)
		free((char *)fontbase[n]);
	fontbase[n] = (struct Font *) malloc(3*255 + (unsigned)dev.nchtab + (128-32) + sizeof(struct Font));
	if (fontbase[n] == NULL) {
		error("Out of space in loadfont %s", s);
		done();
	}
	(void) read(fin, (char *)fontbase[n], 3*255 + nchtab+128-32 + sizeof(struct Font));
	(void) close(fin);
	if (smnt == 0 && fontbase[n]->specfont == 1)
		smnt = n;
	nw = fontbase[n]->nwfont & BMASK;
	/* has to be unsigned because some chars are >127 wide */
	widthtab[n] = (unsigned char *) fontbase[n] + sizeof(struct Font);
	descend[n] = (char *) widthtab[n] + 1 * nw;
	codetab[n] = (char *) widthtab[n] + 2 * nw;
	fitab[n] = (char *) widthtab[n] + 3 * nw;
	t_fp(n, fontbase[n]->namefont, fontbase[n]->intname);
}

/*	Here beginneth the even more Laserjet-dependent stuff	*/

#define RES	720		/* the units in which movements are required */
#define TOPMARGIN	260	/* Top margin in 720ths of an inch */

int	font	= 1;		/* internal font number */
int	size	= 1;		/* internal font size USED IN draw.o */
int	dev_font= -1;		/* device font number */
#ifdef SIZEMUNGE
static int dev_size = -1; /* troff's point size prevalent on device; 8 or 10. */
#endif
int	dev_hpos = -99999;	/* current device x,y pos in res units */
int	dev_vpos = -99999;	/* Ludicrous values force an initial abs mot */
#ifdef DRAW
int	drawdot	= '.';		/* what draw.o uses for plotting */
int	drawsize= 1;		/* and how much it shrinks it by */
int	DX	= 2;		/* x-step for drawing in draw.o */
int	DY	= 2;		/* ditto y */
#endif

/*
 * store for the escape sequence optimiser. Ocmd() put inline for speed.
 * 0 means there is no pending escape sequence terminator
 * 1 means that we are in an escape sequence and prefix[] is valid
 */
static char pending = 0;

#define ocmd(c) do { \
			if (pending) { \
				putchar(uppercase(pending)); \
				pending = 0; \
			} \
			(void) putchar(c); \
		} while (0)

abortjob()
{
#ifdef BSD4_2
	register char *base;

	/* flush stdio buffer without writing out contents,
	 * a munged version of fflush()
	 */
	if ((stdout->_flag&(_IONBF|_IOWRT))==_IOWRT
	 && (base=stdout->_base)!=NULL && (stdout->_ptr-base)>0) {
		stdout->_ptr = base;
		stdout->_cnt = (stdout->_flag&(_IOLBF|_IONBF)) ? 0 : stdout->_bufsiz;
	}
#endif
	/* make any half-baked commands raise their eyebrows. */
	done();
	/* NOTREACHED */
}

/*
 *	Always exit through here
 */
done()
{
	banner();
	account();
#ifdef DEBUG
	if (dbg)
		error("DJET DEBUGGING ENDS");	/* not really an error */
#endif
	exit(0);
	/* NOTREACHED */
}

/*
 *	Set character height to n
 */
t_charht(n)
int n;
{
#ifdef lint
	n=n;
#endif
	error("character height not implemented");
}

/*
 *	Set slant to n
 */
t_slant(n)
int n;
{
#ifdef lint
	n=n;
#endif
	error("slant not implemented, use italics");
}

/*
 *	Initialise
 */
t_init(re_init)
int re_init;
{
	if(!re_init) {
#ifdef DRAW
		int i;
		for(i = 0; i < nchtab; i++)
		if(strcmp(&chname[chtab[i]],"l.") == 0) break;
		if(i < nchtab) {
			drawdot = i + 128;
			drawsize = 1;
		} else {
			drawdot = '.';
			drawsize = 2;	/* draw half size */
		}
#endif

		/* commands to reset the printer to a useable state */
		/*	E	Reset printer
		 *	&l0L	Turn automatic perf-skip off
		 *	&l#E	Set top margin to # lines
		 *	blurb to select fixed-width courier:
			\033&l0O\033(8U\033(s0p10h12v0s0b3T
		 */
		esc("E");
		esc("&l0l0E");
	}
}

/*
 *	Start of page
 */
t_page(num)
int num;
{
	/* we want to put out a form feed before every page except the first */
	static int firstpageout = 1;

	output = in_olist(num);
#ifdef DEBUG
	if(dbg & 1) fprintf(stderr,"%s page %d\n",output?"print":"skip",num);
#endif
	if(output) {
		if (!firstpageout) {
			ocmd('\f');

		} else firstpageout = 0;

		hpos = vpos = 0;
		/*
		 *	\f does not affect hpos on laserjet.
		 *	Heaven only knows where it puts the cursor vertically.
		 */
		dev_vpos = -99999;	/* Force vertical motion */
		acctpages++;
	}
}

t_newline()
{
	if (ferror(stdout)) { /* in case printer has fallen in a heap */
		error("write fails");
		exit(1);
	}
	hpos = 0;	/* implicit */
}

/*
 *	Convert n to internal size number
 *	USED IN draw.o
 */
t_size(n)
int n;
{
	int i;

	if(n <= pstab[0]) return(1);
	if(n >= pstab[nsizes-1]) return(nsizes);
	for(i = 0; n > pstab[i]; i++) ;
	return(i + 1);
}

/*
 *	Convert numeric string to internal font number
 */
t_font(s)
char *s;
{
	int n;

	n = atoi(s);
	if(n < 0 || n > nfonts) n = 1;
	return(n);
}

t_reset(c)
char c;
{
	switch(c) {
	case 'p':
		break;
	case 's':
		break;
	}
}

t_wrapup()
{
}

t_trailer()
{
}

/*
 * br is used to communicate from put1s to xychar that we are setting a \(br
 * so to pretend it is zero-width even if it isn't really. See djet.doc.
 */
static int br = 0;

/*
 *	Put a funny char name (like Fi, bs etc.)
 */
put1s(s)
register char *s;
{
	static int i;

	if(!output) return;
#ifdef DEBUG
	if(dbg & 1) fprintf(stderr,"%s ",s);
#endif

	/* Perchance this 2-char char is the same as the last one */
	if(strcmp(s, &chname[chtab[i]]) != 0)
		/* nope - search for it */
		for(i = 0; i < nchtab; i++)
			/* strcmp here is a big timewaster */
			if (s[0] == chname[chtab[i]] &&
			    s[1] == chname[chtab[i]+1] ) break;
	if(i < nchtab) {
		/* troff assumes br does not advance the print head */
		if (s[0] == 'b' && s[1] == 'r') {
			br = 1; put1(i+128); br = 0;
		} else {
			put1(i + 128);
		}
	}
	else i = 0;
}

/*
 *	Output a char
 *	USED IN draw.o
 */
put1(c)
register int c;
{
	register char *p, *d;
	register int i,j,k;
	int ofont;
	unsigned char code;

	if(!output) return;

	c -= 32;
	if(c <= 0) {
#ifdef DEBUG
		error("character 0%03o non-existent\n",c+32);
#endif
		return;
	}
	k = ofont = font;
	i = fitab[font][c] & BMASK;
	if(i != 0) {	/* it's on this font */
		p = codetab[font];
		d = descend[font];
	}
	else if(smnt > 0) {	/* maybe its on the special */
		for(k = smnt, j = 0; j <= nfonts; j++, k = (k + 1) % (nfonts+1))
			if(fontbase[k] && ((i = fitab[k][c] & BMASK) != 0)) {
				p = codetab[k];
				d = descend[k];
				break;
			}
	}
	if(i == 0 || (code = p[i] & BMASK) == 0 || k > nfonts) {
		/* it wasn't there at all */
#ifdef DEBUG
		fprintf(stderr,"0%03o not found\n",c+32);
#endif
		return;
	}
	if(k != font) setfont(k);
#ifdef DEBUG
	if(dbg & 1) fprintf(stderr,isprint(c+32)?"%c %d\n":"0%03o %d\n",c+32,code);
#endif
	/*
	 *	Set the character updating the Laserjet's current font and x,y
	 *	positions to reflect any buffered changes
	 *	This used to be a function xychar(), inlined for speed.
	 */
	{
		register unsigned short width = widthtab[k][i];

		/* Try to calculate width Laserjet will use for this char
		 * at this point size. Use the algorithm from t10.c (troff).
		 */
#ifdef SIZEMUNGE
		if (pstab[size-1] != unitwidth) {
			width = (width*pstab[size-1] + (unitwidth/2))/unitwidth;
		}
#endif

		/*
		 * br is set by put1s() to indicate that we are setting a
		 * box rule. boxrulewid is set in fileinit()
		 */
		if (br) hmot(-boxrulewid/2);
		if(font != dev_font
#ifdef SIZEMUNGE
				    || size != dev_size
#endif
							) fontflush();
		if (d[i] & 128)
			bitmap(code);	/* synthesise this char */
		else {
			hvflush();
			ocmd(code);	/* o/p the code itself */
			if (br) hmot(boxrulewid/2);
			/* Echo Auto-advance in dev_hpos */
			dev_hpos += ( br ? boxrulewid : width );
		}
	}
	if(font != ofont) setfont(ofont);
}

/*
 * Force the device's current (x,y) position to be the same as
 * ours. Use absolute motion to prevent rounding errors from
 * accumulating in the printer and because it is more robust.
 */
hvflush()
{
	if (dev_hpos != hpos) {
		/* If the gap is just right, use a space instead of a motion */
		if (
#ifdef SIZEMUNGE
			pstab[size-1] == unitwidth &&
#endif
			/* internalname[font][8] is spacewidth+32 */
			/* Use dev_font because font may not have been flushed*/
			hpos - dev_hpos == fontbase[dev_font]->intname[8]-32) {
			ocmd(' ');
		} else
			esc("&a%dH", hpos*RES/res + hoffset);
		dev_hpos = hpos;
	}
	if (dev_vpos != vpos) {
		esc("&a%dV", vpos*RES/res+TOPMARGIN);
		dev_vpos = vpos;
	}
}

/*
 *	Read in the font file fname and return the addresses of the first
 *	character structure and the first raster in *cptr and *rptr
 */
getfont(fname, cptr, rptr)
char	*fname;
charac	**cptr;
u_long	**rptr;
{
	charac	*c;
	u_long	*r;
	u_short	nchars, nrasters;
	FILE	*fp;
	int	i;

	if ((fp = fopen(fname, "r")) == NULL) {
		error("Can't open font file %s\n", fname);
		done();
	}
	nchars = get16(fp);
	nrasters = get16(fp);

	/* Allocate memory for character structures */
	c = (charac *) calloc((unsigned) nchars, sizeof(charac));
	if (c == NULL) {
		error("Not enough memory for characters from %s\n", fname);
		done();
	}

	/* Alocate memory for rasters */
	r = (u_long *) calloc((unsigned) nrasters, sizeof(u_long));
	if (r == NULL) {
		error("Not enough memory for rasters from %s\n", fname);
		done();
	}

	for (i = 0; i < nchars; i++) {
		c[i].width		= get16(fp);
		c[i].height		= get16(fp);
		c[i].xoffs		= get16(fp);
		c[i].yoffs		= get16(fp);
		c[i].raster_index	= get32(fp);
		c[i].effective_width	= get32(fp);
	} 

	for (i = 0; i < nrasters; i++)
		r[i] = get32(fp);

	*cptr = c;	/* return indices into font */
	*rptr = r;
	(void) fclose(fp);
}

/* get a 16 bit quantity from stream fp */
static short
get16(fp)
FILE *fp;
{
	short i;

	i = (short) getc(fp) << 8;
	i += (short) getc(fp);
	return(i);
}
	
/* get a 32 bit quantity from stream fp */
static long
get32(fp)
FILE *fp;
{
	long i;

	i = (long) getc(fp) << 24;
	i += (long) getc(fp) << 16;
	i += (long) getc(fp) << 8;
	i += (long) getc(fp);
	return(i);
}

/*
 *	Output the bitmap of the `c'th character from
 *	the current font's supplementary font file 
 */
bitmap(c)
unsigned char	c;
{
	charac	*ch;
	u_long	*ra, *r;
	char	buf[60];
	int	i, j;

	/* load the required font file, if we haven't already */
	if (chartable[font][size-1] == NULL) {
		(void) sprintf(buf, "%s/dev%s/fonts/%s.%d", fontdir, devname,
			fontbase[font]->namefont, pstab[size-1]);
		(void) getfont(buf, &chartable[font][size-1], &rastable[font][size-1]);
	}

	ch = chartable[font][size-1];		/* characters for this font */
	ra = rastable[font][size-1];		/* rasters for this font */
	r = &ra[ch[c].raster_index];	/* start of bitmap */

	/* set raster graphics resolution to 300 dpi */
	esc("*t300R");

	/* ensure reference point is at current (hpos, vpos) position */
	esc("&a%dV", ((vpos - ch[c].yoffs)*RES + RES/2)/res + TOPMARGIN);
	esc("&a%dH", ((hpos - ch[c].xoffs + X_OFFS)*RES + RES/2)/res + hoffset);

	esc("*r1A");	/* start raster graphics */

	for (i = 0; i < ch[c].height; i++) {
		esc("*b%dW", (ch[c].width+7)/8); /* transfer */

		/* do whole words first */
		for (j = 0; j < (ch[c].width)/32; j++) {
			ocmd((char) (*r >> 24) & 255);
			ocmd((char) (*r >> 16) & 255);
			ocmd((char) (*r >> 8) & 255);
			ocmd((char) *r & 255);
			r++;
		}
		
		/* now do remaining bytes */
		if (ch[c].width%32 != 0) {
			ocmd((char) (*r >> 24) & 255);
			if (ch[c].width%32 > 8) {
				ocmd((char) (*r >> 16) & 255);
				if (ch[c].width%32 > 16) {
					ocmd((char) (*r >> 8) & 255);
					if (ch[c].width%32 > 24)
						ocmd((char) *r & 255);
				}
			}
			r++;
		}
	}
	esc("*rB");	/* end raster graphics */
	/* update the position of the device head */
	dev_hpos = hpos - ch[c].xoffs;
	dev_vpos = vpos - ch[c].yoffs + ch[c].height;
}

setsize(n)
int n;
{
	size = n;
}

/*
 *	Font position n now contains font s, internal name si
 */
/* ARGSUSED */
t_fp(n, s, si)
int n;
char *s,*si;
{
#ifdef lint
	s=s;
#endif
	/* fprintf(stdout, "!AF%d,%s!", n, si); */
}

setfont(n)
{
	if(!output) return;
	if(n < 0 || n > NFONT) {
		error("illegal font %d", n);
		done();
	}
	font = n;
}

#define A_ORIENT	0	/* Portrait/landscape mode */
#define  A_OR_PORT	 0
#define  A_OR_LAND	 1

#define A_SYMSET	1	/* Symbol set */
static char *a_symset[] = {	/* 0 */	"8U",	/* Roman-8	*/
				/* 1 */	"8K",	/* Kana-8	*/
				/* 2 */	"0U",	/* USASCII	*/
				/* 3 */	"0E",	/* Roman Ext	*/
				/* 4 */	"0B",	/* Line Draw	*/
				/* 5 */	"0A",	/* Math 7	*/
				/* 6 */	"8M",	/* Math 8	*/
				/* 7 */	"15U",	/* Pi Font	*/
			  };

#define A_SPACING	2	/* proportional vs fixed-width */
#define  A_SP_FIXED	 '0'
#define  A_SP_PROP	 '1'

#define A_PITCH		3	/* Applies only to fixed-width fonts */
#define  A_PI_10	 0
#define  A_PI_16	 1	/* 16.66 cpi */
#define	 A_PI_12	 2
static char *a_pitch[] = { "10", "16.66", "12" };

#define A_HEIGHT	4	/* ie point size */
#define  A_HE_8		 0
#define  A_HE_85	 1	/* 8.5 point */
#define  A_HE_10	 2
#define  A_HE_12	 3
#define  A_HE_14	 4	/* 14.4 point */
#define	 A_HE_7		 5
static char *a_height[] = { "8", "8.5", "10", "12", "14.4", "7" };

#define A_STYLE		5	/* Italic/upright */
#define A_WEIGHT	6	/* Boldness */
#define A_TYPEFACE	7

#define NATTRIB 8	/* Slot 8 is used for spacewidth */

/*
 *	Tell the Laserjet about any font changes which have occurred.
 *	Life would be so easy if only internalname[] were big enough to hold
 *	the largest font selecting string.
 *	However, this granular approach enables esc() to do more optimisation.
 */

fontflush()
{
	register int i;
	/* Attributes of font as held in laserjet for lazy evaluation */
	static char dev_attrib[NATTRIB] = {'0','0','X','X','X','0','0','X'};
	char attrib[10];	/* big enough to hold internalname */
	char *strncpy();

#ifdef DEBUG
	if(dbg & 1) fprintf(stderr,"Font -> %d\n",font);
#endif
	/* Interpolate internalname string */
	(void) strncpy(attrib, fontbase[font]->intname, sizeof(attrib));
#ifdef PACKF
	if (pstab[size-1] == 8) attrib[A_HEIGHT] = A_HE_8 + '0';
#endif
#ifdef PACKJ
	if (pstab[size-1] == 7) attrib[A_PITCH] = A_PI_16 + '0';
#endif
	for (i=0; i<NATTRIB; i++) {
		if (attrib[i] != dev_attrib[i]) {
			switch (i) {
			case A_ORIENT:	/* No support for landscape */
				break;
			case A_SYMSET:
				esc("(%s", a_symset[attrib[i]-'0']);
				break;
			case A_SPACING:
				esc("(s%cP", attrib[A_SPACING]);
				break;
			case A_PITCH:
				/* Pitch is only relevant to fixed-width fonts */
				if (attrib[A_SPACING]==A_SP_PROP) goto nextattrib;
				esc("(s%sH", a_pitch[attrib[A_PITCH]-'0']);
				break;
			case A_HEIGHT:
				esc("(s%sV", a_height[attrib[A_HEIGHT]-'0']);
				break;
			case A_STYLE:
				esc("(s%cS", attrib[A_STYLE]);
				break;
			case A_WEIGHT:
				esc("(s%dB", attrib[A_WEIGHT]-'0');
				break;
			case A_TYPEFACE:
				esc("(s%cT", attrib[A_TYPEFACE]);
				break;
			}
			dev_attrib[i] = attrib[i];
		}
nextattrib:		;
	}
	dev_font = font;
#ifdef SIZEMUNGE
	dev_size = size;
#endif
}

/*
 *	Because esc tries to combine adjacent escape sequences if possible and
 *	may keep one character in hand to do so, output on stdout should only
 *	be done through ocmd() and esc(), or through other authorised routine
 *	you may care to add.
 *
 *	Clever optimiser: the bottleneck in print speed seems to be the
 *	9600 baud link so minimum characters is a must.
 *
 *	If two consecutive escape sequences start with the same two letters
 *	(not including the initial escape), they may be elided by making the
 *	last character of the first in lower case and omitting the <esc>xy from
 *	the second.
 */

/* VARARGS1 */
static
esc(format, arg)
char *format;
{
	static char prefix0, prefix1;	/* The current two-character prefix, quicker and clearer than a 2-char string */
	char str[16];
	register int i;

	(void) sprintf(str, format, arg);
	if (strlen(str)<3) {
		if (pending) putchar(uppercase(pending));
		putchar('\033');
		fputs(str, stdout);
		pending = 0;
	} else {
		if (pending) {
			if (prefix0==str[0] && prefix1==str[1]) {
				/* Yippee! same prefix! */
				putchar(lowercase(pending));
				i=2;	/* don't output first two chars */
			} else {
				/* Pending char but new prefix */
				putchar(uppercase(pending));
				putchar('\033');
				i=0;	/* output whole string */
			}
		} else {
			putchar('\033');
			i=0;
		}
		for ( ; str[i+1] != '\0'; i++) putchar(str[i]);
		prefix0 = str[0];
		prefix1 = str[1];
		pending = str[i];
	}
}

/*
 * print an error message, with timestamp and process identification.
 * no more than 10 args can be taken.
 */
/* VARARGS1 */
/* PRINTFLIKE */
static
error(format, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char *format;
{
	time_t clock;			/* for timestamp */
	char *ctime();

	(void) time(&clock);
	fprintf(stderr, "%15.15s %s <%d> (%s) ",
			progname, ctime(&clock)+4, getpid(), username);
	fprintf(stderr, format, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
	putc('\n', stderr);
	(void) fflush(stderr);
}

/*
 *	Cobble together a banner page.
 */
banner()
{
	time_t clock;
	char *delivery;
	extern char *ctime();
	extern char *getenv();

	esc("E");	/* Also forces lazy evaluating thing to flush */
	if (printbanner) {
		(void) time(&clock);
		printf(
	"\033&s0C%d page%s of %s printed for %s (%s) from %s on %15.15s\n\r",
					acctpages,
					(acctpages==1) ? "" : "s",
					jobname,
					username,
					jobnumber,
					host,
					ctime(&clock)+4);

		delivery = getenv("DELIVERY");
		if (delivery != NULL && *delivery != '\0')
			printf("Deliver to %s", delivery);
#ifdef DEBUG
		if (dbg & 4)
			fprintf(stderr, "delivery = %d; string = \"%s\"\n",
						delivery, delivery);
#endif
		putchar('\f');
		acctpages++;
	}
}

static
account()
{
	static FILE *fp = NULL;

	if (acctfile == NULL) return;
	fp = fopen(acctfile, "a");
	if (fp == NULL) return;
	fprintf(fp, "%s:\t%s\t%s\t%d page%s\n",
		progname, host, username, acctpages, acctpages==1?"":"s");
	(void) fclose(fp);
}
