#ifndef lint
static char sccsid[] = "@(#)dcan.c	1.22 (UKC) 27/9/88";
#endif  lint

/*
 *	dcan
 *	driver for Canon LBP-10 laser printer
 */

/*
 *	output language from troff:
 *	all numbers are character strings
 *
 *	sn	size in points
 *	fn	font as number from 1-n
 *	cx	ascii character x
 *	Cxyz	funny char xyz. terminated by white space
 *	Hn	go to absolute horizontal position n
 *	Vn	go to absolute vertical position n (down is positive)
 *	hn	go n units horizontally (relative)
 *	vn	ditto vertically
 *	nnc	move right nn, then print c (exactly 2 digits!)
 *			(this wart is an optimization that shrinks output file
 *			 size about 35% and run-time about 15% while preserving
 *			 ascii-ness)
 *	Dt..\n	draw operation 't':
 *			Dl x y		line from here by x,y
 *			Dc d		circle of diameter d with left side here
 *			De x y		ellipse of axes x,y with left side here
 *			Da x y r	arc counter-clockwise by x,y of radius r
 *			D~ x y x y ...	wiggly line by x,y then x,y ...
 *	nb a	end of line (information only)
 *			b = space before line, a = after
 *	p	new page begins -- set v to 0
 *	#...\n	comment
 *	x ...\n	device control functions:
 *			x i	init
 *			x T s	name of device is s
 *			x r n h v	resolution is n/inch
 *				h = min horizontal motion, v = min vert
 *			x p	pause (can restart)
 *			x s	stop -- done for ever
 *			x t	generate trailer
 *			x f n s	font position n contains font s
 *			x H n	set character height to n
 *			x S n	set slant to N
 *
 *	Subcommands like "i" are often spelled out like "init".
 */

#include	<stdio.h>
#include	<signal.h>
#include	<math.h>
#include	<ctype.h>
#include	<pwd.h>
#include	"dev.h"
#include	<canon.h>

#define abs(n)  ((n) >= 0 ? (n) : -(n))
#define min(x,y)	(((x) > (y)) ? (y) : (x))

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

int	hpos, vpos;		/* current internal x,y pos    */
int	dev_hpos,dev_vpos;	/* current device x,y pos */
int hoffset = 0;	/* to shift the page; -me gets .po 1/2" too narrow */

#define	NFONT	10

int	xoffset	= 0;	/* shift output right by this amount */
int	yoffset = 0;	/* shift down (!) by this amount */

int	rotate	= 0;	/* 0 => portrait, 1 => landscape */
int	output	= 0;	/* 0 => skip, 1 => print it */
int	pageno	= -1;	/* output page number */

#define MAXOLIST	200
int	nolist	= 0;	/* output page list if > 0 */
int	olist[MAXOLIST];	/* pairs of page numbers */

int	erase	= 1;
float	aspect	= 1;	/* default aspect ratio */

int	done_init = 0;	/* set once we've done an initialisation */

struct dev dev;
struct Font *fontbase[NFONT+1];
short	*pstab;
int	nsizes	= 1;
int	nfonts;
int	smnt;	/* index of first special font */
int	nchtab;
char	*chname;
short	*chtab;
char	*fitab[NFONT+1];
char	*widthtab[NFONT+1];	/* widtab would be a better name */
char	*codetab[NFONT+1];	/* device codes */

#define ADVISE	0		/* Advise of error -- ie. not fatal */
#define RETRY	1		/* fatal error but retry job (for filter) */
#define	FATAL	2
#define	BMASK	0377
int	dbg	= 0;
long	lineno	= -1;
int	null_in	= 1;		/* Null input file? */
int	res	= 240;		/* resolution assumed when generated */
char	*fontdir	= "/usr/lib/font";
char	*bitdir		= "/usr/lib/vfont";
char	*acctfile	= "/usr/adm/log/canon/dcanacct";
int	acctpages	= 0;
int	copies		= 1;
char	*username	= NULL;
char	*getlogin();
char	devname[20]	= "can";
int	filter		= 0;	/* true iff running from lpr */

