xref: /plan9/sys/src/cmd/postscript/postbgi/postbgi.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 /*
2  *
3  * postbgi - BGI (Basic Graphical Instructions) to PostScript translator.
4  *
5  * A simple program that translates BGI files into PostScript. Probably only
6  * useful in Computer Centers that support STARE or PRISM plotters. Most of the
7  * code was borrowed from the corresponding program that was written for printers
8  * that understand Impress.
9  *
10  * Extending the original program to handle PRISM jobs was not trivial. Graphics
11  * packages that support PRISM occasionally use BGI commands that I ignored in the
12  * STARE implementation. Subroutines, color requests, patterns (for filling), and
13  * filled trapeziods were the most important omissions. All are now implemented,
14  * and at present only repeats, filled slices, and raster rectangles are missing.
15  *
16  * Pattern filling results were not always predictable or even good, unless the
17  * halftone screen definitions were changed and scaling was adjusted so one pixel
18  * in user space mapped into an integral number of device space pixels. Doing that
19  * makes the resulting PostScript output device dependent, but was often necessary.
20  * I've added two booleans to the PostScript prologue (fixscreen and scaletodevice)
21  * that control what's done. By default both are false (check postbgi.ps) but can
22  * be set to true on the command line using the -P option or by hand by changing
23  * the definitions in the prologue. A command line that would set fixscreen and
24  * scaletodevice true would look like,
25  *
26  *	postbgi -P"/fixscreen true" -P"/scaletodevice true" file >file.ps
27  *
28  * Several other approaches are available if you want to have your spooler handle
29  * STARE and PRISM jobs differently. A boolean called prism is defined in the
30  * prologue (postbgi.ps) and if it's set to true PostScript procedure setup will
31  * set fixscreen and scaletodevice to true before anything important is done. That
32  * means the following command line,
33  *
34  *	postbgi -P"/prism true" file >file.ps
35  *
36  * accomplishes the same things as the last example. Two different prologue files,
37  * one for STARE jobs and the other for PRISM, could be used and the spooler could
38  * point postbgi to the appropriate one using the -L option. In that case the only
39  * important difference in the two prologues would be the definition of prism. The
40  * prologue used for PRISM jobs would have prism set to true, while the STARE
41  * prologue would have it set to false.
42  *
43  * Also included is code that ties lines to device space coordinates. What you get
44  * is a consistent line thickness, but placement of lines won't be exact. It's a
45  * trade-off that should be right for most jobs. Everything is implemented in the
46  * prologue (postbgi.ps) and nothing will be done if the linewidth is zero or if
47  * the boolean fixlinewidth (again in postbgi.ps) is false. Once again the -P
48  * option can be used to set fixlinewidth to whatever you choose.
49  *
50  * BGI supports color mixing but PostScript doesn't. BGI files that expect to mix
51  * colors won't print properly. PostScript's fill operator overlays whatever has
52  * already been put down. Implementing color mixing would have been a terribly
53  * difficult job - not worth the effort!
54  *
55  * The PostScript prologue is copied from *prologue before any of the input files
56  * are translated. The program expects that the following PostScript procedures
57  * are defined in that file:
58  *
59  *	setup
60  *
61  *	  mark ... setup -
62  *
63  *	    Handles special initialization stuff that depends on how the program
64  *	    was called. Expects to find a mark followed by key/value pairs on the
65  *	    stack. The def operator is applied to each pair up to the mark, then
66  *	    the default state is set up.
67  *
68  *	pagesetup
69  *
70  *	  page pagesetup -
71  *
72  *	    Does whatever is needed to set things up for the next page. Expects
73  *	    to find the current page number on the stack.
74  *
75  *	v
76  *
77  *	  dx1 dy1 ... dxn dyn x y v -
78  *
79  *	    Draws the vector described by the numbers on the stack. The top two
80  *	    numbers are the coordinates of the starting point. The rest of the
81  *	    numbers are relative displacements from the preceeding point.
82  *
83  *	pp
84  *
85  *	  x1 y1 ... xn yn string pp -
86  *
87  *	    Prints string, which is always a single character, at the points
88  *	    represented by the rest of the numbers on the stack.
89  *
90  *	R
91  *
92  *	  n deltax deltay x y R -
93  *
94  *	    Creates a rectangular path with its lower left corner at (x, y) and
95  *	    sides of length deltax and deltay. The resulting path is stroked if
96  *	    n is 0 and filled otherwise.
97  *
98  *	T
99  *
100  *	  dx3 dy3 dx2 dy2 dx1 dy1 x y T -
101  *
102  *	    Fills a trapezoid starting at (x, y) and having relative displacements
103  *	    given by the (dx, dy) pairs.
104  *
105  *	t
106  *
107  *	  angle x y string t -
108  *
109  *	    Prints string starting at (x, y) using an orientation of angle degrees.
110  *	    The PostScript procedure can handle any angle, but BGI files will only
111  *	    request 0 or 90 degrees. Text printed at any other orientation will be
112  *	    vector generated.
113  *
114  *	p
115  *
116  *	  x y p -
117  *
118  *	    Called to mark the point (x, y). It fills a small circle, that right
119  *	    now has a constant radius. This stuff could probably be much more
120  *	    efficient?
121  *
122  *	l
123  *
124  *	  array l -
125  *
126  *	    Sets the line drawing mode according to the description given in
127  *	    array. The arrays that describe the different line styles are declared
128  *	    in STYLES (file posttek.h), although it would be better to have them
129  *	    defined in the prologue.
130  *
131  *	c
132  *
133  *	  red green blue c -
134  *
135  *	    Sets the current PostScript RGB color using setrgbcolor. Also used for
136  *	    selecting appropriate patterns as colors.
137  *
138  *	f
139  *
140  *	  bgisize f -
141  *
142  *	    Changes the size of the font that's used to print text. bgisize is a
143  *	    grid separation in a 5 by 7 array in which characters are assumed to
144  *	    be built.
145  *
146  *	done
147  *
148  *	  done
149  *
150  *	    Makes sure the last page is printed. Only needed when we're printing
151  *	    more than one page on each sheet of paper.
152  *
153  * The default line width is zero, which forces lines to be one pixel wide. That
154  * works well for 'write to black' engines but won't be right for 'write to white'
155  * engines. The line width can be changed using the -w option, or you can change
156  * the initialization of linewidth in the prologue. Code in the prologue supports
157  * the generation of uniform width lines when linewidth is non-zero and boolean
158  * fixlinewidth is true.
159  *
160  * Many default values, like the magnification and orientation, are defined in
161  * the prologue, which is where they belong. If they're changed (by options), an
162  * appropriate definition is made after the prologue is added to the output file.
163  * The -P option passes arbitrary PostScript through to the output file. Among
164  * other things it can be used to set (or change) values that can't be accessed by
165  * other options.
166  *
167  */
168 
169 #include <stdio.h>
170 #include <sys/types.h>
171 #include <fcntl.h>
172 #include <signal.h>
173 #include <math.h>
174 #include <ctype.h>
175 #ifdef plan9
176 #define	isascii(c)	((unsigned char)(c)<=0177)
177 #endif
178 
179 #include "comments.h"			/* PostScript file structuring comments */
180 #include "gen.h"			/* general purpose definitions */
181 #include "path.h"			/* for the prologue */
182 #include "ext.h"			/* external variable declarations */
183 #include "postbgi.h"			/* a few definitions just used here */
184 
185 char	*optnames = "a:c:f:m:n:o:p:w:x:y:A:C:E:J:L:P:R:DI";
186 
187 char	*prologue = POSTBGI;		/* default PostScript prologue */
188 char	*formfile = FORMFILE;		/* stuff for multiple pages per sheet */
189 
190 int	formsperpage = 1;		/* page images on each piece of paper */
191 int	copies = 1;			/* and this many copies of each sheet */
192 
193 char	*styles[] = STYLES;		/* descriptions of line styles */
194 
195 int	hpos = 0;			/* current horizontal */
196 int	vpos = 0;			/* and vertical position */
197 
198 int	bgisize = BGISIZE;		/* just the character grid spacing */
199 int	linespace;			/* distance between lines of text */
200 
201 int	bgimode;			/* character or graph mode */
202 
203 int	in_subr = FALSE;		/* currently defining a subroutine */
204 int	in_global = FALSE;		/* to save space with subroutine defs */
205 int	subr_id = 0;			/* defining this subroutine */
206 int	shpos = 0;			/* starting horizontal */
207 int	svpos = 0;			/* and vertical positions - subroutines */
208 Disp	displacement[64];		/* dx and dy after a subroutine call */
209 
210 Fontmap	fontmap[] = FONTMAP;		/* for translating font names */
211 char	*fontname = "Courier";		/* use this PostScript font */
212 
213 int	page = 0;			/* page we're working on */
214 int	printed = 0;			/* printed this many pages */
215 
216 FILE	*fp_in = stdin;			/* read from this file */
217 FILE	*fp_out = NULL;			/* and write stuff here */
218 FILE	*fp_acct = NULL;		/* for accounting data */
219 
220 /*****************************************************************************/
221 
main(agc,agv)222 main(agc, agv)
223 
224     int		agc;
225     char	*agv[];
226 
227 {
228 
229 /*
230  *
231  * A program that converts BGI (Basic Graphical Instructions) files generated by
232  * packages like GRAFPAC and DISSPLA into PostScript. It does an adequate job but
233  * is far from perfect. A few things still haven't been implemented (eg. repeats
234  * and raster rectangles), but what's here should be good enough for most of our
235  * STARE and PRISM jobs. Color mixing (in PRISM jobs) won't work on PostScript
236  * printers, and there's no chance I'll implement it!
237  *
238  */
239 
240     argc = agc;				/* global so everyone can use them */
241     argv = agv;
242 
243     prog_name = argv[0];		/* just for error messages */
244 
245     init_signals();			/* set up interrupt handling */
246     header();				/* PostScript header comments */
247     options();				/* command line options */
248     setup();				/* for PostScript */
249     arguments();			/* followed by each input file */
250     done();				/* print the last page etc. */
251     account();				/* job accounting data */
252 
253     exit(x_stat);			/* everything probably went OK */
254 
255 }   /* End of main */
256 
257 /*****************************************************************************/
258 
init_signals()259 init_signals()
260 
261 {
262 
263 /*
264  *
265  * Make sure we handle interrupts.
266  *
267  */
268 
269     if ( signal(SIGINT, interrupt) == SIG_IGN )  {
270 	signal(SIGINT, SIG_IGN);
271 	signal(SIGQUIT, SIG_IGN);
272 	signal(SIGHUP, SIG_IGN);
273     } else {
274 	signal(SIGHUP, interrupt);
275 	signal(SIGQUIT, interrupt);
276     }   /* End else */
277 
278     signal(SIGTERM, interrupt);
279 
280 }   /* End of init_signals */
281 
282 /*****************************************************************************/
283 
header()284 header()
285 
286 {
287 
288     int		ch;			/* return value from getopt() */
289     int		old_optind = optind;	/* for restoring optind - should be 1 */
290 
291 /*
292  *
293  * Scans the option list looking for things, like the prologue file, that we need
294  * right away but could be changed from the default. Doing things this way is an
295  * attempt to conform to Adobe's latest file structuring conventions. In particular
296  * they now say there should be nothing executed in the prologue, and they have
297  * added two new comments that delimit global initialization calls. Once we know
298  * where things really are we write out the job header, follow it by the prologue,
299  * and then add the ENDPROLOG and BEGINSETUP comments.
300  *
301  */
302 
303     while ( (ch = getopt(argc, argv, optnames)) != EOF )
304 	if ( ch == 'L' )
305 	    prologue = optarg;
306 	else if ( ch == '?' )
307 	    error(FATAL, "");
308 
309     optind = old_optind;		/* get ready for option scanning */
310 
311     fprintf(stdout, "%s", CONFORMING);
312     fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
313     fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
314     fprintf(stdout, "%s %s\n", PAGES, ATEND);
315     fprintf(stdout, "%s", ENDCOMMENTS);
316 
317     if ( cat(prologue) == FALSE )
318 	error(FATAL, "can't read %s", prologue);
319 
320     fprintf(stdout, "%s", ENDPROLOG);
321     fprintf(stdout, "%s", BEGINSETUP);
322     fprintf(stdout, "mark\n");
323 
324 }   /* End of header */
325 
326 /*****************************************************************************/
327 
options()328 options()
329 
330 {
331 
332     int		ch;			/* option name - from getopt() */
333 
334 /*
335  *
336  * Reads and processes the command line options.
337  *
338  */
339 
340     while ( (ch = getopt(argc, argv, optnames)) != EOF )  {
341 	switch ( ch )  {
342 	    case 'a':			/* aspect ratio */
343 		    fprintf(stdout, "/aspectratio %s def\n", optarg);
344 		    break;
345 
346 	    case 'c':			/* copies */
347 		    copies = atoi(optarg);
348 		    fprintf(stdout, "/#copies %s def\n", optarg);
349 		    break;
350 
351 	    case 'f':			/* new font */
352 		    fontname = get_font(optarg);
353 		    fprintf(stdout, "/font /%s def\n", fontname);
354 		    break;
355 
356 	    case 'm':			/* magnification */
357 		    fprintf(stdout, "/magnification %s def\n", optarg);
358 		    break;
359 
360 	    case 'n':			/* forms per page */
361 		    formsperpage = atoi(optarg);
362 		    fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
363 		    fprintf(stdout, "/formsperpage %s def\n", optarg);
364 		    break;
365 
366 	    case 'o':			/* output page list */
367 		    out_list(optarg);
368 		    break;
369 
370 	    case 'p':			/* landscape or portrait mode */
371 		    if ( *optarg == 'l' )
372 			fprintf(stdout, "/landscape true def\n");
373 		    else fprintf(stdout, "/landscape false def\n");
374 		    break;
375 
376 	    case 'w':			/* line width */
377 		    fprintf(stdout, "/linewidth %s def\n", optarg);
378 		    break;
379 
380 	    case 'x':			/* shift horizontally */
381 		    fprintf(stdout, "/xoffset %s def\n", optarg);
382 		    break;
383 
384 	    case 'y':			/* and vertically on the page */
385 		    fprintf(stdout, "/yoffset %s def\n", optarg);
386 		    break;
387 
388 	    case 'A':			/* force job accounting */
389 	    case 'J':
390 		    if ( (fp_acct = fopen(optarg, "a")) == NULL )
391 		    	error(FATAL, "can't open accounting file %s", optarg);
392 		    break;
393 
394 	    case 'C':			/* copy file straight to output */
395 		    if ( cat(optarg) == FALSE )
396 			error(FATAL, "can't read %s", optarg);
397 		    break;
398 
399 	    case 'E':			/* text font encoding */
400 		    fontencoding = optarg;
401 		    break;
402 
403 	    case 'L':			/* Postscript prologue file */
404 		    prologue = optarg;
405 		    break;
406 
407 	    case 'P':			/* PostScript pass through */
408 		    fprintf(stdout, "%s\n", optarg);
409 		    break;
410 
411 	    case 'R':			/* special global or page level request */
412 		    saverequest(optarg);
413 		    break;
414 
415 	    case 'D':			/* debug flag */
416 		    debug = ON;
417 		    break;
418 
419 	    case 'I':			/* ignore FATAL errors */
420 		    ignore = ON;
421 		    break;
422 
423 	    case '?':			/* don't know the option */
424 		    error(FATAL, "");
425 		    break;
426 
427 	    default:			/* don't know what to do for ch */
428 		    error(FATAL, "missing case for option %c", ch);
429 		    break;
430 	}   /* End switch */
431     }	/* End while */
432 
433     argc -= optind;			/* get ready for non-option args */
434     argv += optind;
435 
436 }   /* End of options */
437 
438 /*****************************************************************************/
439 
get_font(name)440 char *get_font(name)
441 
442     char	*name;			/* name the user asked for */
443 
444 {
445 
446     int		i;			/* for looking through fontmap[] */
447 
448 /*
449  *
450  * Called from options() to map a user's font name into a legal PostScript name.
451  * If the lookup fails *name is returned to the caller. That should let you choose
452  * any PostScript font.
453  *
454  */
455 
456     for ( i = 0; fontmap[i].name != NULL; i++ )
457 	if ( strcmp(name, fontmap[i].name) == 0 )
458 	    return(fontmap[i].val);
459 
460     return(name);
461 
462 }   /* End of get_font */
463 
464 /*****************************************************************************/
465 
setup()466 setup()
467 
468 {
469 
470 /*
471  *
472  * Handles things that must be done after the options are read but before the
473  * input files are processed.
474  *
475  */
476 
477     writerequest(0, stdout);		/* global requests eg. manual feed */
478     setencoding(fontencoding);
479     fprintf(stdout, "setup\n");
480 
481     if ( formsperpage > 1 )  {
482 	if ( cat(formfile) == FALSE )
483 	    error(FATAL, "can't read %s", formfile);
484 	fprintf(stdout, "%d setupforms\n", formsperpage);
485     }	/* End if */
486 
487     fprintf(stdout, "%s", ENDSETUP);
488 
489 }   /* End of setup */
490 
491 /*****************************************************************************/
492 
arguments()493 arguments()
494 
495 {
496 
497 /*
498  *
499  * Makes sure all the non-option command line options are processed. If we get
500  * here and there aren't any arguments left, or if '-' is one of the input files
501  * we'll process stdin.
502  *
503  */
504 
505     if ( argc < 1 )
506 	conv();
507     else
508 	while ( argc > 0 )  {
509 	    if ( strcmp(*argv, "-") == 0 )
510 		fp_in = stdin;
511 	    else if ( (fp_in = fopen(*argv, "r")) == NULL )
512 		error(FATAL, "can't open %s", *argv);
513 	    conv();
514 	    if ( fp_in != stdin )
515 		fclose(fp_in);
516 	    argc--;
517 	    argv++;
518 	}   /* End while */
519 
520 }   /* End of arguments */
521 
522 /*****************************************************************************/
523 
done()524 done()
525 
526 {
527 
528 /*
529  *
530  * Finished with the last input file, so mark the end of the pages, make sure the
531  * last page is printed, and restore the initial environment.
532  *
533  */
534 
535     fprintf(stdout, "%s", TRAILER);
536     fprintf(stdout, "done\n");
537     fprintf(stdout, "%s %s\n", DOCUMENTFONTS, fontname);
538     fprintf(stdout, "%s %d\n", PAGES, printed);
539 
540 }   /* End of done */
541 
542 /*****************************************************************************/
543 
account()544 account()
545 
546 {
547 
548 /*
549  *
550  * Writes an accounting record to *fp_acct, provided it's not NULL.
551  *
552  */
553 
554     if ( fp_acct != NULL )
555 	fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);
556 
557 }   /* End of account */
558 
559 /*****************************************************************************/
560 
conv()561 conv()
562 
563 {
564 
565     int		ch;			/* next input character */
566 
567 /*
568  *
569  * Controls the conversion of BGI files into PostScript. Not everything has been
570  * implemented, but what's been done should be good enough for our purposes.
571  *
572  */
573 
574     redirect(-1);			/* get ready for the first page */
575     bgimode = 0;
576     formfeed();
577 
578     while ( (ch = get_char()) != EOF )  {
579 	switch ( ch )  {
580 		case BRCHAR:			/* rotated character mode */
581 			    bgimode = ch;
582 			    text(90);
583 			    break;
584 
585 		case BCHAR:			/* graphical character mode */
586 			    bgimode = ch;
587 			    text(0);
588 			    break;
589 
590 		case BGRAPH:			/* graphical master mode */
591 			    bgimode = ch;
592 			    break;
593 
594 		case BSUB:			/* subroutine definition */
595 			    subr_def();
596 			    break;
597 
598 		case BRET:			/* end of subroutine */
599 			    subr_end();
600 			    break;
601 
602 		case BCALL:			/* subroutine call */
603 			    subr_call();
604 			    break;
605 
606 		case BEND:			/* end display - page */
607 			    formfeed();
608 			    break;
609 
610 		case BERASE:			/* erase - shouldn't be used */
611 			    error(FATAL, "BGI erase opcode obsolete");
612 			    break;
613 
614 		case BREP:			/* repeat */
615 			    error(FATAL, "Repeat not implemented");
616 			    repeat();
617 			    break;
618 
619 		case BSETX:			/* new x coordinate */
620 			    hgoto(get_int(0));
621 			    break;
622 
623 		case BSETY:			/* new y coordinate */
624 			    vgoto(get_int(0));
625 			    break;
626 
627 		case BSETXY:			/* new x and y coordinates */
628 			    hgoto(get_int(0));
629 			    vgoto(get_int(0));
630 			    break;
631 
632 		case BINTEN:			/* mark the current point */
633 			    fprintf(fp_out, "%d %d p\n", hpos, vpos);
634 			    break;
635 
636 		case BVISX:			/* visible x */
637 			    vector(X_COORD, VISIBLE);
638 			    break;
639 
640 		case BINVISX:			/* invisible x */
641 			    vector(X_COORD, INVISIBLE);
642 			    break;
643 
644 		case BVISY:			/* visible y */
645 			    vector(Y_COORD, VISIBLE);
646 			    break;
647 
648 		case BINVISY:			/* invisible y */
649 			    vector(Y_COORD, INVISIBLE);
650 			    break;
651 
652 		case BVEC:			/* arbitrary vector */
653 			    vector(LONGVECTOR, VISIBLE);
654 			    break;
655 
656 		case BSVEC:			/* short vector */
657 			    vector(SHORTVECTOR, VISIBLE);
658 			    break;
659 
660 		case BRECT:			/* draw rectangle */
661 			    rectangle(OUTLINE);
662 			    break;
663 
664 		case BPOINT1:			/* point plot 1 */
665 		case BPOINT:			/* point plot 2 */
666 			    point_plot(ch, get_char());
667 			    break;
668 
669 		case BLINE:			/* line plot */
670 			    line_plot();
671 			    break;
672 
673 		case BLTY:			/* line type */
674 			    fprintf(fp_out, "%s l\n", styles[get_data()]);
675 			    break;
676 
677 		case BARC:			/* circular arc */
678 			    arc(OUTLINE);
679 			    break;
680 
681 		case BFARC:			/* filled circle */
682 			    arc(FILL);
683 			    break;
684 
685 		case BFRECT:			/* filled rectangle */
686 			    rectangle(FILL);
687 			    break;
688 
689 		case BRASRECT:			/* raster rectangle */
690 			    error(FATAL, "Raster Rectangle not implemented");
691 			    break;
692 
693 		case BCOL:			/* select color */
694 			    set_color(get_data());
695 			    break;
696 
697 		case BFTRAPH:			/* filled trapezoid */
698 			    trapezoid();
699 			    break;
700 
701 		case BPAT:			/* pattern for area filling */
702 			    pattern();
703 			    break;
704 
705 		case BCSZ:			/* change BGI character 'size' */
706 			    setsize(get_data());
707 			    break;
708 
709 		case BNOISE:			/* from bad file format */
710 			    break;
711 
712 		default:			/* don't recognize the code */
713 			    error(FATAL, "bad BGI command %d (0%o)", ch, ch);
714 			    break;
715 	}   /* End switch */
716 
717 	if ( debug == ON )
718 	    fprintf(stderr, "\n");
719     }	/* End while */
720 
721     formfeed();					/* in case BEND was missing */
722 
723 }   /* End of conv */
724 
725 /*****************************************************************************/
726 
hgoto(n)727 hgoto(n)
728 
729     int		n;			/* new horizontal position */
730 
731 {
732 
733 /*
734  *
735  * Sets the current BGI horizontal position to n.
736  *
737  */
738 
739     hpos = n;
740 
741 }   /* End of hgoto */
742 
743 /*****************************************************************************/
744 
vgoto(n)745 vgoto(n)
746 
747     int		n;			/* move to this vertical position */
748 
749 {
750 
751 /*
752  *
753  * Sets the absolute vertical position to n.
754  *
755  */
756 
757     vpos = n;
758 
759 }   /* End of vgoto */
760 
761 /*****************************************************************************/
762 
setsize(n)763 setsize(n)
764 
765     int		n;			/* BGI size - just a grid separation */
766 
767 {
768 
769 /*
770  *
771  * Called when we're supposed to change the BGI character size to n. The BGI
772  * size is the grid separation in a 5 by 7 array in which characters are assumed
773  * to be built.
774  *
775  */
776 
777     bgisize = n;
778     linespace = LINESPACE(bgisize);
779 
780     fprintf(fp_out, "%d f\n", bgisize);
781 
782     if ( debug == ON )
783 	fprintf(stderr, "BGI size = %d\n", n);
784 
785 }   /* End of setsize */
786 
787 /*****************************************************************************/
788 
repeat()789 repeat()
790 
791 {
792 
793     int		count;			/* repeat this many times */
794     int		ch;			/* next input character */
795 
796 /*
797  *
798  * Haven't implemented repeats, although it wouldn't be difficult. Apparently it's
799  * not used by any graphics packages that generate BGI.
800  *
801  */
802 
803     count = get_int();			/* get the repeat count */
804 
805     while ( (ch = get_char()) != EOF  &&  ch != BENDR ) ;
806 
807 }   /* End of repeat */
808 
809 /*****************************************************************************/
810 
text(angle)811 text(angle)
812 
813     int		angle;			/* either 0 or 90 degrees */
814 
815 {
816 
817     int		ch;			/* next character from file *fp_in */
818 
819 /*
820  *
821  * Called from conv() after we've entered one of the graphical character modes.
822  * Characters are read from the input file and printed until the next mode change
823  * opcode is found (or until EOF). angle will be 90 for rotated character mode
824  * and 0 otherwise.
825  *
826  *
827  */
828 
829     fprintf(fp_out, "%d %d %d(", angle, hpos, vpos);
830 
831     while ( (ch = get_char()) != EOF )  {
832 	if ( ch == BGRAPH || ch == BCHAR || ch == BRCHAR )  {
833 	    ungetc(ch, fp_in);
834 	    position--;
835 	    break;
836 	}   /* End if */
837 
838 	switch ( ch )  {
839 	    case '\012':
840 		vgoto(vpos - linespace);
841 
842 	    case '\015':
843 		hgoto(0);
844 		fprintf(fp_out, ")t\n%d %d %d(", angle, hpos, vpos);
845 		break;
846 
847 	    case '(':
848 	    case ')':
849 	    case '\\':
850 		putc('\\', fp_out);
851 
852 	    default:
853 		if ( isascii(ch) && isprint(ch) )
854 		    putc(ch, fp_out);
855 		else fprintf(fp_out, "\\%.3o", ch & 0377);
856 		break;
857 	}   /* End switch */
858     }	/* End while */
859 
860     fprintf(fp_out, ") t\n");
861 
862 }   /* End of text */
863 
864 /*****************************************************************************/
865 
formfeed()866 formfeed()
867 
868 {
869 
870     int		ch;			/* repeat count for this page */
871 
872 /*
873  *
874  * Does whatever is needed to print the last page and get ready for the next one.
875  * It's called, from conv(), after a BEND code is processed. I'm ignoring the
876  * copy count that's expected to follow each page.
877  *
878  */
879 
880     if ( bgimode == BGRAPH && (ch = get_char()) != EOF  &&  ! (ch & MSB) )  {
881 	ungetc(ch, fp_in);
882 	position--;
883     }	/* End if */
884 
885     if ( fp_out == stdout )		/* count the last page */
886 	printed++;
887 
888     fprintf(fp_out, "cleartomark\n");
889     fprintf(fp_out, "showpage\n");
890     fprintf(fp_out, "saveobj restore\n");
891     fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);
892 
893     while ( (ch = get_char()) == 0 ) ;	/* skip any NULL characters */
894     ungetc(ch, fp_in);
895     position--;
896 
897     if ( ungetc(getc(fp_in), fp_in) == EOF )
898 	redirect(-1);
899     else redirect(++page);
900 
901     fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
902     fprintf(fp_out, "/saveobj save def\n");
903     fprintf(fp_out, "mark\n");
904     writerequest(printed+1, fp_out);
905     fprintf(fp_out, "%d pagesetup\n", printed+1);
906 
907     setsize(bgisize);
908     hpos = vpos = 0;
909 
910 }    /* End of formfeed */
911 
912 /*****************************************************************************/
913 
subr_def()914 subr_def()
915 
916 {
917 
918 /*
919  *
920  * Starts a subroutine definition. All subroutines are defined as PostScript
921  * procedures that begin with the character S and end with the subroutine's id
922  * (a number between 0 and 63 - I guess). The primary, and perhaps only use of
923  * subroutines is in special color plots produced by several graphics libraries,
924  * and even there it's not all that common. I've also chosen not to worry about
925  * nested subroutine definitions - that would certainly be overkill!
926  *
927  * All subroutines set up their own (translated) coordinate system, do their work
928  * in that system, and restore things when they exit. To make everything work
929  * properly we save the current point (in shpos and svpos), set our position to
930  * (0, 0), and restore things at the end of the subroutine definition. That means
931  * hpos and vpos measure the relative displacement after a subroutine returns, and
932  * we save those values in the displacement[] array. The displacements are used
933  * (in subr_call()) to properly adjust our position after each subroutine call,
934  * and all subroutines are called with the current x and y coordinates on top of
935  * the stack.
936  *
937  */
938 
939     if ( in_subr == TRUE )		/* a nested subroutine definition?? */
940 	error(FATAL, "can't handle nested subroutine definitions");
941 
942     if ( (subr_id = get_data()) == EOF )
943 	error(FATAL, "missing subroutine identifier");
944 
945     if ( in_global == FALSE )  {	/* just used to reduce file size some */
946 	fprintf(fp_out, "cleartomark\n");
947 	fprintf(fp_out, "saveobj restore\n");
948 	fprintf(fp_out, "%s", BEGINGLOBAL);
949 	in_global = TRUE;
950     }	/* End if */
951 
952     fprintf(fp_out, "/S%d {\n", subr_id);
953     fprintf(fp_out, "gsave translate\n");
954 
955     shpos = hpos;			/* save our current position */
956     svpos = vpos;
957 
958     hgoto(0);				/* start at the origin */
959     vgoto(0);
960 
961     in_subr = TRUE;			/* in a subroutine definition */
962 
963 }   /* End of subr_def */
964 
965 /*****************************************************************************/
966 
subr_end()967 subr_end()
968 
969 {
970 
971     int		ch;			/* for looking at next opcode */
972 
973 /*
974  *
975  * Handles stuff needed at the end of each subroutine. Want to remember the change
976  * in horizontal and vertical positions for each subroutine so we can adjust our
977  * position after each call - just in case. The current position was set to (0, 0)
978  * before we started the subroutine definition, so when we get here hpos and vpos
979  * are the relative displacements after the subroutine is called. They're saved in
980  * the displacement[] array and used to adjust the current position when we return
981  * from a subroutine.
982  *
983  */
984 
985     if ( in_subr == FALSE )		/* not in a subroutine definition?? */
986 	error(FATAL, "subroutine end without corresponding start");
987 
988     fprintf(fp_out, "grestore\n");
989     fprintf(fp_out, "} def\n");
990 
991     if ( in_global == TRUE && (ch = get_char()) != BSUB )  {
992 	fprintf(fp_out, "%s", ENDGLOBAL);
993 	fprintf(fp_out, "/saveobj save def\n");
994 	fprintf(fp_out, "mark\n");
995 	in_global = FALSE;
996     }	/* End if */
997 
998     ungetc(ch, fp_in);			/* put back the next opcode */
999 
1000     displacement[subr_id].dx = hpos;
1001     displacement[subr_id].dy = vpos;
1002 
1003     hgoto(shpos);			/* back to where we started */
1004     vgoto(svpos);
1005 
1006     in_subr = FALSE;			/* done with the definition */
1007 
1008 }   /* End of subr_end */
1009 
1010 /*****************************************************************************/
1011 
subr_call()1012 subr_call()
1013 
1014 {
1015 
1016     int		ch;			/* next byte from *fp_in */
1017     int		id;			/* subroutine id if ch wasn't an opcode */
1018 
1019 /*
1020  *
1021  * Handles subroutine calls. Everything that follows the BCALL opcode (up to the
1022  * next opcode) is taken as a subroutine identifier - thus the loop that generates
1023  * the subroutine calls.
1024  *
1025  */
1026 
1027     while ( (ch = get_char()) != EOF && (ch & MSB) )  {
1028 	id = ch & DMASK;
1029 	fprintf(fp_out, "%d %d S%d\n", hpos, vpos, id);
1030 
1031 	hgoto(hpos + displacement[id].dx);	/* adjust our position */
1032 	vgoto(vpos + displacement[id].dy);
1033     }	/* End while */
1034 
1035     ungetc(ch, fp_in);
1036 
1037 }   /* End of subr_call */
1038 
1039 /*****************************************************************************/
1040 
vector(var,mode)1041 vector(var, mode)
1042 
1043     int		var;			/* coordinate that varies next? */
1044     int		mode;			/* VISIBLE or INVISIBLE vectors */
1045 
1046 {
1047 
1048     int		ch;			/* next character from *fp_in */
1049     int		x, y;			/* line drawn to this point */
1050     int		count = 0;		/* number of points so far */
1051 
1052 /*
1053  *
1054  * Handles plotting of all types of BGI vectors. If it's a manhattan vector var
1055  * specifies which coordinate will be changed by the next number in the input
1056  * file.
1057  *
1058  */
1059 
1060     x = hpos;				/* set up the first point */
1061     y = vpos;
1062 
1063     while ( (ch = get_char()) != EOF  &&  ch & MSB )  {
1064 	if ( var == X_COORD )		/* next length is change in x */
1065 	    x += get_int(ch);
1066 	else if ( var == Y_COORD )	/* it's the change in y */
1067 	    y += get_int(ch);
1068 	else if ( var == LONGVECTOR )  {	/* long vector */
1069 	    x += get_int(ch);
1070 	    y += get_int(0);
1071 	} else {			/* must be a short vector */
1072 	    x += ((ch & MSBMAG) * ((ch & SGNB) ? -1 : 1));
1073 	    y += (((ch = get_data()) & MSBMAG) * ((ch & SGNB) ? -1 : 1));
1074 	}   /* End else */
1075 
1076 	if ( mode == VISIBLE )  {	/* draw the line segment */
1077 	    fprintf(fp_out, "%d %d\n", hpos - x, vpos - y);
1078 	    count++;
1079 	}   /* End if */
1080 
1081 	hgoto(x);			/* adjust the current BGI position */
1082 	vgoto(y);
1083 
1084 	if ( var == X_COORD )		/* vertical length comes next */
1085 	    var = Y_COORD;
1086 	else if ( var == Y_COORD )	/* change horizontal next */
1087 	    var = X_COORD;
1088     }	/* End while */
1089 
1090     if ( count > 0 )
1091 	fprintf(fp_out, "%d %d v\n", hpos, vpos);
1092 
1093     ungetc(ch, fp_in);			/* it wasn't part of the vector */
1094     position--;
1095 
1096 }   /* End of vector */
1097 
1098 /*****************************************************************************/
1099 
rectangle(mode)1100 rectangle(mode)
1101 
1102     int		mode;			/* FILL or OUTLINE the rectangle */
1103 
1104 {
1105 
1106     int		deltax;			/* displacement for horizontal side */
1107     int		deltay;			/* same but for vertical sides */
1108 
1109 /*
1110  *
1111  * Draws a rectangle and either outlines or fills it, depending on the value of
1112  * mode. Would be clearer, and perhaps better, if {stroke} or {fill} were put on
1113  * the stack instead of 0 or 1. R could then define the path and just do an exec
1114  * to fill or stroke it.
1115  *
1116  */
1117 
1118     deltax = get_int(0);		/* get the height and width */
1119     deltay = get_int(0);
1120 
1121     if ( mode == OUTLINE )
1122 	fprintf(fp_out, "0 %d %d %d %d R\n", deltax, deltay, hpos, vpos);
1123     else fprintf(fp_out, "1 %d %d %d %d R\n", deltax, deltay, hpos, vpos);
1124 
1125 }   /* End of rectangle */
1126 
1127 /*****************************************************************************/
1128 
trapezoid()1129 trapezoid()
1130 
1131 {
1132 
1133     int		kind;			/* which sides are parallel */
1134     int		d[6];			/* true displacements - depends on kind */
1135 
1136 /*
1137  *
1138  * Handles filled trapeziods. A data byte of 0101 following the opcode means the
1139  * horizontal sides are parallel, 0102 means the vertical sides are parallel.
1140  * Filling is handled by eofill so we don't need to get things in the right order.
1141  *
1142  */
1143 
1144     kind = get_data();
1145 
1146     d[0] = get_int(0);
1147     d[1] = 0;
1148     d[2] = get_int(0);
1149     d[3] = get_int(0);
1150     d[4] = get_int(0);
1151     d[5] = 0;
1152 
1153     if ( kind == 2 )  {			/* parallel sides are vertical */
1154 	d[1] = d[0];
1155 	d[0] = 0;
1156 	d[5] = d[4];
1157 	d[4] = 0;
1158     }	/* End if */
1159 
1160     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);
1161 
1162 }   /* End of trapezoid */
1163 
1164 /*****************************************************************************/
1165 
point_plot(mode,ch)1166 point_plot(mode, ch)
1167 
1168     int		mode;			/* plotting mode BPOINT or BPOINT1 */
1169     int		ch;			/* will be placed at the points */
1170 
1171 {
1172 
1173     int		c;			/* next character from input file */
1174     int		x, y;			/* ch gets put here next */
1175     int		deltax;			/* x increment for BPOINT1 mode */
1176 
1177 /*
1178  *
1179  * The two point plot modes are used to place a character at selected points. The
1180  * difference in the two modes, namely BPOINT and BPOINT1, is the way we get the
1181  * coordinates of the next point. In BPOINT1 the two bytes immediately following
1182  * ch select a constant horizontal change, while both coordinates are given for
1183  * all points in BPOINT mode.
1184  *
1185  */
1186 
1187     if ( mode == BPOINT1 )  {		/* first integer is change in x */
1188 	deltax = get_int(0);
1189 	x = hpos - deltax;
1190     }	/* End if */
1191 
1192     while ( (c = get_char()) != EOF  &&  (c & MSB) )  {
1193 	if ( mode == BPOINT1 )  {	/* only read y coordinate */
1194 	    y = get_int(c);
1195 	    x += deltax;
1196 	} else {			/* get new x and y from input file */
1197 	    x = get_int(c);
1198 	    y = get_int(0);
1199 	}   /* End else */
1200 
1201 	hgoto(x);			/* adjust BGI position */
1202 	vgoto(y);
1203 
1204 	fprintf(fp_out, "%d %d\n", hpos, vpos);
1205     }	/* End while */
1206 
1207     putc('(', fp_out);
1208 
1209     switch ( ch )  {
1210 	case '(':
1211 	case ')':
1212 	case '\\':
1213 		putc('\\', fp_out);
1214 
1215 	default:
1216 		putc(ch, fp_out);
1217     }	/* End switch */
1218 
1219     fprintf(fp_out, ")pp\n");
1220 
1221     ungetc(c, fp_in);			/* it wasn't part of the point plot */
1222     position--;
1223 
1224 }   /* End of point_plot */
1225 
1226 /*****************************************************************************/
1227 
line_plot()1228 line_plot()
1229 
1230 {
1231 
1232     int		c;			/* next input character from fp_in */
1233     int		deltax;			/* change in x coordinate */
1234     int		x0, y0;			/* starting point for next segment */
1235     int		x1, y1;			/* endpoint of the line */
1236     int		count = 0;		/* number of points so far */
1237 
1238 /*
1239  *
1240  * Essentially the same format as BPOINT1, except that in this case we connect
1241  * pairs of points by line segments.
1242  *
1243  */
1244 
1245     deltax = get_int(0);		/* again the change in x is first */
1246 
1247     x1 = hpos;				/* so it works first time through */
1248     y1 = get_int(0);
1249 
1250     while ( (c = get_char()) != EOF  &&  (c & MSB) )  {
1251 	x0 = x1;			/* line starts here */
1252 	y0 = y1;
1253 
1254 	x1 += deltax;			/* and ends at this point */
1255     	y1 = get_int(c);
1256 
1257 	fprintf(fp_out, "%d %d\n", -deltax, y0 - y1);
1258 	count++;
1259     }	/* End while */
1260 
1261     hgoto(x1);				/* adjust current BGI position */
1262     vgoto(y1);
1263 
1264     if ( count > 0 )
1265 	fprintf(fp_out, "%d %d v\n", hpos, vpos);
1266 
1267     ungetc(c, fp_in);			/* wasn't part of the line */
1268     position--;
1269 
1270 }   /* End of line_plot */
1271 
1272 /*****************************************************************************/
1273 
arc(mode)1274 arc(mode)
1275 
1276     int		mode;			/* FILL or OUTLINE the path */
1277 
1278 {
1279 
1280     int		dx1, dy1;		/* displacements for first point */
1281     int		dx2, dy2;		/* same for the second point */
1282     int		radius;			/* of the arc */
1283     int		angle1, angle2;		/* starting and ending angles */
1284 
1285 /*
1286  *
1287  * Called whenever we need to draw an arc. I'm ignoring filled slices for now.
1288  *
1289  */
1290 
1291     dx1 = get_int(0);			/* displacements relative to center */
1292     dy1 = get_int(0);
1293     dx2 = get_int(0);
1294     dy2 = get_int(0);
1295 
1296     radius = get_int(0);		/* and the radius */
1297 
1298     if ( radius == 0 )			/* nothing to do */
1299 	return;
1300 
1301     angle1 = (atan2((double) dy1, (double) dx1) * 360) / (2 * PI) + .5;
1302     angle2 = (atan2((double) dy2, (double) dx2) * 360) / (2 * PI) + .5;
1303 
1304     fprintf(fp_out, "%d %d %d %d %d arcn stroke\n", hpos, vpos, radius, angle1, angle2);
1305 
1306 }   /* End of arc */
1307 
1308 /*****************************************************************************/
1309 
pattern()1310 pattern()
1311 
1312 {
1313 
1314     double	red = 0;		/* color components */
1315     double	green = 0;
1316     double	blue = 0;
1317     int		kind;			/* corse or fine pattern */
1318     int		val;			/* next color data byte */
1319     int		i;			/* loop index */
1320 
1321 /*
1322  *
1323  * Handles patterns by setting the current color based of the values assigned to
1324  * the next four data bytes. BGI supports two kinds of patterns (fine or coarse)
1325  * but I'm handling each in the same way - for now. In a fine pattern the four
1326  * data bytes assign a color to four individual pixels (upperleft first) while
1327  * in a coarse pattern the four colors are assigned to groups of four pixels,
1328  * for a total of 16. Again the first color goes to the group in the upper left
1329  * corner. The byte immediately following the BPAT opcode selects fine (040) or
1330  * coarse (041) patterns. The PostScript RGB color is assigned by averaging the
1331  * RED, GREEN, and BLUE components assigned to the four pixels (or groups of
1332  * pixels). Acceptable results, but there's no distinction between fine and
1333  * coarse patterns.
1334  *
1335  */
1336 
1337     if ( (kind = get_char()) == EOF )
1338 	error(FATAL, "bad pattern command");
1339 
1340     for ( i = 0; i < 4; i++ )  {
1341 	val = get_data();
1342 	red += get_color(val, RED);
1343 	green += get_color(val, GREEN);
1344 	blue += get_color(val, BLUE);
1345     }	/* End for */
1346 
1347     fprintf(fp_out, "%g %g %g c\n", red/4, green/4, blue/4);
1348 
1349 }   /* End of pattern */
1350 
1351 /*****************************************************************************/
1352 
get_color(val,component)1353 get_color(val, component)
1354 
1355     int		val;			/* color data byte */
1356     int		component;		/* RED, GREEN, or BLUE component */
1357 
1358 {
1359 
1360 
1361     int		primary;		/* color mixing mode - bits 2 to 4 */
1362     int		plane;			/* primary color plane - bits 5 to 7 */
1363     unsigned	rgbcolor;		/* PostScript expects an RGB triple */
1364 
1365 /*
1366  *
1367  * Picks the requested color component (RED, GREEN, or BLUE) from val and returns
1368  * the result to the caller. BGI works with Cyan, Yellow, and Magenta so the one's
1369  * complement stuff (following the exclusive or'ing) recovers the RED, BLUE, and
1370  * GREEN components that PostScript's setrgbcolor operator needs. The PostScript
1371  * interpreter in the ColorScript 100 has a setcmycolor operator, but it's not
1372  * generally available so I've decided to stick with setrgbcolor.
1373  *
1374  */
1375 
1376     primary = (val >> 3) & 07;
1377     plane = val & 07;
1378     rgbcolor = (~(primary ^ plane)) & 07;
1379 
1380     if ( debug == ON )
1381 	fprintf(stderr, "val = %o, primary = %o, plane = %o, rgbcolor = %o\n",
1382 		val, primary, plane, rgbcolor);
1383 
1384     switch ( component )  {
1385 	case RED:
1386 		return(rgbcolor>>2);
1387 
1388 	case GREEN:
1389 		return(rgbcolor&01);
1390 
1391 	case BLUE:
1392 		return((rgbcolor>>1)&01);
1393 
1394 	default:
1395 		error(FATAL, "unknown color component");
1396 		return(0);
1397     }	/* End switch */
1398 
1399 }   /* End of get_color */
1400 
1401 /*****************************************************************************/
1402 
set_color(val)1403 set_color(val)
1404 
1405     int		val;			/* color data byte */
1406 
1407 {
1408 
1409 /*
1410  *
1411  * Arranges to have the color set to the value requested in the BGI data byte val.
1412  *
1413  */
1414 
1415     fprintf(fp_out, "%d %d %d c\n", get_color(val, RED), get_color(val, GREEN), get_color(val, BLUE));
1416 
1417 }   /* End of set_color */
1418 
1419 /*****************************************************************************/
1420 
get_int(highbyte)1421 get_int(highbyte)
1422 
1423     int		highbyte;		/* already read this byte */
1424 
1425 {
1426 
1427     int		lowbyte;		/* this and highbyte make the int */
1428 
1429 /*
1430  *
1431  * Figures out the value on the integer (sign magnitude form) that's next in the
1432  * input file. If highbyte is nonzero we'll use it and the next byte to build the
1433  * integer, otherwise two bytes are read from fp_in.
1434  *
1435  */
1436 
1437 
1438     if ( highbyte == 0 )		/* need to read the first byte */
1439 	highbyte = get_data();
1440 
1441     lowbyte = get_data();		/* always need the second byte */
1442 
1443     return(highbyte & SGNB ? -MAG(highbyte, lowbyte) : MAG(highbyte, lowbyte));
1444 
1445 }   /* End of get_int */
1446 
1447 /*****************************************************************************/
1448 
get_data()1449 get_data()
1450 
1451 {
1452 
1453     int		val;			/* data value returned to caller */
1454 
1455 /*
1456  *
1457  * Called when we expect to find a single data character in the input file. The
1458  * data bit is turned off and the resulting value is returned to the caller.
1459  *
1460  */
1461 
1462     if ( (val = get_char()) == EOF  ||  ! (val & MSB) )
1463 	error(FATAL, "missing data value");
1464 
1465     return(val & DMASK);
1466 
1467 }   /* End of get_data */
1468 
1469 /*****************************************************************************/
1470 
get_char()1471 get_char()
1472 
1473 {
1474 
1475     int		ch;			/* character we just read */
1476 
1477 /*
1478  *
1479  * Reads the next character from file *fp_in and returns the value to the caller.
1480  * This routine isn't really needed, but we may want to deal directly with some
1481  * screwball file formats so I thought it would probably be a good idea to isolate
1482  * all the input in one routine that could be easily changed.
1483  *
1484  */
1485 
1486     if ( (ch = getc(fp_in)) != EOF )  {
1487 	position++;
1488 	ch &= CHMASK;
1489     }	/* End if */
1490 
1491     if ( debug == ON )
1492 	fprintf(stderr, "%o ", ch);
1493 
1494     return(ch);
1495 
1496 }   /* End of get_char */
1497 
1498 /*****************************************************************************/
1499 
redirect(pg)1500 redirect(pg)
1501 
1502     int		pg;			/* next page we're printing */
1503 
1504 {
1505 
1506     static FILE	*fp_null = NULL;	/* if output is turned off */
1507 
1508 /*
1509  *
1510  * If we're not supposed to print page pg, fp_out will be directed to /dev/null,
1511  * otherwise output goes to stdout.
1512  *
1513  */
1514 
1515     if ( pg >= 0 && in_olist(pg) == ON )
1516 	fp_out = stdout;
1517     else if ( (fp_out = fp_null) == NULL )
1518 	fp_out = fp_null = fopen("/dev/null", "w");
1519 
1520 }   /* End of redirect */
1521 
1522 /*****************************************************************************/
1523 
1524