/*
 *
 * dpcl - troff post-processor for PCL based printers.
 *
 * Translates output generated by the device independent troff into PCL. Written
 * quickly and I made no attempt at cleaning things up. Much was borrowed from
 * dpost and dimpress.
 *
 * 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 x1 y1 x2 y2	arc counter-clockwise from current point (x, y) to
 *			(x + x1 + x2, y + y1 + y2)
 * 	D~ x y x y ...	wiggly line by x,y then x,y ...
 * nb a	end of line (information only -- no action needed)
 * 	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 forever
 * 	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	<fcntl.h>
#include	<signal.h>
#include	<math.h>
#include	<ctype.h>
#include	<time.h>
#include        <stdlib.h>
#include        <string.h>

#include	"gen.h"			/* general purpose definitions */
#include	"path.h"		/* a few important pathnames */
#include	"ext.h"			/* external variable definitions */
#include	"font.h"		/* font descriptions */
#include	"dpcl.h"		/* a few definitions just used here */
#include	"motion.h"		/* positioning macros */

void put1(int c);

char	*fontdir = FONTDIR;		/* font table directories */
char	*rastdir = RASTDIR;		/* host resident raster file directories */

char	*realdev = DEVNAME;		/* use these width tables */
char	*rastdev = NULL;		/* and raster files for this device */
char	dwb_devname[20] = "";		/* job formatted for this device */
Fontmap	fontmap[] = FONTMAP;		/* font translation table - emulation */

int	copies = 1;			/* copies of each sheet */
int	printed = 0;			/* pages processed and printed */
char	*mode = "portrait";		/* portrait or landscape mode */
int	reset = TRUE;			/* no PCL initialization if TRUE */
int	fast = TRUE;			/* bitmap downloading if TRUE */

int	xoffset = XOFFSET;		/* shift everything this far right */
int	yoffset = YOFFSET;		/* and down the page (assumes 300dpi) */

int	drawdot = '.';			/* draw with this character */
int	drawsize = 2;			/* shrink size by this factor */
int	DX = 2;				/* horizontal */
int	DY = 2;				/* and vertical step size */

int	smnt;				/* special fonts start here */
int	devres;				/* device resolution */
int	unitwidth;			/* and unitwidth - from DESC file */

int	nfonts = 0;			/* number of font positions */
int	size = 1;			/* current size - internal value */
int	font = 0;			/* current font position */
int	hpos = 0;			/* where troff wants to be */
int	vpos = 0;
float	lastw = 0;			/* width of the last input character */
int	lastc = 0;			/* and its name (or index) */

int	res;				/* resolution assumed in input file */
float	xfac = 1.0;			/* for scaling horizontal */
float	yfac = 1.0;			/* and vertical postions - emulation */

int	pres = 300;			/* resolution of the printer */
int	rres = 300;			/* and of the raster files - unused */

int	lastsize = -1;			/* for tracking printer's current size */
int	lastfont = -1;			/* current font */
float	lastx = -1;			/* and current position */
int	lasty = -1;

int	gotspecial = FALSE;		/* expect fonts are now all mounted */
int	seenpage = FALSE;		/* append special fonts - emulation */

float	pointslop = SLOP;		/* horizontal error in points */
int	slop;				/* and machine units */

FILE	*tf = NULL;			/* most output goes here */
FILE	*fp_acct = NULL;		/* accounting file */

extern Font	fonts[];		/* data about fonts we've seen */
extern Font	*dwb_mount[];		/* troff mounts fonts here */
extern int	pstab[];		/* list of available sizes */
extern int	nsizes;			/* number of sizes in that list */

/*****************************************************************************/

main(agc, agv)

    int		agc;
    char	*agv[];

{

/*
 *
 * Translates output from troff into PCL. Input files must be formatted for
 * the same device. Each input file begins on a new page. Everything depends
 * on the ability to download fonts, so none of this works on simple LaserJet's
 * or their clones, like the AT&T 495.
 *
 */

    if ( (rastdir = getenv("DPCLRASTER")) == NULL )	/* by request */
	rastdir = RASTDIR;

    argc = agc;				/* global so everyone can use them */
    argv = agv;

    prog_name = argv[0];		/* for error messages */

    init_signals();			/* interrupt handling */
    options();				/* command line options */
    rasterlist();			/* grab raster file data */
    arguments();			/* translate the input files */
    done();				/* clean up */
    account();				/* job accounting data */

    exit(x_stat);

}   /* End of main */