char	*myname,		/* name by which invoked */
	*infname;		/* current input file name */

FILE *fp	= stdin;	/* input file pointer */
extern int DX, DY, maxdots;

char *basename();
char *rindex();		/* used in basename() */

main(argc, argv)
char *argv[];
{
	int tflag = 0,done2();
	struct passwd *thisuser,*getpwuid();
	char *getenv(),*ukcsysid(),host[80],*delivery;
	char *jobname = 0;

	/* First find out if we're running as a filter from lpr ... */
	if(strcmp(myname = basename(*argv),"tilfilt") == NULL) filter++;
	/* then if we must write stdout anyway ... */
	else if(strcmp(myname,"dcan-t") == NULL) tflag++;

	host[0] = '\0'; 
	if(filter) {
		/* decode lpr filter args */
		while (argc > 1 && argv[1][0] == '-') {
			switch(argv[1][1]) {
			case 'x':		/* width in pixels */
			case 'y':		/* length in pixels */
			case 'b':		/* print banner at start */
			case 'e':		/* print banner at end */
			case 'F':		/* print form feed */
				break;
			case 'j':		/* sysid */
				argv++;argc--;
				break;
			case 'n':
				username = argv[2];
				argv++;argc--;
				break;
			case 'f':
				jobname = argv[2];
				argv++;argc--;
				break;
			case 'h':
				strcpy(host,argv[2]);
				argv++;argc--;
				break;
			}
			argv++; argc--;
		}
		tflag++;	/* always write stdout */
		if(argc-- > 1)	/* name of accounting file */
			acctfile = *++argv;
	} else {
		while (argc > 1 && argv[1][0] == '-') {
			/* decode standard command line args */
			switch (argv[1][1]) {
			case 'c':
				copies = atoi(&argv[1][2]);
				if((copies < 1) || (copies > 255)) copies = 1;
				break;
			case 'r':
				rotate = !rotate;
				break;
			case 't':
				tflag++;
				break;
			case 'f':
				bitdir = argv[2];
				argv++; argc--;
				break;
			case 'o':
				outlist(&argv[1][2]);
				break;
			case 'p':	/* pixels of resolution */
				DX = DY = atoi(&argv[1][2]);
				if (DX == 0)
					DX = DY = 1;
				break;
			case 'd':
				dbg = atoi(&argv[1][2]);
				if (dbg == 0) dbg = 1;
				tflag++;
				break;
			case 'h':
				jobname = argv[2];
				argv++; argc--;
				break;
			}
			argc--; argv++;
		}
		gethostname(host,80);
	}

	signal(SIGINT, done2);
	signal(SIGHUP, done2);
	signal(SIGQUIT, done2);

	/* Get delivery variable, unix and EMAS login names */

	delivery = getenv("DELIVERY");

	if(filter) thisuser = getpwnam(username);
	else {
		if(thisuser = getpwuid(getuid()))
			username = thisuser->pw_name;
	}
	if(thisuser == NULL)
		error(FATAL,"can't find login names (please report)");

	if (!tflag) make_pipe();

	/* Fake up a job name if necessary or possible */

	if(!jobname && (argc > 1)) jobname = argv[1];

	header(jobname,username,ukcsysid(thisuser,0),host,delivery);

	if (argc <= 1) {
		infname = "stdin";
		conv(stdin);
	} else
		while (--argc > 0) {
			if (strcmp(*++argv, "-") == 0) {
				infname = "stdin";
				fp = stdin;
			} else if ((fp = fopen(infname = *argv, "r")) == NULL)
				error(FATAL, "can't open %s", *argv);
			conv(fp);
			fclose(fp);
		}
	if(null_in)
		/* We haven't done anything, finish now */
		done();
	t_wrapup();
	account();
	done();
}

/*
 *	Create a pipe to lpr to print the file(s)
 */
