shithub: riscv

ref: 94e26e7576fabcaacd8fa8f75c4f87f9eb6e56f3
dir: /sys/src/cmd/postscript/postbgi/postbgi.c/

View raw version
/*
 *
 * postbgi - BGI (Basic Graphical Instructions) to PostScript translator.
 *
 * A simple program that translates BGI files into PostScript. Probably only
 * useful in Computer Centers that support STARE or PRISM plotters. Most of the
 * code was borrowed from the corresponding program that was written for printers
 * that understand Impress.
 *
 * Extending the original program to handle PRISM jobs was not trivial. Graphics
 * packages that support PRISM occasionally use BGI commands that I ignored in the
 * STARE implementation. Subroutines, color requests, patterns (for filling), and
 * filled trapeziods were the most important omissions. All are now implemented,
 * and at present only repeats, filled slices, and raster rectangles are missing.
 *
 * Pattern filling results were not always predictable or even good, unless the
 * halftone screen definitions were changed and scaling was adjusted so one pixel
 * in user space mapped into an integral number of device space pixels. Doing that
 * makes the resulting PostScript output device dependent, but was often necessary.
 * I've added two booleans to the PostScript prologue (fixscreen and scaletodevice)
 * that control what's done. By default both are false (check postbgi.ps) but can
 * be set to true on the command line using the -P option or by hand by changing
 * the definitions in the prologue. A command line that would set fixscreen and
 * scaletodevice true would look like,
 *
 *	postbgi -P"/fixscreen true" -P"/scaletodevice true" file >file.ps
 *
 * Several other approaches are available if you want to have your spooler handle
 * STARE and PRISM jobs differently. A boolean called prism is defined in the
 * prologue (postbgi.ps) and if it's set to true PostScript procedure setup will
 * set fixscreen and scaletodevice to true before anything important is done. That
 * means the following command line,
 *
 *	postbgi -P"/prism true" file >file.ps
 *
 * accomplishes the same things as the last example. Two different prologue files,
 * one for STARE jobs and the other for PRISM, could be used and the spooler could
 * point postbgi to the appropriate one using the -L option. In that case the only
 * important difference in the two prologues would be the definition of prism. The
 * prologue used for PRISM jobs would have prism set to true, while the STARE
 * prologue would have it set to false.
 *
 * Also included is code that ties lines to device space coordinates. What you get
 * is a consistent line thickness, but placement of lines won't be exact. It's a
 * trade-off that should be right for most jobs. Everything is implemented in the
 * prologue (postbgi.ps) and nothing will be done if the linewidth is zero or if
 * the boolean fixlinewidth (again in postbgi.ps) is false. Once again the -P
 * option can be used to set fixlinewidth to whatever you choose.
 *
 * BGI supports color mixing but PostScript doesn't. BGI files that expect to mix
 * colors won't print properly. PostScript's fill operator overlays whatever has
 * already been put down. Implementing color mixing would have been a terribly
 * difficult job - not worth the effort!
 *
 * The PostScript prologue is copied from *prologue before any of the input files
 * are translated. The program expects that the following PostScript procedures
 * are defined in that file:
 *
 *	setup
 *
 *	  mark ... setup -
 *
 *	    Handles special initialization stuff that depends on how the program
 *	    was called. Expects to find a mark followed by key/value pairs on the
 *	    stack. The def operator is applied to each pair up to the mark, then
 *	    the default state is set up.
 *
 *	pagesetup
 *
 *	  page pagesetup -
 *
 *	    Does whatever is needed to set things up for the next page. Expects
 *	    to find the current page number on the stack.
 *
 *	v
 *
 *	  dx1 dy1 ... dxn dyn x y v -
 *
 *	    Draws the vector described by the numbers on the stack. The top two
 *	    numbers are the coordinates of the starting point. The rest of the
 *	    numbers are relative displacements from the preceeding point.
 *
 *	pp
 *
 *	  x1 y1 ... xn yn string pp -
 *
 *	    Prints string, which is always a single character, at the points
 *	    represented by the rest of the numbers on the stack.
 *
 *	R
 *
 *	  n deltax deltay x y R -
 *
 *	    Creates a rectangular path with its lower left corner at (x, y) and
 *	    sides of length deltax and deltay. The resulting path is stroked if
 *	    n is 0 and filled otherwise.
 *
 *	T
 *
 *	  dx3 dy3 dx2 dy2 dx1 dy1 x y T -
 *
 *	    Fills a trapezoid starting at (x, y) and having relative displacements
 *	    given by the (dx, dy) pairs.
 *
 *	t
 *
 *	  angle x y string t -
 *
 *	    Prints string starting at (x, y) using an orientation of angle degrees.
 *	    The PostScript procedure can handle any angle, but BGI files will only
 *	    request 0 or 90 degrees. Text printed at any other orientation will be
 *	    vector generated.
 *
 *	p
 *
 *	  x y p -
 *
 *	    Called to mark the point (x, y). It fills a small circle, that right
 *	    now has a constant radius. This stuff could probably be much more
 *	    efficient?
 *
 *	l
 *
 *	  array l -
 *
 *	    Sets the line drawing mode according to the description given in
 *	    array. The arrays that describe the different line styles are declared
 *	    in STYLES (file posttek.h), although it would be better to have them
 *	    defined in the prologue.
 *
 *	c
 *
 *	  red green blue c -
 *
 *	    Sets the current PostScript RGB color using setrgbcolor. Also used for
 *	    selecting appropriate patterns as colors.
 *
 *	f
 *
 *	  bgisize f -
 *
 *	    Changes the size of the font that's used to print text. bgisize is a
 *	    grid separation in a 5 by 7 array in which characters are assumed to
 *	    be built.
 *
 *	done
 *
 *	  done
 *
 *	    Makes sure the last page is printed. Only needed when we're printing
 *	    more than one page on each sheet of paper.
 *
 * The default line width is zero, which forces lines to be one pixel wide. That
 * works well for 'write to black' engines but won't be right for 'write to white'
 * engines. The line width can be changed using the -w option, or you can change
 * the initialization of linewidth in the prologue. Code in the prologue supports
 * the generation of uniform width lines when linewidth is non-zero and boolean
 * fixlinewidth is true.
 *
 * Many default values, like the magnification and orientation, are defined in 
 * the prologue, which is where they belong. If they're changed (by options), an
 * appropriate definition is made after the prologue is added to the output file.
 * The -P option passes arbitrary PostScript through to the output file. Among
 * other things it can be used to set (or change) values that can't be accessed by
 * other options.
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <math.h>
#include <ctype.h>
#ifdef plan9
#define	isascii(c)	((unsigned char)(c)<=0177)
#endif

#include "comments.h"			/* PostScript file structuring comments */
#include "gen.h"			/* general purpose definitions */
#include "path.h"			/* for the prologue */
#include "ext.h"			/* external variable declarations */
#include "postbgi.h"			/* a few definitions just used here */

