xref: /plan9/sys/src/cmd/postscript/postdmd/postdmd.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 /*
2  *
3  * postdmd - PostScript translator for DMD bitmap files.
4  *
5  * A simple program that can be used to print DMD bitmaps on PostScript printers.
6  * Much of the code was borrowed from abm, which was written by Guy Riddle.
7  *
8  * Although the program supports two different input bitmap formats, by far the
9  * most important is the Eighth (and Ninth) Edition bitfile format. A bitmap in
10  * the bitfile format begins with a 10 byte header with the first two bytes set to
11  * zero. The next 8 bytes set the x and y coordinates of the bitmap's origin and
12  * corner (ie. the upper left and lower right corners). The compressed raster data
13  * follows the header and consists of control bytes followed an appropriate number
14  * of data bytes. Control bytes (ie. n) less than 127 means read the next 2*n bytes
15  * of raster data directly from the input file, while if n is larger than 128 we
16  * read two bytes from the input file and replicate the bytes n-128 times. After
17  * each scan line is recovered it's exclusive-or'd with the preceeding line to
18  * generate the real raster data.
19  *
20  * After each raster line is recovered postdmd encodes it in a slightly different
21  * format that's designed to be unpacked by a PostScript procedure that's defined
22  * in the prologue. By default no exclusive-or'ing is done and packing of pattern
23  * data can be based on any number of bytes rather than just the next two bytes.
24  * By default 6 byte patterns are used, but any number can be selected with the -b
25  * option. A non-positive argument (eg. -b0) disables all pattern encoding. Larger
26  * patterns increase the size of the output file, but reduce the work load that's
27  * forced on the PostScript interpreter. The default choices I've made (ie. 6 byte
28  * patterns and no exclusive-or'ing) do a decent balancing job across currently
29  * available PostScript printers. Larger patterns (eg. -b16) increase the output
30  * file size, but may be appropriate if you're running at a high baud rate (eg.
31  * 19.2KB), while smaller patter size (eg. -b4) may help if you've got a printer
32  * with a fast processor (eg. a PS-810).
33  *
34  * The encoding produced by the program (and decoded on the printer) looks like,
35  *
36  * 	bytes patterns count
37  *
38  * where bytes and count are decimal integers and patterns is a hex string. Bytes
39  * is the number of bytes represented by the hex patterns and count is the number
40  * of additional times the patterns should be repeated. For example,
41  *
42  * 	2 FFFF 4
43  * 	5 FFFFFFFFFF 1
44  *     10 FFFFFFFFFFFFFFFFFFFF 0
45  *
46  * all represent 10 consecutive bytes of ones. Scanlines are terminated by a 0 on
47  * a line by itself.
48  *
49  * The PostScript prologue is copied from *prologue before any of the input files
50  * are translated. The program expects that the following PostScript procedures
51  * are defined in that file:
52  *
53  *	setup
54  *
55  *	  mark ... setup -
56  *
57  *	    Handles special initialization stuff that depends on how this program
58  *	    was called. Expects to find a mark followed by key/value pairs on the
59  *	    stack. The def operator is applied to each pair up to the mark, then
60  *	    the default state is set up.
61  *
62  *	pagesetup
63  *
64  *	  page pagesetup -
65  *
66  *	    Does whatever is needed to set things up for the next page. Expects
67  *	    to find the current page number on the stack.
68  *
69  *	bitmap
70  *
71  *	  v8format flip scanlength scanlines bitmap -
72  *
73  *	    Prints the bitmap that's read from standard input. The bitmap consists
74  *	    of scanlines lines, each of which includes scanlength pixels. If
75  *	    v8format is true the picture is assumed to be an Eighth Edition bitmap,
76  *	    and the exclusive-or'ing will be done on the printer.
77  *
78  *	done
79  *
80  *	  done
81  *
82  *	    Makes sure the last page is printed. Only needed when we're printing
83  *	    more than one page on each sheet of paper.
84  *
85  * Many default values, like the magnification and orientation, are defined in
86  * the prologue, which is where they belong. If they're changed (by options), an
87  * appropriate definition is made after the prologue is added to the output file.
88  * The -P option passes arbitrary PostScript through to the output file. Among
89  * other things it can be used to set (or change) values that can't be accessed by
90  * other options.
91  *
92  */
93 
94 #include <stdio.h>
95 #include <signal.h>
96 #include <ctype.h>
97 #ifdef plan9
98 #define	isascii(c)	((unsigned char)(c)<=0177)
99 #endif
100 #include <sys/types.h>
101 #include <fcntl.h>
102 
103 #include "comments.h"			/* PostScript file structuring comments */
104 #include "gen.h"			/* general purpose definitions */
105 #include "path.h"			/* for the prologue */
106 #include "ext.h"			/* external variable declarations */
107 
108 char	*optnames = "a:b:c:fm:n:o:p:ux:y:A:C:E:J:L:P:DI";
109 
110 char	*prologue = POSTDMD;		/* default PostScript prologue */
111 char	*formfile = FORMFILE;		/* stuff for multiple pages per sheet */
112 
113 int	bbox[2] = {0, 0};		/* upper right coordinates only */
114 
115 int	formsperpage = 1;		/* page images on each piece of paper */
116 int	copies = 1;			/* and this many copies of each sheet */
117 
118 int	bytespp = 6;			/* bytes per pattern - on output */
119 int	flip = FALSE;			/* ones complement the bitmap */
120 int	v8undo = TRUE;			/* xor'ing done on host if TRUE */
121 int	v8format = FALSE;		/* for Eighth Edition bitmaps */
122 
123 int	page = 0;			/* last page we worked on */
124 int	printed = 0;			/* and the number of pages printed */
125 
126 int	patterns;			/* 16 bit patterns per scan line */
127 int	scanlines;			/* lines in the bitmap */
128 int	patcount = 0;			/* should be patterns * scanlines */
129 
130 char	*raster = NULL;			/* next raster line */
131 char	*prevrast = NULL;		/* and the previous one - v8format */
132 char	*rptr;				/* next free byte in raster */
133 char	*eptr;				/* one past the last byte in raster */
134 
135 FILE	*fp_in = NULL;			/* read from this file */
136 FILE	*fp_out = stdout;		/* and write stuff here */
137 FILE	*fp_acct = NULL;		/* for accounting data */
138 
139 /*****************************************************************************/
140 
main(agc,agv)141 main(agc, agv)
142 
143     int		agc;
144     char	*agv[];
145 
146 {
147 
148 /*
149  *
150  * A simple program that translates DMD bitmap files into PostScript. There can
151  * be more than one bitmap per file, but none can be split across input files.
152  * Each bitmap goes on a page by itself.
153  *
154  */
155 
156     argc = agc;				/* other routines may want them */
157     argv = agv;
158 
159     prog_name = argv[0];		/* really just for error messages */
160 
161     init_signals();			/* sets up interrupt handling */
162     header();				/* PostScript header comments */
163     options();				/* handle the command line options */
164     setup();				/* for PostScript */
165     arguments();			/* followed by each input file */
166     done();				/* print the last page etc. */
167     account();				/* job accounting data */
168 
169     exit(x_stat);			/* not much could be wrong */
170 
171 }   /* End of main */
172 
173 /*****************************************************************************/
174 
init_signals()175 init_signals()
176 
177 {
178 
179 /*
180  *
181  * Make sure we handle interrupts.
182  *
183  */
184 
185     if ( signal(SIGINT, interrupt) == SIG_IGN )  {
186 	signal(SIGINT, SIG_IGN);
187 	signal(SIGQUIT, SIG_IGN);
188 	signal(SIGHUP, SIG_IGN);
189     } else {
190 	signal(SIGHUP, interrupt);
191 	signal(SIGQUIT, interrupt);
192     }   /* End else */
193 
194     signal(SIGTERM, interrupt);
195 
196 }   /* End of init_signals */
197 
198 /*****************************************************************************/
199 
header()200 header()
201 
202 {
203 
204     int		ch;			/* return value from getopt() */
205     int		old_optind = optind;	/* for restoring optind - should be 1 */
206 
207 /*
208  *
209  * Scans the option list looking for things, like the prologue file, that we need
210  * right away but could be changed from the default. Doing things this way is an
211  * attempt to conform to Adobe's latest file structuring conventions. In particular
212  * they now say there should be nothing executed in the prologue, and they have
213  * added two new comments that delimit global initialization calls. Once we know
214  * where things really are we write out the job header, follow it by the prologue,
215  * and then add the ENDPROLOG and BEGINSETUP comments.
216  *
217  */
218 
219     while ( (ch = getopt(argc, argv, optnames)) != EOF )
220 	if ( ch == 'L' )
221 	    prologue = optarg;
222 	else if ( ch == '?' )
223 	    error(FATAL, "");
224 
225     optind = old_optind;		/* get ready for option scanning */
226 
227     fprintf(stdout, "%s", CONFORMING);
228     fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
229     fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
230     fprintf(stdout, "%s %s\n", PAGES, ATEND);
231     fprintf(stdout, "%s", ENDCOMMENTS);
232 
233     if ( cat(prologue) == FALSE )
234 	error(FATAL, "can't read %s", prologue);
235 
236     fprintf(stdout, "%s", ENDPROLOG);
237     fprintf(stdout, "%s", BEGINSETUP);
238     fprintf(stdout, "mark\n");
239 
240 }   /* End of header */
241 
242 /*****************************************************************************/
243 
options()244 options()
245 
246 {
247 
248     int		ch;			/* return value from getopt() */
249 
250 /*
251  *
252  * Reads and processes the command line options. Added the -P option so arbitrary
253  * PostScript code can be passed through. Expect it could be useful for changing
254  * definitions in the prologue for which options have not been defined.
255  *
256  */
257 
258     while ( (ch = getopt(argc, argv, optnames)) != EOF )  {
259 	switch ( ch )  {
260 	    case 'a':			/* aspect ratio */
261 		    fprintf(stdout, "/aspectratio %s def\n", optarg);
262 		    break;
263 
264 	    case 'b':			/* bytes per pattern */
265 		    bytespp = atoi(optarg);
266 		    break;
267 
268 	    case 'c':			/* copies */
269 		    copies = atoi(optarg);
270 		    fprintf(stdout, "/#copies %s store\n", optarg);
271 		    break;
272 
273 	    case 'f':			/* ones complement - sort of */
274 		    flip = TRUE;
275 		    break;
276 
277 	    case 'm':			/* magnification */
278 		    fprintf(stdout, "/magnification %s def\n", optarg);
279 		    break;
280 
281 	    case 'n':			/* forms per page */
282 		    formsperpage = atoi(optarg);
283 		    fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
284 		    fprintf(stdout, "/formsperpage %s def\n", optarg);
285 		    break;
286 
287 	    case 'o':			/* output page list */
288 		    out_list(optarg);
289 		    break;
290 
291 	    case 'p':			/* landscape or portrait mode */
292 		    if ( *optarg == 'l' )
293 			fprintf(stdout, "/landscape true def\n");
294 		    else fprintf(stdout, "/landscape false def\n");
295 		    break;
296 
297 	    case 'u':			/* don't undo Eighth Edition bitmaps */
298 		    v8undo = FALSE;
299 		    break;
300 
301 	    case 'x':			/* shift things horizontally */
302 		    fprintf(stdout, "/xoffset %s def\n", optarg);
303 		    break;
304 
305 	    case 'y':			/* and vertically on the page */
306 		    fprintf(stdout, "/yoffset %s def\n", optarg);
307 		    break;
308 
309 	    case 'A':			/* force job accounting */
310 	    case 'J':
311 		    if ( (fp_acct = fopen(optarg, "a")) == NULL )
312 			error(FATAL, "can't open accounting file %s", optarg);
313 		    break;
314 
315 	    case 'C':			/* copy file straight to output */
316 		    if ( cat(optarg) == FALSE )
317 			error(FATAL, "can't read %s", optarg);
318 		    break;
319 
320 	    case 'E':			/* text font encoding - unnecessary */
321 		    fontencoding = optarg;
322 		    break;
323 
324 	    case 'L':			/* PostScript prologue file */
325 		    prologue = optarg;
326 		    break;
327 
328 	    case 'P':			/* PostScript pass through */
329 		    fprintf(stdout, "%s\n", optarg);
330 		    break;
331 
332 	    case 'R':			/* special global or page level request */
333 		    saverequest(optarg);
334 		    break;
335 
336 	    case 'D':			/* debug flag */
337 		    debug = ON;
338 		    break;
339 
340 	    case 'I':			/* ignore FATAL errors */
341 		    ignore = ON;
342 		    break;
343 
344 	    case '?':			/* don't understand the option */
345 		    error(FATAL, "");
346 		    break;
347 
348 	    default:			/* don't know what to do for ch */
349 		    error(FATAL, "missing case for option %c\n", ch);
350 		    break;
351 	}   /* End switch */
352     }   /* End while */
353 
354     argc -= optind;			/* get ready for non-option args */
355     argv += optind;
356 
357 }   /* End of options */
358 
359 /*****************************************************************************/
360 
setup()361 setup()
362 
363 {
364 
365 /*
366  *
367  * Handles things that must be done after the options are read but before the
368  * input files are processed.
369  *
370  */
371 
372     writerequest(0, stdout);		/* global requests eg. manual feed */
373     setencoding(fontencoding);		/* unnecessary */
374     fprintf(stdout, "setup\n");
375 
376     if ( formsperpage > 1 )  {		/* followed by stuff for multiple pages */
377 	if ( cat(formfile) == FALSE )
378 	    error(FATAL, "can't read %s", formfile);
379 	fprintf(stdout, "%d setupforms\n", formsperpage);
380     }	/* End if */
381 
382     fprintf(stdout, "%s", ENDSETUP);
383 
384 }   /* End of setup */
385 
386 /*****************************************************************************/
387 
arguments()388 arguments()
389 
390 {
391 
392     FILE	*fp;			/* next input file */
393 
394 /*
395  *
396  * Makes sure all the non-option command line arguments are processed. If we get
397  * here and there aren't any arguments left, or if '-' is one of the input files
398  * we'll process stdin.
399  *
400  */
401 
402     if ( argc < 1 )
403 	bitmap(stdin);
404     else  {				/* at least one argument is left */
405 	while ( argc > 0 )  {
406 	    if ( strcmp(*argv, "-") == 0 )
407 		fp = stdin;
408 	    else if ( (fp = fopen(*argv, "r")) == NULL )
409 		error(FATAL, "can't open %s", *argv);
410 	    bitmap(fp);
411 	    if ( fp != stdin )
412 		fclose(fp);
413 	    argc--;
414 	    argv++;
415 	}   /* End while */
416     }   /* End else */
417 
418 }   /* End of arguments */
419 
420 /*****************************************************************************/
421 
done()422 done()
423 
424 {
425 
426 /*
427  *
428  * Finished with all the input files, so mark the end of the pages with a TRAILER
429  * comment, make sure the last page prints, and add things like the PAGES comment
430  * that can only be determined after all the input files have been read.
431  *
432  */
433 
434     fprintf(stdout, "%s", TRAILER);
435     fprintf(stdout, "done\n");
436     fprintf(stdout, "%s 0 0 %d %d\n", BOUNDINGBOX, (bbox[0]*72+100)/100, (bbox[1]*72+100)/100);
437     fprintf(stdout, "%s %d\n", PAGES, printed);
438 
439 }   /* End of done */
440 
441 /*****************************************************************************/
442 
account()443 account()
444 
445 {
446 
447 /*
448  *
449  * Writes an accounting record to *fp_acct provided it's not NULL. Accounting is
450  * requested using the -A or -J options.
451  *
452  */
453 
454     if ( fp_acct != NULL )
455 	fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);
456 
457 }   /* End of account */
458 
459 /*****************************************************************************/
460 
bitmap(fp)461 bitmap(fp)
462 
463     FILE	*fp;			/* next input file */
464 
465 {
466 
467     int		count;			/* pattern repeats this many times */
468     long	total;			/* expect this many patterns */
469 
470 /*
471  *
472  * Reads all the bitmaps from the next input file, translates each one into
473  * PostScript, and arranges to have one bitmap printed on each page. Multiple
474  * bitmaps per input file work.
475  *
476  */
477 
478     fp_in = fp;				/* everyone reads from this file */
479 
480     while ( dimensions() == TRUE )  {
481 	patcount = 0;
482 	total = scanlines * patterns;
483 
484 	bbox[0] = MAX(bbox[0], patterns*16);	/* for BoundingBox comment */
485 	bbox[1] = MAX(bbox[1], scanlines);
486 
487 	redirect(++page);
488 	fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
489 	fprintf(fp_out, "/saveobj save def\n");
490 	writerequest(printed+1, fp_out);
491 
492 	fprintf(fp_out, "%s ", (v8format == TRUE && v8undo == FALSE) ? "true" : "false");
493 	fprintf(fp_out, "%s ", (flip == TRUE) ? "true" : "false");
494 	fprintf(fp_out, "%d %d bitmap\n", patterns * 16, scanlines);
495 
496 	while ( patcount != total && (count = getc(fp)) != EOF )  {
497 	    addrast(count);
498 	    patcount += (count & 0177);
499 	    if ( patcount % patterns == 0 )
500 		putrast();
501 	}   /* End while */
502 
503 	if ( debug == ON )
504 	    fprintf(stderr, "patterns = %d, scanlines = %d, patcount = %d\n", patterns, scanlines, patcount);
505 
506 	if ( total != patcount )
507 	    error(FATAL, "bitmap format error");
508 
509 	if ( fp_out == stdout ) printed++;
510 
511 	fprintf(fp_out, "showpage\n");
512 	fprintf(fp_out, "saveobj restore\n");
513 	fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);
514     }	/* End while */
515 
516 }   /* End of bitmap */
517 
518 /*****************************************************************************/
519 
dimensions()520 dimensions()
521 
522 {
523 
524     int		ox, oy;			/* coordinates of the origin */
525     int		cx, cy;			/* and right corner of the bitmap */
526     int		i;			/* loop index */
527 
528 /*
529  *
530  * Determines the dimensions and type of the next bitmap. Eighth edition bitmaps
531  * have a zero in the first 16 bits. If valid dimensions are read TRUE is returned
532  * to the caller. Changed so the check of whether we're done (by testing scanlines
533  * or patterns) comes before the malloc().
534  *
535  */
536 
537     if ( (scanlines = getint()) == 0 )  {
538 	ox = getint();
539 	oy = getint();
540 	cx = getint();
541 	cy = getint();
542 	scanlines = cy - oy;
543 	patterns = (cx - ox + 15) / 16;
544 	v8format = TRUE;
545     } else patterns = getint();
546 
547     if ( scanlines <= 0 || patterns <= 0 )	/* done - don't do the malloc() */
548 	return(FALSE);
549 
550     if ( raster != NULL ) free(raster);
551     if ( prevrast != NULL ) free(prevrast);
552 
553     if ( (rptr = raster = (char *) malloc(patterns * 2)) == NULL )
554 	error(FATAL, "no memory");
555 
556     if ( (prevrast = (char *) malloc(patterns * 2)) == NULL )
557 	error(FATAL, "no memory");
558 
559     for ( i = 0; i < patterns * 2; i++ )
560 	*(prevrast+i) = 0377;
561 
562     eptr = rptr + patterns * 2;
563 
564     return(TRUE);
565 
566 }   /* End of dimensions */
567 
568 /*****************************************************************************/
569 
addrast(count)570 addrast(count)
571 
572     int		count;			/* repeat count for next pattern */
573 
574 {
575 
576     int		size;			/* number of bytes in next pattern */
577     int		l, h;			/* high and low bytes */
578     int		i, j;			/* loop indices */
579 
580 /*
581  *
582  * Reads the input file and adds the appropriate number of bytes to the output
583  * raster line. If count has bit 7 on, one 16 bit pattern is read and repeated
584  * count & 0177 times. If bit 7 is off, count is the number of patterns read from
585  * fp_in - each one repeated once.
586  *
587  */
588 
589     if ( count & 0200 )  {
590 	size = 1;
591 	count &= 0177;
592     } else {
593 	size = count;
594 	count = 1;
595     }	/* End else */
596 
597     for ( i = size; i > 0; i-- )  {
598 	if ( (l = getc(fp_in)) == EOF || (h = getc(fp_in)) == EOF )
599 	    return;
600 	for ( j = count; j > 0; j-- )  {
601 	    *rptr++ = l;
602 	    *rptr++ = h;
603 	}   /* End for */
604     }	/* End for */
605 
606 }   /* End of addrast */
607 
608 /*****************************************************************************/
609 
putrast()610 putrast()
611 
612 {
613 
614     char	*p1, *p2;		/* starting and ending patterns */
615     int		n;			/* set to bytes per pattern */
616     int		i;			/* loop index */
617 
618 /*
619  *
620  * Takes the scanline that's been saved in *raster, encodes it according to the
621  * value that's been assigned to bytespp, and writes the result to *fp_out. Each
622  * line in the output bitmap is terminated by a 0 on a line by itself.
623  *
624  */
625 
626     n = (bytespp <= 0) ? 2 * patterns : bytespp;
627 
628     if ( v8format == TRUE && v8undo == TRUE )
629 	for ( i = 0; i < patterns * 2; i++ )
630 	    *(raster+i) = (*(prevrast+i) ^= *(raster+i));
631 
632     for ( p1 = raster, p2 = raster + n; p1 < eptr; p1 = p2 )
633 	if ( patncmp(p1, n) == TRUE )  {
634 	    while ( patncmp(p2, n) == TRUE ) p2 += n;
635 	    p2 += n;
636 	    fprintf(fp_out, "%d ", n);
637 	    for ( i = 0; i < n; i++, p1++ )
638 		fprintf(fp_out, "%.2X", ((int) *p1) & 0377);
639 	    fprintf(fp_out, " %d\n", (p2 - p1) / n);
640 	} else {
641 	    while ( p2 < eptr && patncmp(p2, n) == FALSE ) p2 += n;
642 	    if ( p2 > eptr ) p2 = eptr;
643 	    fprintf(fp_out, "%d ", p2 - p1);
644 	    while ( p1 < p2 )
645 		fprintf(fp_out, "%.2X", ((int) *p1++) & 0377);
646 	    fprintf(fp_out, " 0\n");
647 	}   /* End else */
648 
649     fprintf(fp_out, "0\n");
650 
651     rptr = raster;
652 
653 }   /* End of putrast */
654 
655 /*****************************************************************************/
656 
patncmp(p1,n)657 patncmp(p1, n)
658 
659     char	*p1;			/* first patterns starts here */
660     int		n;			/* and extends this many bytes */
661 
662 {
663 
664     char	*p2;			/* address of the second pattern */
665 
666 /*
667  *
668  * Compares the two n byte patterns *p1 and *(p1+n). FALSE is returned if they're
669  * different or extend past the end of the current raster line.
670  *
671  */
672 
673     p2 = p1 + n;
674 
675     for ( ; n > 0; n--, p1++, p2++ )
676 	if ( p2 >= eptr || *p1 != *p2 )
677 	    return(FALSE);
678 
679     return(TRUE);
680 
681 }   /* End of patncmp */
682 
683 /*****************************************************************************/
684 
getint()685 getint()
686 
687 {
688 
689     int		h, l;			/* high and low bytes */
690 
691 /*
692  *
693  * Reads the next two bytes from *fp_in and returns the resulting integer.
694  *
695  */
696 
697     if ( (l = getc(fp_in)) == EOF || (h = getc(fp_in)) == EOF )
698 	return(-1);
699 
700     return((h & 0377) << 8 | (l & 0377));
701 
702 }   /* End of getint */
703 
704 /*****************************************************************************/
705 
redirect(pg)706 redirect(pg)
707 
708     int		pg;			/* next page we're printing */
709 
710 {
711 
712     static FILE	*fp_null = NULL;	/* if output is turned off */
713 
714 /*
715  *
716  * If we're not supposed to print page pg, fp_out will be directed to /dev/null,
717  * otherwise output goes to stdout.
718  *
719  */
720 
721     if ( pg >= 0 && in_olist(pg) == ON )
722 	fp_out = stdout;
723     else if ( (fp_out = fp_null) == NULL )
724 	fp_out = fp_null = fopen("/dev/null", "w");
725 
726 }   /* End of redirect */
727 
728 /*****************************************************************************/
729 
730