make_pipe()
{	int fds[2],p;

	if(pipe(fds)) {
		fprintf(stderr,"%s: pipe fails\n",myname);
		exit(2);
	}
	if((p = vfork()) < 0) {
		fprintf(stderr,"%s: fork fails\n",myname);
		exit(2);
	} else if(p > 0) {	/* parent */
		close(1);	/* re-assign stdout */
		dup(fds[1]);	/* to stdout */
		close(fds[0]);	/* close originals */
		close(fds[1]);
	} else {	/* child */
		close(0);	/* re-assign stdin */
		dup(fds[0]);	/* to stdin */
		close(fds[0]);	/* close originals */
		close(fds[1]);
		/* and start lpr */
		execl("/usr/ucb/lpr","lpr","-l","-Pcanon",0);
		perror(myname);
		exit(2);	/* execl fails */
	}
}

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

	nolist = 0;
	while (*s) {
		if(nolist >= MAXOLIST) {
			error(ADVISE,"too many -o pages, ignoring \"%s\"",s);
			return;
		}
		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;
	if (dbg)
		for (i=0; i<nolist; i += 2)
			fprintf(stderr,"%3d %3d\n", olist[i], olist[i+1]);
}

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, n1, m1;
	char str[100], buf[300];
	extern int size;	/* internal point size -- forward decl */

	lineno = 1;
	while ((c = getc(fp)) != EOF) {
		null_in = 0;	/* We've had some input at least */
		switch (c) {
		case '\n':	/* when input is text */
		case ' ':
		case 0:		/* occasional noise creeps in */
			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':
			fscanf(fp, "%s", str);
			put1s(str);
			break;
		case 'D':	/* draw function */
			fgets(buf, sizeof(buf), fp);
			switch (buf[0]) {
			case 'l':	/* draw a line */
				sscanf(buf+1, "%d %d", &n, &m);
				vector(n, m, ".");
				break;
			case 'c':	/* circle */
				sscanf(buf+1, "%d", &n);
				/* a circle is an ellipse with equal radii */
				drawellip(n,n);
				break;
			case 'e':	/* ellipse */
				sscanf(buf+1, "%d %d", &m, &n);
				drawellip(m, n);
				break;
			case 'a':	/* arc */
				sscanf(buf+1, "%d %d %d %d", &n, &m, &n1, &m1);
				drawarc(n, m, n1, m1);
				break;
			case '~':	/* wiggly line */
				drawwig(pstab[size-1]/4,buf+1);
				break;
			default:
				error(FATAL, "unknown drawing function %s\n", buf);
				break;
			}
			break;
		case 's':
			fscanf(fp, "%d", &n);	/* ignore fractional sizes */
			setsize(t_size(n));
			break;
		case 'f':
			fscanf(fp, "%s", str);
			setfont(t_font(str));
			break;
		case 'H':	/* absolute horizontal motion */
			fscanf(fp, "%d", &n);
			hgoto(n);
			break;
		case 'h':	/* relative horizontal motion */
			fscanf(fp, "%d", &n);
			hmot(n);
			break;
		case 'w':	/* word space */
			break;
		case 'V':
			fscanf(fp, "%d", &n);
			vgoto(n);
			break;
		case 'v':
			fscanf(fp, "%d", &n);
			vmot(n);
			break;
		case 'p':	/* new page */
			fscanf(fp, "%d", &n);
			t_page(n);
			break;
		case 'n':	/* end of line */
			while (getc(fp) != '\n')
				;
			t_newline();
			break;
		case '#':	/* comment */
			while (getc(fp) != '\n')
				;
			break;
		case 'x':	/* device control */
			devcntrl(fp);
			break;
		default:
			error(FATAL, "unknown input character %o %c\n", c, c);
		}
	}
}

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

	fscanf(fp, "%s", str);
	switch (str[0]) {	/* crude for now */
	case 'i':	/* initialize */
		if(!done_init) fileinit();
		t_init(done_init++);
		break;
	case 'T':	/* device name */
		fscanf(fp, "%s", devname);
		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 */
		fscanf(fp, "%d", &res);
		break;
	case 'f':	/* font used */
		fscanf(fp, "%d %s", &n, str);
		fgets(buf, sizeof buf, fp);	/* in case there's a filename */
		ungetc('\n', fp);	/* fgets goes too far */
		str1[0] = 0;	/* in case there's nothing to come in */
		sscanf(buf, "%s", str1);
		loadfont(n, str, str1);
		break;
	case 'd':		/* LOCAL ADDITION -- interpolate dtf file */
		fscanf(fp,"%s",str);
		insert_dtf(str);
		break;
	/* these don't belong here... */
	case 'H':	/* char height */
		fscanf(fp, "%d", &n);
		t_charht(n);
		break;
	case 'S':	/* slant */
		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, fin;
	char *malloc(), *filebase;
	char temp[60];

	/* open table for device,
	/* read in resolution, size info, font info, etc.
	/* and set params
	*/
	sprintf(temp, "%s/dev%s/DESC.out", fontdir, devname);
	if ((fin = open(temp, 0)) < 0)
		error(FATAL, "can't open tables for %s\n", temp);
	read(fin, &dev, sizeof(struct dev));
	nfonts = dev.nfonts;
	nsizes = dev.nsizes;
	nchtab = dev.nchtab;
	filebase = malloc(dev.filesize);	/* enough room for whole file */
	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;
		widthtab[i] = codetab[i] = fitab[i] = NULL;
	}
	close(fin);
}