char	*optnames = "a:c:f:m:n:o:p:w:x:y:A:C:E:J:L:P:R:DI";

char	*prologue = POSTBGI;		/* default PostScript prologue */
char	*formfile = FORMFILE;		/* stuff for multiple pages per sheet */

int	formsperpage = 1;		/* page images on each piece of paper */
int	copies = 1;			/* and this many copies of each sheet */

char	*styles[] = STYLES;		/* descriptions of line styles */

int	hpos = 0;			/* current horizontal */
int	vpos = 0;			/* and vertical position */

int	bgisize = BGISIZE;		/* just the character grid spacing */
int	linespace;			/* distance between lines of text */

int	bgimode;			/* character or graph mode */

int	in_subr = FALSE;		/* currently defining a subroutine */
int	in_global = FALSE;		/* to save space with subroutine defs */
int	subr_id = 0;			/* defining this subroutine */
int	shpos = 0;			/* starting horizontal */
int	svpos = 0;			/* and vertical positions - subroutines */
Disp	displacement[64];		/* dx and dy after a subroutine call */

Fontmap	fontmap[] = FONTMAP;		/* for translating font names */
char	*fontname = "Courier";		/* use this PostScript font */

int	page = 0;			/* page we're working on */
int	printed = 0;			/* printed this many pages */

FILE	*fp_in = stdin;			/* read from this file */
FILE	*fp_out = NULL;			/* and write stuff here */
FILE	*fp_acct = NULL;		/* for accounting data */

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

main(agc, agv)

    int		agc;
    char	*agv[];

{

/*
 *
 * A program that converts BGI (Basic Graphical Instructions) files generated by
 * packages like GRAFPAC and DISSPLA into PostScript. It does an adequate job but
 * is far from perfect. A few things still haven't been implemented (eg. repeats
 * and raster rectangles), but what's here should be good enough for most of our
 * STARE and PRISM jobs. Color mixing (in PRISM jobs) won't work on PostScript
 * printers, and there's no chance I'll implement it!
 *
 */

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

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

    init_signals();			/* set up interrupt handling */
    header();				/* PostScript header comments */
    options();				/* command line options */
    setup();				/* for PostScript */
    arguments();			/* followed by each input file */
    done();				/* print the last page etc. */
    account();				/* job accounting data */

    exit(x_stat);			/* everything probably went OK */

}   /* 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;			/* return value from getopt() */
    int		old_optind = optind;	/* for restoring optind - should be 1 */

