/*
 *
 * dpost - troff post-processor for PostScript printers.
 *
 * A program that translates output generated by the device independent troff
 * into PostScript. Much was borrowed from dimpress and dps (formally dlzw),
 * and even though the code has changed, credit has to be given to Richard
 * Flood for his early work on the PostScript driver.
 *
 * The big change is in the font table routines. The old binary format and
 * makedev are gone. dpost and troff now read ASCII tables, and both skip
 * unrecognized entries in the ASCII tables. That means problems, like where
 * to put the real name of the PostScript font, have disappeared. The added
 * flexibility means some overhead translating the ASCII tables, but the
 * overhead isn't too bad.
 *
 * dpost can also now calculate a reasonably tight BoundingBox, which helps
 * picture inclusion. The calculations, by default, are disabled. Couldn't
 * justify the overhead for a comment, particularly one that's only needed
 * occasionally. Use the -B option to get the comment.
 *
 * Output produced by dpost is still nonconforming. Definitions made in pages
 * and exported to the job's global environment are the primary problem. It's
 * an efficient approach, but means pages are not independent. Violations are
 * bracketed by %%BeginGlobal and %%EndGlobal comments and can be pulled into
 * the prologue by utility programs (like postreverse) that recognize the new
 * comments.
 *
 * The program handles files formatted for any device, although the best and
 * most efficient output is generated when the font and description files
 * match PostScript's resident fonts. Emulation is relatively expensive, and
 * can produce output files that are more than twice the size of the input
 * files.
 *
 * Several different methods can be used to encode lines of text. What's done
 * depends on the value assigned to encoding. Print time should decrease as
 * encoding increases (up to MAXENCODING). Setting encoding to 3 (or higher)
 * is not normally recommended. It's fast and produces very compact output,
 * but rounding errors in troff's width tables can accumulate and lead to a
 * ragged right margin. encoding can be changed on the command line using the
 * -e option.
 *
 * PostScript fonts don't support all of troff's characters. Some are built
 * by special PostScript procedures in directory *fontdir/devpost/charlib.
 * The charlib approach is not meant to replace user defined fonts. It was
 * a quick implementation designed to handle characters that aren't used
 * often - charlib should not be overused! The charlib lookup is triggered
 * when a character in a font table is assigned a code less than 32.
 *
 * Most defaults are set in the prologue, but can be changed by options. The
 * -P option passes arbitrary PostScript into the setup section of the output
 * file. It can be used to set (or change) values that can't be accessed by
 * other options. For example,
 *
 *	dpost -P'/useclippath false def' file > file.ps
 *
 * defines useclippath to be false. Everything passed through using the -P
 * (-C to copy a file) options become part of the job's global environment.
 * Definitions override defaults in the prologue.
 *
 * dpost expects to find the following procedures in the prologue:
 *
 *	setup
 *
 *	  mark ... setup -
 *
 *	    Initialization procedure mainly responsible for setting up an
 *	    appropriate coordinate system.
 *
 *	pagesetup
 *
 *	  page pagesetup -
 *
 *	    Called at the start of each page, immediately after the page
 *	    level save. Argument is the current page number.
 *
 *	setdecoding
 *
 *	  num setdecoding -
 *
 *	    Select the decoding procedure used to print text strings encoded
 *	    by dpost. num is whatever has been assigned to encoding.
 *
 *	f
 *
 *	  size font f -
 *
 *	    Set the font and size used for character imaging. The font name
 *	    argument is (normally) the name troff used. Mapping to the real
 *	    PostScript font name is made using the fontname field in the
 *	    ASCII width tables.
 *
 *	m
 *
 *	  x y m -
 *
 *	    Move to point (x, y). Not used for positioning words in text
 *	    strings.
 *
 *	t
 *
 *	  mark text t mark
 *
 *	    Everything on the stack (up to the mark) is treated as a line
 *	    of text to be decoded and printed. What's on the stack depends
 *	    on encoding.
 *
 *	w
 *
 *	  string x y w -
 *
 *	    Print a single word starting at position (x, y). Only used in
 *	    the more complicated encoding schemes, like the ones based on
 *	    widthshow.
 *
 *	done
 *
 *	    Make sure the last page prints. Always called, but only needed
 *	    when printing more than one page on each sheet of paper.
 *
 * 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 <string.h>
#include	<stdio.h>
#include	<fcntl.h>
#include	<signal.h>
#include	<math.h>
#include	<ctype.h>
#include	<time.h>

#include	"comments.h"		/* structuring comments */
#include	"gen.h"			/* general purpose definitions */
#include	"path.h"		/* prologue and a few other files */
#include	"ext.h"			/* external variable declarations */
#include	"font.h"		/* font table definitions */
#include	"dpost.h"		/* a few definitions just used here */
#include	"motion.h"		/* positioning macros */