/*
 *	Debugging print of font i (0,...)
 */
fontprint(i)
{
	int j, n;
	char *p;

	fprintf(stderr,"font %d:\n", i);
	p = (char *) fontbase[i];
	n = fontbase[i]->nwfont & BMASK;
	fprintf(stderr,"base=0%o, nchars=%d, spec=%d, name=%s, widtab=0%o, fitab=0%o\n",
		p, n, fontbase[i]->specfont, fontbase[i]->namefont, widthtab[i], fitab[i]);
	fprintf(stderr,"widths:\n");
	for (j=0; j <= n; j++) {
		fprintf(stderr," %2d", widthtab[i][j] & BMASK);
		if (j % 20 == 19) printf("\n");
	}
	fprintf(stderr,"\ncodetab:\n");
	for (j=0; j <= n; j++) {
		fprintf(stderr," %2d", codetab[i][j] & BMASK);
		if (j % 20 == 19) fprintf(stderr,"\n");
	}
	fprintf(stderr,"\nfitab:\n");
	for (j=0; j <= dev.nchtab + 128-32; j++) {
		fprintf(stderr," %2d", fitab[i][j] & BMASK);
		if (j % 20 == 19) fprintf(stderr,"\n");
	}
	fprintf(stderr,"\n");
}

/*
 *	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(FATAL, "illegal fp command %d %s", n, s);
	if (fontbase[n] != NULL && strcmp(s, fontbase[n]->namefont) == 0)
		return;
	if (s1 == NULL || s1[0] == '\0')
		sprintf(temp, "%s/dev%s/%s.out", fontdir, devname, s);
	else
		sprintf(temp, "%s/%s.out", s1, s);
	if ((fin = open(temp, 0)) < 0) {
		error(ADVISE, "can't open font table %s", temp);
		return;
	}
	if (fontbase[n] != NULL)
		free(fontbase[n]);
	fontbase[n] = (struct Font *) malloc(3*255 + dev.nchtab +
				(128-32) + sizeof(struct Font));
	if (fontbase[n] == NULL)
		error(FATAL, "Out of space in loadfont %s\n", s);
	read(fin, fontbase[n], 3*255 + nchtab+128-32 + sizeof(struct Font));
	close(fin);
	if (smnt == 0 && fontbase[n]->specfont == 1)
		smnt = n;
	nw = fontbase[n]->nwfont & BMASK;
	widthtab[n] = (char *) fontbase[n] + sizeof(struct Font);
	codetab[n] = (char *) widthtab[n] + 2 * nw;
	fitab[n] = (char *) widthtab[n] + 3 * nw;
	t_fp(n, fontbase[n]->namefont, fontbase[n]->intname);
	if (dbg > 1) fontprint(n);
}


error(f, s, a1, a2, a3, a4, a5, a6, a7) {
	fprintf(stderr, "%s: ",myname);
	fprintf(stderr, s, a1, a2, a3, a4, a5, a6, a7);
	if(lineno > 0)
		fprintf(stderr, " near line %ld", lineno);
	fprintf(stderr," (file %s)\n",infname);
	if (f)
		exit(f);
}

/*	Here beginneth the Canon dependent stuff	*/

