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