Bug #21128. Reviewed by John Darrington.
[pspp-builds.git] / src / output / postscript.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2007 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <time.h>
24 #include <unistd.h>
25
26 #include <libpspp/alloc.h>
27 #include <libpspp/assertion.h>
28 #include <libpspp/bit-vector.h>
29 #include <libpspp/compiler.h>
30 #include <libpspp/freaderror.h>
31 #include <libpspp/hash.h>
32 #include <libpspp/misc.h>
33 #include <libpspp/start-date.h>
34 #include <libpspp/version.h>
35
36 #include <data/file-name.h>
37
38 #include "afm.h"
39 #include "chart.h"
40 #include "error.h"
41 #include "intprops.h"
42 #include "manager.h"
43 #include "minmax.h"
44 #include "output.h"
45 #include "size_max.h"
46
47 #include "gettext.h"
48 #define _(msgid) gettext (msgid)
49
50 /* PostScript driver options: (defaults listed first)
51
52    output-file="pspp.ps"
53
54    paper-size=letter (see "papersize" file)
55    orientation=portrait|landscape
56    headers=on|off
57
58    left-margin=0.5in
59    right-margin=0.5in
60    top-margin=0.5in
61    bottom-margin=0.5in
62
63    prop-font=Times-Roman
64    emph-font=Times-Italic
65    fixed-font=Courier
66    font-size=10000
67
68    line-gutter=1pt
69    line-spacing=1pt
70    line-width=0.5pt
71  */
72
73 /* The number of `psus' (PostScript driver UnitS) per inch. */
74 #define PSUS 72000
75
76 /* A PostScript font. */
77 struct font
78   {
79     struct afm *metrics;        /* Metrics. */
80     char *embed_fn;             /* Name of file to embed. */
81     char *encoding_fn;          /* Name of file with encoding. */
82   };
83
84 /* PostScript output driver extension record. */
85 struct ps_driver_ext
86   {
87     char *file_name;            /* Output file name. */
88     FILE *file;                 /* Output file. */
89
90     bool draw_headers;          /* Draw headers at top of page? */
91     int page_number;            /* Current page number. */
92
93     bool portrait;              /* Portrait mode? */
94     int paper_width;            /* Width of paper before dropping margins. */
95     int paper_length;           /* Length of paper before dropping margins. */
96     int left_margin;            /* Left margin in psus. */
97     int right_margin;           /* Right margin in psus. */
98     int top_margin;             /* Top margin in psus. */
99     int bottom_margin;          /* Bottom margin in psus. */
100
101     int line_gutter;            /* Space around lines. */
102     int line_space;             /* Space between lines. */
103     int line_width;             /* Width of lines. */
104
105     struct font *fonts[OUTP_FONT_CNT];
106     int last_font;              /* Index of last font set with setfont. */
107   };
108
109 /* Transform logical y-ordinate Y into a page ordinate. */
110 #define YT(Y) (this->length - (Y))
111
112 static bool handle_option (struct outp_driver *this, const char *key,
113                            const struct string *val);
114 static void draw_headers (struct outp_driver *this);
115
116 static void write_ps_prologue (struct outp_driver *);
117
118 static char *quote_ps_name (const char *string);
119
120 static struct font *load_font (const char *string);
121 static void free_font (struct font *);
122 static void setup_font (struct outp_driver *this, struct font *, int index);
123 \f
124 /* Driver initialization. */
125
126 static bool
127 ps_open_driver (struct outp_driver *this, struct substring options)
128 {
129   struct ps_driver_ext *x;
130   size_t i;
131
132   this->width = this->length = 0;
133   this->font_height = PSUS * 10 / 72;
134
135   this->ext = x = xmalloc (sizeof *x);
136   x->file_name = xstrdup ("pspp.ps");
137   x->file = NULL;
138   x->draw_headers = true;
139   x->page_number = 0;
140   x->portrait = true;
141   outp_get_paper_size ("", &x->paper_width, &x->paper_length);
142   x->left_margin = PSUS / 2;
143   x->right_margin = PSUS / 2;
144   x->top_margin = PSUS / 2;
145   x->bottom_margin = PSUS / 2;
146   x->line_gutter = PSUS / 72;
147   x->line_space = PSUS / 72;
148   x->line_width = PSUS / 144;
149   for (i = 0; i < OUTP_FONT_CNT; i++)
150     x->fonts[i] = NULL;
151
152   outp_parse_options (options, handle_option, this);
153
154   x->file = fn_open (x->file_name, "w");
155   if (x->file == NULL)
156     {
157       error (0, errno, _("opening PostScript output file \"%s\""),
158              x->file_name);
159       goto error;
160     }
161
162   if (x->portrait)
163     {
164       this->width = x->paper_width;
165       this->length = x->paper_length;
166     }
167   else
168     {
169       this->width = x->paper_length;
170       this->length = x->paper_width;
171     }
172   this->width -= x->left_margin + x->right_margin;
173   this->length -= x->top_margin + x->bottom_margin;
174   if (x->draw_headers)
175     {
176       int header_length = 3 * this->font_height;
177       this->length -= header_length;
178       x->top_margin += header_length;
179     }
180
181   for (i = 0; i < OUTP_FONT_CNT; i++)
182     if (x->fonts[i] == NULL)
183       {
184         const char *default_fonts[OUTP_FONT_CNT];
185         default_fonts[OUTP_FIXED] = "Courier.afm";
186         default_fonts[OUTP_PROPORTIONAL] = "Times-Roman.afm";
187         default_fonts[OUTP_EMPHASIS] = "Times-Italic.afm";
188         x->fonts[i] = load_font (default_fonts[i]);
189         if (x->fonts[i] == NULL)
190           goto error;
191       }
192
193   if (this->length / this->font_height < 15)
194     {
195       error (0, 0, _("The defined PostScript page is not long "
196                      "enough to hold margins and headers, plus least 15 "
197                      "lines of the default fonts.  In fact, there's only "
198                      "room for %d lines of each font at the default size "
199                      "of %d.%03d points."),
200            this->length / this->font_height,
201            this->font_height / 1000, this->font_height % 1000);
202       goto error;
203     }
204
205   this->fixed_width =
206     afm_get_character (x->fonts[OUTP_FIXED]->metrics, '0')->width
207     * this->font_height / 1000;
208   this->prop_em_width =
209     afm_get_character (x->fonts[OUTP_PROPORTIONAL]->metrics, '0')->width
210     * this->font_height / 1000;
211
212   this->horiz_line_width[OUTP_L_NONE] = 0;
213   this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
214   this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
215                                            + 2 * x->line_width);
216   memcpy (this->vert_line_width, this->horiz_line_width,
217           sizeof this->vert_line_width);
218
219   write_ps_prologue (this);
220
221   return true;
222
223  error:
224   this->class->close_driver (this);
225   return false;
226 }
227
228 static bool
229 ps_close_driver (struct outp_driver *this)
230 {
231   struct ps_driver_ext *x = this->ext;
232   bool ok = true;
233   size_t i;
234
235   if (x->file != NULL)
236     {
237       fprintf (x->file,
238                "%%%%Trailer\n"
239                "%%%%Pages: %d\n"
240                "%%%%EOF\n",
241                x->page_number);
242
243       ok = fn_close (x->file_name, x->file) == 0;
244       if (!ok)
245         error (0, errno, _("closing PostScript output file \"%s\""),
246                x->file_name);
247     }
248
249   free (x->file_name);
250   for (i = 0; i < OUTP_FONT_CNT; i++)
251     free_font (x->fonts[i]);
252   free (x);
253
254   return ok;
255 }
256
257 /* Generic option types. */
258 enum
259 {
260   output_file_arg,
261   paper_size_arg,
262   orientation_arg,
263   line_style_arg,
264   boolean_arg,
265   pos_int_arg,
266   dimension_arg,
267   string_arg,
268   nonneg_int_arg
269 };
270
271 /* All the options that the PostScript driver supports. */
272 static const struct outp_option option_tab[] =
273 {
274   {"output-file",               output_file_arg,0},
275   {"paper-size",                paper_size_arg, 0},
276   {"orientation",               orientation_arg,0},
277
278   {"headers",                   boolean_arg,    1},
279
280   {"prop-font",                 string_arg,     OUTP_PROPORTIONAL},
281   {"emph-font",                 string_arg,     OUTP_EMPHASIS},
282   {"fixed-font",                string_arg,     OUTP_FIXED},
283
284   {"left-margin",               pos_int_arg,    0},
285   {"right-margin",              pos_int_arg,    1},
286   {"top-margin",                pos_int_arg,    2},
287   {"bottom-margin",             pos_int_arg,    3},
288   {"font-size",                 pos_int_arg,    4},
289
290   {"line-width",                dimension_arg,  0},
291   {"line-gutter",               dimension_arg,  1},
292   {"line-width",                dimension_arg,  2},
293   {NULL, 0, 0},
294 };
295
296 static bool
297 handle_option (struct outp_driver *this, const char *key,
298                const struct string *val)
299 {
300   struct ps_driver_ext *x = this->ext;
301   int subcat;
302   char *value = ds_cstr (val);
303
304   switch (outp_match_keyword (key, option_tab, &subcat))
305     {
306     case -1:
307       error (0, 0,
308              _("unknown configuration parameter `%s' for PostScript device "
309                "driver"), key);
310       break;
311     case output_file_arg:
312       free (x->file_name);
313       x->file_name = xstrdup (value);
314       break;
315     case paper_size_arg:
316       outp_get_paper_size (value, &this->width, &this->length);
317       break;
318     case orientation_arg:
319       if (!strcmp (value, "portrait"))
320         x->portrait = true;
321       else if (!strcmp (value, "landscape"))
322         x->portrait = false;
323       else
324         error (0, 0, _("unknown orientation `%s' (valid orientations are "
325                        "`portrait' and `landscape')"), value);
326       break;
327     case boolean_arg:
328       if (!strcmp (value, "on") || !strcmp (value, "true")
329           || !strcmp (value, "yes") || atoi (value))
330         x->draw_headers = true;
331       else if (!strcmp (value, "off") || !strcmp (value, "false")
332                || !strcmp (value, "no") || !strcmp (value, "0"))
333         x->draw_headers = false;
334       else
335         {
336           error (0, 0, _("boolean value expected for %s"), key);
337           return false;
338         }
339       break;
340     case pos_int_arg:
341       {
342         char *tail;
343         int arg;
344
345         errno = 0;
346         arg = strtol (value, &tail, 0);
347         if (arg < 1 || errno == ERANGE || *tail)
348           {
349             error (0, 0, _("positive integer value required for `%s'"), key);
350             break;
351           }
352         if ((subcat == 4 || subcat == 5) && arg < 1000)
353           {
354             error (0, 0, _("default font size must be at least 1 point (value "
355                            "of 1000 for key `%s')"), key);
356             break;
357           }
358         switch (subcat)
359           {
360           case 0:
361             x->left_margin = arg;
362             break;
363           case 1:
364             x->right_margin = arg;
365             break;
366           case 2:
367             x->top_margin = arg;
368             break;
369           case 3:
370             x->bottom_margin = arg;
371             break;
372           case 4:
373             this->font_height = arg;
374             break;
375           default:
376             NOT_REACHED ();
377           }
378       }
379       break;
380     case dimension_arg:
381       {
382         int dimension = outp_evaluate_dimension (value);
383
384         if (dimension <= 0)
385           break;
386         switch (subcat)
387           {
388           case 0:
389             x->line_width = dimension;
390             break;
391           case 1:
392             x->line_gutter = dimension;
393             break;
394           case 2:
395             x->line_width = dimension;
396             break;
397           default:
398             NOT_REACHED ();
399           }
400       }
401       break;
402     case string_arg:
403       {
404         struct font *font = load_font (value);
405         if (font != NULL)
406           {
407             struct font **dst = &x->fonts[subcat];
408             if (*dst != NULL)
409               free_font (*dst);
410             *dst = font;
411           }
412       }
413       break;
414     default:
415       NOT_REACHED ();
416     }
417
418   return true;
419 }
420
421 /* Looks for a PostScript font file or config file in all the
422    appropriate places.  Returns the file name on success, NULL on
423    failure. */
424 static char *
425 find_ps_file (const char *name)
426 {
427   if (fn_is_absolute (name))
428     return xstrdup (name);
429   else
430     {
431       char *base_name = xasprintf ("psfonts/%s", name);
432       char *file_name = fn_search_path (base_name, config_path);
433       free (base_name);
434       return file_name;
435     }
436 }
437 \f
438 /* Basic file operations. */
439
440 /* Writes the PostScript prologue to file F. */
441 static void
442 write_ps_prologue (struct outp_driver *this)
443 {
444   struct ps_driver_ext *x = this->ext;
445   size_t embedded_cnt, preloaded_cnt;
446   size_t i;
447
448   fputs ("%!PS-Adobe-3.0\n", x->file);
449   fputs ("%%Pages: (atend)\n", x->file);
450
451   embedded_cnt = preloaded_cnt = 0;
452   for (i = 0; i < OUTP_FONT_CNT; i++)
453     {
454       bool embed = x->fonts[i]->embed_fn != NULL;
455       embedded_cnt += embed;
456       preloaded_cnt += !embed;
457     }
458   if (preloaded_cnt > 0)
459     {
460       fputs ("%%DocumentNeededResources: font", x->file);
461       for (i = 0; i < OUTP_FONT_CNT; i++)
462         {
463           struct font *f = x->fonts[i];
464           if (f->embed_fn == NULL)
465             fprintf (x->file, " %s", afm_get_findfont_name (f->metrics));
466         }
467       fputs ("\n", x->file);
468     }
469   if (embedded_cnt > 0)
470     {
471       fputs ("%%DocumentSuppliedResources: font", x->file);
472       for (i = 0; i < OUTP_FONT_CNT; i++)
473         {
474           struct font *f = x->fonts[i];
475           if (f->embed_fn != NULL)
476             fprintf (x->file, " %s", afm_get_findfont_name (f->metrics));
477         }
478       fputs ("\n", x->file);
479     }
480   fputs ("%%Copyright: This prologue is public domain.\n", x->file);
481   fprintf (x->file, "%%%%Creator: %s\n", version);
482   fprintf (x->file, "%%%%DocumentMedia: Plain %g %g 75 white ()\n",
483            x->paper_width / (PSUS / 72.0), x->paper_length / (PSUS / 72.0));
484   fprintf (x->file, "%%%%Orientation: %s\n",
485            x->portrait ? "Portrait" : "Landscape");
486   fputs ("%%EndComments\n", x->file);
487   fputs ("%%BeginDefaults\n", x->file);
488   fputs ("%%PageResources: font", x->file);
489   for (i = 0; i < OUTP_FONT_CNT; i++)
490     fprintf (x->file, " %s", afm_get_findfont_name (x->fonts[i]->metrics));
491   fputs ("\n", x->file);
492   fputs ("%%EndDefaults\n", x->file);
493   fputs ("%%BeginProlog\n", x->file);
494   fputs ("/ED{exch def}bind def\n", x->file);
495   fputs ("/L{moveto lineto stroke}bind def\n", x->file);
496   fputs ("/D{moveto lineto moveto lineto stroke}bind def\n", x->file);
497   fputs ("/S{show}bind def\n", x->file);
498   fputs ("/GS{glyphshow}def\n", x->file);
499   fputs ("/RF{\n", x->file);
500   fputs (" exch dup maxlength 1 add dict begin\n", x->file);
501   fputs (" {\n", x->file);
502   fputs ("  1 index/FID ne{def}{pop pop}ifelse\n", x->file);
503   fputs (" }forall\n", x->file);
504   fputs (" /Encoding ED\n", x->file);
505   fputs (" currentdict end\n", x->file);
506   fputs ("}bind def\n", x->file);
507   fputs ("/F{setfont}bind def\n", x->file);
508   fputs ("/EP{\n", x->file);
509   fputs (" pg restore\n", x->file);
510   fputs (" showpage\n", x->file);
511   fputs ("}bind def\n", x->file);
512   fputs ("/GB{\n", x->file);
513   fputs (" /y2 ED/x2 ED/y1 ED/x1 ED\n", x->file);
514   fputs (" x1 y1 moveto x2 y1 lineto x2 y2 lineto x1 y2 lineto closepath\n",
515          x->file);
516   fputs (" gsave 0.9 setgray fill grestore stroke\n", x->file);
517   fputs ("}bind def\n", x->file);
518   fputs ("/K{0 rmoveto}bind def\n", x->file);
519   fputs ("%%EndProlog\n", x->file);
520   fputs ("%%BeginSetup\n", x->file);
521   for (i = 0; i < OUTP_FONT_CNT; i++)
522     setup_font (this, x->fonts[i], i);
523   fputs ("%%EndSetup\n", x->file);
524 }
525
526 /* Returns STRING as a Postscript name, which is just '/'
527    followed by STRING unless characters need to be quoted.
528    The caller must free the string. */
529 static char *
530 quote_ps_name (const char *string)
531 {
532   const char *cp;
533
534   for (cp = string; *cp != '\0'; cp++)
535     {
536       unsigned char c = *cp;
537       if (!isalpha (c) && strchr ("^_|!$&:;.,-+", c) == NULL
538           && (cp == string || !isdigit (c)))
539         {
540           struct string out = DS_EMPTY_INITIALIZER;
541           ds_put_char (&out, '<');
542           for (cp = string; *cp != '\0'; cp++)
543             {
544               c = *cp;
545               ds_put_format (&out, "%02x", c);
546             }
547           ds_put_cstr (&out, ">cvn");
548           return ds_cstr (&out);
549         }
550     }
551   return xasprintf ("/%s", string);
552 }
553
554 static void
555 ps_open_page (struct outp_driver *this)
556 {
557   struct ps_driver_ext *x = this->ext;
558
559   /* Assure page independence. */
560   x->last_font = -1;
561
562   x->page_number++;
563
564   fprintf (x->file,
565            "%%%%Page: %d %d\n"
566            "%%%%BeginPageSetup\n"
567            "/pg save def 0.001 dup scale\n",
568            x->page_number, x->page_number);
569
570   if (!x->portrait)
571     fprintf (x->file,
572              "%d 0 translate 90 rotate\n",
573              x->paper_width);
574
575   if (x->bottom_margin != 0 || x->left_margin != 0)
576     fprintf (x->file,
577              "%d %d translate\n",
578              x->left_margin, x->bottom_margin);
579
580   fprintf (x->file,
581            "/LW %d def %d setlinewidth\n"
582            "%%%%EndPageSetup\n",
583            x->line_width, x->line_width);
584
585   if (x->draw_headers)
586     draw_headers (this);
587 }
588
589 static void
590 ps_close_page (struct outp_driver *this)
591 {
592   struct ps_driver_ext *x = this->ext;
593   fputs ("%%PageTrailer\n"
594          "EP\n",
595          x->file);
596 }
597
598 static void
599 ps_submit (struct outp_driver *this UNUSED, struct som_entity *s)
600 {
601   switch (s->type)
602     {
603     case SOM_CHART:
604       break;
605     default:
606       NOT_REACHED ();
607     }
608 }
609 \f
610 /* Draws a line from (x0,y0) to (x1,y1). */
611 static void
612 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
613 {
614   struct ps_driver_ext *ext = this->ext;
615   fprintf (ext->file, "%d %d %d %d L\n", x0, YT (y0), x1, YT (y1));
616 }
617
618 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
619    shortening it to X0...X1 if SHORTEN is true.
620    Draws a horizontal line X1...X3 at Y if RIGHT says so,
621    shortening it to X2...X3 if SHORTEN is true. */
622 static void
623 horz_line (struct outp_driver *this,
624            int x0, int x1, int x2, int x3, int y,
625            enum outp_line_style left, enum outp_line_style right,
626            bool shorten)
627 {
628   if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
629     dump_line (this, x0, y, x3, y);
630   else
631     {
632       if (left != OUTP_L_NONE)
633         dump_line (this, x0, y, shorten ? x1 : x2, y);
634       if (right != OUTP_L_NONE)
635         dump_line (this, shorten ? x2 : x1, y, x3, y);
636     }
637 }
638
639 /* Draws a vertical line Y0...Y2 at X if TOP says so,
640    shortening it to Y0...Y1 if SHORTEN is true.
641    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
642    shortening it to Y2...Y3 if SHORTEN is true. */
643 static void
644 vert_line (struct outp_driver *this,
645            int y0, int y1, int y2, int y3, int x,
646            enum outp_line_style top, enum outp_line_style bottom,
647            bool shorten)
648 {
649   if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
650     dump_line (this, x, y0, x, y3);
651   else
652     {
653       if (top != OUTP_L_NONE)
654         dump_line (this, x, y0, x, shorten ? y1 : y2);
655       if (bottom != OUTP_L_NONE)
656         dump_line (this, x, shorten ? y2 : y1, x, y3);
657     }
658 }
659
660 /* Draws a generalized intersection of lines in the rectangle
661    (X0,Y0)-(X3,Y3).  The line coming from the top to the center
662    is of style TOP, from left to center of style LEFT, from
663    bottom to center of style BOTTOM, and from right to center of
664    style RIGHT. */
665 static void
666 ps_line (struct outp_driver *this,
667          int x0, int y0, int x3, int y3,
668          enum outp_line_style top, enum outp_line_style left,
669          enum outp_line_style bottom, enum outp_line_style right)
670 {
671   /* The algorithm here is somewhat subtle, to allow it to handle
672      all the kinds of intersections that we need.
673
674      Three additional ordinates are assigned along the x axis.  The
675      first is xc, midway between x0 and x3.  The others are x1 and
676      x2; for a single vertical line these are equal to xc, and for
677      a double vertical line they are the ordinates of the left and
678      right half of the double line.
679
680      yc, y1, and y2 are assigned similarly along the y axis.
681
682      The following diagram shows the coordinate system and output
683      for double top and bottom lines, single left line, and no
684      right line:
685
686                  x0       x1 xc  x2      x3
687                y0 ________________________
688                   |        #     #       |
689                   |        #     #       |
690                   |        #     #       |
691                   |        #     #       |
692                   |        #     #       |
693      y1 = y2 = yc |#########     #       |
694                   |        #     #       |
695                   |        #     #       |
696                   |        #     #       |
697                   |        #     #       |
698                y3 |________#_____#_______|
699   */
700   struct ps_driver_ext *ext = this->ext;
701
702   /* Offset from center of each line in a pair of double lines. */
703   int double_line_ofs = (ext->line_space + ext->line_width) / 2;
704
705   /* Are the lines along each axis single or double?
706      (It doesn't make sense to have different kinds of line on the
707      same axis, so we don't try to gracefully handle that case.) */
708   bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
709   bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
710
711   /* When horizontal lines are doubled,
712      the left-side line along y1 normally runs from x0 to x2,
713      and the right-side line along y1 from x3 to x1.
714      If the top-side line is also doubled, we shorten the y1 lines,
715      so that the left-side line runs only to x1,
716      and the right-side line only to x2.
717      Otherwise, the horizontal line at y = y1 below would cut off
718      the intersection, which looks ugly:
719                x0       x1     x2      x3
720              y0 ________________________
721                 |        #     #       |
722                 |        #     #       |
723                 |        #     #       |
724                 |        #     #       |
725              y1 |#########     ########|
726                 |                      |
727                 |                      |
728              y2 |######################|
729                 |                      |
730                 |                      |
731              y3 |______________________|
732      It is more of a judgment call when the horizontal line is
733      single.  We actually choose to cut off the line anyhow, as
734      shown in the first diagram above.
735   */
736   bool shorten_y1_lines = top == OUTP_L_DOUBLE;
737   bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
738   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
739   int horz_line_ofs = double_vert ? double_line_ofs : 0;
740   int xc = (x0 + x3) / 2;
741   int x1 = xc - horz_line_ofs;
742   int x2 = xc + horz_line_ofs;
743
744   bool shorten_x1_lines = left == OUTP_L_DOUBLE;
745   bool shorten_x2_lines = right == OUTP_L_DOUBLE;
746   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
747   int vert_line_ofs = double_horz ? double_line_ofs : 0;
748   int yc = (y0 + y3) / 2;
749   int y1 = yc - vert_line_ofs;
750   int y2 = yc + vert_line_ofs;
751
752   if (!double_horz)
753     horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
754   else
755     {
756       horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
757       horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
758     }
759
760   if (!double_vert)
761     vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
762   else
763     {
764       vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
765       vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
766     }
767 }
768
769 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
770    and with the given JUSTIFICATION for THIS driver. */
771 static int
772 draw_text (struct outp_driver *this,
773            const char *string, int x, int y, int max_width,
774            enum outp_justification justification)
775 {
776   struct outp_text text;
777   int width;
778
779   text.font = OUTP_PROPORTIONAL;
780   text.justification = justification;
781   text.string = ss_cstr (string);
782   text.h = max_width;
783   text.v = this->font_height;
784   text.x = x;
785   text.y = y;
786   this->class->text_metrics (this, &text, &width, NULL);
787   this->class->text_draw (this, &text);
788   return width;
789 }
790
791 /* Writes LEFT left-justified and RIGHT right-justified within
792    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
793 static void
794 draw_header_line (struct outp_driver *this,
795                   const char *left, const char *right,
796                   int x0, int x1, int y)
797 {
798   int right_width = 0;
799   if (right != NULL)
800     right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
801                    + this->prop_em_width);
802   if (left != NULL)
803     draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
804 }
805
806 /* Draw top of page headers for THIS driver. */
807 static void
808 draw_headers (struct outp_driver *this)
809 {
810   struct ps_driver_ext *ext = this->ext;
811   char *r1, *r2;
812   int x0, x1;
813   int y;
814
815   y = -3 * this->font_height;
816   x0 = this->prop_em_width;
817   x1 = this->width - this->prop_em_width;
818
819   /* Draw box. */
820   fprintf (ext->file, "%d %d %d %d GB\n",
821            0, YT (y),
822            this->width, YT (y + 2 * this->font_height + ext->line_gutter));
823   y += ext->line_width + ext->line_gutter;
824
825   r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
826   r2 = xasprintf ("%s - %s", version, host_system);
827
828   draw_header_line (this, outp_title, r1, x0, x1, y);
829   y += this->font_height;
830
831   draw_header_line (this, outp_subtitle, r2, x0, x1, y);
832
833   free (r1);
834   free (r2);
835 }
836 \f
837 /* Writes the CHAR_CNT characters in CHARS at (X0,Y0), using the
838    given FONT.
839    The characters are justified according to JUSTIFICATION in a
840    field that has WIDTH_LEFT space remaining after the characters
841    themselves are accounted for.
842    Before character I is written, its x-position is adjusted by
843    KERNS[I]. */
844 static void
845 write_text (struct outp_driver *this,
846             int x0, int y0,
847             enum outp_font font,
848             enum outp_justification justification,
849             const struct afm_character **chars, int *kerns, size_t char_cnt,
850             int width_left)
851 {
852   struct ps_driver_ext *ext = this->ext;
853   struct afm *afm = ext->fonts[font]->metrics;
854   struct string out;
855   size_t i, j;
856
857   if (justification == OUTP_RIGHT)
858     x0 += width_left;
859   else if (justification == OUTP_CENTER)
860     x0 += width_left / 2;
861   y0 += afm_get_ascent (afm) * this->font_height / 1000;
862
863   fprintf (ext->file, "\n%d %d moveto\n", x0, YT (y0));
864
865   if (ext->last_font != font)
866     {
867       ext->last_font = font;
868       fprintf (ext->file, "F%d setfont\n", font);
869     }
870
871   ds_init_empty (&out);
872   for (i = 0; i < char_cnt; i = j)
873     {
874       for (j = i + 1; j < char_cnt; j++)
875         if (kerns[j] != 0)
876           break;
877
878       if (kerns[i] != 0)
879         fprintf (ext->file, "%d K", kerns[i]);
880       while (i < j)
881         {
882           size_t encoded = afm_encode_string (afm, chars + i, j - i, &out);
883           if (encoded > 0)
884             {
885               fprintf (ext->file, "%sS\n", ds_cstr (&out));
886               ds_clear (&out);
887               i += encoded;
888             }
889
890           if (i < j)
891             {
892               fprintf (ext->file, "/%s GS\n", chars[i]->name);
893               i++;
894             }
895         }
896     }
897   ds_destroy (&out);
898 }
899
900 /* State of a text formatting operation. */
901 struct text_state
902   {
903     /* Input. */
904     const struct outp_text *text;
905     bool draw;
906
907     /* Output. */
908     const struct afm_character **glyphs;
909     int *glyph_kerns;
910
911     /* State. */
912     size_t glyph_cnt;           /* Number of glyphs output. */
913     int width_left;             /* Width left over. */
914     int height_left;            /* Height left over. */
915
916     /* State as of last space. */
917     const char *space_char;     /* Just past last space. */
918     size_t space_glyph_cnt;     /* Number of glyphs as of last space. */
919     int space_width_left;       /* Width left over as of last space. */
920
921     /* Statistics. */
922     int max_width;             /* Widest line so far. */
923   };
924
925 /* Adjusts S to complete a line of text,
926    and draws the current line if appropriate. */
927 static void
928 finish_line (struct outp_driver *this, struct text_state *s)
929 {
930   int width;
931
932   if (s->draw)
933     {
934       write_text (this,
935                   s->text->x, s->text->y + (s->text->v - s->height_left),
936                   s->text->font,
937                   s->text->justification,
938                   s->glyphs, s->glyph_kerns, s->glyph_cnt,
939                   s->width_left);
940       s->glyph_cnt = 0;
941     }
942
943   /* Update maximum width. */
944   width = s->text->h - s->width_left;
945   if (width > s->max_width)
946     s->max_width = width;
947
948   /* Move to next line. */
949   s->width_left = s->text->h;
950   s->height_left -= this->font_height;
951
952   /* No spaces on this line yet. */
953   s->space_char = NULL;
954 }
955
956 /* Format TEXT on THIS driver.
957    If DRAW is nonzero, draw the text.
958    The width of the widest line is stored into *WIDTH, if WIDTH
959    is nonnull.
960    The total height of the text written is stored into *HEIGHT,
961    if HEIGHT is nonnull. */
962 static void
963 text (struct outp_driver *this, const struct outp_text *text, bool draw,
964       int *width, int *height)
965 {
966   struct ps_driver_ext *ext = this->ext;
967   struct afm *afm = ext->fonts[text->font]->metrics;
968   const char *cp;
969   size_t glyph_cap;
970   struct text_state s;
971
972   s.text = text;
973   s.draw = draw;
974
975   s.glyphs = NULL;
976   s.glyph_kerns = NULL;
977   glyph_cap = 0;
978
979   s.glyph_cnt = 0;
980   s.width_left = s.text->h;
981   s.height_left = s.text->v;
982
983   s.space_char = 0;
984
985   s.max_width = 0;
986
987   cp = ss_data (s.text->string);
988   while (s.height_left >= this->font_height && cp < ss_end (s.text->string))
989     {
990       const struct afm_character *cur;
991       int char_width;
992       int kern_adjust;
993
994       if (*cp == '\n')
995         {
996           finish_line (this, &s);
997           cp++;
998           continue;
999         }
1000
1001       /* Get character and resolve ligatures. */
1002       cur = afm_get_character (afm, *cp);
1003       while (++cp < ss_end (s.text->string))
1004         {
1005           const struct afm_character *next = afm_get_character (afm, *cp);
1006           const struct afm_character *ligature = afm_get_ligature (cur, next);
1007           if (ligature == NULL)
1008             break;
1009           cur = ligature;
1010         }
1011       char_width = cur->width * this->font_height / 1000;
1012
1013       /* Get kern adjustment. */
1014       if (s.glyph_cnt > 0)
1015         kern_adjust = (afm_get_kern_adjustment (s.glyphs[s.glyph_cnt - 1], cur)
1016                        * this->font_height / 1000);
1017       else
1018         kern_adjust = 0;
1019
1020       /* Record the current status if this is a space character. */
1021       if (cur->code == ' ' && cp > ss_data (s.text->string))
1022         {
1023           s.space_char = cp;
1024           s.space_glyph_cnt = s.glyph_cnt;
1025           s.space_width_left = s.width_left;
1026         }
1027
1028       /* Enough room on this line? */
1029       if (char_width + kern_adjust > s.width_left)
1030         {
1031           if (s.space_char == NULL)
1032             {
1033               finish_line (this, &s);
1034               kern_adjust = 0;
1035             }
1036           else
1037             {
1038               cp = s.space_char;
1039               s.glyph_cnt = s.space_glyph_cnt;
1040               s.width_left = s.space_width_left;
1041               finish_line (this, &s);
1042               continue;
1043             }
1044         }
1045
1046       if (s.glyph_cnt >= glyph_cap)
1047         {
1048           glyph_cap = 2 * (glyph_cap + 8);
1049           s.glyphs = xnrealloc (s.glyphs, glyph_cap, sizeof *s.glyphs);
1050           s.glyph_kerns = xnrealloc (s.glyph_kerns,
1051                                      glyph_cap, sizeof *s.glyph_kerns);
1052         }
1053       s.glyphs[s.glyph_cnt] = cur;
1054       s.glyph_kerns[s.glyph_cnt] = kern_adjust;
1055       s.glyph_cnt++;
1056
1057       s.width_left -= char_width + kern_adjust;
1058     }
1059   if (s.height_left >= this->font_height && s.glyph_cnt > 0)
1060     finish_line (this, &s);
1061
1062   if (width != NULL)
1063     *width = s.max_width;
1064   if (height != NULL)
1065     *height = text->v - s.height_left;
1066   free (s.glyphs);
1067   free (s.glyph_kerns);
1068 }
1069
1070 static void
1071 ps_text_metrics (struct outp_driver *this, const struct outp_text *t,
1072                  int *width, int *height)
1073 {
1074   text (this, t, false, width, height);
1075 }
1076
1077 static void
1078 ps_text_draw (struct outp_driver *this, const struct outp_text *t)
1079 {
1080   assert (this->page_open);
1081   text (this, t, true, NULL, NULL);
1082 }
1083 \f
1084 static void
1085 ps_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch)
1086 {
1087 #ifdef NO_CHARTS
1088   ch->lp = NULL;
1089 #else
1090   struct ps_driver_ext *x = this->ext;
1091   char page_size[128];
1092   int size;
1093   int x_origin, y_origin;
1094
1095   ch->file = tmpfile ();
1096   if (ch->file == NULL)
1097     {
1098       ch->lp = NULL;
1099       return;
1100     }
1101
1102   size = this->width < this->length ? this->width : this->length;
1103   x_origin = x->left_margin + (size - this->width) / 2;
1104   y_origin = x->bottom_margin + (size - this->length) / 2;
1105
1106   snprintf (page_size, sizeof page_size,
1107             "a,xsize=%.3f,ysize=%.3f,xorigin=%.3f,yorigin=%.3f",
1108             (double) size / PSUS, (double) size / PSUS,
1109             (double) x_origin / PSUS, (double) y_origin / PSUS);
1110
1111   ch->pl_params = pl_newplparams ();
1112   pl_setplparam (ch->pl_params, "PAGESIZE", page_size);
1113   ch->lp = pl_newpl_r ("ps", NULL, ch->file, stderr, ch->pl_params);
1114 #endif
1115 }
1116
1117 static void
1118 ps_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
1119 {
1120 #ifndef NO_CHARTS
1121   struct ps_driver_ext *x = this->ext;
1122   char buf[BUFSIZ];
1123   static int doc_num = 0;
1124
1125   outp_eject_page (this);
1126   fprintf (x->file,
1127            "/sp save def\n"
1128            "%d %d translate 1000 dup scale\n"
1129            "userdict begin\n"
1130            "/showpage { } def\n"
1131            "0 setgray 0 setlinecap 1 setlinewidth\n"
1132            "0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath clear\n"
1133            "%%%%BeginDocument: %d\n",
1134            -x->left_margin, -x->bottom_margin,
1135            doc_num++);
1136
1137   rewind (ch->file);
1138   while (fwrite (buf, 1, fread (buf, 1, sizeof buf, ch->file), x->file))
1139     continue;
1140   fclose (ch->file);
1141
1142   fputs ("%%EndDocument\n"
1143          "end\n"
1144          "sp restore\n",
1145          x->file);
1146   outp_close_page (this);
1147 #endif
1148 }
1149 \f
1150 static void embed_font (struct outp_driver *this, struct font *font);
1151 static void reencode_font (struct outp_driver *this, struct font *font);
1152
1153 /* Loads and returns the font for STRING, which has the format
1154    "AFM,PFA,ENC", where AFM is the AFM file's name, PFA is the
1155    PFA or PFB file's name, and ENC is the encoding file's name.
1156    PFA and ENC are optional.
1157    Returns a null pointer if unsuccessful. */
1158 static struct font *
1159 load_font (const char *string_)
1160 {
1161   char *string = xstrdup (string_);
1162   struct font *font;
1163   char *position = string;
1164   char *token;
1165   char *afm_file_name;
1166
1167   font = xmalloc (sizeof *font);
1168   font->metrics = NULL;
1169   font->embed_fn = NULL;
1170   font->encoding_fn = NULL;
1171
1172   token = strsep (&position, ",");
1173   if (token == NULL)
1174     {
1175       error (0, 0, _("\"%s\": bad font specification"), string);
1176       goto error;
1177     }
1178
1179   /* Read AFM file. */
1180   afm_file_name = find_ps_file (token);
1181   if (afm_file_name == NULL)
1182     {
1183       error (0, 0, _("could not find AFM file \"%s\""), token);
1184       goto error;
1185     }
1186   font->metrics = afm_open (afm_file_name);
1187   free (afm_file_name);
1188   if (font->metrics == NULL)
1189     goto error;
1190
1191   /* Find font file to embed. */
1192   token = strsep (&position, ",");
1193   if (token != NULL && *token != '\0')
1194     {
1195       font->embed_fn = find_ps_file (token);
1196       if (font->embed_fn == NULL)
1197         error (0, 0, _("could not find font \"%s\""), token);
1198     }
1199
1200   /* Find encoding. */
1201   token = strsep (&position, ",");
1202   if (token != NULL && *token == '\0')
1203     {
1204       font->encoding_fn = find_ps_file (token);
1205       if (font->encoding_fn == NULL)
1206         error (0, 0, _("could not find encoding \"%s\""), token);
1207     }
1208
1209   free (string);
1210   return font;
1211
1212  error:
1213   free (string);
1214   free_font (font);
1215   return NULL;
1216 }
1217
1218 /* Frees FONT. */
1219 static void
1220 free_font (struct font *font)
1221 {
1222   if (font != NULL)
1223     {
1224       afm_close (font->metrics);
1225       free (font->embed_fn);
1226       free (font->encoding_fn);
1227       free (font);
1228     }
1229 }
1230
1231 /* Emits PostScript code to embed FONT (if necessary), scale it
1232    to the proper size, re-encode it (if necessary), and store the
1233    resulting font as an object named F#, where INDEX is
1234    substituted for #. */
1235 static void
1236 setup_font (struct outp_driver *this, struct font *font, int index)
1237 {
1238   struct ps_driver_ext *x = this->ext;
1239   char *ps_name;
1240
1241   if (font->embed_fn != NULL)
1242     embed_font (this, font);
1243   else
1244     fprintf (x->file, "%%%%IncludeResource: font %s\n",
1245              afm_get_findfont_name (font->metrics));
1246
1247   ps_name = quote_ps_name (afm_get_findfont_name (font->metrics));
1248   fprintf (x->file, "%s findfont %d scalefont\n", ps_name, this->font_height);
1249   free (ps_name);
1250
1251   if (font->encoding_fn != NULL)
1252     reencode_font (this, font);
1253
1254   fprintf (x->file, "/F%d ED\n", index);
1255 }
1256
1257 /* Copies up to COPY_BYTES bytes from SRC to DST, stopping at
1258    end-of-file or on error. */
1259 static void
1260 copy_bytes_literally (FILE *src, FILE *dst, unsigned long copy_bytes)
1261 {
1262   while (copy_bytes > 0)
1263     {
1264       char buffer[BUFSIZ];
1265       unsigned long chunk_bytes = MIN (copy_bytes, sizeof buffer);
1266       size_t read_bytes = fread (buffer, 1, chunk_bytes, src);
1267       size_t write_bytes = fwrite (buffer, 1, read_bytes, dst);
1268       if (write_bytes != chunk_bytes)
1269         break;
1270       copy_bytes -= chunk_bytes;
1271     }
1272 }
1273
1274 /* Copies up to COPY_BYTES bytes from SRC to DST, stopping at
1275    end-of-file or on error.  The bytes are translated into
1276    hexadecimal during copying and broken into lines with
1277    new-line characters. */
1278 static void
1279 copy_bytes_as_hex (FILE *src, FILE *dst, unsigned long copy_bytes)
1280 {
1281   unsigned long i;
1282
1283   for (i = 0; i < copy_bytes; i++)
1284     {
1285       int c = getc (src);
1286       if (c == EOF)
1287         break;
1288       if (i > 0 && i % 36 == 0)
1289         putc ('\n', dst);
1290       fprintf (dst, "%02X", c);
1291     }
1292   putc ('\n', dst);
1293 }
1294
1295 /* Embeds the given FONT into THIS driver's output stream. */
1296 static void
1297 embed_font (struct outp_driver *this, struct font *font)
1298 {
1299   struct ps_driver_ext *x = this->ext;
1300   FILE *file;
1301   int c;
1302
1303   file = fopen (font->embed_fn, "rb");
1304   if (file == NULL)
1305     {
1306       error (errno, 0, _("cannot open font file \"%s\""), font->embed_fn);
1307       return;
1308     }
1309
1310   fprintf (x->file, "%%%%BeginResource: font %s\n",
1311            afm_get_findfont_name (font->metrics));
1312
1313   c = getc (file);
1314   ungetc (c, file);
1315   if (c != 128)
1316     {
1317       /* PFA file.  Copy literally. */
1318       copy_bytes_literally (file, x->file, ULONG_MAX);
1319     }
1320   else
1321     {
1322       /* PFB file.  Translate as specified in Adobe Technical
1323          Note #5040. */
1324       while ((c = getc (file)) == 128)
1325         {
1326           int type;
1327           unsigned long length;
1328
1329           type = getc (file);
1330           if (type == 3)
1331             break;
1332
1333           length = getc (file);
1334           length |= (unsigned long) getc (file) << 8;
1335           length |= (unsigned long) getc (file) << 16;
1336           length |= (unsigned long) getc (file) << 24;
1337
1338           if (type == 1)
1339             copy_bytes_literally (file, x->file, length);
1340           else if (type == 2)
1341             copy_bytes_as_hex (file, x->file, length);
1342           else
1343             break;
1344         }
1345     }
1346   if (freaderror (file))
1347     error (errno, 0, _("reading font file \"%s\""), font->embed_fn);
1348   fputs ("%%EndResource\n", x->file);
1349 }
1350
1351 /* Re-encodes FONT according to the specified encoding. */
1352 static void
1353 reencode_font (struct outp_driver *this, struct font *font)
1354 {
1355   struct ps_driver_ext *x = this->ext;
1356
1357   struct string line;
1358
1359   int line_number;
1360   FILE *file;
1361
1362   char *tab[256];
1363
1364   int i;
1365
1366   file = fopen (font->encoding_fn, "r");
1367   if (file == NULL)
1368     {
1369       error (errno, 0, _("cannot open font encoding file \"%s\""),
1370              font->encoding_fn);
1371       return;
1372     }
1373
1374   for (i = 0; i < 256; i++)
1375     tab[i] = NULL;
1376
1377   line_number = 0;
1378
1379   ds_init_empty (&line);
1380   while (ds_read_config_line (&line, &line_number, file))
1381     {
1382       char *pschar, *code;
1383       char *save_ptr, *tail;
1384       int code_val;
1385
1386       if (ds_is_empty (&line) == 0)
1387         continue;
1388
1389       pschar = strtok_r (ds_cstr (&line), " \t\r\n", &save_ptr);
1390       code = strtok_r (NULL, " \t\r\n", &save_ptr);
1391       if (pschar == NULL || code == NULL)
1392         continue;
1393
1394       code_val = strtol (code, &tail, 0);
1395       if (*tail)
1396         {
1397           error_at_line (0, 0, font->encoding_fn, line_number,
1398                          _("invalid numeric format"));
1399           continue;
1400         }
1401       if (code_val < 0 || code_val > 255)
1402         continue;
1403       if (tab[code_val] != 0)
1404         free (tab[code_val]);
1405       tab[code_val] = xstrdup (pschar);
1406     }
1407   ds_destroy (&line);
1408
1409   fputs ("[", x->file);
1410   for (i = 0; i < 256; i++)
1411     {
1412       char *name = quote_ps_name (tab[i] != NULL ? tab[i] : ".notdef");
1413       fprintf (x->file, "%s\n", name);
1414       free (name);
1415       free (tab[i]);
1416     }
1417   fputs ("] RF\n", x->file);
1418
1419   if (freaderror (file) != 0)
1420     error (errno, 0, _("closing Postscript encoding \"%s\""),
1421            font->encoding_fn);
1422 }
1423
1424 /* PostScript driver class. */
1425 const struct outp_class postscript_class =
1426 {
1427   "postscript",
1428   0,
1429
1430   ps_open_driver,
1431   ps_close_driver,
1432
1433   ps_open_page,
1434   ps_close_page,
1435   NULL,
1436
1437   ps_submit,
1438
1439   ps_line,
1440   ps_text_metrics,
1441   ps_text_draw,
1442
1443   ps_chart_initialise,
1444   ps_chart_finalise
1445 };