int	font	= 1;		/* internal font number */
int	size	= 1;		/* internal font size */
int	dev_font= -1;		/* device font number */
int	dev_size= -1;		/* device font size */
int	drawdot	= '.';		/* what draw uses     */
int	drawsize= 1;		/* and how much to shrink by */
int	DX	= 2;		/* x-step for drawing */
int	DY	= 2;		/* ditto y */
int	leftmost= MAXX+1;	/* for leftmost page position */

struct {
	char *name;
	int number;
} fontname[NFONT + 1];		/* font position info */

int newfont[NFONT + 1]	= 0;	/* New font defined - BEWARE */

# define MAXDEVFONTS 254	/* DON'T USE 255 -- leave it for insert_dtf */

struct {
	int font,size,last_used;
} dev_fonts[MAXDEVFONTS + 1];	/* fonts known to Canon by index */

int df_ix = 0;			/* and something to index them by */
int used  = 0;			/* font use count */

struct {
	char int_name[3],dev_name[12];
} railmag[MAXDEVFONTS] = 0;		/* maps R, I, NI etc to file names */
int nmagent = 0;		/* number of entries in file */

#define put8(c) { if((putchar(c) == EOF) && ferror(stdout)) error(RETRY,"output fails"); }

done()
{
	exit(0);
}

done2()
{
	exit(2);
}

/*
 *	Interpolate existing DTF file
 */
insert_dtf(name)
char *name;
{	FILE *dtf;
	int n;
	char b[BUFSIZ];

	if(!output) return;

	if((dtf = fopen(name,"r")) == NULL) {
		error(ADVISE,"can't find dtf file %s",name);
		return;
	}

	put8(MOVETOXY);
	put16(leftmost);	/* go to left margin */
	put16(vpos + (pstab[size-1]*240/72));	/* and ~1v down */

	dev_font = dev_size = -1;	/* don't know how we'll finish */
	dev_hpos = dev_vpos = -1;	/* or where */

	/* pass named dtf file straight through */
	while((n = fread(b,sizeof(char),BUFSIZ,dtf)) > 0)
		fwrite(b,sizeof(char),n,stdout);
	fclose(dtf);
	/* font, size and position are unset, but should be recovered */
}

/*
 *	Set character height to n
 */
t_charht(n)
int n;
{
	error(ADVISE,"character height not implemented, set point size");
}

/*
 *	Set slant to n
 */
t_slant(n)
int n;
{
	error(ADVISE,"slant not implemented, use italics");
}

/*
 *	Record paper use
 */
account()
{
	FILE *f = NULL;

	if((f = fopen(acctfile,"a")) != NULL) {
		if(username == NULL) username = "???";
		fprintf(f,"%4d %s\n",acctpages,username);
	}
}

/*
 *	Initialise
 */
t_init(re_init)
int re_init;
{
	int i;

	if(!re_init) {
		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 */
		}
		getmagent();

		put8(LPSETUP);		/* set up to use auto-advance later */
		put16(0);put16(0);	/* don't bother with the other bits */
		put16(0);put16(0);
		put8(LPMODEON);		/* and turn it on */
	}
	setsize(t_size(10));	/* must start somewhere */
}

/*
 *	Start of page
 */
t_page(num)
int num;
{
	output = in_olist(num);
	if(dbg) fprintf(stderr,"%s page %d\n",output?"print":"skip",num);

	if(output) {
		put8(BEGINPAGE);
		put16(num);
		put8(copies);
		hpos = vpos = dev_hpos = dev_vpos = 0;
		acctpages += copies;
	}
}