void put1(int c);

char	*prologue = DPOST;		/* the PostScript prologue */
char	*colorfile = COLOR;		/* color support */
char	*drawfile = DRAW;		/* drawing routines */
char	*formfile = FORMFILE;		/* multiple pages on each sheet */
char	*baselinefile = BASELINE;	/* for text along curved baseline */

char	*fontdir = FONTDIR;		/* font table directories */
char	*hostfontdir = NULL;		/* host resident font directory */

char	*realdev = DEVNAME;		/* use these width tables */
char	dwb_devname[20] = "";		/* job formatted for this device */
Fontmap	fontmap[] = FONTMAP;		/* font translation table - emulation */
char	*useencoding = NULL;		/* text font encoding - from -E option */

int	copies = 1;			/* copies of each sheet */
int	printed = 0;			/* pages processed and printed */
int	formsperpage = 1;		/* pages on each sheet of paper */
int	picflag = ON;			/* enable/disable picture inclusion */

int	encoding = DFLTENCODING;	/* how text is translated to PostScript */
int	realencoding = DFLTENCODING;	/* where we started */
int	maxencoding = MAXENCODING;	/* max that users can select */

int	landscape = FALSE;		/* for BoundingBox calculations only */
double	magnification = 1.0;
double	xoffset = 0.0;
double	yoffset = 0.0;

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

char	downloaded[MAXCH+32+ALPHABET];	/* status of charlib characters */

int	nfonts = 0;			/* number of font positions */
int	size = 10;			/* current point size */
int	font = 0;			/* and 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;			/* its name (or index) - for charlib() */

int	fontheight = 0;			/* points from x H ... */
int	fontslant = 0;			/* angle from x S ... */

int	res;				/* resolution assumed in input file */
float	widthfac = 1.0;			/* for emulation = res/devres */

int	lastsize = -1;			/* for tracking printer's current size */
int	lastfont = -1;			/* current font */
float	lastx = -1;			/* and current position */
int	lasty = -1;
int	lastend;			/* where last character on this line was */

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

float	pointslop = SLOP;		/* horizontal error in points */
int	slop;				/* and machine units */
int	rvslop;				/* to extend box in reverse video mode */

int	textcount = 0;			/* strings accumulated so far */
int	stringstart = 0;		/* where the next one starts */
int	spacecount = 0;			/* spaces in current string */

Line	line[MAXSTACK+3];		/* data about words accumulated so far */
char	strings[STRINGSPACE];		/* strings temporarily saved here */
char	*strptr;			/* next free slot in strings[] */

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

char	*optnames = "a:c:e:m:n:o:p:tw:x:y:A:BC:E:J:F:H:L:OP:R:S:T:DI";

extern int	gotcolor;		/* read *colorfile when TRUE */
extern Font	fonts[];		/* data about every font we see */
extern Font	*dwb_mount[];		/* troff mounts fonts here */

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

main(agc, agv)

    int		agc;
    char	*agv[];