/*
 *
 * Scans the option list looking for things, like the prologue file, that we need
 * right away but could be changed from the default. Doing things this way is an
 * attempt to conform to Adobe's latest file structuring conventions. In particular
 * they now say there should be nothing executed in the prologue, and they have
 * added two new comments that delimit global initialization calls. Once we know
 * where things really are we write out the job header, follow it by the prologue,
 * and then add the ENDPROLOG and BEGINSETUP comments.
 *
 */

    while ( (ch = getopt(argc, argv, optnames)) != EOF )
	if ( ch == 'L' )
	    prologue = optarg;
	else if ( ch == '?' )
	    error(FATAL, "");

    optind = old_optind;		/* get ready for option scanning */

    fprintf(stdout, "%s", CONFORMING);
    fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
    fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
    fprintf(stdout, "%s %s\n", PAGES, ATEND);
    fprintf(stdout, "%s", ENDCOMMENTS);

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

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

}   /* End of header */

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

options()

{

    int		ch;			/* option name - from getopt() */

/*
 *
 * Reads and processes the command line options.
 *
 */

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

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

	    case 'f':			/* new font */
		    fontname = get_font(optarg);
		    fprintf(stdout, "/font /%s def\n", fontname);
		    break;

	    case 'm':			/* magnification */
		    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 */
		    if ( *optarg == 'l' )
			fprintf(stdout, "/landscape true def\n");
		    else fprintf(stdout, "/landscape false def\n");
		    break;

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

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

	    case 'y':			/* and vertically on the page */
		    fprintf(stdout, "/yoffset %s def\n", optarg);
		    break;

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

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

	    case 'E':			/* text font encoding */
		    fontencoding = optarg;
		    break;

	    case 'L':			/* Postscript prologue file */
		    prologue = optarg;
		    break;

	    case 'P':			/* PostScript pass through */
		    fprintf(stdout, "%s\n", optarg);
		    break;

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

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

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

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

	    default:			/* don't know what to do for ch */
		    error(FATAL, "missing case for option %c", ch);
		    break;
	}   /* End switch */
    }	/* End while */

    argc -= optind;			/* get ready for non-option args */
    argv += optind;

}   /* End of options */

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

char *get_font(name)

    char	*name;			/* name the user asked for */

{

    int		i;			/* for looking through fontmap[] */

/*
 *
 * Called from options() to map a user's font name into a legal PostScript name.
 * If the lookup fails *name is returned to the caller. That should let you choose
 * any PostScript font.
 *
 */

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

    return(name);

}   /* End of get_font */

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

setup()

{

/*
 *
 * Handles things that must be done after the options are read but before the
 * input files are processed.
 *
 */

    writerequest(0, stdout);		/* global requests eg. manual feed */
    setencoding(fontencoding);
    fprintf(stdout, "setup\n");

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

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

}   /* End of setup */

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

arguments()

{

/*
 *
 * Makes sure all the non-option command line options are processed. If we get
 * here and there aren't any arguments left, or if '-' is one of the input files
 * we'll process stdin.
 *
 */

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

}   /* End of arguments */

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

done()

{

/*
 *
 * Finished with the last input file, so mark the end of the pages, make sure the
 * last page is printed, and restore the initial environment.
 *
 */

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

}   /* End of done */

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

account()

