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