/*****************************************************************************/

init_signals()

{

/*
 *
 * Make sure we handle interrupts.
 *
 */

    if ( signal(SIGINT, interrupt) == SIG_IGN ) {
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
    } else {
	signal(SIGHUP, interrupt);
	signal(SIGQUIT, interrupt);
    }   /* End else */

    signal(SIGTERM, interrupt);

}   /* End of init_signals */

/*****************************************************************************/

options()

{

    int		ch;
    char	*names = "c:d:fo:p:rstw:x:y:A:B:J:F:R:S:T:DI";

    extern char	*optarg;
    extern int	optind;

    double	atof();

/*
 *
 * Command line options.
 *
 */

    while ( (ch = getopt(argc, argv, names)) != EOF ) {
	switch ( ch ) {
	    case 'c':			/* number of copies */
		    copies = atoi(optarg);
		    break;

	    case 'd':			/* drawing step size */
		    if ( (DX = DY = atoi(optarg)) <= 0 )
			DX = DY = 1;
		    break;

	    case 'f':			/* fast method - download bitmaps */
		    fast = TRUE;
		    break;

	    case 'o':			/* output page list */
		    out_list(optarg);
		    break;

	    case 'p':			/* landscape or portrait mode */
		    mode = optarg;
		    break;

	    case 'r':			/* no PCL initialization */
		    reset = FALSE;
		    break;

	    case 's':			/* slow method - download entire file */
		    fast = FALSE;
		    break;

	    case 't':			/* compatibility */
		    break;

	    case 'w':			/* line width for drawing */
		    break;

	    case 'x':			/* shift horizontally */
		    xoffset = atof(optarg) * 300 + XOFFSET;
		    break;

	    case 'y':			/* shift vertically */
		    yoffset = atof(optarg) * 300 + YOFFSET;
		    break;

	    case 'A':			/* job accounting */
	    case 'J':
		    if ( (fp_acct = fopen(optarg, "a")) == NULL )
			error(FATAL, "can't open accounting file %s", optarg);
		    break;

	    case 'B':			/* target raster files */
		    rastdev = optarg;
		    break;

	    case 'F':			/* font table directory */
		    fontdir = optarg;
		    break;

	    case 'R':			/* use raster files for this device */
		    rastdir = optarg;
		    break;

	    case 'S':			/* horizontal position error */
		    pointslop = atof(optarg);
		    break;

	    case 'T':			/* target printer */
		    realdev = optarg;
		    break;

	    case 'D':			/* debug flag */
		    debug = ON;
		    tf = stdout;
		    break;

	    case 'I':			/* ignore FATAL errors */
		    ignore = ON;
		    break;

	    case '?':			/* don't know the option */
		    error(FATAL, "");
		    break;

	    default:
		    error(FATAL, "missing case for option %c", ch);
		    break;
	}   /* End switch */
    }	/* End while */

    argc -= optind;
    argv += optind;

}   /* End of options */

/*****************************************************************************/

setup()

{

/*
 *
 * Initialization that must come after the options. Software reset can probably
 * be removed.
 *
 */

    if ( reset == TRUE )
	fprintf(stdout, "\033E");		/* reset - just to be safe */
    fprintf(stdout, "\033&l%dX", copies);

    if ( *mode == 'l' )			/* landscape */
	fprintf(stdout, "\033&l1O");
    else fprintf(stdout, "\033&l0O");

}   /* End of setup */

/*****************************************************************************/

arguments()

{

    FILE	*fp;

/*
 *
 * Everything else is an input file. No arguments or '-' means stdin.
 *
 */

    if ( argc < 1 )
	conv(stdin);
    else
	while ( argc > 0 ) {
	    if ( strcmp(*argv, "-") == 0 )
		fp = stdin;
	    else if ( (fp = fopen(*argv, "r")) == NULL )
		error(FATAL, "can't open %s", *argv);
	    conv(fp);
	    if ( fp != stdin )
		fclose(fp);
	    argc--;
	    argv++;
	}   /* End while */

}   /* End of arguments */

/*****************************************************************************/

done()

{

/*
 *
 * Software reset and unlink the temp file.
 *
 */

    if ( reset == TRUE )
	fprintf(stdout, "\033E");		/* software reset */

    if ( temp_file == NULL )
	unlink(temp_file);

}   /* End of done */