{

/*
 *
 * Writes an 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()

{

    int		ch;			/* next input character */

/*
 *
 * Controls the conversion of BGI files into PostScript. Not everything has been
 * implemented, but what's been done should be good enough for our purposes.
 *
 */

    redirect(-1);			/* get ready for the first page */
    bgimode = 0;
    formfeed();

    while ( (ch = get_char()) != EOF )  {
	switch ( ch )  {
		case BRCHAR:			/* rotated character mode */
			    bgimode = ch;
			    text(90);
			    break;

		case BCHAR:			/* graphical character mode */
			    bgimode = ch;
			    text(0);
			    break;

		case BGRAPH:			/* graphical master mode */
			    bgimode = ch;
			    break;

		case BSUB:			/* subroutine definition */
			    subr_def();
			    break;

		case BRET:			/* end of subroutine */
			    subr_end();
			    break;

		case BCALL:			/* subroutine call */
			    subr_call();
			    break;

		case BEND:			/* end display - page */
			    formfeed();
			    break;

		case BERASE:			/* erase - shouldn't be used */
			    error(FATAL, "BGI erase opcode obsolete");
			    break;

		case BREP:			/* repeat */
			    error(FATAL, "Repeat not implemented");
			    repeat();
			    break;

		case BSETX:			/* new x coordinate */
			    hgoto(get_int(0));
			    break;

		case BSETY:			/* new y coordinate */
			    vgoto(get_int(0));
			    break;

		case BSETXY:			/* new x and y coordinates */
			    hgoto(get_int(0));
			    vgoto(get_int(0));
			    break;

		case BINTEN:			/* mark the current point */
			    fprintf(fp_out, "%d %d p\n", hpos, vpos);
			    break;

		case BVISX:			/* visible x */
			    vector(X_COORD, VISIBLE);
			    break;

		case BINVISX:			/* invisible x */
			    vector(X_COORD, INVISIBLE);
			    break;

		case BVISY:			/* visible y */
			    vector(Y_COORD, VISIBLE);
			    break;

		case BINVISY:			/* invisible y */
			    vector(Y_COORD, INVISIBLE);
			    break;

		case BVEC:			/* arbitrary vector */
			    vector(LONGVECTOR, VISIBLE);
			    break;

		case BSVEC:			/* short vector */
			    vector(SHORTVECTOR, VISIBLE);
			    break;

		case BRECT:			/* draw rectangle */
			    rectangle(OUTLINE);
			    break;

		case BPOINT1:			/* point plot 1 */
		case BPOINT:			/* point plot 2 */
			    point_plot(ch, get_char());
			    break;

		case BLINE:			/* line plot */
			    line_plot();
			    break;

		case BLTY:			/* line type */
			    fprintf(fp_out, "%s l\n", styles[get_data()]);
			    break;

		case BARC:			/* circular arc */
			    arc(OUTLINE);
			    break;

		case BFARC:			/* filled circle */
			    arc(FILL);
			    break;

		case BFRECT:			/* filled rectangle */
			    rectangle(FILL);
			    break;

		case BRASRECT:			/* raster rectangle */
			    error(FATAL, "Raster Rectangle not implemented");
			    break;

		case BCOL:			/* select color */
			    set_color(get_data());
			    break;

		case BFTRAPH:			/* filled trapezoid */
			    trapezoid();
			    break;

		case BPAT:			/* pattern for area filling */
			    pattern();
			    break;

		case BCSZ:			/* change BGI character 'size' */
			    setsize(get_data());
			    break;

		case BNOISE:			/* from bad file format */
			    break;

		default:			/* don't recognize the code */
			    error(FATAL, "bad BGI command %d (0%o)", ch, ch);
			    break;
	}   /* End switch */

	if ( debug == ON )
	    fprintf(stderr, "\n");
    }	/* End while */

    formfeed();					/* in case BEND was missing */

}   /* End of conv */

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

hgoto(n)

    int		n;			/* new horizontal position */

{

/*
 *
 * Sets the current BGI horizontal position to n.
 *
 */

    hpos = n;

}   /* End of hgoto */

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

vgoto(n)

    int		n;			/* move to this vertical position */

{

/*
 *
 * Sets the absolute vertical position to n.
 * 
 */

    vpos = n;

}   /* End of vgoto */

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

setsize(n)

    int		n;			/* BGI size - just a grid separation */

{

/*
 *
 * Called when we're supposed to change the BGI character size to n. The BGI
 * size is the grid separation in a 5 by 7 array in which characters are assumed
 * to be built.
 *
 */

    bgisize = n;
    linespace = LINESPACE(bgisize);

    fprintf(fp_out, "%d f\n", bgisize);

    if ( debug == ON )
	fprintf(stderr, "BGI size = %d\n", n);

}   /* End of setsize */

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

repeat()

{

    int		count;			/* repeat this many times */
    int		ch;			/* next input character */

/*
 *
 * Haven't implemented repeats, although it wouldn't be difficult. Apparently it's
 * not used by any graphics packages that generate BGI.
 *
 */

    count = get_int();			/* get the repeat count */

    while ( (ch = get_char()) != EOF  &&  ch != BENDR ) ;

}   /* End of repeat */

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

text(angle)

    int		angle;			/* either 0 or 90 degrees */