t_newline()
{
	hpos = 0;	/* implicit */
	lineno++;	/* for error reporting */
}

/*
 *	Convert n to internal size number
 */
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 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)
{
	if(output) acctpages++;
}

t_wrapup()
{
	put8(STOP);		/* all over */
}

/*
 *	Set a rule
 */
t_rule(w, h)
{
	if(!output) return;

	hvflush();
	put8(SETRULE);
	put16(w);
	put16(h);
}

t_trailer()
{
}

vector(n,m,s)
char *s;
{	/* Set a vector from current position by (n,m). Ignore character */

	if(!output) return;

	hvflush();
	put8(DRAWLINE);
	put16(hpos+n);	/* dtf takes absolute (x,y) of end-point */
	put16(vpos+m);
	put8(pstab[size-1]/4);	/* fake width -- matches br & ul sizes */
	put8(SOLID);	/* solid line */
	dev_hpos += n;	/* Leaves us at end of line */
	dev_vpos += m;
	hmot(n);	/* move to end */
	vmot(m);
}

/*
 * draw an ellipse with diameters d1 and d2.
 * Using dtf conic arc routine
 */
drawellip(d1,d2)
register int d1,d2;
{	register int od1;

	od1 = d1 ; d1 /= 2 ; d2 /= 2;
	conic(hpos+d1,vpos,d1,d2,0,0);
	hmot(od1);	/* finish @ rhs */
}

/*
 * draw a circular arc from (hpos,vpos) with:
 * centre at (hpos+rxc,vpos+ryc)
 * to (hpos+rxc+rxe,vpos+ryc+rye)
 */
drawarc(rxc,ryc,rxe,rye)
register int rxc,ryc,rxe,rye;
{	register int rad;
	int sa,ea;

#define TWO_PI_mR	6283	/* 2pi milliradians */

	/* calculate radius */
	rad = sqrt((double)(rxe*rxe+rye*rye))+0.5;
	/*
	 * and start and end angles
	 * for the start angle, we're given the offsets to the centre
	 * from the start of the arc. We want the opposite.
	 */
	sa = atan2((double)-ryc,(double)-rxc)*1000.0+0.5;
	ea = atan2((double)rye,(double)rxe)*1000.0+0.5;

	if(sa < 0) sa += TWO_PI_mR;	/* atan2 only valid in +- pi */
	if(ea < 0) ea += TWO_PI_mR;

	rxc += hpos ; ryc += vpos;	/* make absolute */
	conic(rxc,ryc,rad,rad,sa,ea);
	hgoto(rxe+rxc) ; vgoto(rye+ryc);	/* finish @ endpoint */
}

/*
 * generate the dtf to do conic arc drawing
 */
conic(xc,yc,rx,ry,sa,ea)
{	int ohpos,ovpos;

	if(!output) return;

	ohpos = hpos ; ovpos = vpos;
	hgoto(xc) ; vgoto(yc);
	hvflush();
	put8(ARC);
	put16(rx) ; put16(ry);
	put16(ea) ; put16(sa);	/* canon draws arcs cw, troff wants ccw */
	put8(pstab[size-1]/4);	/* so they've all got the same widths */

	hgoto(ohpos) ; vgoto(ovpos);	/* go back to where we started */
}

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

	if(!output) return;

	if(dbg) fprintf(stderr,"%s ",s);
	if(strcmp(s, &chname[chtab[i]]) != 0)
		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) put1(i + 128);
	else i = 0;
}

/*
 *	Output a char
 */