{

/*
 *
 * Translates output from troff into PostScript. Input files must be formatted
 * for the same device. Each input file begins on a new page.
 *
 */

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

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

    init_signals();			/* interrupt handling */
    header();				/* structuring comments */
    options();				/* command line options */
    arguments();			/* translate the input files */
    done();				/* add trailing comments etc. */
    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 */

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

header()

{

    int		ch;
    int		old_optind = optind;

/*
 *
 * Scan the option list for things needed now (e.g. prologue file), but could
 * be changed from defaults. An attempt to follow to Adobe's 2.0 structuring
 * conventions.
 *
 */

    while ( (ch = getopt(argc, argv, optnames)) != EOF )
	if ( ch == 'L' )
	    setpaths(optarg);
	else if ( ch == 'B' )
	    dobbox = TRUE;
	else if ( ch == '?' )
	    ERROR(FATAL, 0, (stderr, NULL));

    optind = old_optind;		/* restored for options() */

    fprintf(stdout, "%s", NONCONFORMING);
    fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
    fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
    fprintf(stdout, "%s %s\n", PAGES, ATEND);
    if ( dobbox == TRUE )
	fprintf(stdout, "%s %s\n", BOUNDINGBOX, ATEND);
    fprintf(stdout, "%s", ENDCOMMENTS);

    if ( cat(prologue) == FALSE )
	ERROR(FATAL, 1, (stderr, "can't read %s", prologue));

    if ( DOROUND )
	cat(ROUNDPAGE);

    fprintf(stdout, "%s", ENDPROLOG);
    fprintf(stdout, "%s", BEGINSETUP);
    fprintf(stdout, "mark\n");

}   /* End of header */

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

options()

{

    int		ch;

    extern char	*optarg;
    extern int	optind;

/*
 *
 * Command line options - there are too many!
 *
 */

    while ( (ch = getopt(argc, argv, optnames)) != EOF ) {
	switch ( ch ) {
	    case 'a':			/* aspect ratio */
		    fprintf(stdout, "/aspectratio %s def\n", optarg);
		    break;

	    case 'c':			/* number of copies */
		    copies = atoi(optarg);
		    fprintf(stdout, "/#copies %s store\n", optarg);
		    break;

	    case 'e':			/* select the encoding scheme */
		    if ( (encoding = atoi(optarg)) < 0 || encoding > MAXENCODING )
			encoding = DFLTENCODING;
		    realencoding = encoding;
		    break;

	    case 'm':			/* magnification */
		    magnification = atof(optarg);
		    fprintf(stdout, "/magnification %s def\n", optarg);
		    break;

	    case 'n':			/* forms per page */
		    formsperpage = atoi(optarg);
		    fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
		    fprintf(stdout, "/formsperpage %s def\n", optarg);
		    break;

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

	    case 'p':			/* landscape or portrait mode */
		    landscape = (*optarg == 'l') ? TRUE : FALSE;
		    if ( landscape == TRUE )
			fprintf(stdout, "/landscape true def\n");
		    else fprintf(stdout, "/landscape false def\n");
		    break;

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

	    case 'w':			/* line width - for drawing */
		    fprintf(stdout, "/linewidth %s def\n", optarg);
		    break;

	    case 'x':			/* shift horizontally */
		    xoffset = atof(optarg);
		    fprintf(stdout, "/xoffset %s def\n", optarg);
		    break;

	    case 'y':			/* shift vertically */
		    yoffset = atof(optarg);
		    fprintf(stdout, "/yoffset %s def\n", optarg);
		    break;

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

	    case 'B':			/* enable BoundingBox calculations */
		    dobbox = TRUE;
		    fprintf(stdout, "/rotation 1 def\n");
		    fprintf(stdout, "/gotpagebbox true def\n");
		    break;

	    case 'C':			/* copy file to output */
		    if ( cat(optarg) == FALSE )
			ERROR(FATAL, 1, (stderr, "can't read %s", optarg));
		    break;

	    case 'E':			/* text font encoding - override DESC */
		    useencoding = optarg;
		    break;

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

	    case 'H':			/* host resident font directory */
		    hostfontdir = optarg;
		    break;

	    case 'L':			/* prologue file */
		    setpaths(optarg);	/* already been done in header() */
		    break;

	    case 'O':			/* disable picture inclusion */
		    picflag = OFF;
		    break;

	    case 'P':			/* copy string to output */
		    fprintf(stdout, "%s\n", optarg);
		    break;

	    case 'R':			/* global or page level request */
		    saverequest(optarg);
		    break;

	    case 'S':			/* horizontal position error */
		    if ( (pointslop = atof(optarg)) < 0 )
			pointslop = 0;
		    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, 0, (stderr, NULL));
		    break;

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

    argc -= optind;
    argv += optind;

}   /* End of options */

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

setpaths(name)

    char	*name;

{

    char	*path;

/*
 *
 * Extends the -L option to permit modification of more than just the prologue
 * file pathname. Syntax is -Lpath or -Lname:path. For debugging and development
 * only!
 *
 */

    for ( path = name; *path; path++ )
	if ( *path == ':' || *path == ' ' ) {
	    while ( *path == ':' || *path == ' ' ) path++;
	    break;
	}   /* End if */

    if ( *path == '\0' )		/* didn't find "name:" prefix */
	path = name;

    if ( path == name || strncmp(name, "prologue", strlen("prologue")) == 0 )
	prologue = path;
    else if ( strncmp(name, "draw", strlen("draw")) == 0 )
	drawfile = path;
    else if ( strncmp(name, "color", strlen("color")) == 0 )
	colorfile = path;
    else if ( strncmp(name, "form", strlen("form")) == 0 )
	formfile = path;
    else if ( strncmp(name, "baseline", strlen("baseline")) == 0 )
	baselinefile = path;

}   /* End of setpaths */

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

setup()

{

    double	t;

/*
 *
 * Job and BoundingBox initialization. Called once from t_init() - must know
 * the resolution before generating the PostScript call to setup. dpost only
 * includes an encoding file if it's set in the DESC file or requested with
 * the -E option.
 *
 */

    writerequest(0, stdout);		/* global requests e.g. manual feed */
    fprintf(stdout, "/resolution %d def\n", res);
    if ( useencoding != NULL || fontencoding != NULL )
	setencoding((useencoding != NULL) ? useencoding : fontencoding);
    fprintf(stdout, "setup\n");
    fprintf(stdout, "%d setdecoding\n", realencoding);

    if ( formsperpage > 1 ) {		/* multiple pages */
	if ( cat(formfile) == FALSE )
	    ERROR(FATAL, 1, (stderr, "can't read %s", formfile));
	fprintf(stdout, "%d setupforms\n", formsperpage);
    }	/* End if */

    fprintf(stdout, "%s", ENDSETUP);

    if ( dobbox == TRUE ) {		/* ctm[] - must agree with prologue */
	translate(pagewidth/2.0, pageheight/2.0);
	if ( landscape == TRUE ) {
	    rotate(90.0);
	    t = pagewidth;
	    pagewidth = pageheight;
	    pageheight = t;
	}   /* End if */
	translate(-pagewidth/2.0, pageheight/2.0);
	translate(72.0 * xoffset, -72.0 * yoffset);
	scale(magnification, magnification);
	scale(72.0/devres, 72.0/devres);
    }	/* End if */

}   /* 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, 1, (stderr, "can't open %s", *argv));
	    conv(fp);
	    if ( fp != stdin )
		fclose(fp);
	    argc--;
	    argv++;
	}   /* End while */

}   /* End of arguments */

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

done()

{

    int		i;
    int		n;

/*
 *
 * Force out the last page and add trailing comments.
 *
 */

    fprintf(stdout, "%s", TRAILER);
    fprintf(stdout, "done\n");
    fprintf(stdout, "%s %d\n", PAGES, printed);

    for ( i = 0, n = 0; i < MAXFONTS+1; i++ )
	if ( (fonts[i].flags & USED) && fonts[i].fontname != NULL ) {
	    if ( n++ == 0 )
		fprintf(stdout, "%s", DOCUMENTFONTS);
	    else if ( (n - 1) % 8 == 0 )
		fprintf(stdout, "\n%s", CONTINUECOMMENT);
	    fprintf(stdout, " %s", fonts[i].fontname);
	}   /* End if */
    if ( n > 0 )
	putc('\n', stdout);

    if ( dobbox == TRUE )
	writebbox(stdout, BOUNDINGBOX, 10);

}   /* 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[50];

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

    redirect(-1);			/* do output only 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);
		    flushtext();
		    oput(m);
		    break;

	    case 'D':			/* drawing functions */
		    flushtext();
		    getdraw();
		    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':	/* counter-clockwise arc */
			case 'A':	/* clockwise arc */
			    fscanf(fp, "%d %d %d %d", &n, &m, &n1, &m1);
			    drawarc(n, m, n1, m1, c);
			    break;

			case 'q':	/* spline without end points */
			    drawspline(fp, 1);
			    lineno++;
			    break;

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

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

	    case 's':			/* use this point size */
		    fscanf(fp, "%d", &size);	/* ignore fractional sizes */
		    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, 1, (stderr, "unknown input character %o %c", c, c));
		    done();
	}   /* End switch */
    }	/* End while */

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

}   /* End of conv */

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