{

    int		ch;			/* next character from file *fp_in */

/*
 *
 * Called from conv() after we've entered one of the graphical character modes.
 * Characters are read from the input file and printed until the next mode change
 * opcode is found (or until EOF). angle will be 90 for rotated character mode
 * and 0 otherwise.
 *
 *
 */

    fprintf(fp_out, "%d %d %d(", angle, hpos, vpos);

    while ( (ch = get_char()) != EOF )  {
	if ( ch == BGRAPH || ch == BCHAR || ch == BRCHAR )  {
	    ungetc(ch, fp_in);
	    position--;
	    break;
	}   /* End if */

	switch ( ch )  {
	    case '\012':
		vgoto(vpos - linespace);

	    case '\015':
		hgoto(0);
		fprintf(fp_out, ")t\n%d %d %d(", angle, hpos, vpos);
		break;

	    case '(':
	    case ')':
	    case '\\':
		putc('\\', fp_out);

	    default:
		if ( isascii(ch) && isprint(ch) )
		    putc(ch, fp_out);
		else fprintf(fp_out, "\\%.3o", ch & 0377);
		break;
	}   /* End switch */
    }	/* End while */

    fprintf(fp_out, ") t\n");

}   /* End of text */

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

formfeed()

{

    int		ch;			/* repeat count for this page */

/*
 *
 * Does whatever is needed to print the last page and get ready for the next one.
 * It's called, from conv(), after a BEND code is processed. I'm ignoring the
 * copy count that's expected to follow each page.
 *
 */

    if ( bgimode == BGRAPH && (ch = get_char()) != EOF  &&  ! (ch & MSB) )  {
	ungetc(ch, fp_in);
	position--;
    }	/* End if */

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

    fprintf(fp_out, "cleartomark\n");
    fprintf(fp_out, "showpage\n");
    fprintf(fp_out, "saveobj restore\n");
    fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);

    while ( (ch = get_char()) == 0 ) ;	/* skip any NULL characters */
    ungetc(ch, fp_in);
    position--;

    if ( ungetc(getc(fp_in), fp_in) == EOF )
	redirect(-1);
    else redirect(++page);

    fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
    fprintf(fp_out, "/saveobj save def\n");
    fprintf(fp_out, "mark\n");
    writerequest(printed+1, fp_out);
    fprintf(fp_out, "%d pagesetup\n", printed+1);

    setsize(bgisize);
    hpos = vpos = 0;

}    /* End of formfeed */

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

subr_def()

{

/*
 *
 * Starts a subroutine definition. All subroutines are defined as PostScript
 * procedures that begin with the character S and end with the subroutine's id
 * (a number between 0 and 63 - I guess). The primary, and perhaps only use of
 * subroutines is in special color plots produced by several graphics libraries,
 * and even there it's not all that common. I've also chosen not to worry about
 * nested subroutine definitions - that would certainly be overkill!
 *
 * All subroutines set up their own (translated) coordinate system, do their work
 * in that system, and restore things when they exit. To make everything work
 * properly we save the current point (in shpos and svpos), set our position to
 * (0, 0), and restore things at the end of the subroutine definition. That means
 * hpos and vpos measure the relative displacement after a subroutine returns, and
 * we save those values in the displacement[] array. The displacements are used
 * (in subr_call()) to properly adjust our position after each subroutine call,
 * and all subroutines are called with the current x and y coordinates on top of
 * the stack.
 *
 */

    if ( in_subr == TRUE )		/* a nested subroutine definition?? */
	error(FATAL, "can't handle nested subroutine definitions");

    if ( (subr_id = get_data()) == EOF )
	error(FATAL, "missing subroutine identifier");

    if ( in_global == FALSE )  {	/* just used to reduce file size some */
	fprintf(fp_out, "cleartomark\n");
	fprintf(fp_out, "saveobj restore\n");
	fprintf(fp_out, "%s", BEGINGLOBAL);
	in_global = TRUE;
    }	/* End if */

    fprintf(fp_out, "/S%d {\n", subr_id);
    fprintf(fp_out, "gsave translate\n");

    shpos = hpos;			/* save our current position */
    svpos = vpos;

    hgoto(0);				/* start at the origin */
    vgoto(0);

    in_subr = TRUE;			/* in a subroutine definition */

}   /* End of subr_def */

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

subr_end()

{

    int		ch;			/* for looking at next opcode */

/*
 *
 * Handles stuff needed at the end of each subroutine. Want to remember the change
 * in horizontal and vertical positions for each subroutine so we can adjust our
 * position after each call - just in case. The current position was set to (0, 0)
 * before we started the subroutine definition, so when we get here hpos and vpos
 * are the relative displacements after the subroutine is called. They're saved in
 * the displacement[] array and used to adjust the current position when we return
 * from a subroutine.
 *
 */

    if ( in_subr == FALSE )		/* not in a subroutine definition?? */
	error(FATAL, "subroutine end without corresponding start");

    fprintf(fp_out, "grestore\n");
    fprintf(fp_out, "} def\n");

    if ( in_global == TRUE && (ch = get_char()) != BSUB )  {
	fprintf(fp_out, "%s", ENDGLOBAL);
	fprintf(fp_out, "/saveobj save def\n");
	fprintf(fp_out, "mark\n");
	in_global = FALSE;
    }	/* End if */

    ungetc(ch, fp_in);			/* put back the next opcode */

    displacement[subr_id].dx = hpos;
    displacement[subr_id].dy = vpos;

    hgoto(shpos);			/* back to where we started */
    vgoto(svpos);

    in_subr = FALSE;			/* done with the definition */

}   /* End of subr_end */

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

