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