xref: /plan9/sys/src/cmd/postscript/postprint/postprint.c (revision 14f51593fd82e19ba95969a8c07ff71131015979)
1 /*
2  * postprint - PostScript translator for ASCII files.
3  *
4  * A simple program that translates ASCII files into PostScript. All it really
5  * does is expand tabs and backspaces, handle character quoting, print text lines,
6  * and control when pages are started based on the requested number of lines per
7  * page.
8  *
9  * The PostScript prologue is copied from *prologue before any of the input files
10  * are translated. The program expects that the following procedures are defined
11  * in that file:
12  *
13  *	setup
14  *
15  *	  mark ... setup -
16  *
17  *	    Handles special initialization stuff that depends on how the program
18  *	    was called. Expects to find a mark followed by key/value pairs on the
19  *	    stack. The def operator is applied to each pair up to the mark, then
20  *	    the default state is set up.
21  *
22  *	pagesetup
23  *
24  *	  page pagesetup -
25  *
26  *	    Does whatever is needed to set things up for the next page. Expects
27  *	    to find the current page number on the stack.
28  *
29  *	l
30  *
31  *	  string l -
32  *
33  *	    Prints string starting in the first column and then goes to the next
34  *	    line.
35  *
36  *	L
37  *
38  *	  mark string column string column ... L mark
39  *
40  *	    Prints each string on the stack starting at the horizontal position
41  *	    selected by column. Used when tabs and spaces can be sufficiently well
42  *	    compressed to make the printer overhead worthwhile. Always used when
43  *	    we have to back up.
44  *
45  *	LL
46  *
47  *	  mark string column string column ... LL mark
48  *
49  *	    Like L, but only used to prevent potential PostScript stack overflow
50  *	    from too many string/column pairs. Stays on the current line. It will
51  *	    not be needed often!!
52  *
53  *	done
54  *
55  *	  done
56  *
57  *	    Makes sure the last page is printed. Only needed when we're printing
58  *	    more than one page on each sheet of paper.
59  *
60  * Almost everything has been changed in this version of postprint. The program
61  * is more intelligent, especially about tabs, spaces, and backspacing, and as a
62  * result output files usually print faster. Output files also now conform to
63  * Adobe's file structuring conventions, which is undoubtedly something I should
64  * have done in the first version of the program. If the number of lines per page
65  * is set to 0, which can be done using the -l option, pointsize will be used to
66  * guess a reasonable value. The estimate is based on the values of LINESPP,
67  * POINTSIZE, and pointsize, and assumes LINESPP lines would fit on a page if
68  * we printed in size POINTSIZE. Selecting a point size using the -s option and
69  * adding -l0 to the command line forces the guess to be made.
70  *
71  * Many default values, like the magnification and orientation, are defined in
72  * the prologue, which is where they belong. If they're changed (by options), an
73  * appropriate definition is made after the prologue is added to the output file.
74  * The -P option passes arbitrary PostScript through to the output file. Among
75  * other things it can be used to set (or change) values that can't be accessed by
76  * other options.
77  */
78 
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <signal.h>
83 #include <ctype.h>
84 #ifdef plan9
85 #define	isascii(c)	((unsigned char)(c)<=0177)
86 #endif
87 #include <sys/types.h>
88 #include <fcntl.h>
89 
90 #include "comments.h"			/* PostScript file structuring comments */
91 #include "gen.h"			/* general purpose definitions */
92 #include "path.h"			/* for the prologue */
93 #include "ext.h"			/* external variable declarations */
94 #include "postprint.h"			/* a few special definitions */
95 
96 char	*optnames = "a:c:ef:l:m:n:o:p:r:s:t:x:y:A:C:E:J:L:P:R:DI";
97 
98 char	*prologue = POSTPRINT;		/* default PostScript prologue */
99 char	*formfile = FORMFILE;		/* stuff for multiple pages per sheet */
100 
101 int	formsperpage = 1;		/* page images on each piece of paper */
102 int	copies = 1;			/* and this many copies of each sheet */
103 
104 int	linespp = LINESPP;		/* number of lines per page */
105 int	pointsize = POINTSIZE;		/* in this point size */
106 int	tabstops = TABSTOPS;		/* tabs set at these columns */
107 int	crmode = 0;			/* carriage return mode - 0, 1, or 2 */
108 int	extended = TRUE;		/* use escapes for unprintable chars */
109 
110 int	col = 1;			/* next character goes in this column */
111 int	line = 1;			/* on this line */
112 
113 int	stringcount = 0;		/* number of strings on the stack */
114 int	stringstart = 1;		/* column where current one starts */
115 
116 Fontmap	fontmap[] = FONTMAP;		/* for translating font names */
117 char	*fontname = "Courier";		/* use this PostScript font */
118 
119 int	page = 0;			/* page we're working on */
120 int	printed = 0;			/* printed this many pages */
121 
122 FILE	*fp_in = stdin;			/* read from this file */
123 FILE	*fp_out = stdout;		/* and write stuff here */
124 FILE	*fp_acct = NULL;		/* for accounting data */
125 
126 /*****************************************************************************/
127 
main(agc,agv)128 main(agc, agv)
129 
130     int		agc;
131     char	*agv[];
132 
133 {
134 
135 /*
136  *
137  * A simple program that translates ASCII files into PostScript. If there's more
138  * than one input file, each begins on a new page.
139  *
140  */
141 
142     argc = agc;				/* other routines may want them */
143     argv = agv;
144 
145     prog_name = argv[0];		/* really just for error messages */
146 
147     init_signals();			/* sets up interrupt handling */
148     header();				/* PostScript header and prologue */
149     options();				/* handle the command line options */
150     setup();				/* for PostScript */
151     arguments();			/* followed by each input file */
152     done();				/* print the last page etc. */
153     account();				/* job accounting data */
154 
155     exit(x_stat);			/* not much could be wrong */
156 
157 }   /* End of main */
158 
159 /*****************************************************************************/
160 
init_signals()161 init_signals()
162 
163 {
164 
165 /*
166  *
167  * Makes sure we handle interrupts.
168  *
169  */
170 
171     if ( signal(SIGINT, interrupt) == SIG_IGN ) {
172 	signal(SIGINT, SIG_IGN);
173 	signal(SIGQUIT, SIG_IGN);
174 	signal(SIGHUP, SIG_IGN);
175     } else {
176 	signal(SIGHUP, interrupt);
177 	signal(SIGQUIT, interrupt);
178     }   /* End else */
179 
180     signal(SIGTERM, interrupt);
181 
182 }   /* End of init_signals */
183 
184 /*****************************************************************************/
185 
header()186 header()
187 
188 {
189 
190     int		ch;			/* return value from getopt() */
191     int		old_optind = optind;	/* for restoring optind - should be 1 */
192 
193 /*
194  *
195  * Scans the option list looking for things, like the prologue file, that we need
196  * right away but could be changed from the default. Doing things this way is an
197  * attempt to conform to Adobe's latest file structuring conventions. In particular
198  * they now say there should be nothing executed in the prologue, and they have
199  * added two new comments that delimit global initialization calls. Once we know
200  * where things really are we write out the job header, follow it by the prologue,
201  * and then add the ENDPROLOG and BEGINSETUP comments.
202  *
203  */
204 
205     while ( (ch = getopt(argc, argv, optnames)) != EOF )
206 	if ( ch == 'L' )
207 	    prologue = optarg;
208 	else if ( ch == '?' )
209 	    error(FATAL, "");
210 
211     optind = old_optind;		/* get ready for option scanning */
212 
213     fprintf(stdout, "%s", CONFORMING);
214     fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
215     fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
216     fprintf(stdout, "%s %s\n", PAGES, ATEND);
217     fprintf(stdout, "%s", ENDCOMMENTS);
218 
219     if ( cat(prologue) == FALSE )
220 	error(FATAL, "can't read %s", prologue);
221 
222     if ( DOROUND )
223 	cat(ROUNDPAGE);
224 
225     fprintf(stdout, "%s", ENDPROLOG);
226     fprintf(stdout, "%s", BEGINSETUP);
227     fprintf(stdout, "mark\n");
228 
229 }   /* End of header */
230 
231 /*****************************************************************************/
232 
options()233 options()
234 
235 {
236 
237     int		ch;			/* return value from getopt() */
238 
239 /*
240  *
241  * Reads and processes the command line options. Added the -P option so arbitrary
242  * PostScript code can be passed through. Expect it could be useful for changing
243  * definitions in the prologue for which options have not been defined.
244  *
245  * Although any PostScript font can be used, things will only work well for
246  * constant width fonts.
247  *
248  */
249 
250     while ( (ch = getopt(argc, argv, optnames)) != EOF ) {
251 	switch ( ch ) {
252 
253 	    case 'a':			/* aspect ratio */
254 		    fprintf(stdout, "/aspectratio %s def\n", optarg);
255 		    break;
256 
257 	    case 'c':			/* copies */
258 		    copies = atoi(optarg);
259 		    fprintf(stdout, "/#copies %s store\n", optarg);
260 		    break;
261 
262 	    case 'e':			/* obsolete - it's now always on */
263 		    extended = TRUE;
264 		    break;
265 
266 	    case 'f':			/* use this PostScript font */
267 		    fontname = get_font(optarg);
268 		    fprintf(stdout, "/font /%s def\n", fontname);
269 		    break;
270 
271 	    case 'l':			/* lines per page */
272 		    linespp = atoi(optarg);
273 		    break;
274 
275 	    case 'm':			/* magnification */
276 		    fprintf(stdout, "/magnification %s def\n", optarg);
277 		    break;
278 
279 	    case 'n':			/* forms per page */
280 		    formsperpage = atoi(optarg);
281 		    fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
282 		    fprintf(stdout, "/formsperpage %s def\n", optarg);
283 		    break;
284 
285 	    case 'o':			/* output page list */
286 		    out_list(optarg);
287 		    break;
288 
289 	    case 'p':			/* landscape or portrait mode */
290 		    if ( *optarg == 'l' )
291 			fprintf(stdout, "/landscape true def\n");
292 		    else fprintf(stdout, "/landscape false def\n");
293 		    break;
294 
295 	    case 'r':			/* carriage return mode */
296 		    crmode = atoi(optarg);
297 		    break;
298 
299 	    case 's':			/* point size */
300 		    pointsize = atoi(optarg);
301 		    fprintf(stdout, "/pointsize %s def\n", optarg);
302 		    break;
303 
304 	    case 't':			/* tabstops */
305 		    tabstops = atoi(optarg);
306 		    break;
307 
308 	    case 'x':			/* shift things horizontally */
309 		    fprintf(stdout, "/xoffset %s def\n", optarg);
310 		    break;
311 
312 	    case 'y':			/* and vertically on the page */
313 		    fprintf(stdout, "/yoffset %s def\n", optarg);
314 		    break;
315 
316 	    case 'A':			/* force job accounting */
317 	    case 'J':
318 		    if ( (fp_acct = fopen(optarg, "a")) == NULL )
319 			error(FATAL, "can't open accounting file %s", optarg);
320 		    break;
321 
322 	    case 'C':			/* copy file straight to output */
323 		    if ( cat(optarg) == FALSE )
324 			error(FATAL, "can't read %s", optarg);
325 		    break;
326 
327 	    case 'E':			/* text font encoding */
328 		    fontencoding = optarg;
329 		    break;
330 
331 	    case 'L':			/* PostScript prologue file */
332 		    prologue = optarg;
333 		    break;
334 
335 	    case 'P':			/* PostScript pass through */
336 		    fprintf(stdout, "%s\n", optarg);
337 		    break;
338 
339 	    case 'R':			/* special global or page level request */
340 		    saverequest(optarg);
341 		    break;
342 
343 	    case 'D':			/* debug flag */
344 		    debug = ON;
345 		    break;
346 
347 	    case 'I':			/* ignore FATAL errors */
348 		    ignore = ON;
349 		    break;
350 
351 	    case '?':			/* don't understand the option */
352 		    error(FATAL, "");
353 		    break;
354 
355 	    default:			/* don't know what to do for ch */
356 		    error(FATAL, "missing case for option %c\n", ch);
357 		    break;
358 	}   /* End switch */
359     }   /* End while */
360 
361     argc -= optind;			/* get ready for non-option args */
362     argv += optind;
363 
364 }   /* End of options */
365 
366 /*****************************************************************************/
367 
get_font(name)368 char *get_font(name)
369 
370     char	*name;			/* name the user asked for */
371 
372 {
373 
374     int		i;			/* for looking through fontmap[] */
375 
376 /*
377  *
378  * Called from options() to map a user's font name into a legal PostScript name.
379  * If the lookup fails *name is returned to the caller. That should let you choose
380  * any PostScript font, although things will only work well for constant width
381  * fonts.
382  *
383  */
384 
385     for ( i = 0; fontmap[i].name != NULL; i++ )
386 	if ( strcmp(name, fontmap[i].name) == 0 )
387 	    return(fontmap[i].val);
388 
389     return(name);
390 
391 }   /* End of get_font */
392 
393 /*****************************************************************************/
394 
setup()395 setup()
396 
397 {
398 
399 /*
400  *
401  * Handles things that must be done after the options are read but before the
402  * input files are processed. linespp (lines per page) can be set using the -l
403  * option. If it's not positive we calculate a reasonable value using the
404  * requested point size - assuming LINESPP lines fit on a page in point size
405  * POINTSIZE.
406  *
407  */
408 
409     writerequest(0, stdout);		/* global requests eg. manual feed */
410     setencoding(fontencoding);
411     fprintf(stdout, "setup\n");
412 
413     if ( formsperpage > 1 ) {
414 	if ( cat(formfile) == FALSE )
415 	    error(FATAL, "can't read %s", formfile);
416 	fprintf(stdout, "%d setupforms\n", formsperpage);
417     }	/* End if */
418 
419     fprintf(stdout, "%s", ENDSETUP);
420 
421     if ( linespp <= 0 )
422 	linespp = LINESPP * POINTSIZE / pointsize;
423 
424 }   /* End of setup */
425 
426 /*****************************************************************************/
427 
arguments()428 arguments()
429 
430 {
431 
432 /*
433  *
434  * Makes sure all the non-option command line arguments are processed. If we get
435  * here and there aren't any arguments left, or if '-' is one of the input files
436  * we'll translate stdin.
437  *
438  */
439 
440     if ( argc < 1 )
441 	text();
442     else {				/* at least one argument is left */
443 	while ( argc > 0 ) {
444 	    if ( strcmp(*argv, "-") == 0 )
445 		fp_in = stdin;
446 	    else if ( (fp_in = fopen(*argv, "r")) == NULL )
447 		error(FATAL, "can't open %s", *argv);
448 	    text();
449 	    if ( fp_in != stdin )
450 		fclose(fp_in);
451 	    argc--;
452 	    argv++;
453 	}   /* End while */
454     }   /* End else */
455 
456 }   /* End of arguments */
457 
458 /*****************************************************************************/
459 
done()460 done()
461 
462 {
463 
464 /*
465  *
466  * Finished with all the input files, so mark the end of the pages with a TRAILER
467  * comment, make sure the last page prints, and add things like the PAGES comment
468  * that can only be determined after all the input files have been read.
469  *
470  */
471 
472     fprintf(stdout, "%s", TRAILER);
473     fprintf(stdout, "done\n");
474     fprintf(stdout, "%s %s\n", DOCUMENTFONTS, fontname);
475     fprintf(stdout, "%s %d\n", PAGES, printed);
476 
477 }   /* End of done */
478 
479 /*****************************************************************************/
480 
account()481 account()
482 
483 {
484 
485 /*
486  *
487  * Writes an accounting record to *fp_acct provided it's not NULL. Accounting is
488  * requested using the -A or -J options.
489  *
490  */
491 
492     if ( fp_acct != NULL )
493 	fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);
494 
495 }   /* End of account */
496 
497 /*****************************************************************************/
498 
text()499 text()
500 
501 {
502 
503     int		ch;			/* next input character */
504 
505 /*
506  *
507  * Translates *fp_in into PostScript. Intercepts space, tab, backspace, newline,
508  * return, and formfeed. Everything else goes to oput(), which handles quoting
509  * (if needed) and escapes for nonascii characters if extended is TRUE. The
510  * redirect(-1) call forces the initial output to go to /dev/null - so stuff
511  * that formfeed() does at the end of each page goes to /dev/null rather than
512  * the real output file.
513  *
514  */
515 
516     redirect(-1);			/* get ready for the first page */
517     formfeed();				/* force PAGE comment etc. */
518 
519     while ( (ch = getc(fp_in)) != EOF )
520 	switch ( ch ) {
521 	    case '\n':
522 		    newline();
523 		    break;
524 
525 	    case '\t':
526 	    case '\b':
527 	    case ' ':
528 		    spaces(ch);
529 		    break;
530 
531 	    case '\014':
532 		    formfeed();
533 		    break;
534 
535 	    case '\r':
536 		    if ( crmode == 1 )
537 			spaces(ch);
538 		    else if ( crmode == 2 )
539 			newline();
540 		    break;
541 
542 	    default:
543 		    oput(ch);
544 		    break;
545 	}   /* End switch */
546 
547     formfeed();				/* next file starts on a new page? */
548 
549 }   /* End of text */
550 
551 /*****************************************************************************/
552 
formfeed()553 formfeed()
554 
555 {
556 
557 /*
558  *
559  * Called whenever we've finished with the last page and want to get ready for the
560  * next one. Also used at the beginning and end of each input file, so we have to
561  * be careful about what's done. The first time through (up to the redirect() call)
562  * output goes to /dev/null.
563  *
564  * Adobe now recommends that the showpage operator occur after the page level
565  * restore so it can be easily redefined to have side-effects in the printer's VM.
566  * Although it seems reasonable I haven't implemented it, because it makes other
567  * things, like selectively setting manual feed or choosing an alternate paper
568  * tray, clumsy - at least on a per page basis.
569  *
570  */
571 
572     if ( fp_out == stdout )		/* count the last page */
573 	printed++;
574 
575     endline();				/* print the last line */
576 
577     fprintf(fp_out, "cleartomark\n");
578     fprintf(fp_out, "showpage\n");
579     fprintf(fp_out, "saveobj restore\n");
580     fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);
581 
582     if ( ungetc(getc(fp_in), fp_in) == EOF )
583 	redirect(-1);
584     else redirect(++page);
585 
586     fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
587     fprintf(fp_out, "/saveobj save def\n");
588     fprintf(fp_out, "mark\n");
589     writerequest(printed+1, fp_out);
590     fprintf(fp_out, "%d pagesetup\n", printed+1);
591 
592     line = 1;
593 
594 }   /* End of formfeed */
595 
596 /*****************************************************************************/
597 
newline()598 newline()
599 
600 {
601 
602 /*
603  *
604  * Called when we've read a newline character. The call to startline() ensures
605  * that at least an empty string is on the stack.
606  *
607  */
608 
609     startline();
610     endline();				/* print the current line */
611 
612     if ( ++line > linespp )		/* done with this page */
613 	formfeed();
614 
615 }   /* End of newline */
616 
617 /*****************************************************************************/
618 
spaces(ch)619 spaces(ch)
620 
621     int		ch;			/* next input character */
622 
623 {
624 
625     int		endcol;			/* ending column */
626     int		i;			/* final distance - in spaces */
627 
628 /*
629  *
630  * Counts consecutive spaces, tabs, and backspaces and figures out where the next
631  * string should start. Once that's been done we try to choose an efficient way
632  * to output the required number of spaces. The choice is between using procedure
633  * l with a single string on the stack and L with several string and column pairs.
634  * We usually break even, in terms of the size of the output file, if we need four
635  * consecutive spaces. More means using L decreases the size of the file. For now
636  * if there are less than 6 consecutive spaces we just add them to the current
637  * string, otherwise we end that string, follow it by its starting position, and
638  * begin a new one that starts at endcol. Backspacing is always handled this way.
639  *
640  */
641 
642     startline();			/* so col makes sense */
643     endcol = col;
644 
645     do {
646 	if ( ch == ' ' )
647 	    endcol++;
648 	else if ( ch == '\t' )
649 	    endcol += tabstops - ((endcol - 1) % tabstops);
650 	else if ( ch == '\b' )
651 	    endcol--;
652 	else if ( ch == '\r' )
653 	    endcol = 1;
654 	else break;
655     } while ( ch = getc(fp_in) );	/* if ch is 0 we'd quit anyway */
656 
657     ungetc(ch, fp_in);			/* wasn't a space, tab, or backspace */
658 
659     if ( endcol < 1 )			/* can't move past left edge */
660 	endcol = 1;
661 
662     if ( (i = endcol - col) >= 0 && i < 6 )
663 	for ( ; i > 0; i-- )
664 	    oput((int)' ');
665     else {
666 	endstring();
667 	col = stringstart = endcol;
668     }	/* End else */
669 
670 }   /* End of spaces */
671 
672 /*****************************************************************************/
673 
startline()674 startline()
675 
676 {
677 
678 /*
679  *
680  * Called whenever we want to be certain we're ready to start pushing characters
681  * into an open string on the stack. If stringcount is positive we've already
682  * started, so there's nothing to do. The first string starts in column 1.
683  *
684  */
685 
686     if ( stringcount < 1 ) {
687 	putc('(', fp_out);
688 	stringstart = col = 1;
689 	stringcount = 1;
690     }	/* End if */
691 
692 }   /* End of startline */
693 
694 /*****************************************************************************/
695 
endstring()696 endstring()
697 
698 {
699 
700 /*
701  *
702  * End the current string and start a new one.
703  *
704  */
705 
706     if ( stringcount > 100 ) {		/* don't put too much on the stack */
707 	fprintf(fp_out, ")%d LL\n(", stringstart-1);
708 	stringcount = 2;		/* kludge - don't let endline() use l */
709     } else {
710 	fprintf(fp_out, ")%d(", stringstart-1);
711 	stringcount++;
712     }   /* End else */
713 
714 }   /* End of endstring */
715 
716 /*****************************************************************************/
717 
endline()718 endline()
719 
720 {
721 
722 /*
723  *
724  * Generates a call to the PostScript procedure that processes all the text on
725  * the stack - provided stringcount is positive. If one string is on the stack
726  * the fast procedure (ie. l) is used to print the line, otherwise the slower
727  * one that processes string and column pairs is used.
728  *
729  */
730 
731     if ( stringcount == 1 )
732 	fprintf(fp_out, ")l\n");
733     else if ( stringcount > 1 )
734 	fprintf(fp_out, ")%d L\n", stringstart-1);
735 
736     stringcount = 0;
737 
738 }   /* End of endline */
739 
740 /*****************************************************************************/
741 
oput(ch)742 oput(ch)
743 
744     int		ch;			/* next output character */
745 
746 {
747 
748 /*
749  *
750  * Responsible for adding all printing characters from the input file to the
751  * open string on top of the stack.
752  *
753  */
754 
755     if ( isascii(ch) && isprint(ch) ) {
756 	startline();
757 	if ( ch == '(' || ch == ')' || ch == '\\' )
758 	    putc('\\', fp_out);
759 	putc(ch, fp_out);
760 	col++;
761     } else if ( extended == TRUE ) {
762 	startline();
763 	fprintf(fp_out, "\\%.3o", ch & 0377);
764 	col++;
765     }	/* End if */
766 
767 }   /* End of oput */
768 
769 /*****************************************************************************/
770 
redirect(pg)771 redirect(pg)
772 
773     int		pg;			/* next page we're printing */
774 
775 {
776 
777     static FILE	*fp_null = NULL;	/* if output is turned off */
778 
779 /*
780  *
781  * If we're not supposed to print page pg, fp_out will be directed to /dev/null,
782  * otherwise output goes to stdout.
783  *
784  */
785 
786     if ( pg >= 0 && in_olist(pg) == ON )
787 	fp_out = stdout;
788     else if ( (fp_out = fp_null) == NULL )
789 	fp_out = fp_null = fopen("/dev/null", "w");
790 
791 }   /* End of redirect */
792 
793 /*****************************************************************************/
794 
795