/*****************************************************************************/

account()

{

/*
 *
 * Accounting record to fp_acct - provided it's not NULL.
 *
 */

    if ( fp_acct != NULL )
	fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);

}   /* End of account */

/*****************************************************************************/

conv(fp)

    register FILE	*fp;

{

    register int	c;
    int			m, n, n1, m1;
    char		str[100];
    char		buf[300];

/*
 *
 * Read troff output from file fp and translate it into PCL. The final t_page()
 * call means input files start on a new page.
 *
 */

    redirect(-1);			/* only do output after a page command */
    lineno = 1;

    while ((c = getc(fp)) != EOF) {
	switch (c) {
	    case '\n':			/* just count this line */
		    lineno++;
		    break;

	    case ' ':			/* when input is text */
	    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':			/* special character */
		    fscanf(fp, "%s", str);
		    put1(chindex(str));
		    break;

	    case 'N':		/* character at position n */
		    fscanf(fp, "%d", &m);
		    oput(m);
		    break;

	    case 'D':			/* drawing function */
		    if ( size != lastsize )
			t_sf();
		    switch ((c=getc(fp))) {
			case 'p':	/* draw a path */
			    while (fscanf(fp, "%d %d", &n, &m) == 2)
				drawline(n, m);
			    lineno++;
			    break;

			case 'l':	/* draw a line */
			    fscanf(fp, "%d %d %c", &n, &m, &n1);
			    drawline(n, m);
			    break;

			case 'c':	/* circle */
			    fscanf(fp, "%d", &n);
			    drawcirc(n);
			    break;

			case 'e':	/* ellipse */
			    fscanf(fp, "%d %d", &m, &n);
			    drawellip(m, n);
			    break;

			case 'a':	/* arc */
			    fscanf(fp, "%d %d %d %d", &n, &m, &n1, &m1);
			    drawarc(n, m, n1, m1);
			    break;

			case 'q':	/* spline without end points */
			    /*drawspline(fp, 1);*/
			    fgets(buf, sizeof(buf), fp);
			    drawwig(buf);		/* WRONG!! */
			    lineno++;
			    break;

			case '~':	/* wiggly line */
			    /*drawspline(fp, 2);*/
			    fgets(buf, sizeof(buf), fp);
			    drawwig(buf);
			    lineno++;
			    break;

			default:
			    error(FATAL, "unknown drawing function %c", c);
			    break;
		    }	/* End switch */
		    break;

	    case 's':			/* use this point size */
		    fscanf(fp, "%d", &n);	/* ignore fractional sizes */
		    setsize(t_size(n));
		    break;

	    case 'f':			/* use font mounted here */
		    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':			/* absolute vertical position */
		    fscanf(fp, "%d", &n);
		    vgoto(n);
		    break;

	    case 'v':			/* relative vertical motion */
		    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 ( (c = getc(fp)) != '\n' && c != EOF ) ;
		    hgoto(0);
		    lineno++;
		    break;

	    case '#':			/* comment */
		    while ( (c = getc(fp)) != '\n' && c != EOF ) ;
		    lineno++;
		    break;

	    case 'x':			/* device control function */
		    devcntrl(fp);
		    lineno++;
		    break;

	    default:
		    error(FATAL, "unknown input character %o %c", c, c);
		    done();
	}   /* End switch */
    }	/* End while */

    t_page(-1);				/* print the last page */

}   /* End of conv */

/*****************************************************************************/

devcntrl(fp)

    FILE	*fp;

{

    char	str[20], str1[50], buf[150];
    int		c, n;

/*
 *
 * Interpret device control commands, ignoring any we don't recognize.
 *
 */

    fscanf(fp, "%s", str);

    switch ( str[0] ) {
	case 'i':			/* initialize */
		t_init();
		break;

	case 'T':			/* device name */
		fscanf(fp, "%s", dwb_devname);
		strcpy(dwb_devname, realdev);
		break;

	case 'p':			/* pause */
	case 's':			/* stop */
	case 't':			/* trailer */
		break;

	case 'r':			/* resolution assumed when prepared */
		fscanf(fp, "%d", &res);
		break;

	case 'f':			/* load font in a position */
		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;

	/* these don't belong here... */
	case 'H':			/* char height */
		fscanf(fp, "%d", &n);
		break;

	case 'S':			/* slant */
		fscanf(fp, "%d", &n);
		break;
    }	/* End switch */

    while ( (c = getc(fp)) != '\n' && c != EOF ) ;

}   /* End of devcntrl */