devcntrl(fp)

    FILE	*fp;

{

    char	str[50], buf[256], str1[100];
    int		c, n;

/*
 *
 * Interpret device control commands, ignoring any we don't recognize. The
 * "x X ..." commands are a device dependent collection generated by troff's
 * \X'...' request.
 *
 */

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

    switch ( str[0] ) {
	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;

	case 'i':			/* initialize */
		t_init();
		break;

	case 'p':			/* pause */
		break;

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

	case 's':			/* stop */
	case 't':			/* trailer */
		flushtext();
		break;

	case 'H':			/* char height */
		fscanf(fp, "%d", &n);
		t_charht(n);
		break;

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

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

	case 'X':			/* copy through - from troff */
		fscanf(fp, " %[^: \n]:", str);
		fgets(buf, sizeof(buf), fp);
		ungetc('\n', fp);
		if ( strcmp(str, "PI") == 0 || strcmp(str, "PictureInclusion") == 0 )
		    picture(buf);
		else if ( strcmp(str, "InlinePicture") == 0 )
		    inlinepic(fp, buf);
		else if ( strcmp(str, "BeginPath") == 0 )
		    beginpath(buf, FALSE);
		else if ( strcmp(str, "DrawPath") == 0 )
		    drawpath(buf, FALSE);
		else if ( strcmp(str, "BeginObject") == 0 )
		    beginpath(buf, TRUE);
		else if ( strcmp(str, "EndObject") == 0 )
		    drawpath(buf, TRUE);
		else if ( strcmp(str, "NewBaseline") == 0 )
		    newbaseline(buf);
		else if ( strcmp(str, "DrawText") == 0 )
		    drawtext(buf);
		else if ( strcmp(str, "SetText") == 0 )
		    settext(buf);
		else if ( strcmp(str, "SetColor") == 0 ) {
		    newcolor(buf);
		    setcolor();
		} else if ( strcmp(str, "INFO") == 0 ) {
		    flushtext();
		    fprintf(tf, "%%INFO%s", buf);
		} else if ( strcmp(str, "PS") == 0 || strcmp(str, "PostScript") == 0 ) {
		    flushtext();
		    fprintf(tf, "%s", buf);
		} else if ( strcmp(str, "ExportPS") == 0 ) {	/* dangerous!! */
		    if ( tf == stdout ) {
			restore();
			fprintf(tf, "%s", buf);
			save();
		    }	/* End if */
		}   /* End else */
		break;
    }	/* End switch */

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

}   /* End of devcntrl */

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