subr_call()

{

    int		ch;			/* next byte from *fp_in */
    int		id;			/* subroutine id if ch wasn't an opcode */

/*
 *
 * Handles subroutine calls. Everything that follows the BCALL opcode (up to the
 * next opcode) is taken as a subroutine identifier - thus the loop that generates
 * the subroutine calls.
 *
 */

    while ( (ch = get_char()) != EOF && (ch & MSB) )  {
	id = ch & DMASK;
	fprintf(fp_out, "%d %d S%d\n", hpos, vpos, id);

	hgoto(hpos + displacement[id].dx);	/* adjust our position */
	vgoto(vpos + displacement[id].dy);
    }	/* End while */

    ungetc(ch, fp_in);

}   /* End of subr_call */

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

vector(var, mode)

    int		var;			/* coordinate that varies next? */
    int		mode;			/* VISIBLE or INVISIBLE vectors */

{

    int		ch;			/* next character from *fp_in */
    int		x, y;			/* line drawn to this point */
    int		count = 0;		/* number of points so far */

/*
 *
 * Handles plotting of all types of BGI vectors. If it's a manhattan vector var
 * specifies which coordinate will be changed by the next number in the input
 * file.
 *
 */

    x = hpos;				/* set up the first point */
    y = vpos;

    while ( (ch = get_char()) != EOF  &&  ch & MSB )  {
	if ( var == X_COORD )		/* next length is change in x */
	    x += get_int(ch);
	else if ( var == Y_COORD )	/* it's the change in y */
	    y += get_int(ch);
	else if ( var == LONGVECTOR )  {	/* long vector */
	    x += get_int(ch);
	    y += get_int(0);
	} else {			/* must be a short vector */
	    x += ((ch & MSBMAG) * ((ch & SGNB) ? -1 : 1));
	    y += (((ch = get_data()) & MSBMAG) * ((ch & SGNB) ? -1 : 1));
	}   /* End else */

	if ( mode == VISIBLE )  {	/* draw the line segment */
	    fprintf(fp_out, "%d %d\n", hpos - x, vpos - y);
	    count++;
	}   /* End if */

	hgoto(x);			/* adjust the current BGI position */
	vgoto(y);

	if ( var == X_COORD )		/* vertical length comes next */
	    var = Y_COORD;
	else if ( var == Y_COORD )	/* change horizontal next */
	    var = X_COORD;
    }	/* End while */

    if ( count > 0 )
	fprintf(fp_out, "%d %d v\n", hpos, vpos);

    ungetc(ch, fp_in);			/* it wasn't part of the vector */
    position--;

}   /* End of vector */

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

rectangle(mode)

    int		mode;			/* FILL or OUTLINE the rectangle */

{

    int		deltax;			/* displacement for horizontal side */
    int		deltay;			/* same but for vertical sides */

/*
 *
 * Draws a rectangle and either outlines or fills it, depending on the value of
 * mode. Would be clearer, and perhaps better, if {stroke} or {fill} were put on
 * the stack instead of 0 or 1. R could then define the path and just do an exec
 * to fill or stroke it.
 *
 */

    deltax = get_int(0);		/* get the height and width */
    deltay = get_int(0);

    if ( mode == OUTLINE )
	fprintf(fp_out, "0 %d %d %d %d R\n", deltax, deltay, hpos, vpos);
    else fprintf(fp_out, "1 %d %d %d %d R\n", deltax, deltay, hpos, vpos);

}   /* End of rectangle */

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

trapezoid()

{

    int		kind;			/* which sides are parallel */
    int		d[6];			/* true displacements - depends on kind */

/*
 *
 * Handles filled trapeziods. A data byte of 0101 following the opcode means the
 * horizontal sides are parallel, 0102 means the vertical sides are parallel.
 * Filling is handled by eofill so we don't need to get things in the right order.
 *
 */

    kind = get_data();

    d[0] = get_int(0);
    d[1] = 0;
    d[2] = get_int(0);
    d[3] = get_int(0);
    d[4] = get_int(0);
    d[5] = 0;

    if ( kind == 2 )  {			/* parallel sides are vertical */
	d[1] = d[0];
	d[0] = 0;
	d[5] = d[4];
	d[4] = 0;
    }	/* End if */

    fprintf(fp_out, "%d %d %d %d %d %d %d %d T\n", d[4], d[5], d[2], d[3], d[0], d[1], hpos, vpos);

}   /* End of trapezoid */

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