put1(c)
register int c;
{
	register char *p;
	register int i,j,k;
	int ofont,code;

	if(!output) return;

	c -= 32;
	if(c <= 0) {
		if(dbg) fprintf(stderr,"0%o non-existent\n",c+32);
		return;
	}
	k = ofont = font;
	i = fitab[font][c] & BMASK;
	if(i != 0)	/* it's on this font */
		p = codetab[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];
				break;
			}
	}
	if(i == 0 || (code = p[i] & BMASK) == 0 || k > nfonts) {
		/* it wasn't there at all */
		if(dbg) fprintf(stderr,"0%o not found\n",c+32);
		return;
	}
	if(k != font) setfont(k);
	if(dbg) fprintf(stderr,isprint(c+32)?"%c %d\n":"0%3o %d\n",c+32,code);
	else {
		/* test before calling for speed */
		if (hpos != dev_hpos || vpos != dev_vpos) {
			hvflush();
		}
		if (newfont[font] || (font != dev_font) || (size != dev_size)) {
			fontflush();
		}
		if(code > MAXSETCHAR) {
			put8(EXTSETCHAR);
			put16(code);
		} else {
			put8(code);
		}
		/* echo auto-advance in dev_hpos */
		dev_hpos += (widthtab[k][i] * pstab[size-1] + (dev.unitwidth/2))/dev.unitwidth;
	}
	if(font != ofont) setfont(ofont);
}

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

/*
 *	Font position n now contains font s, internal name si
 */
t_fp(n, s, si)
int n;
char *s,*si;
{
	if(strcmp(fontname[n],s) != 0)
		newfont[n] = 1;	/* reset by fontflush when seen */
	fontname[n].name = s;
	fontname[n].number = atoi(si);
}

setfont(n)
{
	if(n < 0 || n > NFONT) error(FATAL,"illegal font %d\n",n);
	font = n;
}

/*
 *	Send a DTF header
 */
header(jname,uname,emas_uname,host,delivery)
char *jname,*uname,*emas_uname,*host,*delivery;
{
	put8(HEADER);	/* Must be 1st command in DTF */

	/* First the optional job name */
	if(jname) {
		put8(min(strlen(jname),255));
		printf("%-.255s",jname);
	} else {
		put8(0);
	}
	/* next the users names */
	put8(min(strlen(uname),255)); printf("%-.255s",uname);
	put8(min(strlen(emas_uname),255)); printf("%-.255s",emas_uname);
	/* and the host name */
	put8(min(strlen(host),255)); printf("%-.255s",host);
	/* and the delivery info (if any) */
	if(delivery) {
		put8(min(strlen(delivery),255));
		printf("%-.255s",delivery);
	} else {
		put8(0);
	}
}

/*
 *	Set the character c, updating the Canon's current font and x,y
 *	positions to reflect any buffered changes
 */
xychar(c,width)
register int c,width;
{
	if(!output) return;

	hvflush();
	fontflush();

	if(c > MAXSETCHAR) {
		put8(EXTSETCHAR);
		put16(c);
	} else {
		put8(c);
	}
	/* echo auto-advance in dev_hpos */
	dev_hpos += (width * pstab[size-1] + (dev.unitwidth/2))/dev.unitwidth;
}

/*
 *	Force the Canon's current (x,y) position to be the same as ours
 */
hvflush()
{
	if(hpos != dev_hpos) {
		if(hpos < 0) {	/* sometimes we need to be outside the page */
			put8(MOVEBYX);
			put16(hpos-dev_hpos);
		} else if((hpos > dev_hpos) &&
		  (((hpos - dev_hpos) + MAXSETCHAR) <= MAXSHORTMOVE)) {
			put8((hpos - dev_hpos) + MAXSETCHAR);
		} else {
			put8(MOVETOX);
			put16(hpos + hoffset);
		}
	}
	if(vpos != dev_vpos) {
		if(vpos < 0) {
			put8(MOVEBYY);
			put16(vpos-dev_vpos);
		} else {
			put8(MOVETOY);
			put16(vpos);
		}
	}
	dev_hpos = hpos;
	dev_vpos = vpos;
	if(dev_hpos < leftmost) leftmost = dev_hpos;	/* left margin? */
}

/*
 *	Tell the Canon about any font changes which have occurred.
 *	May also cause the loading of a new font.
 *	As a first attempt, keep the Canon's local font storage down to
 *	MAXDEVFONTS by discarding Least Recently Used.
 */