loadfont(m, f, name)

    int		m;
    char	*f;
    char	*name;

{

    char	path[150];

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

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

    if ( mountfont(path, m) == -1 ) {
	if ( name[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, 1, (stderr, "can't load %s at %d", path, m));
	    }	/* End if */
	} else ERROR(FATAL, 1, (stderr, "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 used when
 * we're emulating another device and the first mount fails. Consider deleting
 * this routine.
 *
 */

    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.
 *
 */

    flushtext();			/* moved - for cat'ed troff files */

    if ( initialized == FALSE ) {
	if ( strcmp(dwb_devname, realdev) ) {
	    sprintf(path, "%s/dev%s/DESC", fontdir, dwb_devname);
	    if ( checkdesc(path) )
		realdev = dwb_devname;
	}   /* End if */

	sprintf(path, "%s/dev%s/DESC", fontdir, realdev);
	if ( getdesc(path) == -1 )
	    ERROR(FATAL, 1, (stderr, "can't open %s", path));
	nfonts = 0;
	gotspecial = FALSE;
	widthfac = (float) res /devres;
	slop = pointslop * res / POINTS + .5;
	rvslop = res * .025;
	setup();
	initialized = TRUE;
    }	/* End if */

    hpos = vpos = 0;
    size = 10;
    reset();

}   /* End of t_init */

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

t_page(pg)

    int		pg;

{

    static int	lastpg = 0;

/*
 *
 * Finish the previous page and get ready for the next one. End page output
 * goes to /dev/null at the start of each input file. Start page output goes
 * to /dev/null at the end of each input file.
 *
 * Consider doing showpage after page level restore (as Adobe recommends). If
 * the order is changed use restore() and save(). forms.ps will likely also
 * need fixing.
 *
 */

    if ( tf == stdout )
	printed++;

    flushtext();			/* just in case */

    fprintf(tf, "cleartomark\n");
    fprintf(tf, "showpage\n");
    fprintf(tf, "saveobj restore\n");
    if ( dobbox == TRUE )
	writebbox(tf, PAGEBOUNDINGBOX, 10);
    fprintf(tf, "%s %d %d\n", ENDPAGE, lastpg, printed);

    redirect(pg);

    fprintf(tf, "%s %d %d\n", PAGE, pg, printed+1);
    if ( dobbox == TRUE )
	fprintf(tf, "%s %s\n", PAGEBOUNDINGBOX, ATEND);
    fprintf(tf, "/saveobj save def\n");
    fprintf(tf, "mark\n");
    writerequest(printed+1, tf);
    fprintf(tf, "%d pagesetup\n", printed+1);

    if ( encoding != realencoding )
	fprintf(tf, "%d setdecoding\n", encoding);

    if ( gotcolor == TRUE )
	setcolor();

    lastpg = pg;			/* for the next ENDPAGE comment */
    hpos = vpos = 0;			/* get ready for the next page */
    reset();				/* force position and font stuff - later */

    seenpage = TRUE;

}   /* End of t_page */

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

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, 1, (stderr, "illegal font position %d", n));

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

    return(n);

}   /* End of t_font */

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

setfont(m)

    int		m;

{

/*
 *
 * Use the font mounted at position m. Bounds checks are probably unnecessary.
 * Changing the font and size used by the printer is handled in t_sf().
 *
 */

    if ( m < 0 || m > MAXFONTS )
	ERROR(FATAL, 1, (stderr, "illegal font %d", m));
    font = m;

}   /* End of setfont */

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