point_plot(mode, ch)

    int		mode;			/* plotting mode BPOINT or BPOINT1 */
    int		ch;			/* will be placed at the points */

{

    int		c;			/* next character from input file */
    int		x, y;			/* ch gets put here next */
    int		deltax;			/* x increment for BPOINT1 mode */

/*
 *
 * The two point plot modes are used to place a character at selected points. The
 * difference in the two modes, namely BPOINT and BPOINT1, is the way we get the
 * coordinates of the next point. In BPOINT1 the two bytes immediately following
 * ch select a constant horizontal change, while both coordinates are given for
 * all points in BPOINT mode.
 *
 */

    if ( mode == BPOINT1 )  {		/* first integer is change in x */
	deltax = get_int(0);
	x = hpos - deltax;
    }	/* End if */

    while ( (c = get_char()) != EOF  &&  (c & MSB) )  {
	if ( mode == BPOINT1 )  {	/* only read y coordinate */
	    y = get_int(c);
	    x += deltax;
	} else {			/* get new x and y from input file */
	    x = get_int(c);
	    y = get_int(0);
	}   /* End else */

	hgoto(x);			/* adjust BGI position */
	vgoto(y);

	fprintf(fp_out, "%d %d\n", hpos, vpos);
    }	/* End while */

    putc('(', fp_out);

    switch ( ch )  {
	case '(':
	case ')':
	case '\\':
		putc('\\', fp_out);

	default:
		putc(ch, fp_out);
    }	/* End switch */

    fprintf(fp_out, ")pp\n");

    ungetc(c, fp_in);			/* it wasn't part of the point plot */
    position--;

}   /* End of point_plot */

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

line_plot()

{

    int		c;			/* next input character from fp_in */
    int		deltax;			/* change in x coordinate */
    int		x0, y0;			/* starting point for next segment */
    int		x1, y1;			/* endpoint of the line */
    int		count = 0;		/* number of points so far */

/*
 *
 * Essentially the same format as BPOINT1, except that in this case we connect
 * pairs of points by line segments.
 *
 */

    deltax = get_int(0);		/* again the change in x is first */

    x1 = hpos;				/* so it works first time through */
    y1 = get_int(0);

    while ( (c = get_char()) != EOF  &&  (c & MSB) )  {
	x0 = x1;			/* line starts here */
	y0 = y1;

	x1 += deltax;			/* and ends at this point */
    	y1 = get_int(c);

	fprintf(fp_out, "%d %d\n", -deltax, y0 - y1);
	count++;
    }	/* End while */

    hgoto(x1);				/* adjust current BGI position */
    vgoto(y1);

    if ( count > 0 )
	fprintf(fp_out, "%d %d v\n", hpos, vpos);

    ungetc(c, fp_in);			/* wasn't part of the line */
    position--;

}   /* End of line_plot */

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

arc(mode)

    int		mode;			/* FILL or OUTLINE the path */

{

    int		dx1, dy1;		/* displacements for first point */
    int		dx2, dy2;		/* same for the second point */
    int		radius;			/* of the arc */
    int		angle1, angle2;		/* starting and ending angles */

/*
 *
 * Called whenever we need to draw an arc. I'm ignoring filled slices for now.
 *
 */

    dx1 = get_int(0);			/* displacements relative to center */
    dy1 = get_int(0);
    dx2 = get_int(0);
    dy2 = get_int(0);

    radius = get_int(0);		/* and the radius */

    if ( radius == 0 )			/* nothing to do */
	return;

    angle1 = (atan2((double) dy1, (double) dx1) * 360) / (2 * PI) + .5;
    angle2 = (atan2((double) dy2, (double) dx2) * 360) / (2 * PI) + .5;

    fprintf(fp_out, "%d %d %d %d %d arcn stroke\n", hpos, vpos, radius, angle1, angle2);

}   /* End of arc */

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

pattern()