/*****************************************************************************/

loadfont(m, f, dir)

    int		m;
    char	*f;
    char	*dir;

{

    char	path[150];

/*
 *
 * Load position m with font f. Font file pathname is *fontdir/dev*realdev/*f
 * or *dir/*f, if dir isn't empty. Use mapfont() to replace the missing font
 * if we're emulating another device, dir is empty, and the first mount fails.
 *
 */

    if ( dir[0] == '\0' )
	sprintf(path, "%s/dev%s/%s", fontdir, realdev, f);
    else sprintf(path, "%s/%s", dir, f);

    if ( mountfont(path, m) == -1 ) {
	if ( dir[0] == '\0' ) {
	    sprintf(path, "%s/dev%s/%s", fontdir, realdev, mapfont(f));
	    if ( mountfont(path, m) == -1 ) {
		sprintf(path, "%s/dev%s/%s", fontdir, realdev, f);
		error(FATAL, "can't load %s at %d", path, m);
	    }	/* End if */
	} else error(FATAL, "can't load %s at %d", path, m);
    }	/* End if */

    if ( smnt == 0 && dwb_mount[m]->specfont )
	smnt = m;

    if ( m == lastfont )		/* force a call to t_sf() */
	lastfont = -1;

    if ( m > nfonts ) {			/* got more positions */
	nfonts = m;
	gotspecial = FALSE;
    }	/* End if */

}   /* End of loadfont */

/*****************************************************************************/

char *mapfont(name)

    char	*name;

{

    int		i;

/*
 *
 * Map a missing font name into one that should be available. Only important
 * when were emulating another device.
 *
 */

    for ( i = 0; fontmap[i].name != NULL; i++ )
	if ( strcmp(name, fontmap[i].name) == 0 )
	    return(fontmap[i].use);

    switch ( *++name ) {
	case 'I': return("I");
	case 'B': return("B");
	case 'X': return("BI");
	default:  return("R");
    }	/* End switch */

}   /* End of mapfont */

/*****************************************************************************/

loadspecial()

{

/*
 *
 * Fix - later.
 *
 */

    gotspecial = TRUE;

}   /* End of loadspecial */

/*****************************************************************************/

t_init()

{

    char	path[150];
    static int	initialized = FALSE;

/*
 *
 * Finish initialization - just read an "x init" command. Assumes we already
 * know the input file resolution.
 *
 */

    if ( initialized == FALSE ) {
	sprintf(path, "%s/dev%s/DESC", fontdir, realdev);
	if ( getdesc(path) == -1 )
	    error(FATAL, "can't open %s", path);
	nfonts = 0;
	gotspecial = FALSE;
	slop = pointslop * res / POINTS + .5;
	xfac = (float) pres / res;
	yfac = (float) pres / res;
	setup();
	initialized = TRUE;
    }	/* End if */

    hpos = vpos = 0;			/* upper left corner */
    setsize(t_size(10));		/* start somewhere */

}   /* End of t_init */

/*****************************************************************************/

t_page(pg)

    int		pg;			/* troff's current page number */

{

    static int	lastpg = 0;		/* last one we started - for PAGE */

/*
 *
 * Finish the last page and get ready for the next one.
 *
 */

    if ( tf == stdout )			/* count the last page */
	printed++;

    fprintf(tf, "\014");
    redirect(pg);

    hpos = vpos = 0;
    lastx = lasty = -(slop + 1);
    t_sf();

    seenpage = TRUE;
    lastpg = pg;

}   /* End of t_page */

/*****************************************************************************/

t_size(n)

    int		n;			/* convert this point size */

{

    int		i;			/* loop index */

/*
 *
 * Converts a point size into an internal size that can be used as an index into
 * pstab[]. The internal size is one plus the index of the least upper bound of
 * n in pstab[], or nsizes if n is larger than all the listed sizes.
 *
 */

    if ( n <= pstab[0] )
	return(1);
    else if (n >= pstab[nsizes-1])
	return(nsizes);

    for ( i = 0; n > pstab[i]; i++ ) ;

    return(i+1);

}   /* End of t_size */

/*****************************************************************************/

t_font(s)

    char	*s;