t_sf()

{

    Font	*fpos;
    char	temp[150];

/*
 *
 * Force a new font or size. Generates name definitions for fonts that haven't
 * been named, grabs host resident font files and keeps track of the fonts used
 * by this job. When necessary also adjusts the font's height and slant. Should
 * only be called immediately before printing a character.
 *
 */

    if ( tf == stdout && mounted(font) ) {
	flushtext();

	fpos = dwb_mount[font];
	if ( (fpos->flags & USED) == 0 ) {
	    if ( (fpos->flags & NAMED) == 0 && fpos->fontname != NULL ) {
		sprintf(temp, "/%s /%s def\n", fpos->name, fpos->fontname);
		exportstring(temp);
		fpos->flags |= NAMED;		/* unnecessary */
	    }	/* End if */

	    if ( hostfontdir != NULL ) {
		sprintf(temp, "%s/%s", hostfontdir, fpos->name);
		exportfile(temp);
	    }	/* End if */
	}   /* End if */

	fprintf(tf, "%d %s f\n", size, fpos->name);
	if ( fontheight != 0 || fontslant != 0 )
	    fprintf(tf, "%d %d changefont\n", fontslant, (fontheight != 0) ? fontheight : size);

	lastfont = font;
	lastsize = size;
	fpos->flags |= USED;
    }	/* End if */

}   /* End of t_sf */

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

t_charht(n)

    int		n;

{

/*
 *
 * Set character height to n points. Disabled if n is 0 or the current size.
 *
 */

    fontheight = (n == size) ? 0 : n;
    lastfont = -1;

}   /* End of t_charht */

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

t_slant(n)

    int		n;

{

/*
 *
 * Set slant to n degrees. Disable slanting if n is 0.
 *
 */

    fontslant = n;
    lastfont = -1;

}   /* End of t_slant */

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

xymove(x, y)

    int		x, y;

{

/*
 *
 * Make the the printer and post-processor agree about the current position.
 *
 */

    flushtext();

    hgoto(x);
    vgoto(y);

    fprintf(tf, "%d %d m\n", hpos, vpos);

    lastx = hpos;
    lasty = vpos;

}   /* End of xymove */

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

void
put1(int c)

{

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

/*
 *
 * Print character c. ASCII if c < ALPHABET, otherwise it's special. Look for
 * c in the current font, then in others starting at the first special font.
 * Save c in lastc so it's available when oput() runs. Restore original font
 * before leaving.
 *
 */

    lastc = c;				/* charlib() needs name not code */
    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 = widthfac * (((int)dwb_mount[k]->wp[i].wid * size + unitwidth/2) / unitwidth);
	oput(code);
    }	/* End if */

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

}   /* End of put1 */

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

oput(c)

    int		c;

{

    double	llx, lly, urx, ury;	/* boundingbox corners */

/*
 *
 * Arranges to print the character whose code is c in the current font. All the
 * actual positioning is done here, in charlib(), or in the drawing routines.
 *
 */

    if ( textcount > MAXSTACK )		/* don't put too much on the stack? */
	flushtext();

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

    if ( vpos != lasty )
	endline();

    starttext();

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

    if ( isascii(c) && isprint(c) )
	switch ( c ) {
	    case '(':
	    case ')':
	    case '\\':
		    addchar('\\');

	    default:
		    addchar(c);
	}   /* End switch */
    else if ( c > 040 )
	addoctal(c);
    else charlib(c);

    if ( dobbox == TRUE ) {
	llx = lastx;
	lly = -(vpos + 0.5 * (devres * size / 72.0));
	urx = lastx + lastw;
	ury = -(vpos - (devres * size / 72.0));
	cover(llx, lly);
	cover(urx, ury);
    }	/* End if */

    lastx += lastw;

}   /* End of oput */

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

starttext()

{

/*
 * Called whenever we want to be sure we're ready to start collecting characters
 * for the next call to PostScript procedure t (ie. the one that prints them). If
 * textcount is positive we've already started, so there's nothing to do. The more
 * complicated encoding schemes save text strings in the strings[] array and need
 * detailed information about the strings when they're written to the output file
 * in flushtext().
 *
 */

    if ( textcount < 1 ) {
	switch ( encoding ) {
	    case 0:
	    case 1:
		putc('(', tf);
		break;

	    case 2:
	    case 3:
		strptr = strings;
		spacecount = 0;
		line[1].str = strptr;
		line[1].dx = 0;
		line[1].spaces = 0;
		line[1].start = hpos;
		line[1].width = 0;
		break;

	    case MAXENCODING+1:			/* reverse video */
		if ( lastend == -1 )
		    lastend = hpos;
		putc('(', tf);
		break;

	    case MAXENCODING+2:			/* follow a funny baseline */
		putc('(', tf);
		break;
	}   /* End switch */

	textcount = 1;
	lastx = stringstart = hpos;
    }	/* End if */

}   /* End of starttext */

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