{

    double	red = 0;		/* color components */
    double	green = 0;
    double	blue = 0;
    int		kind;			/* corse or fine pattern */
    int		val;			/* next color data byte */
    int		i;			/* loop index */

/*
 *
 * Handles patterns by setting the current color based of the values assigned to
 * the next four data bytes. BGI supports two kinds of patterns (fine or coarse)
 * but I'm handling each in the same way - for now. In a fine pattern the four
 * data bytes assign a color to four individual pixels (upperleft first) while
 * in a coarse pattern the four colors are assigned to groups of four pixels,
 * for a total of 16. Again the first color goes to the group in the upper left
 * corner. The byte immediately following the BPAT opcode selects fine (040) or
 * coarse (041) patterns. The PostScript RGB color is assigned by averaging the
 * RED, GREEN, and BLUE components assigned to the four pixels (or groups of
 * pixels). Acceptable results, but there's no distinction between fine and
 * coarse patterns.
 *
 */

    if ( (kind = get_char()) == EOF )
	error(FATAL, "bad pattern command");

    for ( i = 0; i < 4; i++ )  {
	val = get_data();
	red += get_color(val, RED);
	green += get_color(val, GREEN);
	blue += get_color(val, BLUE);
    }	/* End for */

    fprintf(fp_out, "%g %g %g c\n", red/4, green/4, blue/4);

}   /* End of pattern */

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

get_color(val, component)

    int		val;			/* color data byte */
    int		component;		/* RED, GREEN, or BLUE component */

{


    int		primary;		/* color mixing mode - bits 2 to 4 */
    int		plane;			/* primary color plane - bits 5 to 7 */
    unsigned	rgbcolor;		/* PostScript expects an RGB triple */

/*
 *
 * Picks the requested color component (RED, GREEN, or BLUE) from val and returns
 * the result to the caller. BGI works with Cyan, Yellow, and Magenta so the one's
 * complement stuff (following the exclusive or'ing) recovers the RED, BLUE, and
 * GREEN components that PostScript's setrgbcolor operator needs. The PostScript
 * interpreter in the ColorScript 100 has a setcmycolor operator, but it's not
 * generally available so I've decided to stick with setrgbcolor.
 *
 */

    primary = (val >> 3) & 07;
    plane = val & 07;
    rgbcolor = (~(primary ^ plane)) & 07;

    if ( debug == ON )
	fprintf(stderr, "val = %o, primary = %o, plane = %o, rgbcolor = %o\n",
		val, primary, plane, rgbcolor);

    switch ( component )  {
	case RED:
		return(rgbcolor>>2);

	case GREEN:
		return(rgbcolor&01);

	case BLUE:
		return((rgbcolor>>1)&01);

	default:
		error(FATAL, "unknown color component");
		return(0);
    }	/* End switch */

}   /* End of get_color */

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

set_color(val)

    int		val;			/* color data byte */

{

/*
 *
 * Arranges to have the color set to the value requested in the BGI data byte val.
 *
 */

    fprintf(fp_out, "%d %d %d c\n", get_color(val, RED), get_color(val, GREEN), get_color(val, BLUE));

}   /* End of set_color */

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

get_int(highbyte)

    int		highbyte;		/* already read this byte */

{

    int		lowbyte;		/* this and highbyte make the int */

/*
 *
 * Figures out the value on the integer (sign magnitude form) that's next in the
 * input file. If highbyte is nonzero we'll use it and the next byte to build the
 * integer, otherwise two bytes are read from fp_in.
 *
 */


    if ( highbyte == 0 )		/* need to read the first byte */
	highbyte = get_data();

    lowbyte = get_data();		/* always need the second byte */

    return(highbyte & SGNB ? -MAG(highbyte, lowbyte) : MAG(highbyte, lowbyte));

}   /* End of get_int */

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

get_data()

{

    int		val;			/* data value returned to caller */

/*
 *
 * Called when we expect to find a single data character in the input file. The
 * data bit is turned off and the resulting value is returned to the caller.
 *
 */

    if ( (val = get_char()) == EOF  ||  ! (val & MSB) )
	error(FATAL, "missing data value");

    return(val & DMASK);

}   /* End of get_data */

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

get_char()

{

    int		ch;			/* character we just read */

/*
 *
 * Reads the next character from file *fp_in and returns the value to the caller.
 * This routine isn't really needed, but we may want to deal directly with some
 * screwball file formats so I thought it would probably be a good idea to isolate
 * all the input in one routine that could be easily changed.
 *
 */

    if ( (ch = getc(fp_in)) != EOF )  {
	position++;
	ch &= CHMASK;
    }	/* End if */

    if ( debug == ON )
	fprintf(stderr, "%o ", ch);

    return(ch);

}   /* End of get_char */

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

redirect(pg)

    int		pg;			/* next page we're printing */

{

    static FILE	*fp_null = NULL;	/* if output is turned off */

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

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

}   /* End of redirect */

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