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