flushtext()

{

    int		i;

/*
 *
 * Generates a call to the PostScript procedure that processes all the text we've
 * accumulated - provided textcount is positive.
 *
 */

    if ( textcount > 0 ) {
	switch ( encoding ) {
	    case 0:
		fprintf(tf, ")%d t\n", stringstart);
		break;

	    case 1:
		fprintf(tf, ")%d %d t\n", stringstart, lasty);
		break;

	    case 2:
		*strptr = '\0';
		line[textcount].width = lastx - line[textcount].start;
		if ( spacecount != 0 || textcount != 1 ) {
		    for ( i = textcount; i > 0; i-- )
			fprintf(tf, "(%s)%d %d", line[i].str, line[i].spaces, line[i].width);
		    fprintf(tf, " %d %d %d t\n", textcount, stringstart, lasty);
		} else fprintf(tf, "(%s)%d %d w\n", line[1].str, stringstart, lasty);
		break;

	    case 3:
		*strptr = '\0';
		if ( spacecount != 0 || textcount != 1 ) {
		    for ( i = textcount; i > 0; i-- )
			fprintf(tf, "(%s)%d", line[i].str, line[i].dx);
		    fprintf(tf, " %d %d %d t\n", textcount, stringstart, lasty);
		} else fprintf(tf, "(%s)%d %d w\n", line[1].str, stringstart, lasty);
		break;

	    case MAXENCODING+1:
		fprintf(tf, ")%d ", stringstart);
		fprintf(tf, "%d %d drawrvbox ", lastend - rvslop, (int)(lastx + .5) + rvslop);
		fprintf(tf, "t\n", stringstart);
		lastend = (lastx + .5) + 2 * rvslop;
		break;

	    case MAXENCODING+2:
		fprintf(tf, ")%d %d t\n", stringstart, lasty);
		break;
	}   /* End switch */
    }	/* End if */

    textcount = 0;

}   /* End of flushtext */

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

endstring()

{

    int		dx;

/*
 *
 * Horizontal positions are out of sync. End the last open string, adjust the
 * printer's position, and start a new string. Assumes we've already started
 * accumulating text.
 *
 */

    switch ( encoding ) {
	case 0:
	case 1:
	    fprintf(tf, ")%d(", stringstart);
	    textcount++;
	    lastx = stringstart = hpos;
	    break;

	case 2:
	case 3:
	    dx = hpos - lastx;
	    if ( spacecount++ == 0 )
		line[textcount].dx = dx;
	    if ( line[textcount].dx != dx ) {
		*strptr++ = '\0';
		line[textcount].width = lastx - line[textcount].start;
		line[++textcount].str = strptr;
		*strptr++ = ' ';
		line[textcount].dx = dx;
		line[textcount].start = lastx;
		line[textcount].width = 0;
		line[textcount].spaces = 1;
	    } else {
		*strptr++ = ' ';
		line[textcount].spaces++;
	    }	/* End else */
	    lastx += dx;
	    break;

	case MAXENCODING+1:
	    fprintf(tf, ")%d(", stringstart);
	    textcount++;
	    lastx = stringstart = hpos;
	    break;

	case MAXENCODING+2:
	    flushtext();
	    starttext();
	    break;
    }	/* End switch */

}   /* End of endstring */

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

endline()

{

/*
 *
 * The vertical position has changed. Dump any accumulated text, then adjust
 * the printer's vertical position.
 *
 */

    flushtext();

    if ( encoding == 0 || encoding == MAXENCODING+1 )
	fprintf(tf, "%d %d m\n", hpos, vpos);

    lastx = stringstart = lastend = hpos;
    lasty = vpos;

}   /* End of endline */

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

addchar(c)

    int		c;

{

/*
 *
 * Does whatever is needed to add character c to the current string.
 *
 */

    switch ( encoding ) {
	case 0:
	case 1:
	    putc(c, tf);
	    break;

	case 2:
	case 3:
	    *strptr++ = c;
	    break;

	case MAXENCODING+1:
	case MAXENCODING+2:
	    putc(c, tf);
	    break;
    }	/* End switch */

}   /* End of addchar */

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

addoctal(c)

    int		c;