{

    int		n;

/*
 *
 * Converts the string *s into an integer and checks to make sure it's a legal
 * font position. Also arranges to mount all the special fonts after the last
 * legitimate font (by calling loadspecial()), provided it hasn't already been
 * done.
 *
 */

    n = atoi(s);

    if ( seenpage == TRUE ) {
	if ( n < 0 || n > nfonts )
	    error(FATAL, "illegal font position %d", n);

	if ( gotspecial == FALSE )
	    loadspecial();
    }	/* End if */

    return(n);

}   /* End of t_font */

/*****************************************************************************/

t_hmove()

{

    int		x;

/*
 *
 * Force the printer to move horizontally. Move relative to the origin if
 * current x position is negative.
 *
 */

    if ( (x = hpos * xfac + xoffset) < 0 )
	fprintf(tf, "\033*p0X\033*p%dX", x);
    else fprintf(tf, "\033*p%dX", x);
    lastx = hpos;

}   /* End of t_hmove */

/*****************************************************************************/

t_vmove()

{

    int		y;

/*
 *
 * Force the printer to move vertically. Move relative to the origin if
 * current y position is negative.
 *
 */

    if ( (y = vpos * yfac + yoffset) < 0 )
	fprintf(tf, "\033*p0Y\033*p%dY", y);
    else fprintf(tf, "\033*p%dY", y);
    lasty = vpos;

}   /* End of t_vmove */

/*****************************************************************************/

setsize(n)

    int		n;

{

/*
 *
 * Use internal size n, where pstab[n-1] is the best available approximation
 * to the size troff asked for.
 *
 */

    size = n;

}   /* End of setsize */

/*****************************************************************************/

setfont(m)

    int		m;

{

/*
 *
 * Use the font mounted a position m. Bounds checks are probably unnecessary.
 *
 */

    if ( m < 0 || m > MAXFONTS )
	error(FATAL, "illegal font %d", m);

    font = m;

}   /* End of setfont */

/*****************************************************************************/

t_sf()

{

/*
 *
 * Force a new font and size. Only done immediately before printing a character.
 *
 */

    if ( tf == stdout && mounted(font) ) {
	getrastdata(dwb_mount[font]->name, pstab[size-1]);
	setfamily();
	lastsize = size;
	lastfont = font;
    }	/* End if */

}   /* End of t_sf */

/*****************************************************************************/

void
put1(int c)
{

    register int	i;
    register int	j;
    register int	k;
    int			code;
    int			ofont;

/*
 *
 * Print character c. ASCII character if c < 128, otherwise it's special. Look
 * for c in the current font, then in all others starting at the first special
 * font.
 *
 */

    lastc = c;
    if ( (c -= 32) <= 0 )
	return;

    k = ofont = font;

    if ( (i = onfont(lastc, k)) == -1 && smnt > 0 )
	for ( k = smnt, j = 0; j < nfonts; j++, k = k % nfonts + 1 ) {
	    if ( (i = onfont(lastc, k)) != -1 ) {
		setfont(k);
		break;
	    }	/* End if */
	}   /* End for */

    if ( i != -1 && (code = dwb_mount[k]->wp[i].code) != 0 ) {
	lastw = ((int)dwb_mount[k]->wp[i].wid * pstab[size-1] + unitwidth/2) / unitwidth;
	oput(code);
    }	/* End if */

    if ( font != ofont )
	setfont(ofont);

}   /* End of put1 */

/*****************************************************************************/

oput(c)

    int		c;

{

/*
 *
 * Arranges to print the character whose code is c in the current font. Actual
 * positioning and font changes are usually handled here.
 *
 */

    if ( font != lastfont || size != lastsize )
	t_sf();

    if ( vpos != lasty )
	t_vmove();

    if ( ABS(hpos - lastx) > slop )
	t_hmove();

    if ( fast == TRUE )
	download(c);
    putc(c, tf);

    lastx += lastw;

}   /* End of oput */

/*****************************************************************************/

redirect(pg)

    int		pg;

{

    static FILE	*fp_null = NULL;

/*
 *
 * If we're not supposed to print page pg, tf will be directed to /dev/null,
 * otherwise output goes to stdout.
 *
 */

    if ( pg >= 0 && in_olist(pg) == ON )
	tf = stdout;
    else if ( (tf = fp_null) == NULL )
	tf = fp_null = fopen("/dev/null", "w");

}   /* End of redirect */

/*****************************************************************************/

