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