fontflush()
{
	int fnum;
	int lru,lru_ix;

	if(!newfont[font])
		if((font == dev_font) && (size == dev_size)) return;

	/* EITHER a changefont to a not-yet-referenced font
	   OR a changefont with an intervening redefinition of the font
	   has occured
	*/
	for(lru = used,fnum = 0; fnum < df_ix; fnum++) {
		/* If we've had a new font, mark all the old ones
		   in that position to be discarded as follows:
		*/
		if(newfont[dev_fonts[fnum].font]) {
			dev_fonts[fnum].font = -1;	/* invalid */
			dev_fonts[fnum].last_used = -1;	/* earliest possible */
		}

		/* calculate least recently used in passing */
		if(dev_fonts[fnum].last_used < lru) {
			lru = dev_fonts[fnum].last_used;
			lru_ix = fnum;
		}
		if((dev_fonts[fnum].font == font) && (dev_fonts[fnum].size == size)) {
			put8(CHANGEFONT);
			put8(fnum);
			dev_font = font;
			dev_size = size;
			/* note 'time' of last use in font desc */
			dev_fonts[fnum].last_used = used++;
			/* newfont[font] cannot be true, since we just found
			   a corresponding font
			*/
			return;
		}
	}
	newfont[font] = 0;	/* we must have coped by now */

	if(df_ix > MAXDEVFONTS) {
		/* font storage full, discard least recently used */
		put8(FORGETFONT);
		put8(lru_ix);
		fnum = lru_ix;
	} else {
		fnum = df_ix;
		df_ix++;
	}
	/* and load font */
	send_font(font,size,fnum);
	dev_font = dev_fonts[fnum].font = font;
	dev_size = dev_fonts[fnum].size = size;
	dev_fonts[fnum].last_used = used++;
	put8(CHANGEFONT);
	put8(fnum);
}

/*
 *	Send the font whose internal name and point size are f and s
 *	to the Canon as font ix
 */
send_font(f,s,ix)
int f,s,ix;
{
	int i;
	char tmp[60],*findmagent();

	sprintf(tmp,"%s.%d",findmagent(fontname[f].name),pstab[s-1]);
	if(dbg) fprintf(stderr,"declaring font %s\n",tmp);

	put8(DECLAREFONT);
	put8(ix);
	put8(strlen(tmp));
	for(i = 0; i < strlen(tmp); i++) {
		put8(tmp[i]);
	}
}

put16(n)
unsigned short n;
{
	swab(&n,&n,2);
	if(fwrite(&n,sizeof(short),1,stdout) == 0) error(RETRY,"output fails");
}

put32(n)
unsigned int n;
{
	n = (n << 16) | (n >> 16);
	swab(&n,&n,4);
	if((putw(n,stdout) == EOF) && ferror(stdout))
		error(RETRY,"output fails");
}

/*	Get internal name to font file name mapping from railmag file */
getmagent()
{	FILE *mf;
	register int i;
	char fn[50];

	sprintf(fn,"%s/dev%s/railmag",fontdir,devname);
	if((mf = fopen(fn,"r")) == NULL) error(FATAL,"can't get file %s",fn);

	for(nmagent = 0; fscanf(mf,"%s\t%s\n",railmag[nmagent].int_name,railmag[nmagent].dev_name) != EOF; nmagent++) ;
	if(dbg) for(i = 0; i < nmagent; i++)
		fprintf(stderr,"%s -> %s\n",railmag[i].int_name,railmag[i].dev_name);
	fclose(mf);
}

char *
findmagent(in)
char *in;
{	register int i;

	for(i = 0; i < nmagent; i++)
		if(strcmp(in,railmag[i].int_name) == 0)
			return(railmag[i].dev_name);
	/* not found */
	return(railmag[0].dev_name);	/* default first in list */
}

static char *
basename(s)
char *s;
{
	register char *cp;

	cp = rindex(s, '/');
	return ( cp==NULL ? s : cp+1 );
}