{

/*
 *
 * Add c to the current string as an octal escape.
 *
 */

    switch ( encoding ) {
	case 0:
	case 1:
	    fprintf(tf, "\\%o", c);
	    break;

	case 2:
	case 3:
	    sprintf(strptr, "\\%o", c);
	    strptr += strlen(strptr);
	    break;

	case MAXENCODING+1:
	case MAXENCODING+2:
	    fprintf(tf, "\\%o", c);
	    break;
    }	/* End switch */

}   /* End of addoctal */

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

charlib(code)

    int		code;			/* either 1 or 2 */

{

    char	*name;			/* name of the character */
    char	tname[10];		/* in case it's a single ASCII character */
    char	temp[150];

/*
 *
 * Called from oput() for characters having codes less than 040. Special files
 * that define PostScript procedures for certain characters can be found in
 * directory *fontdir/devpost/charlib. If there's a file that has the same name as
 * the character we're trying to print it's copied to the output file, otherwise
 * nothing, except some positioning, is done.
 *
 * All character definitions are only made once. Subsequent requests to print the
 * character generate a call to a procedure that begins with the prefix build_ and
 * ends with the character's name. Special characters that are assigned codes
 * other than 1 are assumed to have additional data files that should be copied
 * to the output file immediately after the build_ call. Those data files should
 * end in the suffix .map, and usually will be a hex representation of a bitmap.
 *
 */

    flushtext();

    if ( lastc < ALPHABET ) {		/* ASCII character */
	sprintf(tname, "%.3o", lastc);
	name = tname;
    } else name = chname(lastc);

    if ( downloaded[lastc] == 0 ) {
	sprintf(temp, "%s/dev%s/charlib/%s", fontdir, realdev, name);
	if ( exportfile(temp) == TRUE ) {
	    downloaded[lastc] = 1;
	    t_sf();
	}   /* End if */
    }	/* End if */

    if ( downloaded[lastc] == 1 ) {
	xymove(hpos, vpos);
	fprintf(tf, "%d build_%s\n", (int) lastw, name);
	if ( code != 1 ) {		/* get the bitmap or whatever */
	    sprintf(temp, "%s/dev%s/charlib/%s.map", fontdir, realdev, name);
	    if ( access(temp, 04) == 0 && tf == stdout )
		cat(temp);
	}   /* End if */
	fprintf(tf, "%d %d m\n", stringstart = hpos + lastw, vpos);
    }	/* End if */

}   /* End of charlib */

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

reset()

{

/*
 *
 * Reset variables that keep track of the printer's current position, size and
 * font. Eventually forces things back in sync before oput() prints the next
 * character.
 *
 */

    lastx = -(slop + 1);
    lasty = -1;
    lastfont = lastsize = -1;

}   /* End of reset */

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

resetpos()

{

/*
 *
 * Reset the position tracking variables. Forces oput() to get positions back
 * in sync before printing the next character.
 *
 */

    lastx = -(slop + 1);
    lasty = -1;

}   /* End of resetpos */

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

save()

{

/*
 *
 * Save the current PostScript environment. Initialize things that may have
 * disappeared after the preceeding restore.
 *
 */

    fprintf(tf, "/saveobj save def\n");
    fprintf(tf, "mark\n");

    if ( encoding != realencoding )
	fprintf(tf, "%d setdecoding\n", encoding);

    if ( gotcolor == TRUE )		/* prevent getcolor() recursion */
	setcolor();

}   /* End of save */

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

restore()

{

/*
 *
 * Restore the previous PostScript environment.
 *
 */

    flushtext();
    fprintf(tf, "cleartomark\n");
    fprintf(tf, "saveobj restore\n");
    reset();

}   /* End of restore */

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

exportfile(path)

    char	*path;

{

    int		val = FALSE;

/*
 *
 * Exports the contents of file path to the global environment. Returns TRUE
 * if we're doing output (i.e. tf == stdout) and the copy worked.
 *
 */

    if ( tf == stdout && access(path, 04) == 0 ) {
	restore();
	fprintf(tf, "%s", BEGINGLOBAL);
	val = cat(path);
	fprintf(tf, "%s", ENDGLOBAL);
	save();
    }	/* End if */

    return(val);

}   /* End of exportfile */

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

exportstring(str)

    char	*str;

{

/*
 *
 * Exports string str to the global environment. No return value needed yet.
 *
 */

    if ( tf == stdout && str != NULL && *str != '\0' ) {
	restore();
	fprintf(tf, "%s", BEGINGLOBAL);
	fprintf(tf, "%s", str);
	fprintf(tf, "%s", ENDGLOBAL);
	save();
    }	/* End if */

}   /* End of exportstring */

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

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 */

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

