Avoid overflowing user_font_name by using dynamic allocation.
[enscript.git] / src / psgen.c
1 /*
2  * Convert ASCII to PostScript.
3  * Copyright (c) 1995-2002 Markku Rossi.
4  *
5  * Author: Markku Rossi <mtr@iki.fi>
6  */
7
8 /*
9  * This file is part of GNU Enscript.
10  *
11  * Enscript is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * Enscript is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with Enscript.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 #include "gsint.h"
26 #include <libgen.h>
27
28 /*
29  * Types and definitions.
30  */
31
32 /* Values for token flags. */
33
34 /* EPSF. */
35 #define F_EPSF_CENTER                   0x01
36 #define F_EPSF_RIGHT                    0x02
37 #define M_EPSF_JUSTIFICATION            0x03
38
39 #define F_EPSF_NO_CPOINT_UPDATE_X       0x04
40 #define F_EPSF_NO_CPOINT_UPDATE_Y       0x08
41
42 #define F_EPSF_ABSOLUTE_X               0x10
43 #define F_EPSF_ABSOLUTE_Y               0x20
44
45 #define F_EPSF_SCALE_X                  0x40
46 #define F_EPSF_SCALE_Y                  0x80
47
48
49 /* Predicate to check if we are at the correct slice. */
50 #define CORRECT_SLICE() (slicing == 0 || current_slice == slice)
51
52 /* Predicates for the current body font. */
53
54 /* Is character <ch> printable. */
55 #define ISPRINT(ch) (font_ctype[(unsigned char) (ch)] != ' ')
56
57 /* Does character <ch> exist in current body font? */
58 #define EXISTS(ch) (font_ctype[(unsigned char) (ch)] == '*')
59
60
61 #define RESOURCE_LINE_WIDTH 75
62
63 /* Token types. */
64 typedef enum
65 {
66   tNONE,
67   tEOF,
68   tSTRING,
69   tFORMFEED,
70   tNEWLINE,
71   tCARRIAGE_RETURN,
72   tWRAPPED_NEWLINE,
73   tEPSF,
74   tSETFILENAME,
75   tSETPAGENUMBER,
76   tNEWPAGE,
77   tFONT,
78   tCOLOR,
79   tBGCOLOR,
80   tSAVEX,
81   tLOADX,
82   tPS
83 } TokenType;
84
85 /* Special escape tokens. */
86 typedef enum
87 {
88   ESC_COMMENT,
89   ESC_EPSF,
90   ESC_FONT,
91   ESC_COLOR,
92   ESC_BGCOLOR,
93   ESC_NEWPAGE,
94   ESC_SETFILENAME,
95   ESC_SETPAGENUMBER,
96   ESC_SHADE,
97   ESC_BGGRAY,
98   ESC_ESCAPE,
99   ESC_SAVEX,
100   ESC_LOADX,
101   ESC_PS
102 } SpecialEscape;
103
104 /* Token structure. */
105 struct gs_token_st
106 {
107   TokenType type;
108   unsigned int flags;
109   double new_x;                 /* Current point x after this token. */
110   double new_y;                 /* Current point y after this token. */
111   int new_col;                  /* Line column after this token. */
112
113   union
114     {
115       int i;
116       char *str;
117       struct
118         {
119           double x;             /* x-offset */
120           double y;             /* y-offset */
121           double w;             /* width */
122           double h;             /* height */
123           double xscale;
124           double yscale;
125           int llx, lly, urx, ury; /* Bounding box. */
126           char filename[512];
127           char *skipbuf;
128           unsigned int skipbuf_len;
129           unsigned int skipbuf_pos;
130           FILE *fp;             /* File from which eps image is read. */
131           int pipe;             /* Is <fp> opened to pipe?  */
132         } epsf;
133       Color color;
134       Color bgcolor;
135       struct
136         {
137           char name[512];
138           FontPoint size;
139           InputEncoding encoding;
140         } font;
141       char filename[512];
142     } u;
143 };
144
145 typedef struct gs_token_st Token;
146
147
148 /*
149  * Prototypes for static functions.
150  */
151
152 static void get_next_token ___P ((InputStream *is, double linestart,
153                                   double linepos, unsigned int col,
154                                   double linew, Token *token));
155
156 static void dump_ps_page_header ___P ((char *fname, int empty));
157
158 static void dump_ps_page_trailer ();
159
160 static void dump_empty_page ();
161
162 /*
163  * Recognize a EPS file described by <token>.  Returns 1 if file was a
164  * valid EPS file or 0 otherwise.  File is accepted if it starts with
165  * the PostScript magic `%!' and it has a valid `%%BoundingBox' DSC
166  * comment.
167  */
168 static int recognize_eps_file ___P ((Token *token));
169
170 /*
171  * Insert EPS file described by <token> to the output stream.
172  */
173 static void paste_epsf ___P ((Token *token));
174
175 /*
176  * Check if InputStream <is> contains a file which can be passed
177  * through without any modifications.  Returns 1 if file was passed or
178  * 0 otherwise.
179  */
180 static int do_pass_through ___P ((char *fname, InputStream *is));
181
182 /*
183  * Read one float dimension from InputStream <is>.  If <units> is
184  * true, number can be followed by an optional unit specifier.  If
185  * <horizontal> is true, dimension is horizontal, otherwise it is
186  * vertical (this is used to find out how big `line' units are).
187  */
188 static double read_float ___P ((InputStream *is, int units, int horizontal));
189
190 /*
191  * Print linenumber <linenum> to the beginning of the current line.
192  * Current line start is specified by point (x, y).
193  */
194 static void print_line_number ___P ((double x, double y, double space,
195                                      double margin, unsigned int linenum));
196
197 /* Send PostScript to the output file. */
198 #define OUTPUT(body)    \
199   do {                  \
200     if (cofp == NULL)   \
201       cofp = ofp;       \
202     if (do_print)       \
203       fprintf body;     \
204   } while (0)
205
206 /* Divert output to tmp file so the total page count can be counted. */
207 static void divert ();
208
209 /* Paste diverted data to the output and patch the total page counts. */
210 static void undivert ();
211
212 /*
213  * Handle two-side printing related binding options.  This function is
214  * called once for each even-numbered page.
215  */
216 static void handle_two_side_options ();
217
218 /*
219  * Global variables.
220  */
221
222 unsigned int current_pagenum = 0; /* The number of the current page. */
223 unsigned int total_pages_in_file;
224 unsigned int input_filenum = 0;
225 unsigned int current_file_linenum;
226 int first_pagenum_for_file;
227 char *fname = NULL;             /* The name of the current input file. */
228
229
230 /*
231  * Static variables
232  */
233
234 /* Have we dumped PS header? */
235 static int ps_header_dumped = 0;
236
237 /* Divert file. */
238 static FILE *divertfp = NULL;
239
240 /* Current output() file. */
241 static FILE *cofp = NULL;
242
243 /* To print or not to print, that's a question. */
244 static int do_print = 1;
245
246 /* Is ^@font{}-defined font active? */
247 static int user_fontp = 0;
248
249 /* The user ^@font{}-defined font. */
250 static char *user_font_name;
251 static FontPoint user_font_pt;
252 static InputEncoding user_font_encoding;
253
254 /* Is ^@color{}-defined color active? */
255 static int user_colorp = 0;
256
257 /* The user ^@color{}-defined color. */
258 static Color user_color;
259
260 /* Is ^@bgcolor{}-defined color active? */
261 static int user_bgcolorp = 0;
262
263 /* The user ^@bgcolor{}-defined color. */
264 static Color user_bgcolor;
265
266 /* The last linenumber printed by print_line_number(). */
267 static unsigned int print_line_number_last;
268
269 /* Registers to store X-coordinates with the ^@savex{} escape.
270    Initially these are uninitialized. */
271 static double xstore[256];
272
273 /*
274  * Global functions.
275  */
276
277 void
278 dump_ps_header ()
279 {
280   char *cp, *cp2;
281   int i, j, got;
282
283   /* Dump PS header only once. */
284   if (ps_header_dumped)
285     return;
286   ps_header_dumped = 1;
287
288   /*
289    * Header.
290    */
291
292   OUTPUT ((cofp, "%s\n", output_first_line));
293   OUTPUT ((cofp, "%%%%BoundingBox: %d %d %d %d\n", media->llx, media->lly,
294            media->urx, media->ury));
295   OUTPUT ((cofp, "%%%%Title: %s\n", title));
296   OUTPUT ((cofp, "%%%%For: %s\n", passwd->pw_gecos));
297   OUTPUT ((cofp, "%%%%Creator: %s\n", version_string));
298   OUTPUT ((cofp, "%%%%CreationDate: %s\n", date_string));
299   OUTPUT ((cofp, "%%%%Orientation: %s\n",
300            ((nup > 1) && nup_landscape)
301            || ((nup == 1) && landscape) ? "Landscape" : "Portrait"));
302   OUTPUT ((cofp, "%%%%Pages: (atend)\n"));
303   OUTPUT ((cofp, "%%%%DocumentMedia: %s %d %d 0 () ()\n",
304            media->name, media->w, media->h));
305   OUTPUT ((cofp, "%%%%DocumentNeededResources: (atend)\n"));
306
307   if (count_key_value_set (pagedevice) > 0)
308     OUTPUT ((cofp, "%%%%LanguageLevel: 2\n"));
309
310   OUTPUT ((cofp, "%%%%EndComments\n"));
311
312
313   /*
314    * Procedure Definitions.
315    */
316
317   OUTPUT ((cofp, "%%%%BeginProlog\n"));
318
319   /* Prolog. */
320   OUTPUT ((cofp, "%%%%BeginResource: procset Enscript-Prolog %s\n",
321            ps_version_string));
322   if (!paste_file ("enscript", ".pro"))
323     FATAL ((stderr, _("couldn't find prolog \"%s\": %s\n"), "enscript.pro",
324             strerror (errno)));
325   OUTPUT ((cofp, "%%%%EndResource\n"));
326
327   /* Encoding vector. */
328   OUTPUT ((cofp, "%%%%BeginResource: procset Enscript-Encoding-%s %s\n",
329            encoding_name, ps_version_string));
330   if (!paste_file (encoding_name, ".enc"))
331     FATAL ((stderr, _("couldn't find encoding file \"%s.enc\": %s\n"),
332             encoding_name, strerror (errno)));
333   OUTPUT ((cofp, "%%%%EndResource\n"));
334
335   OUTPUT ((cofp, "%%%%EndProlog\n"));
336
337
338   /*
339    * Document Setup.
340    */
341
342   OUTPUT ((cofp, "%%%%BeginSetup\n"));
343
344   /* Download fonts. */
345   for (got = strhash_get_first (download_fonts, &cp, &j, (void **) &cp2); got;
346        got = strhash_get_next (download_fonts, &cp, &j, (void **) &cp2))
347     download_font (cp);
348
349   /* For each required font, emit %%IncludeResouce comment. */
350   for (got = strhash_get_first (res_fonts, &cp, &j, (void **) &cp2); got;
351        got = strhash_get_next (res_fonts, &cp, &j, (void **) &cp2))
352     OUTPUT ((cofp, "%%%%IncludeResource: font %s\n", cp));
353
354   OUTPUT ((cofp, "/HFpt_w %g def\n", HFpt.w));
355   OUTPUT ((cofp, "/HFpt_h %g def\n", HFpt.h));
356
357
358   /* Select our fonts. */
359
360   /* Header font HF. */
361   OUTPUT ((cofp, "/%s /HF-gs-font MF\n", HFname));
362   OUTPUT ((cofp,
363            "/HF /HF-gs-font findfont [HFpt_w 0 0 HFpt_h 0 0] makefont def\n"));
364
365   /* Our default typing font F. */
366   OUTPUT ((cofp, "/%s /F-gs-font MF\n", Fname));
367   OUTPUT ((cofp, "/F-gs-font %g %g SF\n", Fpt.w, Fpt.h));
368
369   /* Underlay. */
370   if (underlay != NULL)
371     {
372       OUTPUT ((cofp, "/ul_str (%s) def\n", underlay));
373       OUTPUT ((cofp, "/ul_w_ptsize %g def\n", ul_ptsize.w));
374       OUTPUT ((cofp, "/ul_h_ptsize %g def\n", ul_ptsize.h));
375       OUTPUT ((cofp, "/ul_gray %g def\n", ul_gray));
376       OUTPUT ((cofp, "/ul_x %g def\n", ul_x));
377       OUTPUT ((cofp, "/ul_y %g def\n", ul_y));
378       OUTPUT ((cofp, "/ul_angle %g def\n", ul_angle));
379       OUTPUT ((cofp, "/ul_style %d def\n", ul_style));
380       OUTPUT ((cofp, "/%s /F-ul-font MF\n", ul_font));
381       OUTPUT ((cofp, "/ul_font /F-ul-font findfont \
382 [ul_w_ptsize 0 0 ul_h_ptsize 0 0] makefont def\n"));
383     }
384
385   /* Number of copies. */
386   OUTPUT ((cofp, "/#copies %d def\n", num_copies));
387
388   /* Page prefeed. */
389   if (page_prefeed)
390     OUTPUT ((cofp, "true page_prefeed\n"));
391
392   /* Statusdict definitions. */
393   if (count_key_value_set (statusdict) > 0)
394     {
395       OUTPUT ((cofp, "%% Statustdict definitions:\nstatusdict begin\n  "));
396       i = 2;
397       for (got = strhash_get_first (statusdict, &cp, &j, (void **) &cp2); got;
398            got = strhash_get_next (statusdict, &cp, &j, (void **) &cp2))
399         {
400           j = strlen (cp) + 1 + strlen (cp2) + 1;
401           if (i + j > RESOURCE_LINE_WIDTH)
402             {
403               OUTPUT ((cofp, "\n  "));
404               i = 2;
405             }
406           OUTPUT ((cofp, "%s %s ", cp2, cp));
407           i += j;
408         }
409       OUTPUT ((cofp, "\nend\n"));
410     }
411
412   /* Page device definitions. */
413   if (pslevel >= 2 &&
414       (count_key_value_set (pagedevice) > 0 || generate_PageSize))
415     {
416       OUTPUT ((cofp, "%% Pagedevice definitions:\n"));
417       OUTPUT ((cofp, "gs_languagelevel 1 gt {\n  <<\n    "));
418
419       i = 4;
420       for (got = strhash_get_first (pagedevice, &cp, &j, (void **) &cp2); got;
421            got = strhash_get_next (pagedevice, &cp, &j, (void **) &cp2))
422         {
423           j = strlen (cp2) + 1 + strlen (cp) + 2;
424           if (i + j > RESOURCE_LINE_WIDTH)
425             {
426               OUTPUT ((cofp, "\n    "));
427               i = 4;
428             }
429           OUTPUT ((cofp, "/%s %s ", cp, cp2));
430           i += j;
431         }
432
433       if (generate_PageSize)
434         {
435           if (i + 21 > RESOURCE_LINE_WIDTH)
436             {
437               OUTPUT ((cofp, "\n    "));
438               i = 4;
439             }
440           OUTPUT ((cofp, "/PageSize [%d %d] ", media->w, media->h));
441           i += 21;
442         }
443
444       OUTPUT ((cofp, "\n  >> setpagedevice\n} if\n"));
445     }
446
447   /*
448    * Dump header procset.  Header must come after all font inclusions
449    * and enscript's dynamic state definition.
450    */
451   if (header != HDR_NONE)
452     {
453       char *hdr;
454       if (header == HDR_SIMPLE)
455         hdr = "simple";
456       else
457         hdr = fancy_header_name;
458
459       OUTPUT ((cofp, "%%%%BeginResource: procset Enscript-Header-%s %s\n",
460                hdr, ps_version_string));
461       if (!paste_file (hdr, ".hdr"))
462         FATAL ((stderr,
463                 _("couldn't find header definition file \"%s.hdr\": %s\n"),
464                 hdr, strerror (errno)));
465       OUTPUT ((cofp, "%%%%EndResource\n"));
466     }
467
468   /*
469    * Count output width and height here; we can't do it earlier because
470    * header might have just allocated some extra space.
471    */
472   d_output_w = d_page_w;
473   d_output_h = d_page_h - d_header_h - d_footer_h;
474
475   /* Dump our current dynamic state. */
476   OUTPUT ((cofp, "/d_page_w %d def\n", d_page_w));
477   OUTPUT ((cofp, "/d_page_h %d def\n", d_page_h));
478
479   OUTPUT ((cofp, "/d_header_x %d def\n", 0));
480   OUTPUT ((cofp, "/d_header_y %d def\n", d_output_h + d_footer_h));
481   OUTPUT ((cofp, "/d_header_w %d def\n", d_header_w));
482   OUTPUT ((cofp, "/d_header_h %d def\n", d_header_h));
483
484   OUTPUT ((cofp, "/d_footer_x %d def\n", 0));
485   OUTPUT ((cofp, "/d_footer_y %d def\n", 0));
486   OUTPUT ((cofp, "/d_footer_w %d def\n", d_header_w));
487   OUTPUT ((cofp, "/d_footer_h %d def\n", d_footer_h));
488
489   OUTPUT ((cofp, "/d_output_w %d def\n", d_output_w));
490   OUTPUT ((cofp, "/d_output_h %d def\n", d_output_h));
491   OUTPUT ((cofp, "/cols %d def\n", num_columns));
492
493   OUTPUT ((cofp, "%%%%EndSetup\n"));
494 }
495
496
497 void
498 dump_ps_trailer ()
499 {
500   int i, j, got;
501   char *cp;
502   void *value;
503   unsigned int nup_subpage;
504
505   if (!ps_header_dumped)
506     /* No header, let's be consistent and forget trailer also. */
507     return;
508
509   /* The possible pending N-up showpage. */
510   nup_subpage = (total_pages - 1) % nup;
511   if (nup > 1 && nup_subpage + 1 != nup)
512     /* N-up showpage missing. */
513     OUTPUT ((cofp, "_R\nS\n"));
514
515   /* Trailer. */
516
517   OUTPUT ((cofp, "%%%%Trailer\n"));
518
519   if (page_prefeed)
520     OUTPUT ((cofp, "false page_prefeed\n"));
521
522   OUTPUT ((cofp, "%%%%Pages: %d\n", total_pages));
523
524   /* Document needed resources. */
525
526   /* fonts. */
527   OUTPUT ((cofp, "%%%%DocumentNeededResources: font "));
528   i = 32;                       /* length of the previous string. */
529   for (got = strhash_get_first (res_fonts, &cp, &j, &value); got;
530        got = strhash_get_next (res_fonts, &cp, &j, &value))
531     {
532       if (i + strlen (cp) + 1 > RESOURCE_LINE_WIDTH)
533         {
534           OUTPUT ((cofp, "\n%%%%+ font "));
535           i = 9;                /* length of the previous string. */
536         }
537       OUTPUT ((cofp, "%s ", cp));
538       i += strlen (cp) + 1;
539     }
540   OUTPUT ((cofp, "\n%%%%EOF\n"));
541 }
542
543
544 void
545 process_file (char *fname_arg, InputStream *is, int is_toc)
546 {
547   int col;
548   double x, y;
549   double lx, ly;
550   double linewidth;             /* Line width in points. */
551   double lineend;
552   int done = 0;
553   int page_clear = 1;
554   unsigned int line_column;
555   unsigned int current_linenum;
556   double linenumber_space = 0;
557   double linenumber_margin = 0;
558   Token token;
559   int reuse_last_token = 0;
560   unsigned int current_slice = 1;
561   int last_wrapped_line = -1;
562   int last_spaced_file_linenum = -1;
563   int save_current_pagenum;
564   int toc_pagenum = 0;
565
566   /* Save filename. */
567   xfree (fname);
568   fname = xstrdup (fname_arg);
569
570   /* Init page number and line counters. */
571   if (!continuous_page_numbers)
572     current_pagenum = 0;
573   total_pages_in_file = 0;
574   current_file_linenum = start_line_number;
575
576   /*
577    * Count possible line number spaces.  This should be enought for 99999
578    * lines
579    */
580   linenumber_space = CHAR_WIDTH ('0') * 5 + 1.0;
581   linenumber_margin = CHAR_WIDTH (':') + CHAR_WIDTH ('m');
582
583   /* We got a new input file. */
584   input_filenum++;
585
586   /* We haven't printed any line numbers yet. */
587   print_line_number_last = (unsigned int) -1;
588
589   if (pass_through || output_language_pass_through)
590     if (do_pass_through (fname, is))
591       /* All done. */
592       return;
593
594   /* We have work to do, let's give header a chance to dump itself. */
595   dump_ps_header ();
596
597   /*
598    * Align files to the file_align boundary, this is handy for two-side
599    * printing.
600    */
601   while ((total_pages % file_align) != 0)
602     {
603       total_pages++;
604       dump_empty_page ();
605     }
606
607   MESSAGE (1, (stderr, _("processing file \"%s\"...\n"), fname));
608
609   linewidth = d_output_w / num_columns - 2 * d_output_x_margin
610     - line_indent;
611
612   /* Save the current running page number for possible toc usage. */
613   first_pagenum_for_file = total_pages + 1;
614
615   /*
616    * Divert our output to a temp file.  We will re-process it
617    * afterwards to patch, for example, the number of pages in the
618    * document.
619    */
620   divert ();
621
622   /* Process this input file. */
623   while (!done)
624     {
625       /* Start a new page. */
626       page_clear = 1;
627
628       for (col = 0; !done && col < num_columns; col++)
629         {
630           /* Move to the beginning of the column <col>. */
631           lx = x = col * d_output_w / (float) num_columns + d_output_x_margin
632             + line_indent;
633           lineend = lx + linewidth;
634
635           ly = y = d_footer_h + d_output_h - d_output_y_margin - LINESKIP;
636           current_linenum = 0;
637           line_column = 0;
638
639           while (1)
640             {
641               if (line_numbers && line_column == 0
642                   && (current_file_linenum != last_spaced_file_linenum))
643                 {
644                   /* Forward x by the amount needed by our line numbers. */
645                   x += linenumber_space + linenumber_margin;
646                   last_spaced_file_linenum = current_file_linenum;
647                 }
648
649               /* Get token. */
650               if (!reuse_last_token)
651                 get_next_token (is, lx, x, line_column, lineend, &token);
652               reuse_last_token = 0;
653
654               /*
655                * Page header printing is delayed to this point because
656                * we want to handle files ending with a newline character
657                * with care.  If the last newline would cause a pagebreak,
658                * otherwise we would print page header to the non-existent
659                * next page and that would be ugly ;)
660                */
661
662               if (token.type == tEOF)
663                 {
664                   done = 1;
665                   goto end_of_page;
666                 }
667
668               /*
669                * Now we know that we are going to make marks to this page
670                * => print page header.
671                */
672
673               if (page_clear)
674                 {
675                   PageRange *pr;
676
677                   current_pagenum++;
678                   total_pages_in_file++;
679
680                   /* Check page ranges. */
681                   if (page_ranges == NULL)
682                     do_print = 1;
683                   else
684                     {
685                       do_print = 0;
686                       for (pr = page_ranges; pr; pr = pr->next)
687                         {
688                           if (pr->odd || pr->even)
689                             {
690                               if ((pr->odd && (current_pagenum % 2) == 1)
691                                   || (pr->even && (current_pagenum % 2) == 0))
692                                 {
693                                   do_print = 1;
694                                   break;
695                                 }
696                             }
697                           else
698                             {
699                               if (pr->start <= current_pagenum
700                                   && current_pagenum <= pr->end)
701                                 {
702                                   do_print = 1;
703                                   break;
704                                 }
705                             }
706                         }
707                     }
708
709                   if (do_print)
710                     total_pages++;
711
712                   if (is_toc)
713                     {
714                       save_current_pagenum = current_pagenum;
715                       toc_pagenum--;
716                       current_pagenum = toc_pagenum;
717                     }
718
719                   dump_ps_page_header (fname, 0);
720                   page_clear = 0;
721
722                   if (is_toc)
723                     current_pagenum = save_current_pagenum;
724                 }
725
726               /* Print line highlight. */
727               if (line_column == 0 && line_highlight_gray < 1.0)
728                 OUTPUT ((cofp, "%g %g %g %g %g line_highlight\n",
729                          lx, (y - baselineskip
730                               + (font_bbox_lly * Fpt.h / UNITS_PER_POINT)),
731                          linewidth, Fpt.h + baselineskip,
732                          line_highlight_gray));
733
734               /* Print line numbers if needed. */
735               if (line_numbers && line_column == 0 && token.type != tFORMFEED)
736                 print_line_number (lx, y, linenumber_space, linenumber_margin,
737                                    current_file_linenum);
738
739               /* Check rest of tokens. */
740               switch (token.type)
741                 {
742                 case tFORMFEED:
743                   switch (formfeed_type)
744                     {
745                     case FORMFEED_COLUMN:
746                       goto end_of_column;
747                       break;
748
749                     case FORMFEED_PAGE:
750                       goto end_of_page;
751                       break;
752
753                     case FORMFEED_HCOLUMN:
754                       /*
755                        * Advance y-coordinate to the next even
756                        * `horizontal_column_height' position.
757                        */
758                       {
759                         int current_row;
760
761                         current_row = (ly - y) / horizontal_column_height;
762                         y = ly - (current_row + 1) * horizontal_column_height;
763
764                         /* Check the end of the page. */
765                         if (y < d_footer_h + d_output_y_margin)
766                           goto end_of_column;
767                       }
768                       break;
769                     }
770                   break;
771
772                 case tSTRING:
773                   if (CORRECT_SLICE ())
774                     {
775                       if (bggray < 1.0)
776                         {
777                           OUTPUT ((cofp, "%g %g %g %g %g (%s) bgs\n", x, y,
778                                    Fpt.h + baselineskip,
779                                    baselineskip
780                                    - (font_bbox_lly * Fpt.h / UNITS_PER_POINT),
781                                    bggray,
782                                    token.u.str));
783                         }
784                       else if (user_bgcolorp)
785                         {
786                           OUTPUT ((cofp, "%g %g %g %g %g %g %g (%s) bgcs\n",
787                                    x, y, Fpt.h + baselineskip,
788                                    baselineskip
789                                    - (font_bbox_lly * Fpt.h / UNITS_PER_POINT),
790                                    user_bgcolor.r,
791                                    user_bgcolor.g,
792                                    user_bgcolor.b,
793                                    token.u.str));
794                         }
795                       else
796                         {
797                           OUTPUT ((cofp, "%g %g M\n(%s) s\n", x, y,
798                                    token.u.str));
799                         }
800                     }
801                   x = token.new_x;
802                   line_column = token.new_col;
803                   break;
804
805                 case tCARRIAGE_RETURN:
806                   /* Just reset the x-coordinate. */
807                   x = col * d_output_w / (float) num_columns
808                     + d_output_x_margin + line_indent;
809                   line_column = 0;
810                   break;
811
812                 case tNEWLINE:
813                 case tWRAPPED_NEWLINE:
814                   if (token.type == tNEWLINE)
815                     {
816                       current_file_linenum++;
817                       current_slice = 1;
818                       y -= LINESKIP;
819                     }
820                   else
821                     {
822                       current_slice++;
823                       if (!slicing)
824                         {
825                           /* Mark wrapped line marks. */
826                           switch (mark_wrapped_lines_style)
827                             {
828                             case MWLS_NONE:
829                               /* nothing */
830                               break;
831
832                             case MWLS_PLUS:
833                               OUTPUT ((cofp, "%g %g M (+) s\n", x, y));
834                               break;
835
836                             default:
837                               /* Print some fancy graphics. */
838                               OUTPUT ((cofp,
839                                        "%g %g %g %g %d wrapped_line_mark\n",
840                                        x, y, Fpt.w, Fpt.h,
841                                        mark_wrapped_lines_style));
842                               break;
843                             }
844
845                           /*
846                            * For wrapped newlines, decrement y only if
847                            * we are not slicing the input.
848                            */
849                           y -= LINESKIP;
850                         }
851
852                       /* Count the wrapped lines here. */
853                       if (!slicing || current_slice > slice)
854                         if (current_file_linenum != last_wrapped_line)
855                           {
856                             if (do_print)
857                               num_truncated_lines++;
858                             last_wrapped_line = current_file_linenum;
859                           }
860                     }
861
862                   current_linenum++;
863                   if (current_linenum >= lines_per_page
864                       || y < d_footer_h + d_output_y_margin)
865                     goto end_of_column;
866
867                   x = col * d_output_w / (float) num_columns
868                     + d_output_x_margin + line_indent;
869                   line_column = 0;
870                   break;
871
872                 case tEPSF:
873                   /* Count current point movement. */
874
875                   if (token.flags & F_EPSF_ABSOLUTE_Y)
876                     token.new_y = ly;
877                   else
878                     token.new_y = y;
879                   token.new_y += token.u.epsf.y - token.u.epsf.h;
880
881                   if (token.flags & F_EPSF_ABSOLUTE_X)
882                     token.new_x = lx;
883                   else
884                     token.new_x = x;
885                   token.new_x += token.u.epsf.x;
886
887                   /* Check flags. */
888
889                   /* Justification flags overwrite <x_ofs>. */
890                   if (token.flags & F_EPSF_CENTER)
891                     token.new_x = lx + (linewidth - token.u.epsf.w) / 2;
892                   if (token.flags & F_EPSF_RIGHT)
893                     token.new_x = lx + (linewidth - token.u.epsf.w);
894
895                   /* Check if eps file does not fit to this column. */
896                   if ((token.flags & F_EPSF_NO_CPOINT_UPDATE_Y) == 0
897                       && token.new_y < d_footer_h + d_output_y_margin)
898                     {
899                       if (current_linenum == 0)
900                         {
901                           /*
902                            * At the beginning of the column, warn user
903                            * and print image.
904                            */
905                           MESSAGE (0, (stderr, _("EPS file \"%s\" is too \
906 large for page\n"),
907                                        token.u.epsf.filename));
908                         }
909                       else
910                         {
911                           /* Must start a new column. */
912                           reuse_last_token = 1;
913                           goto end_of_column;
914                         }
915                     }
916
917                   /* Do paste. */
918                   if (CORRECT_SLICE ())
919                     paste_epsf (&token);
920
921                   /* Update current point? */
922                   if (!(token.flags & F_EPSF_NO_CPOINT_UPDATE_Y))
923                     y = token.new_y;
924                   if (!(token.flags & F_EPSF_NO_CPOINT_UPDATE_X))
925                     x = token.new_x + token.u.epsf.w;
926
927                   if (y < d_footer_h + d_output_y_margin)
928                     goto end_of_column;
929                   break;
930
931                 case tFONT:
932                   /* Select a new current font. */
933                   if (line_column == 0)
934                     {
935                       double newh;
936
937                       /* Check for possible line skip change. */
938                       if (token.u.font.name[0] == '\0')
939                         newh = default_Fpt.h;
940                       else
941                         newh = token.u.font.size.h;
942
943                       if (newh != Fpt.h)
944                         {
945                           /* We need a different line skip value. */
946                           y -= (newh - Fpt.h);
947                         }
948                       /*
949                        * We must check for page overflow after we have
950                        * set the new font.
951                        */
952                     }
953
954                   MESSAGE (2, (stderr, "^@font="));
955                   if (token.u.font.name[0] == '\0')
956                     {
957                       /* Select the default font. */
958                       Fpt.w = default_Fpt.w;
959                       Fpt.h = default_Fpt.h;
960                       Fname = default_Fname;
961                       encoding = default_Fencoding;
962                       OUTPUT ((cofp, "/F-gs-font %g %g SF\n", Fpt.w, Fpt.h));
963                       user_fontp = 0;
964                     }
965                   else
966                     {
967                       strhash_put (res_fonts, token.u.font.name,
968                                    strlen (token.u.font.name) + 1,
969                                    NULL, NULL);
970                       if (token.u.font.encoding == default_Fencoding)
971                         OUTPUT ((cofp, "/%s %g %g SUF\n", token.u.font.name,
972                                  token.u.font.size.w, token.u.font.size.h));
973                       else if (token.u.font.encoding == ENC_PS)
974                         OUTPUT ((cofp, "/%s %g %g SUF_PS\n", token.u.font.name,
975                                  token.u.font.size.w, token.u.font.size.h));
976                       else
977                         FATAL ((stderr,
978                                 _("user font encoding can be only the system's default or `ps'")));
979
980                       xrealloc(user_font_name, strlen(token.u.font.name) + 1);
981                       strcpy (user_font_name, token.u.font.name);
982                       user_font_pt.w = token.u.font.size.w;
983                       user_font_pt.h = token.u.font.size.h;
984                       user_font_encoding = token.u.font.encoding;
985                       user_fontp = 1;
986
987                       Fpt.w = user_font_pt.w;
988                       Fpt.h = user_font_pt.h;
989                       Fname = user_font_name;
990                       encoding = user_font_encoding;
991                     }
992                   MESSAGE (2, (stderr, "%s %g/%gpt\n", Fname, Fpt.w, Fpt.h));
993                   read_font_info ();
994
995                   /*
996                    * Check for page overflow in that case that we were
997                    * at the first column and font were changed to a bigger
998                    * one.
999                    */
1000                   if (y < d_footer_h + d_output_y_margin)
1001                     goto end_of_column;
1002                   break;
1003
1004                 case tCOLOR:
1005                   /* Select a new color. */
1006                   MESSAGE (2, (stderr, "^@color{%f %f %f}\n",
1007                                token.u.color.r,
1008                                token.u.color.g,
1009                                token.u.color.b));
1010                   if (token.u.color.r == token.u.color.g
1011                       && token.u.color.g == token.u.color.b
1012                       && token.u.color.b == 0.0)
1013                     {
1014                       /* Select the default color (black). */
1015                       OUTPUT ((cofp, "0 setgray\n"));
1016                       user_colorp = 0;
1017                     }
1018                   else
1019                     {
1020                       OUTPUT ((cofp, "%g %g %g setrgbcolor\n",
1021                                token.u.color.r,
1022                                token.u.color.g,
1023                                token.u.color.b));
1024
1025                       user_color.r = token.u.color.r;
1026                       user_color.g = token.u.color.g;
1027                       user_color.b = token.u.color.b;
1028                       user_colorp = 1;
1029                     }
1030                   break;
1031
1032                 case tBGCOLOR:
1033                   /* Select a new background color. */
1034                   MESSAGE (2, (stderr, "^@bgcolor{%f %f %f}\n",
1035                                token.u.color.r,
1036                                token.u.color.g,
1037                                token.u.color.b));
1038
1039                   if (token.u.color.r == token.u.color.g
1040                       && token.u.color.g == token.u.color.b
1041                       && token.u.color.b == 1.0)
1042                     {
1043                       /* Select the default bgcolor (white). */
1044                       user_bgcolorp = 0;
1045                     }
1046                   else
1047                     {
1048                       user_bgcolor.r = token.u.color.r;
1049                       user_bgcolor.g = token.u.color.g;
1050                       user_bgcolor.b = token.u.color.b;
1051                       user_bgcolorp = 1;
1052                     }
1053                   break;
1054
1055                 case tSETFILENAME:
1056                   xfree (fname);
1057                   fname = xstrdup (token.u.filename);
1058                   break;
1059
1060                 case tSETPAGENUMBER:
1061                   current_pagenum = token.u.i - 1;
1062                   break;
1063
1064                 case tNEWPAGE:
1065                   if (current_linenum >= token.u.i)
1066                     goto end_of_page;
1067                   break;
1068
1069                 case tSAVEX:
1070                   xstore[(unsigned char) token.u.i] = x;
1071                   break;
1072
1073                 case tLOADX:
1074                   x = xstore[(unsigned char) token.u.i];
1075                   break;
1076
1077                 case tPS:
1078                   OUTPUT ((cofp, "%g %g M\n%s\n", x, y, token.u.str));
1079                   xfree (token.u.str);
1080                   break;
1081
1082                 case tNONE:
1083                 default:
1084                   FATAL ((stderr, "process_file(): got illegal token %d",
1085                           token.type));
1086                   break;
1087                 }
1088             }
1089         end_of_column:
1090           ;                     /* ULTRIX's cc needs this line. */
1091         }
1092
1093     end_of_page:
1094       if (!page_clear)
1095         dump_ps_page_trailer ();
1096     }
1097
1098   /*
1099    * Reset print flag to true so all the required document trailers
1100    * etc. get printed properly.
1101    */
1102   do_print = 1;
1103
1104   /* Undivert our output from the temp file to our output stream. */
1105   undivert ();
1106
1107   /* Table of contents? */
1108   if (toc)
1109     {
1110       char *cp;
1111       int save_total_pages = total_pages;
1112
1113       /* use first pagenum in file for toc */
1114       total_pages = first_pagenum_for_file;
1115
1116       cp = format_user_string ("TOC", toc_fmt_string);
1117       fprintf (toc_fp, "%s\n", cp);
1118       xfree (cp);
1119
1120       total_pages = save_total_pages;
1121     }
1122 }
1123
1124 \f
1125 /*
1126  * Static functions.
1127  */
1128
1129 /* Help macros. */
1130
1131 /* Check if character <ch> fits to current line. */
1132 #define FITS_ON_LINE(ch) ((linepos + CHAR_WIDTH (ch) < linew) || col == 0)
1133
1134 /* Is line buffer empty? */
1135 #define BUFFER_EMPTY() (bufpos == 0)
1136
1137 /* Unconditionally append character <ch> to the line buffer. */
1138 #define APPEND_CHAR(ch)                                 \
1139   do {                                                  \
1140     if (bufpos >= buflen)                               \
1141       {                                                 \
1142         buflen += 4096;                                 \
1143         buffer = xrealloc (buffer, buflen);             \
1144       }                                                 \
1145     buffer[bufpos++] = ch;                              \
1146   } while (0)
1147
1148 /*
1149  * Copy character <ch> (it fits to this line) to output buffer and
1150  * update current point counters.
1151  */
1152 #define EMIT(ch)                \
1153   do {                          \
1154     APPEND_CHAR (ch);           \
1155     linepos += CHAR_WIDTH (ch); \
1156     col++;                      \
1157   } while (0)
1158
1159 #define UNEMIT(ch)              \
1160   do {                          \
1161     linepos -= CHAR_WIDTH (ch); \
1162     col--;                      \
1163   } while (0)
1164
1165 #define ISSPACE(ch) ((ch) == ' ' || (ch) == '\t')
1166 #define ISOCTAL(ch) ('0' <= (ch) && (ch) <= '7')
1167
1168 /* Read one special escape from input <fp>. */
1169
1170 static struct
1171 {
1172   char *name;
1173   SpecialEscape escape;
1174 } escapes[] =
1175   {
1176     {"comment",         ESC_COMMENT},
1177     {"epsf",            ESC_EPSF},
1178     {"font",            ESC_FONT},
1179     {"color",           ESC_COLOR},
1180     {"bgcolor",         ESC_BGCOLOR},
1181     {"newpage",         ESC_NEWPAGE},
1182     {"ps",              ESC_PS},
1183     {"setfilename",     ESC_SETFILENAME},
1184     {"setpagenumber",   ESC_SETPAGENUMBER},
1185     {"shade",           ESC_SHADE},
1186     {"bggray",          ESC_BGGRAY},
1187     {"escape",          ESC_ESCAPE},
1188     {"savex",           ESC_SAVEX},
1189     {"loadx",           ESC_LOADX},
1190     {NULL, 0},
1191   };
1192
1193
1194 static void
1195 read_special_escape (InputStream *is, Token *token)
1196 {
1197   char escname[256];
1198   char buf[4096];
1199   int i, e;
1200   int ch;
1201
1202   /* Get escape name. */
1203   for (i = 0; i < sizeof (escname) - 1 && (ch = is_getc (is)) != EOF; i++)
1204     {
1205       if (!isalnum (ch))
1206         {
1207           is_ungetc (ch, is);
1208           break;
1209         }
1210       else
1211         escname[i] = ch;
1212     }
1213   escname[i] = '\0';
1214
1215   /* Lookup escape. */
1216   for (e = 0; escapes[e].name; e++)
1217     if (strcmp (escname, escapes[e].name) == 0)
1218       break;
1219   if (escapes[e].name == NULL)
1220     FATAL ((stderr, _("unknown special escape: %s"), escname));
1221
1222   /*
1223    * The epsf escape takes optional arguments so it must be handled
1224    * differently.
1225    */
1226   if (escapes[e].escape == ESC_EPSF)
1227     {
1228       int i;
1229       int pw, ph;
1230       double scale;
1231
1232       token->flags = 0;
1233       token->u.epsf.x = 0.0;
1234       token->u.epsf.y = 0.0;
1235       token->u.epsf.h = 0.0;
1236       token->u.epsf.pipe = 0;
1237
1238       ch = is_getc (is);
1239       if (ch == '[')
1240         {
1241           /* Read options. */
1242           while ((ch = is_getc (is)) != EOF && ch != ']')
1243             {
1244               switch (ch)
1245                 {
1246                 case 'c':       /* center justification */
1247                   token->flags &= ~M_EPSF_JUSTIFICATION;
1248                   token->flags |= F_EPSF_CENTER;
1249                   break;
1250
1251                 case 'n':       /* no current point update */
1252                   /* Check the next character. */
1253                   ch = is_getc (is);
1254                   switch (ch)
1255                     {
1256                     case 'x':
1257                       token->flags |= F_EPSF_NO_CPOINT_UPDATE_X;
1258                       break;
1259
1260                     case 'y':
1261                       token->flags |= F_EPSF_NO_CPOINT_UPDATE_Y;
1262                       break;
1263
1264                     default:
1265                       is_ungetc (ch, is);
1266                       token->flags |= F_EPSF_NO_CPOINT_UPDATE_X;
1267                       token->flags |= F_EPSF_NO_CPOINT_UPDATE_Y;
1268                       break;
1269                     }
1270                   break;
1271
1272                 case 'r':       /* right justification */
1273                   token->flags &= ~M_EPSF_JUSTIFICATION;
1274                   token->flags |= F_EPSF_RIGHT;
1275                   break;
1276
1277
1278                 case 's':       /* scale */
1279                   /* Check the next character. */
1280                   ch = is_getc (is);
1281                   switch (ch)
1282                     {
1283                     case 'x':
1284                       token->flags |= F_EPSF_SCALE_X;
1285                       token->u.epsf.xscale = read_float (is, 0, 1);
1286                       break;
1287
1288                     case 'y':
1289                       token->flags |= F_EPSF_SCALE_Y;
1290                       token->u.epsf.yscale = read_float (is, 0, 0);
1291                       break;
1292
1293                     default:
1294                       is_ungetc (ch, is);
1295                       token->flags |= F_EPSF_SCALE_X;
1296                       token->flags |= F_EPSF_SCALE_Y;
1297                       token->u.epsf.xscale = token->u.epsf.yscale
1298                         = read_float (is, 0, 1);
1299                       break;
1300                     }
1301                   break;
1302
1303                 case 'x':       /* x-position */
1304                   token->u.epsf.x = read_float (is, 1, 1);
1305
1306                   /* Check the next character. */
1307                   ch = is_getc (is);
1308                   switch (ch)
1309                     {
1310                     case 'a':
1311                       token->flags |= F_EPSF_ABSOLUTE_X;
1312                       break;
1313
1314                     default:
1315                       is_ungetc (ch, is);
1316                       break;
1317                     }
1318                   break;
1319
1320                 case 'y':       /* y-position */
1321                   token->u.epsf.y = - read_float (is, 1, 0);
1322
1323                   /* Check the next character. */
1324                   ch = is_getc (is);
1325                   switch (ch)
1326                     {
1327                     case 'a':
1328                       token->flags |= F_EPSF_ABSOLUTE_Y;
1329                       break;
1330
1331                     default:
1332                       is_ungetc (ch, is);
1333                       break;
1334                     }
1335                   break;
1336
1337                 case 'h':       /* height */
1338                   token->u.epsf.h = read_float (is, 1, 0);
1339                   break;
1340
1341                 case ' ':
1342                 case '\t':
1343                   break;
1344
1345                 default:
1346                   FATAL ((stderr, _("illegal option %c for ^@epsf escape"),
1347                           ch));
1348                 }
1349             }
1350           if (ch != ']')
1351             FATAL ((stderr,
1352                     _("malformed ^@epsf escape: no ']' after options")));
1353
1354           ch = is_getc (is);
1355         }
1356       if (ch == '{')
1357         {
1358           /* Read filename. */
1359           for (i = 0; (ch = is_getc (is)) != EOF && ch != '}'; i++)
1360             {
1361               token->u.epsf.filename[i] = ch;
1362               if (i + 1 >= sizeof (token->u.epsf.filename))
1363                 FATAL ((stderr,
1364                         _("too long file name for ^@epsf escape:\n%.*s"),
1365                         i, token->u.epsf.filename));
1366             }
1367           if (ch == EOF)
1368             FATAL ((stderr, _("unexpected EOF while scanning ^@epsf escape")));
1369
1370           token->u.epsf.filename[i] = '\0';
1371           token->type = tEPSF;
1372         }
1373       else
1374         FATAL ((stderr, _("malformed ^@epsf escape: no '{' found")));
1375
1376       /*
1377        * Now we have a valid epsf-token in <token>.  Let's read BoundingBox
1378        * and do some calculations.
1379        */
1380       if (!recognize_eps_file (token))
1381         /* Recognize eps has already printed error message so we are done. */
1382         token->type = tNONE;
1383       else
1384         {
1385           /* Some fixups for x and y dimensions. */
1386           token->u.epsf.y += LINESKIP - 1;
1387           if (token->u.epsf.h != 0.0)
1388             token->u.epsf.h -= 1.0;
1389
1390           /* Count picture's width and height. */
1391
1392           pw = token->u.epsf.urx - token->u.epsf.llx;
1393           ph = token->u.epsf.ury - token->u.epsf.lly;
1394
1395           /* The default scale. */
1396           if (token->u.epsf.h == 0.0)
1397             scale = 1.0;
1398           else
1399             scale = token->u.epsf.h / ph;
1400
1401           if ((token->flags & F_EPSF_SCALE_X) == 0)
1402             token->u.epsf.xscale = scale;
1403           if ((token->flags & F_EPSF_SCALE_Y) == 0)
1404             token->u.epsf.yscale = scale;
1405
1406           pw *= token->u.epsf.xscale;
1407           ph *= token->u.epsf.yscale;
1408
1409           token->u.epsf.w = pw;
1410           token->u.epsf.h = ph;
1411         }
1412     }
1413   else if (escapes[e].escape == ESC_COMMENT)
1414     {
1415       /* Comment the rest of this line. */
1416       while ((ch = is_getc (is)) != EOF && ch != nl)
1417         ;
1418       token->type = tNONE;
1419     }
1420   else
1421     {
1422       char *cp;
1423       int parenlevel;
1424
1425       /*
1426        * Handle the rest of the escapes.
1427        */
1428
1429       /* Read argument. */
1430       ch = is_getc (is);
1431       if (ch != '{')
1432         FATAL ((stderr, _("malformed %s escape: no '{' found"),
1433                 escapes[e].name));
1434
1435       parenlevel = 0;
1436       for (i = 0;
1437            (ch = is_getc (is)) != EOF && (parenlevel > 0 || ch != '}'); i++)
1438         {
1439           if (ch == '{')
1440             parenlevel++;
1441           else if (ch == '}')
1442             parenlevel--;
1443
1444           buf[i] = ch;
1445           if (i + 1 >= sizeof (buf))
1446             FATAL ((stderr, _("too long argument for %s escape:\n%.*s"),
1447                     escapes[i].name, i, buf));
1448         }
1449       buf[i] = '\0';
1450
1451       /* And now handle the escape. */
1452       switch (escapes[e].escape)
1453         {
1454         case ESC_FONT:
1455           strcpy (token->u.font.name, buf);
1456
1457           /* Check for the default font. */
1458           if (strcmp (token->u.font.name, "default") == 0)
1459             token->u.font.name[0] = '\0';
1460           else
1461             {
1462               if (!parse_font_spec (token->u.font.name, &cp,
1463                                     &token->u.font.size,
1464                                     &token->u.font.encoding))
1465                 FATAL ((stderr, _("malformed font spec for ^@font escape: %s"),
1466                         token->u.font.name));
1467
1468               strcpy (token->u.font.name, cp);
1469               xfree (cp);
1470             }
1471           token->type = tFONT;
1472           break;
1473
1474         case ESC_COLOR:
1475         case ESC_BGCOLOR:
1476           /* Check for the default color. */
1477           if (strcmp (buf, "default") == 0)
1478             {
1479               double val = 0;
1480
1481               if (escapes[e].escape == ESC_BGCOLOR)
1482                 val = 1;
1483
1484               token->u.color.r = val;
1485               token->u.color.g = val;
1486               token->u.color.b = val;
1487             }
1488           else
1489             {
1490               int got;
1491
1492               got = sscanf (buf, "%g %g %g",
1493                             &token->u.color.r,
1494                             &token->u.color.g,
1495                             &token->u.color.b);
1496               switch (got)
1497                 {
1498                 case 0:
1499                 case 2:
1500                   FATAL ((stderr,
1501                           _("malformed color spec for ^@%s escape: %s"),
1502                           escapes[e].escape == ESC_COLOR
1503                           ? "color" : "bgcolor",
1504                           buf));
1505                   break;
1506
1507                 case 1:
1508                   token->u.color.g = token->u.color.b = token->u.color.r;
1509                   break;
1510
1511                 default:
1512                   /* Got all three components. */
1513                   break;
1514                 }
1515             }
1516           if (escapes[e].escape == ESC_COLOR)
1517             token->type = tCOLOR;
1518           else
1519             token->type = tBGCOLOR;
1520           break;
1521
1522         case ESC_SHADE:
1523           line_highlight_gray = atof (buf);
1524           if (line_highlight_gray < 0.0 || line_highlight_gray > 1.0)
1525             FATAL ((stderr, _("invalid value for ^@shade escape: %s"), buf));
1526
1527           token->type = tNONE;
1528           break;
1529
1530         case ESC_BGGRAY:
1531           bggray = atof (buf);
1532           if (bggray < 0.0 || bggray > 1.0)
1533             FATAL ((stderr, _("invalid value for ^@bggray escape: %s"), buf));
1534
1535           token->type = tNONE;
1536           break;
1537
1538         case ESC_ESCAPE:
1539           if (strcmp (buf, "default") == 0)
1540             escape_char = default_escape_char;
1541           else
1542             escape_char = atoi (buf);
1543           token->type = tNONE;
1544           break;
1545
1546         case ESC_SETFILENAME:
1547           strcpy (token->u.filename, buf);
1548           token->type = tSETFILENAME;
1549           break;
1550
1551         case ESC_SETPAGENUMBER:
1552           token->u.i = atoi (buf);
1553           token->type = tSETPAGENUMBER;
1554           break;
1555
1556         case ESC_NEWPAGE:
1557           if (i == 0)
1558             token->u.i = 1;     /* The default is the first line. */
1559           else
1560             token->u.i = atoi (buf);
1561           token->type = tNEWPAGE;
1562           break;
1563
1564         case ESC_SAVEX:
1565           token->type = tSAVEX;
1566           token->u.i = atoi (buf);
1567           break;
1568
1569         case ESC_LOADX:
1570           token->type = tLOADX;
1571           token->u.i = atoi (buf);
1572           break;
1573
1574         case ESC_PS:
1575           token->u.str = xstrdup (buf);
1576           token->type = tPS;
1577           break;
1578
1579         default:
1580           /* NOTREACHED */
1581           abort ();
1582           break;
1583         }
1584     }
1585 }
1586
1587
1588 /* Get next token from input file <fp>. */
1589 static void
1590 get_next_token (InputStream *is, double linestart, double linepos,
1591                 unsigned int col, double linew, Token *token)
1592 {
1593   static unsigned char *buffer = NULL; /* output buffer */
1594   static unsigned int buflen = 0; /* output buffer's length */
1595   unsigned int bufpos = 0;      /* current position in output buffer */
1596   int ch = 0;
1597   int done = 0;
1598   int i;
1599   static int pending_token = tNONE;
1600   unsigned int original_col = col;
1601
1602   if (pending_token != tNONE)
1603     {
1604       token->type = pending_token;
1605       pending_token = tNONE;
1606       return;
1607     }
1608
1609 #define DONE_DONE 1
1610 #define DONE_WRAP 2
1611
1612   while (!done)
1613     {
1614       ch = is_getc (is);
1615       switch (ch)
1616         {
1617         case EOF:
1618           if (BUFFER_EMPTY ())
1619             {
1620               token->type = tEOF;
1621               return;
1622             }
1623
1624           done = DONE_DONE;
1625           break;
1626
1627         case '\r':
1628         case '\n':
1629           /*
1630            * One of these is the newline character and the other one
1631            * is carriage return.
1632            */
1633           if (ch == nl)
1634             {
1635               /* The newline character. */
1636               if (BUFFER_EMPTY ())
1637                 {
1638                   token->type = tNEWLINE;
1639                   return;
1640                 }
1641               else
1642                 {
1643                   is_ungetc (ch, is);
1644                   done = DONE_DONE;
1645                 }
1646             }
1647           else
1648             {
1649               /* The carriage return character. */
1650               if (BUFFER_EMPTY ())
1651                 {
1652                   token->type = tCARRIAGE_RETURN;
1653                   return;
1654                 }
1655               else
1656                 {
1657                   is_ungetc (ch, is);
1658                   done = DONE_DONE;
1659                 }
1660             }
1661           break;
1662
1663         case '\t':
1664           if (font_is_fixed)
1665             {
1666               i = tabsize - (col % tabsize);
1667               for (; i > 0; i--)
1668                 {
1669                   if (FITS_ON_LINE (' '))
1670                     EMIT (' ');
1671                   else
1672                     {
1673                       done = DONE_WRAP;
1674                       break;
1675                     }
1676                 }
1677             }
1678           else
1679             {
1680               /* Proportional font. */
1681
1682               double grid = tabsize * CHAR_WIDTH (' ');
1683               col++;
1684
1685               /* Move linepos to the next multiple of <grid>. */
1686               linepos = (((int) ((linepos - linestart) / grid) + 1) * grid
1687                          + linestart);
1688               if (linepos >= linew)
1689                 done = DONE_WRAP;
1690               else
1691                 done = DONE_DONE;
1692             }
1693           break;
1694
1695         case '\f':
1696           if (BUFFER_EMPTY ())
1697             {
1698               if (interpret_formfeed)
1699                 token->type = tFORMFEED;
1700               else
1701                 token->type = tNEWLINE;
1702               return;
1703             }
1704           else
1705             {
1706               is_ungetc (ch, is);
1707               done = DONE_DONE;
1708             }
1709           break;
1710
1711         default:
1712           /* Handle special escapes. */
1713           if (special_escapes && ch == escape_char)
1714             {
1715               if (BUFFER_EMPTY ())
1716                 {
1717                   /* Interpret special escapes. */
1718                   read_special_escape (is, token);
1719                   if (token->type != tNONE)
1720                     return;
1721
1722                   /*
1723                    * Got tNONE special escape => read_special_escape()
1724                    * has already done what was needed.  Just read more.
1725                    */
1726                   break;
1727                 }
1728               else
1729                 {
1730                   is_ungetc (ch, is);
1731                   done = DONE_DONE;
1732                   break;
1733                 }
1734             }
1735
1736           /* Handle backspace character. */
1737           if (ch == bs)
1738             {
1739               if (BUFFER_EMPTY () || !EXISTS (buffer[bufpos - 1]))
1740                 linepos -= CHAR_WIDTH ('m');
1741               else
1742                 linepos -= CHAR_WIDTH (buffer[bufpos - 1]);
1743
1744               done = DONE_DONE;
1745               break;
1746             }
1747
1748           /* Check normal characters. */
1749           if (EXISTS (ch))
1750             {
1751               if (FITS_ON_LINE (ch))
1752                 {
1753                   /*
1754                    * Print control characters (and optionally
1755                    * characters greater than 127) in the escaped form
1756                    * so PostScript interpreter will not hang on them.
1757                    */
1758                   if (ch < 040 || (clean_7bit && ch >= 0200))
1759                     {
1760                       char buf[10];
1761
1762                       sprintf (buf, "\\%03o", ch);
1763                       for (i = 0; buf[i]; i++)
1764                         APPEND_CHAR (buf[i]);
1765
1766                       /* Update current point counters manually. */
1767                       linepos += CHAR_WIDTH (ch);
1768                       col++;
1769                     }
1770                   else if (ch == '(' || ch == ')' || ch == '\\')
1771                     {
1772                       /* These must be quoted in PostScript strings. */
1773                       APPEND_CHAR ('\\');
1774                       EMIT (ch);
1775                     }
1776                   else
1777                     EMIT (ch);
1778                 }
1779               else
1780                 {
1781                   is_ungetc (ch, is);
1782                   done = DONE_WRAP;
1783                 }
1784             }
1785           else if (ISPRINT (ch))
1786             {
1787               /* Printable, but do not exists in this font. */
1788               if (FITS_ON_LINE ('?'))
1789                 {
1790                   EMIT ('?');
1791                   if (missing_chars[ch]++ == 0)
1792                     num_missing_chars++;
1793                 }
1794               else
1795                 {
1796                   is_ungetc (ch, is);
1797                   done = DONE_WRAP;
1798                 }
1799             }
1800           else
1801             {
1802               char buf[20];
1803               double len = 0.0;
1804
1805               /*
1806                * Non-printable and does not exist in current font, print
1807                * it in the format specified by non_printable_format.
1808                */
1809
1810               if (non_printable_chars[ch]++ == 0)
1811                 num_non_printable_chars++;
1812
1813               switch (non_printable_format)
1814                 {
1815                 case NPF_SPACE:
1816                   strcpy (buf, " ");
1817                   break;
1818
1819                 case NPF_QUESTIONMARK:
1820                   strcpy (buf, "?");
1821                   break;
1822
1823                 case NPF_CARET:
1824                   if (ch < 0x20)
1825                     {
1826                       buf[0] = '^';
1827                       buf[1] = '@' + ch;
1828                       buf[2] = '\0';
1829                       break;
1830                     }
1831                   /* FALLTHROUGH */
1832
1833                 case NPF_OCTAL:
1834                   sprintf (buf, "\\%03o", ch);
1835                   break;
1836                 }
1837
1838               /* Count length. */
1839               for (i = 0; buf[i]; i++)
1840                 len += CHAR_WIDTH (buf[i]);
1841
1842               if (linepos + len < linew || col == 0)
1843                 {
1844                   /* Print it. */
1845                   for (i = 0; buf[i]; i++)
1846                     {
1847                       if (buf[i] == '\\')
1848                         APPEND_CHAR ('\\'); /* Escape '\\' characters. */
1849                       EMIT (buf[i]);
1850                     }
1851                 }
1852               else
1853                 {
1854                   is_ungetc (ch, is);
1855                   done = DONE_WRAP;
1856                 }
1857             }
1858           break;
1859         }
1860     }
1861
1862   /* Got a string. */
1863
1864   /* Check for wrapped line. */
1865   if (done == DONE_WRAP)
1866     {
1867       /* This line is too long. */
1868       ch = nl;
1869       if (line_end == LE_TRUNCATE)
1870         {
1871           /* Truncate this line. */
1872           while ((ch = is_getc (is)) != EOF && ch != nl)
1873             ;
1874         }
1875       else if (!BUFFER_EMPTY () && line_end == LE_WORD_WRAP)
1876         {
1877           int w;
1878
1879           if (ISSPACE (buffer[bufpos - 1]))
1880             {
1881               /* Skip all whitespace from the end of the wrapped line. */
1882               while ((w = is_getc (is)) != EOF && ISSPACE (w))
1883                 ;
1884               is_ungetc (w, is);
1885             }
1886           else
1887             {
1888               /* Find the previous word boundary for the wrap. */
1889               for (w = bufpos - 1; w >= 0 && !ISSPACE (buffer[w]); w--)
1890                 ;
1891               w++;
1892               if (w > 0 || original_col > 0)
1893                 {
1894                   /*
1895                    * Ok, we found a word boundary.  Now we must unemit
1896                    * characters from the buffer to the intput stream.
1897                    *
1898                    * Note:
1899                    *  - bufpos is unsigned integer variable
1900                    *  - some characters are escaped with '\\'
1901                    *  - some characters are printed in octal notation
1902                    */
1903                   do
1904                     {
1905                       bufpos--;
1906
1907                       /* Check for '(', ')' and '\\'. */
1908                       if (bufpos > w
1909                           && (buffer[bufpos] == '('
1910                               || buffer[bufpos] ==  ')'
1911                               || buffer[bufpos] == '\\')
1912                           && buffer[bufpos - 1] == '\\')
1913                         {
1914                           is_ungetc (buffer[bufpos], is);
1915                           UNEMIT (buffer[bufpos]);
1916                           bufpos--;
1917                         }
1918                       /* Check the octal notations "\\%03o". */
1919                       else if (bufpos - 2 > w
1920                                && ISOCTAL (buffer[bufpos])
1921                                && ISOCTAL (buffer[bufpos - 1])
1922                                && ISOCTAL (buffer[bufpos - 2])
1923                                && buffer[bufpos - 3] == '\\')
1924                         {
1925                           unsigned int ti;
1926
1927                           /*
1928                            * It is a potential octal character.  Now we
1929                            * must process the buffer from the beginning
1930                            * and see if `bufpos - 3' really starts a character.
1931                            */
1932                           for (ti = w; ti < bufpos - 3; ti++)
1933                             {
1934                               if (buffer[ti] == '\\')
1935                                 {
1936                                   if (ISOCTAL (buffer[ti + 1]))
1937                                     {
1938                                       unsigned int tti;
1939
1940                                       for (tti = 0;
1941                                            tti < 3 && ISOCTAL (buffer[ti + 1]);
1942                                            tti++, ti++)
1943                                         ;
1944                                     }
1945                                   else
1946                                     /* Simple escape. */
1947                                     ti++;
1948                                 }
1949                             }
1950
1951                           /*
1952                            * If <ti> is equal to <bufpos - 3>, we found
1953                            * an octal character, otherwise the leading
1954                            * backslash at <bufpos - 3> belongs to the
1955                            * previous character.
1956                            */
1957                           if (ti == bufpos - 3)
1958                             {
1959                               int tch;
1960
1961                               tch = (((buffer[bufpos - 2] - '0') << 6)
1962                                      + ((buffer[bufpos - 1] - '0') << 3)
1963                                      + (buffer[bufpos] - '0'));
1964                               is_ungetc (tch, is);
1965                               UNEMIT (tch);
1966                               bufpos -= 3;
1967                             }
1968                           else
1969                             /* Normal character. */
1970                             goto unemit_normal;
1971                         }
1972                       else
1973                         {
1974                           /* Normal character, just unget it. */
1975                         unemit_normal:
1976                           is_ungetc (buffer[bufpos], is);
1977                           UNEMIT (buffer[bufpos]);
1978                         }
1979                     }
1980                   while (bufpos > w);
1981                 }
1982             }
1983         }
1984
1985       if (ch == nl)
1986         {
1987           if (line_end == LE_TRUNCATE)
1988             {
1989               if (do_print)
1990                 num_truncated_lines++;
1991               pending_token = tNEWLINE;
1992             }
1993           else
1994             pending_token = tWRAPPED_NEWLINE;
1995         }
1996       else
1997         pending_token = tEOF;
1998     }
1999
2000   APPEND_CHAR ('\0');
2001   token->type = tSTRING;
2002   token->u.str = (char *) buffer;
2003   token->new_x = linepos;
2004   token->new_col = col;
2005 }
2006
2007
2008 static void
2009 dump_ps_page_header (char *fname, int empty)
2010 {
2011   char *dirc, *basec, *fdir, *ftail;
2012   int got, i;
2013   char *cp, *cp2;
2014   char *cstr = "%%";
2015   unsigned int nup_subpage;
2016
2017   /* The N-up printing sub-page. */
2018   nup_subpage = (total_pages - 1) % nup;
2019
2020   /* Split fname into fdir and ftail. */
2021   dirc = strdup(fname);
2022   basec = strdup(fname);
2023   fdir = dirname(dirc);
2024   ftail = basename(basec);
2025
2026   if (nup > 1)
2027     {
2028       /* N-up printing is active. */
2029       cstr = "%";
2030
2031       if (nup_subpage == 0)
2032         {
2033           /* This is a real page start. */
2034
2035           switch (page_label)
2036             {
2037             case LABEL_SHORT:
2038               OUTPUT ((cofp, "%%%%Page: (%d-%d) %d\n", current_pagenum,
2039                        current_pagenum + nup - 1, total_pages / nup + 1));
2040               break;
2041
2042             case LABEL_LONG:
2043               OUTPUT ((cofp, "%%%%Page: (%s:%3d-%3d) %d\n", ftail,
2044                        current_pagenum, current_pagenum + nup - 1,
2045                        total_pages / nup + 1));
2046               break;
2047             }
2048
2049           /* Page setup. */
2050           OUTPUT ((cofp, "%%%%BeginPageSetup\n_S\n"));
2051
2052           if ((total_pages / nup + 1) % 2 == 0)
2053             /* Two-side binding options for the even pages. */
2054             handle_two_side_options ();
2055
2056 #define PRINT_BOUNDING_BOXES 0
2057
2058 #if PRINT_BOUNDING_BOXES
2059           OUTPUT ((cofp,
2060                    "%d %d moveto %d %d lineto %d %d lineto %d %d lineto closepath stroke\n",
2061                    media->llx, media->lly, media->llx, media->ury,
2062                    media->urx, media->ury, media->urx, media->lly));
2063 #endif
2064
2065           if (landscape)
2066             {
2067               if (nup_landscape)
2068                 OUTPUT ((cofp, "90 rotate\n%d %d translate\n",
2069                          media->lly, -media->urx));
2070               else
2071                 OUTPUT ((cofp, "%d %d translate\n", media->llx, media->lly));
2072             }
2073           else
2074             {
2075               if (nup_landscape)
2076                 OUTPUT ((cofp, "90 rotate\n%d %d translate\n",
2077                          media->lly, -media->llx));
2078               else
2079                 OUTPUT ((cofp, "%d %d translate\n", media->llx, media->ury));
2080             }
2081         }
2082     }
2083
2084   /* Page start comment. */
2085   switch (page_label)
2086     {
2087     case LABEL_SHORT:
2088       OUTPUT ((cofp, "%sPage: (%d) %d\n", cstr, current_pagenum, total_pages));
2089       break;
2090
2091     case LABEL_LONG:
2092       OUTPUT ((cofp, "%sPage: (%s:%3d) %d\n", cstr, ftail, current_pagenum,
2093                total_pages));
2094       break;
2095     }
2096
2097   /*
2098    * Page Setup.
2099    */
2100
2101   OUTPUT ((cofp, "%sBeginPageSetup\n_S\n", cstr));
2102
2103   if (nup > 1)
2104     {
2105       int xm, ym;
2106
2107       OUTPUT ((cofp, "%% N-up sub-page %d/%d\n", nup_subpage + 1, nup));
2108       if (landscape)
2109         {
2110           if (nup_columnwise)
2111             {
2112               xm = nup_subpage % nup_columns;
2113               ym = nup_subpage / nup_columns;
2114             }
2115           else
2116             {
2117               xm = nup_subpage / nup_rows;
2118               ym = nup_subpage % nup_rows;
2119             }
2120
2121           OUTPUT ((cofp, "%d %d translate\n",
2122                    xm * (nup_width + nup_xpad),
2123                    ym * (nup_height + nup_ypad)));
2124         }
2125       else
2126         {
2127           if (nup_columnwise)
2128             {
2129               xm = nup_subpage / nup_rows;
2130               ym = nup_subpage % nup_rows;
2131             }
2132           else
2133             {
2134               xm = nup_subpage % nup_columns;
2135               ym = nup_subpage / nup_columns;
2136             }
2137
2138           OUTPUT ((cofp, "%d %d translate\n",
2139                    xm * (nup_width + nup_xpad),
2140                    -((int) (ym * (nup_height + nup_ypad) + nup_height))));
2141         }
2142       OUTPUT ((cofp, "%g dup scale\n", nup_scale));
2143
2144       /* And finally, the real page setup. */
2145       if (landscape)
2146         OUTPUT ((cofp, "90 rotate\n%d %d translate\n", 0, -d_page_h));
2147     }
2148   else
2149     {
2150       /* No N-up printing. */
2151
2152       if (total_pages % 2 == 0)
2153         /* Two-side binding options for the even pages. */
2154         handle_two_side_options ();
2155
2156       if (landscape)
2157         OUTPUT ((cofp, "90 rotate\n%d %d translate\n",
2158                  media->lly, -media->urx));
2159       else
2160         OUTPUT ((cofp, "%d %d translate\n", media->llx, media->lly));
2161     }
2162
2163   /* Some constants etc. */
2164   OUTPUT ((cofp, "/pagenum %d def\n", current_pagenum));
2165
2166   cp = escape_string (fname);
2167   OUTPUT ((cofp, "/fname (%s) def\n", cp));
2168   xfree (cp);
2169
2170   cp = escape_string (fdir);
2171   OUTPUT ((cofp, "/fdir (%s) def\n", cp));
2172   xfree (cp);
2173   xfree (dirc);
2174
2175   cp = escape_string (ftail);
2176   OUTPUT ((cofp, "/ftail (%s) def\n", cp));
2177   xfree (cp);
2178   xfree (basec);
2179
2180   /* Do we have a pending ^@font{} font? */
2181   if (user_fontp)
2182     {
2183       if (encoding == default_Fencoding)
2184         OUTPUT ((cofp, "/%s %g %g SUF\n", Fname, Fpt.w, Fpt.h));
2185       else
2186         /* This must be the case. */
2187         OUTPUT ((cofp, "/%s %g %g SUF_PS\n", Fname, Fpt.w, Fpt.h));
2188     }
2189
2190   /* Dump user defined strings. */
2191   if (count_key_value_set (user_strings) > 0)
2192     {
2193       OUTPUT ((cofp, "%% User defined strings:\n"));
2194       for (got = strhash_get_first (user_strings, &cp, &i, (void **) &cp2);
2195            got;
2196            got = strhash_get_next (user_strings, &cp, &i, (void **) &cp2))
2197         {
2198           cp2 = format_user_string ("%Format", cp2);
2199           OUTPUT ((cofp, "/%s (%s) def\n", cp, cp2));
2200           xfree (cp2);
2201         }
2202     }
2203
2204   /* User supplied header? */
2205   if (page_header)
2206     {
2207       char *h_left;
2208       char *h_center;
2209       char *h_right = NULL;
2210
2211       h_left = format_user_string ("page header", page_header);
2212       h_center = strchr (h_left, '|');
2213       if (h_center)
2214         {
2215           *h_center = '\0';
2216           h_center++;
2217
2218           h_right = strchr (h_center, '|');
2219           if (h_right)
2220             {
2221               *h_right = '\0';
2222               h_right++;
2223             }
2224         }
2225
2226       OUTPUT ((cofp, "/user_header_p true def\n"));
2227       OUTPUT ((cofp, "/user_header_left_str (%s) def\n", h_left));
2228       OUTPUT ((cofp, "/user_header_center_str (%s) def\n",
2229                h_center ? h_center : ""));
2230       OUTPUT ((cofp, "/user_header_right_str (%s) def\n",
2231                h_right ? h_right : ""));
2232       xfree (h_left);
2233     }
2234   else
2235     OUTPUT ((cofp, "/user_header_p false def\n"));
2236
2237   /* User supplied footer? */
2238   if (page_footer)
2239     {
2240       char *f_left;
2241       char *f_center;
2242       char *f_right = NULL;
2243
2244       f_left = format_user_string ("page footer", page_footer);
2245       f_center = strchr (f_left, '|');
2246       if (f_center)
2247         {
2248           *f_center = '\0';
2249           f_center++;
2250
2251           f_right = strchr (f_center, '|');
2252           if (f_right)
2253             {
2254               *f_right = '\0';
2255               f_right++;
2256             }
2257         }
2258
2259       OUTPUT ((cofp, "/user_footer_p true def\n"));
2260       OUTPUT ((cofp, "/user_footer_left_str (%s) def\n", f_left));
2261       OUTPUT ((cofp, "/user_footer_center_str (%s) def\n",
2262                f_center ? f_center : ""));
2263       OUTPUT ((cofp, "/user_footer_right_str (%s) def\n",
2264                f_right ? f_right : ""));
2265       xfree (f_left);
2266     }
2267   else
2268     OUTPUT ((cofp, "/user_footer_p false def\n"));
2269
2270   OUTPUT ((cofp, "%%%%EndPageSetup\n"));
2271
2272   /*
2273    * Mark standard page decorations.
2274    */
2275
2276   if (!empty)
2277     {
2278       /* Highlight bars. */
2279       if (highlight_bars)
2280         OUTPUT ((cofp, "%d %f %d %f highlight_bars\n", highlight_bars,
2281                  LINESKIP, d_output_y_margin, highlight_bar_gray));
2282
2283       /* Underlay. */
2284       if (underlay != NULL)
2285         {
2286           if (ul_position_p || ul_angle_p)
2287             OUTPUT ((cofp, "user_underlay\n"));
2288           else
2289             OUTPUT ((cofp, "underlay\n"));
2290         }
2291
2292       /* Column lines. */
2293       if (num_columns > 1 && (header == HDR_FANCY || borders))
2294         OUTPUT ((cofp, "column_lines\n"));
2295
2296       /* Borders around columns. */
2297       if (borders)
2298         OUTPUT ((cofp, "column_borders\n"));
2299
2300       /* Header. */
2301       switch (header)
2302         {
2303         case HDR_NONE:
2304           break;
2305
2306         case HDR_SIMPLE:
2307         case HDR_FANCY:
2308           OUTPUT ((cofp, "do_header\n"));
2309           break;
2310         }
2311     }
2312
2313   /* Do we have a pending ^@color{} color? */
2314   if (user_colorp)
2315     OUTPUT ((cofp, "%g %g %g setrgbcolor\n", user_color.r, user_color.g,
2316              user_color.b));
2317 }
2318
2319
2320 static void
2321 dump_ps_page_trailer ()
2322 {
2323   unsigned int nup_subpage = (total_pages - 1) % nup;
2324
2325   OUTPUT ((cofp, "_R\n"));
2326
2327   if (nup > 1)
2328     {
2329       if (nup_subpage + 1 == nup)
2330         /* Real end of page. */
2331         OUTPUT ((cofp, "_R\nS\n"));
2332     }
2333   else
2334     OUTPUT ((cofp, "S\n"));
2335 }
2336
2337
2338 static void
2339 dump_empty_page ()
2340 {
2341   if (nup > 1)
2342     {
2343       unsigned int nup_subpage = (total_pages - 1) % nup;
2344
2345       if (nup_subpage == 0)
2346         {
2347           /* Real start of the page, must do it the harder way. */
2348           dump_ps_page_header ("", 1);
2349           OUTPUT ((cofp, "_R\n"));
2350         }
2351       else
2352         OUTPUT ((cofp, "%%Page: (-) %d\n", total_pages));
2353
2354       if (nup_subpage + 1 == nup)
2355         /* This is the last page on this sheet, dump us. */
2356         OUTPUT ((cofp, "_R\nS\n"));
2357     }
2358   else
2359     OUTPUT ((cofp, "%%%%Page: (-) %d\nS\n", total_pages));
2360 }
2361
2362
2363 static int
2364 recognize_eps_file (Token *token)
2365 {
2366   int i;
2367   char buf[4096];
2368   char *filename;
2369   int line;
2370   int valid_epsf;
2371   float llx, lly, urx, ury;
2372
2373   MESSAGE (2, (stderr, "^@epsf=\"%s\"\n", token->u.epsf.filename));
2374
2375   i = strlen (token->u.epsf.filename);
2376
2377   /* Read EPS data from file. */
2378   filename = tilde_subst (token->u.epsf.filename);
2379
2380   token->u.epsf.fp = fopen (filename, "rb");
2381   xfree (filename);
2382
2383   if (token->u.epsf.fp == NULL)
2384     {
2385       if (token->u.epsf.filename[0] != '/')
2386         {
2387           /* Name is not absolute, let's lookup path. */
2388           FileLookupCtx ctx;
2389
2390           ctx.name = token->u.epsf.filename;
2391           ctx.suffix = "";
2392           ctx.fullname = buffer_alloc ();
2393
2394           if (pathwalk (libpath, file_lookup, &ctx))
2395             token->u.epsf.fp = fopen (buffer_ptr (ctx.fullname), "rb");
2396
2397           buffer_free (ctx.fullname);
2398         }
2399       if (token->u.epsf.fp == NULL)
2400         {
2401           MESSAGE (0, (stderr, _("couldn't open EPS file \"%s\": %s\n"),
2402                        token->u.epsf.filename, strerror (errno)));
2403           return 0;
2404         }
2405     }
2406
2407   /* Find BoundingBox DSC comment. */
2408
2409   line = 0;
2410   valid_epsf = 0;
2411   token->u.epsf.skipbuf = NULL;
2412   token->u.epsf.skipbuf_len = 0;
2413   token->u.epsf.skipbuf_pos = 0;
2414
2415   while (fgets (buf, sizeof (buf), token->u.epsf.fp))
2416     {
2417       line++;
2418
2419       /* Append data to the skip buffer. */
2420       i = strlen (buf);
2421       if (i + token->u.epsf.skipbuf_pos >= token->u.epsf.skipbuf_len)
2422         {
2423           token->u.epsf.skipbuf_len += 8192;
2424           token->u.epsf.skipbuf = xrealloc (token->u.epsf.skipbuf,
2425                                             token->u.epsf.skipbuf_len);
2426         }
2427       memcpy (token->u.epsf.skipbuf + token->u.epsf.skipbuf_pos, buf, i);
2428       token->u.epsf.skipbuf_pos += i;
2429
2430       /* Check the "%!" magic cookie. */
2431       if (line == 1)
2432         {
2433           if (buf[0] != '%' || buf[1] != '!')
2434             {
2435               MESSAGE (0,
2436                        (stderr,
2437                         _("EPS file \"%s\" does not start with \"%%!\" magic\n"),
2438                         token->u.epsf.filename));
2439               break;
2440             }
2441         }
2442
2443 #define BB_DSC "%%BoundingBox:"
2444
2445       if (strncmp (buf, BB_DSC, strlen (BB_DSC)) == 0)
2446         {
2447           i = sscanf (buf + strlen (BB_DSC), "%f %f %f %f",
2448                       &llx, &lly, &urx, &ury);
2449           if (i != 4)
2450             {
2451               /* (atend) ? */
2452
2453               /* Skip possible whitespace. */
2454               for (i = strlen (BB_DSC);
2455                    buf[i] && (buf[i] == ' ' || buf[i] == '\t');
2456                    i++)
2457                 ;
2458 #define BB_DSC_ATEND "(atend)"
2459               if (strncmp (buf + i, BB_DSC_ATEND, strlen (BB_DSC_ATEND)) != 0)
2460                 {
2461                   /* No, this BoundingBox comment is corrupted. */
2462                   MESSAGE (0, (stderr, _("EPS file \"%s\" contains malformed \
2463 %%%%BoundingBox row:\n\"%.*s\"\n"),
2464                                token->u.epsf.filename, strlen (buf) - 1, buf));
2465                   break;
2466                 }
2467             }
2468           else
2469             {
2470               /* It was a valid EPS file. */
2471
2472               /* We store bounding box in int format. */
2473               token->u.epsf.llx = llx;
2474               token->u.epsf.lly = lly;
2475               token->u.epsf.urx = urx;
2476               token->u.epsf.ury = ury;
2477
2478               valid_epsf = 1;
2479               break;
2480             }
2481         }
2482     }
2483
2484   /* Check that we found the BoundingBox comment. */
2485   if (!valid_epsf)
2486     {
2487       MESSAGE (0, (stderr, _("EPS file \"%s\" is not a valid EPS file\n"),
2488                    token->u.epsf.filename));
2489       if (token->u.epsf.pipe)
2490         pclose (token->u.epsf.fp);
2491       else
2492         fclose (token->u.epsf.fp);
2493       xfree (token->u.epsf.skipbuf);
2494       return 0;
2495     }
2496
2497   MESSAGE (2, (stderr, "BoundingBox: %d %d %d %d\n",
2498                token->u.epsf.llx, token->u.epsf.lly,
2499                token->u.epsf.urx, token->u.epsf.ury));
2500
2501   return 1;
2502 }
2503
2504
2505 static void
2506 paste_epsf (Token *token)
2507 {
2508   char buf[4096];
2509   int i;
2510
2511   /* EPSF import header. */
2512   OUTPUT ((cofp, "BeginEPSF\n"));
2513   OUTPUT ((cofp, "%g %g translate\n", token->new_x, token->new_y));
2514   OUTPUT ((cofp, "%g %g scale\n", token->u.epsf.xscale, token->u.epsf.yscale));
2515   OUTPUT ((cofp, "%d %d translate\n", -token->u.epsf.llx,
2516            -token->u.epsf.lly));
2517   OUTPUT ((cofp, "%d %d %d %d Box clip newpath\n",
2518            token->u.epsf.llx - 1,
2519            token->u.epsf.lly - 1,
2520            token->u.epsf.urx - token->u.epsf.llx + 2,
2521            token->u.epsf.ury - token->u.epsf.lly + 2));
2522   OUTPUT ((cofp, "%%%%BeginDocument: %s%s\n", token->u.epsf.filename,
2523            token->u.epsf.pipe ? "|" : ""));
2524
2525   if (do_print)
2526     {
2527       /* Dump skip buffer. */
2528       fwrite (token->u.epsf.skipbuf, 1, token->u.epsf.skipbuf_pos, cofp);
2529
2530       /* Dump file. */
2531       while ((i = fread (buf, 1, sizeof (buf), token->u.epsf.fp)) != 0)
2532         fwrite (buf, 1, i, cofp);
2533     }
2534
2535   /* Add a newline to keep comments correct */
2536   OUTPUT ((cofp, "\n"));
2537
2538   /* EPSF import trailer. */
2539   OUTPUT ((cofp, "%%%%EndDocument\nEndEPSF\n"));
2540
2541   /* Cleanup. */
2542   if (token->u.epsf.pipe)
2543     pclose (token->u.epsf.fp);
2544   else
2545     fclose (token->u.epsf.fp);
2546   xfree (token->u.epsf.skipbuf);
2547 }
2548
2549
2550 static double
2551 read_float (InputStream *is, int units, int horizontal)
2552 {
2553   char buf[256];
2554   int i, ch;
2555   double val;
2556
2557   for (i = 0; (i < sizeof (buf) - 1
2558                && (ch = is_getc (is)) != EOF
2559                && ISNUMBERDIGIT (ch));
2560        i++)
2561     buf[i] = ch;
2562   buf[i] = '\0';
2563   if (ch != EOF)
2564     is_ungetc (ch, is);
2565
2566   val = atof (buf);
2567
2568   if (units)
2569     {
2570       /* Get unit. */
2571       ch = is_getc (is);
2572       switch (ch)
2573         {
2574         case 'c':               /* centimeters */
2575           val *= 72 / 2.54;
2576           break;
2577
2578         case 'p':               /* PostScript points */
2579           break;
2580
2581         case 'i':               /* inches */
2582           val *= 72;
2583           break;
2584
2585         default:
2586           is_ungetc (ch, is);
2587           /* FALLTHROUGH */
2588
2589         case 'l':               /* lines or characters */
2590           if (horizontal)
2591             val *= CHAR_WIDTH ('m');
2592           else
2593             val *= LINESKIP;
2594           break;
2595         }
2596     }
2597
2598   return val;
2599 }
2600
2601
2602 /* Magics used to recognize different pass-through files. */
2603 static struct
2604 {
2605   char *magic;
2606   unsigned int magiclen;
2607   char *name;
2608   int revert_delta;
2609 } pass_through_magics[] =
2610   {
2611     {"%!",      2, "PostScript",        -2},
2612     {"\004%!",  3, "PostScript",        -2},
2613     {"\033E",   2, "PCL",               -2},
2614     {"\033%",   2, "PCL",               -2},
2615     {NULL, 0, NULL, 0},
2616   };
2617
2618
2619 static int
2620 do_pass_through (char *fname, InputStream *is)
2621 {
2622   int ch;
2623   unsigned long saved_pos = is->bufpos;
2624   int i, j;
2625
2626   if (output_language_pass_through)
2627     MESSAGE (1,
2628              (stderr,
2629               _("passing through all input files for output language `%s'\n"),
2630               output_language));
2631   else
2632     {
2633       /*
2634        * Try to recognize pass-through files.
2635        */
2636
2637       for (i = 0; pass_through_magics[i].magic; i++)
2638         {
2639           for (j = 0; j < pass_through_magics[i].magiclen; j++)
2640             {
2641               ch = is_getc (is);
2642               if (ch == EOF
2643                   || ch != (unsigned char) pass_through_magics[i].magic[j])
2644                 break;
2645             }
2646
2647           if (j >= pass_through_magics[i].magiclen)
2648             /* The <i>th one matched. */
2649             break;
2650
2651           /*
2652            * Try the next one, but first, seek the input stream to its
2653            * start.
2654            */
2655           is->bufpos = saved_pos;
2656         }
2657
2658       /* Did we find any? */
2659       if (pass_through_magics[i].magic == NULL)
2660         /* No we didn't. */
2661         return 0;
2662
2663       /* Yes, it really is a pass-through file.  Now do the pass through. */
2664
2665       is->bufpos += pass_through_magics[i].revert_delta;
2666
2667       if (ps_header_dumped)
2668         {
2669           /* A pass-through file between normal ASCII files, obey DSC. */
2670
2671           /*
2672            * XXX I don't know how to handle PCL files... Let's hope none
2673            * mixes them with the normal ASCII files.
2674            */
2675
2676           OUTPUT ((cofp,
2677                    "%%%%Page: (%s) -1\n_S\n%%%%BeginDocument: %s\n",
2678                    fname, fname));
2679         }
2680
2681       MESSAGE (1, (stderr, _("passing through %s file \"%s\"\n"),
2682                    pass_through_magics[i].name, fname));
2683     }
2684
2685   /* And now, do the actual pass-through. */
2686   do
2687     {
2688       /* Note: this will be written directly to the <ofp>. */
2689       fwrite (is->buf + is->bufpos, 1, is->data_in_buf - is->bufpos, ofp);
2690       is->bufpos = is->data_in_buf;
2691
2692       /* Read more data to the input buffer. */
2693       ch = is_getc (is);
2694       is->bufpos = 0;
2695     }
2696   while (ch != EOF);
2697
2698   if (!output_language_pass_through)
2699     {
2700       if (ps_header_dumped)
2701         /*
2702          * XXX How to end a PCL file mixed between ASCII files?
2703          */
2704         OUTPUT ((cofp, "%%%%EndDocument\n_R\n"));
2705     }
2706
2707   return 1;
2708 }
2709
2710
2711 static void
2712 print_line_number (double x, double y, double space, double margin,
2713                    unsigned int linenum)
2714 {
2715   double len = 0.0;
2716   char buf[20];
2717   int i;
2718   char *saved_Fname = "";
2719   FontPoint saved_Fpt;
2720   InputEncoding saved_Fencoding;
2721
2722   saved_Fpt.w = 0.0;
2723   saved_Fpt.h = 0.0;
2724
2725   /* Do not print linenumbers for wrapped lines. */
2726   if (linenum == print_line_number_last)
2727     return;
2728   print_line_number_last = linenum;
2729
2730   if (user_fontp)
2731     {
2732       /* Re-select our default typing font. */
2733       saved_Fname = Fname;
2734       saved_Fpt.w = Fpt.w;
2735       saved_Fpt.h = Fpt.h;
2736       saved_Fencoding = encoding;
2737
2738       Fname = default_Fname;
2739       Fpt.w = default_Fpt.w;
2740       Fpt.h = default_Fpt.h;
2741       encoding = default_Fencoding;
2742
2743       OUTPUT ((cofp, "/F-gs-font %g %g SF\n", Fpt.w, Fpt.h));
2744       read_font_info ();
2745     }
2746
2747   /* Count linenumber string length. */
2748   sprintf (buf, "%d", linenum);
2749   for (i = 0; buf[i]; i++)
2750     len += CHAR_WIDTH (buf[i]);
2751
2752   /* Print line numbers. */
2753   OUTPUT ((cofp, "%g %g M (%s:) s\n", x + space - len, y, buf));
2754
2755   if (user_fontp)
2756     {
2757       /* Switch back to the user font. */
2758       Fname = saved_Fname;
2759       Fpt.w = saved_Fpt.w;
2760       Fpt.h = saved_Fpt.h;
2761       encoding = saved_Fencoding;
2762
2763       OUTPUT ((cofp, "/%s %g %g SUF\n", Fname, Fpt.w, Fpt.h));
2764       read_font_info ();
2765     }
2766 }
2767
2768 \f
2769 /*
2770  * The name of the divert file, shared between divert() and undivert()
2771  * functions.
2772  */
2773 static char divertfname[512];
2774
2775 static void
2776 divert ()
2777 {
2778   assert (divertfp == NULL);
2779
2780   /* Open divert file. */
2781
2782   divertfp = tmpfile ();
2783   if (divertfp == NULL)
2784     FATAL ((stderr, _("couldn't create temporary divert file: %s"),
2785             strerror (errno)));
2786
2787   cofp = divertfp;
2788 }
2789
2790
2791 static void
2792 undivert ()
2793 {
2794   char buf[1024];
2795   int doc_level = 0;
2796   char *cp;
2797
2798   assert (divertfp != NULL);
2799
2800   if (fseek (divertfp, 0, SEEK_SET) != 0)
2801     FATAL ((stderr, _("couldn't rewind divert file: %s"), strerror (errno)));
2802
2803   while (fgets (buf, sizeof (buf), divertfp))
2804     {
2805       if (strncmp (buf, "%%BeginDocument", 15) == 0)
2806         doc_level++;
2807       else if (strncmp (buf, "%%EndDocument", 13) == 0)
2808         doc_level--;
2809
2810       if (doc_level == 0)
2811         {
2812           if (strncmp (buf, "% User defined strings", 22) == 0)
2813             {
2814               fputs (buf, ofp);
2815               while (fgets (buf, sizeof (buf), divertfp))
2816                 {
2817                   if (strncmp (buf, "%%EndPageSetup", 14) == 0)
2818                     break;
2819
2820                   /* Patch total pages to the user defined strings. */
2821                   cp = strchr (buf, '\001');
2822                   if (cp)
2823                     {
2824                       *cp = '\0';
2825                       fputs (buf, ofp);
2826                       fprintf (ofp, "%d", total_pages_in_file);
2827                       fputs (cp + 1, ofp);
2828                     }
2829                   else
2830                     fputs (buf, ofp);
2831                 }
2832             }
2833         }
2834
2835       fputs (buf, ofp);
2836     }
2837
2838   fclose (divertfp);
2839   divertfp = NULL;
2840
2841   cofp = ofp;
2842 }
2843
2844
2845 static void
2846 handle_two_side_options ()
2847 {
2848   if (rotate_even_pages)
2849     /* Rotate page 180 degrees. */
2850     OUTPUT ((cofp, "180 rotate\n%d %d translate\n",
2851              -media->w, -media->h));
2852
2853   if (swap_even_page_margins)
2854     OUTPUT ((cofp, "%d 0 translate\n",
2855              -(media->llx - (media->w - media->urx))));
2856 }