7554dba5787d35d2fdd4b116b912e9679a8a34ba
[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/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/filename.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 #include "strsep.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53
54 /* PostScript driver options: (defaults listed first)
55
56    output-file="pspp.ps"
57
58    paper-size=letter (see "papersize" file)
59    orientation=portrait|landscape
60    headers=on|off
61
62    left-margin=0.5in
63    right-margin=0.5in
64    top-margin=0.5in
65    bottom-margin=0.5in
66
67    prop-font=Times-Roman
68    emph-font=Times-Italic
69    fixed-font=Courier
70    font-size=10000
71
72    line-gutter=1pt
73    line-spacing=1pt
74    line-width=0.5pt
75  */
76
77 /* The number of `psus' (PostScript driver UnitS) per inch. */
78 #define PSUS 72000
79
80 /* A PostScript font. */
81 struct font
82   {
83     struct afm *metrics;        /* Metrics. */
84     char *embed_fn;             /* Name of file to embed. */
85     char *encoding_fn;          /* Name of file with encoding. */
86   };
87
88 /* PostScript output driver extension record. */
89 struct ps_driver_ext
90   {
91     char *file_name;            /* Output file name. */
92     FILE *file;                 /* Output file. */
93
94     bool draw_headers;          /* Draw headers at top of page? */
95     int page_number;            /* Current page number. */
96
97     bool portrait;              /* Portrait mode? */
98     int paper_width;            /* Width of paper before dropping margins. */
99     int paper_length;           /* Length of paper before dropping margins. */
100     int left_margin;            /* Left margin in psus. */
101     int right_margin;           /* Right margin in psus. */
102     int top_margin;             /* Top margin in psus. */
103     int bottom_margin;          /* Bottom margin in psus. */
104
105     int line_gutter;            /* Space around lines. */
106     int line_space;             /* Space between lines. */
107     int line_width;             /* Width of lines. */
108
109     struct font *fonts[OUTP_FONT_CNT];
110     int last_font;              /* Index of last font set with setfont. */
111   }
112 ps_driver_ext;
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, const struct string *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 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_c_str (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             abort ();
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             abort ();
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       abort ();
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 filename 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%c%s", DIR_SEPARATOR, name);
437       char *file_name = fn_search_path (base_name, config_path, NULL);
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_INITIALIZER;
546           ds_putc (&out, '<');
547           for (cp = string; *cp != '\0'; cp++)
548             {
549               c = *cp;
550               ds_printf (&out, "%02x", c);
551             }
552           ds_puts (&out, ">cvn");
553           return ds_c_str (&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       abort ();
612       break;
613     }
614 }
615 \f
616 /* Draws a line from (x0,y0) to (x1,y1). */
617 static void
618 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
619 {
620   struct ps_driver_ext *ext = this->ext;
621   fprintf (ext->file, "%d %d %d %d L\n", x0, YT (y0), x1, YT (y1));
622 }
623
624 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
625    shortening it to X0...X1 if SHORTEN is true.
626    Draws a horizontal line X1...X3 at Y if RIGHT says so,
627    shortening it to X2...X3 if SHORTEN is true. */
628 static void
629 horz_line (struct outp_driver *this,
630            int x0, int x1, int x2, int x3, int y,
631            enum outp_line_style left, enum outp_line_style right,
632            bool shorten)
633 {
634   if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
635     dump_line (this, x0, y, x3, y);
636   else
637     {
638       if (left != OUTP_L_NONE)
639         dump_line (this, x0, y, shorten ? x1 : x2, y);
640       if (right != OUTP_L_NONE)
641         dump_line (this, shorten ? x2 : x1, y, x3, y);
642     }
643 }
644
645 /* Draws a vertical line Y0...Y2 at X if TOP says so,
646    shortening it to Y0...Y1 if SHORTEN is true.
647    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
648    shortening it to Y2...Y3 if SHORTEN is true. */
649 static void
650 vert_line (struct outp_driver *this,
651            int y0, int y1, int y2, int y3, int x,
652            enum outp_line_style top, enum outp_line_style bottom,
653            bool shorten)
654 {
655   if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
656     dump_line (this, x, y0, x, y3);
657   else
658     {
659       if (top != OUTP_L_NONE)
660         dump_line (this, x, y0, x, shorten ? y1 : y2);
661       if (bottom != OUTP_L_NONE)
662         dump_line (this, x, shorten ? y2 : y1, x, y3);
663     }
664 }
665
666 /* Draws a generalized intersection of lines in the rectangle
667    (X0,Y0)-(X3,Y3).  The line coming from the top to the center
668    is of style TOP, from left to center of style LEFT, from
669    bottom to center of style BOTTOM, and from right to center of
670    style RIGHT. */
671 static void
672 ps_line (struct outp_driver *this,
673          int x0, int y0, int x3, int y3,
674          enum outp_line_style top, enum outp_line_style left,
675          enum outp_line_style bottom, enum outp_line_style right)
676 {
677 /* The algorithm here is somewhat subtle, to allow it to handle
678    all the kinds of intersections that we need.
679
680    Three additional ordinates are assigned along the x axis.  The
681    first is xc, midway between x0 and x3.  The others are x1 and
682    x2; for a single vertical line these are equal to xc, and for
683    a double vertical line they are the ordinates of the left and
684    right half of the double line.
685
686    yc, y1, and y2 are assigned similarly along the y axis.
687
688    The following diagram shows the coordinate system and output
689    for double top and bottom lines, single left line, and no
690    right line:
691
692                x0       x1 xc  x2      x3
693              y0 ________________________
694                 |        #     #       |
695                 |        #     #       |
696                 |        #     #       |
697                 |        #     #       |
698                 |        #     #       |
699    y1 = y2 = yc |#########     #       |
700                 |        #     #       |
701                 |        #     #       |
702                 |        #     #       |
703                 |        #     #       |
704              y3 |________#_____#_______|
705 */
706   struct ps_driver_ext *ext = this->ext;
707
708   /* Offset from center of each line in a pair of double lines. */
709   int double_line_ofs = (ext->line_space + ext->line_width) / 2;
710
711   /* Are the lines along each axis single or double?
712      (It doesn't make sense to have different kinds of line on the
713      same axis, so we don't try to gracefully handle that case.) */
714   bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
715   bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
716
717   /* When horizontal lines are doubled,
718      the left-side line along y1 normally runs from x0 to x2,
719      and the right-side line along y1 from x3 to x1.
720      If the top-side line is also doubled, we shorten the y1 lines,
721      so that the left-side line runs only to x1,
722      and the right-side line only to x2.
723      Otherwise, the horizontal line at y = y1 below would cut off
724      the intersection, which looks ugly:
725                x0       x1     x2      x3
726              y0 ________________________
727                 |        #     #       |
728                 |        #     #       |
729                 |        #     #       |
730                 |        #     #       |
731              y1 |#########     ########|
732                 |                      |
733                 |                      |
734              y2 |######################|
735                 |                      |
736                 |                      |
737              y3 |______________________|
738      It is more of a judgment call when the horizontal line is
739      single.  We actually choose to cut off the line anyhow, as
740      shown in the first diagram above.
741   */
742   bool shorten_y1_lines = top == OUTP_L_DOUBLE;
743   bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
744   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
745   int horz_line_ofs = double_vert ? double_line_ofs : 0;
746   int xc = (x0 + x3) / 2;
747   int x1 = xc - horz_line_ofs;
748   int x2 = xc + horz_line_ofs;
749
750   bool shorten_x1_lines = left == OUTP_L_DOUBLE;
751   bool shorten_x2_lines = right == OUTP_L_DOUBLE;
752   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
753   int vert_line_ofs = double_horz ? double_line_ofs : 0;
754   int yc = (y0 + y3) / 2;
755   int y1 = yc - vert_line_ofs;
756   int y2 = yc + vert_line_ofs;
757
758   if (!double_horz)
759     horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
760   else
761     {
762       horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
763       horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
764     }
765
766   if (!double_vert)
767     vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
768   else
769     {
770       vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
771       vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
772     }
773 }
774
775 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
776    and with the given JUSTIFICATION for THIS driver. */
777 static int
778 draw_text (struct outp_driver *this,
779            const char *string, int x, int y, int max_width,
780            enum outp_justification justification)
781 {
782   struct outp_text text;
783   int width;
784
785   text.font = OUTP_PROPORTIONAL;
786   text.justification = justification;
787   ls_init (&text.string, (char *) string, strlen (string));
788   text.h = max_width;
789   text.v = this->font_height;
790   text.x = x;
791   text.y = y;
792   this->class->text_metrics (this, &text, &width, NULL);
793   this->class->text_draw (this, &text);
794   return width;
795 }
796
797 /* Writes LEFT left-justified and RIGHT right-justified within
798    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
799 static void
800 draw_header_line (struct outp_driver *this,
801                   const char *left, const char *right,
802                   int x0, int x1, int y) 
803 {
804   int right_width = 0;
805   if (right != NULL)
806     right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
807                    + this->prop_em_width);
808   if (left != NULL)
809     draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
810 }
811
812 /* Draw top of page headers for THIS driver. */
813 static void
814 draw_headers (struct outp_driver *this)
815 {
816   struct ps_driver_ext *ext = this->ext;
817   char *r1, *r2;
818   int x0, x1;
819   int y;
820
821   y = -3 * this->font_height;
822   x0 = this->prop_em_width;
823   x1 = this->width - this->prop_em_width;
824
825   /* Draw box. */
826   fprintf (ext->file, "%d %d %d %d GB\n",
827            0, YT (y),
828            this->width, YT (y + 2 * this->font_height + ext->line_gutter));
829   y += ext->line_width + ext->line_gutter;
830
831   r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
832   r2 = xasprintf ("%s - %s", version, host_system);
833  
834   draw_header_line (this, outp_title, r1, x0, x1, y);
835   y += this->font_height;
836   
837   draw_header_line (this, outp_subtitle, r2, x0, x1, y);
838  
839   free (r1);
840   free (r2);
841 }
842 \f
843 /* Writes the CHAR_CNT characters in CHARS at (X0,Y0), using the
844    given FONT.
845    The characters are justified according to JUSTIFICATION in a
846    field that has WIDTH_LEFT space remaining after the characters
847    themselves are accounted for.
848    Before character I is written, its x-position is adjusted by
849    KERNS[I]. */
850 static void
851 write_text (struct outp_driver *this,
852             int x0, int y0,
853             enum outp_font font, 
854             enum outp_justification justification,
855             const struct afm_character **chars, int *kerns, size_t char_cnt,
856             int width_left)
857 {
858   struct ps_driver_ext *ext = this->ext;
859   struct afm *afm = ext->fonts[font]->metrics;
860   struct string out;
861   size_t i, j;
862
863   if (justification == OUTP_RIGHT)
864     x0 += width_left;
865   else if (justification == OUTP_CENTER)
866     x0 += width_left / 2;
867   y0 += afm_get_ascent (afm) * this->font_height / 1000;
868
869   fprintf (ext->file, "\n%d %d moveto\n", x0, YT (y0));
870
871   if (ext->last_font != font)
872     {
873       ext->last_font = font;
874       fprintf (ext->file, "F%d setfont\n", font);
875     }
876
877   ds_init (&out, 0);
878   for (i = 0; i < char_cnt; i = j)
879     {
880       for (j = i + 1; j < char_cnt; j++)
881         if (kerns[j] != 0)
882           break;
883
884       if (kerns[i] != 0)
885         fprintf (ext->file, "%d K", kerns[i]);
886       while (i < j)
887         {
888           size_t encoded = afm_encode_string (afm, chars + i, j - i, &out);
889           if (encoded > 0)
890             {
891               fprintf (ext->file, "%sS\n", ds_c_str (&out));
892               ds_clear (&out);
893               i += encoded;
894             }
895
896           if (i < j)
897             {
898               fprintf (ext->file, "/%s GS\n", chars[i]->name);
899               i++;
900             }
901         }
902     }
903   ds_destroy (&out);
904 }
905
906 /* State of a text formatting operation. */
907 struct text_state
908   {
909     /* Input. */
910     const struct outp_text *text;
911     bool draw;
912
913     /* Output. */
914     const struct afm_character **glyphs;
915     int *glyph_kerns;
916
917     /* State. */
918     size_t glyph_cnt;           /* Number of glyphs output. */
919     int width_left;             /* Width left over. */
920     int height_left;            /* Height left over. */
921
922     /* State as of last space. */
923     const char *space_char;     /* Just past last space. */
924     size_t space_glyph_cnt;     /* Number of glyphs as of last space. */
925     int space_width_left;       /* Width left over as of last space. */
926
927     /* Statistics. */
928     int max_width;             /* Widest line so far. */
929   };
930
931 /* Adjusts S to complete a line of text,
932    and draws the current line if appropriate. */
933 static void
934 finish_line (struct outp_driver *this, struct text_state *s)
935 {
936   int width;
937
938   if (s->draw)
939     {
940       write_text (this,
941                   s->text->x, s->text->y + (s->text->v - s->height_left),
942                   s->text->font,
943                   s->text->justification,
944                   s->glyphs, s->glyph_kerns, s->glyph_cnt,
945                   s->width_left);
946       s->glyph_cnt = 0;
947     }
948
949   /* Update maximum width. */
950   width = s->text->h - s->width_left;
951   if (width > s->max_width)
952     s->max_width = width;
953
954   /* Move to next line. */
955   s->width_left = s->text->h;
956   s->height_left -= this->font_height;
957
958   /* No spaces on this line yet. */
959   s->space_char = NULL;
960 }
961
962 /* Format TEXT on THIS driver.
963    If DRAW is nonzero, draw the text.
964    The width of the widest line is stored into *WIDTH, if WIDTH
965    is nonnull.
966    The total height of the text written is stored into *HEIGHT,
967    if HEIGHT is nonnull. */
968 static void
969 text (struct outp_driver *this, const struct outp_text *text, bool draw,
970       int *width, int *height)
971 {
972   struct ps_driver_ext *ext = this->ext;
973   struct afm *afm = ext->fonts[text->font]->metrics;
974   const char *cp;
975   size_t glyph_cap;
976   struct text_state s;
977
978   s.text = text;
979   s.draw = draw;
980
981   s.glyphs = NULL;
982   s.glyph_kerns = NULL;
983   glyph_cap = 0;
984
985   s.glyph_cnt = 0;
986   s.width_left = s.text->h;
987   s.height_left = s.text->v;
988
989   s.space_char = 0;
990
991   s.max_width = 0;
992
993   cp = ls_c_str (&s.text->string);
994   while (s.height_left >= this->font_height && cp < ls_end (&s.text->string))
995     {
996       const struct afm_character *cur;
997       int char_width;
998       int kern_adjust;
999
1000       if (*cp == '\n')
1001         {
1002           finish_line (this, &s);
1003           cp++;
1004           continue;
1005         }
1006
1007       /* Get character and resolve ligatures. */
1008       cur = afm_get_character (afm, *cp);
1009       while (++cp < ls_end (&s.text->string))
1010         {
1011           const struct afm_character *next = afm_get_character (afm, *cp);
1012           const struct afm_character *ligature = afm_get_ligature (cur, next);
1013           if (ligature == NULL)
1014             break;
1015           cur = ligature;
1016         }
1017       char_width = cur->width * this->font_height / 1000;
1018
1019       /* Get kern adjustment. */
1020       if (s.glyph_cnt > 0) 
1021         kern_adjust = (afm_get_kern_adjustment (s.glyphs[s.glyph_cnt - 1], cur)
1022                        * this->font_height / 1000);
1023       else
1024         kern_adjust = 0;
1025
1026       /* Record the current status if this is a space character. */
1027       if (cur->code == ' ' && cp > ls_c_str (&s.text->string))
1028         {
1029           s.space_char = cp;
1030           s.space_glyph_cnt = s.glyph_cnt;
1031           s.space_width_left = s.width_left;
1032         }
1033
1034       /* Enough room on this line? */
1035       if (char_width + kern_adjust > s.width_left)
1036         {
1037           if (s.space_char == NULL)
1038             {
1039               finish_line (this, &s);
1040               kern_adjust = 0;
1041             }
1042           else
1043             {
1044               cp = s.space_char;
1045               s.glyph_cnt = s.space_glyph_cnt;
1046               s.width_left = s.space_width_left;
1047               finish_line (this, &s);
1048               continue;
1049             }
1050         }
1051
1052       if (s.glyph_cnt >= glyph_cap)
1053         {
1054           glyph_cap = 2 * (glyph_cap + 8);
1055           s.glyphs = xnrealloc (s.glyphs, glyph_cap, sizeof *s.glyphs);
1056           s.glyph_kerns = xnrealloc (s.glyph_kerns,
1057                                      glyph_cap, sizeof *s.glyph_kerns);
1058         }
1059       s.glyphs[s.glyph_cnt] = cur;
1060       s.glyph_kerns[s.glyph_cnt] = kern_adjust;
1061       s.glyph_cnt++;
1062
1063       s.width_left -= char_width + kern_adjust;
1064     }
1065   if (s.height_left >= this->font_height && s.glyph_cnt > 0)
1066     finish_line (this, &s);
1067
1068   if (width != NULL)
1069     *width = s.max_width;
1070   if (height != NULL)
1071     *height = text->v - s.height_left;
1072   free (s.glyphs);
1073   free (s.glyph_kerns);
1074 }
1075
1076 static void
1077 ps_text_metrics (struct outp_driver *this, const struct outp_text *t,
1078                  int *width, int *height)
1079 {
1080   text (this, t, false, width, height);
1081 }
1082
1083 static void
1084 ps_text_draw (struct outp_driver *this, const struct outp_text *t)
1085 {
1086   assert (this->page_open);
1087   text (this, t, true, NULL, NULL);
1088 }
1089 \f
1090 static void
1091 ps_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch)
1092 {
1093 #ifdef NO_CHARTS
1094   ch->lp = NULL;
1095 #else
1096   struct ps_driver_ext *x = this->ext;
1097   char page_size[128];
1098   int size;
1099   int x_origin, y_origin;
1100
1101   ch->file = tmpfile ();
1102   if (ch->file == NULL)
1103     {
1104       ch->lp = NULL;
1105       return;
1106     }
1107
1108   size = this->width < this->length ? this->width : this->length;
1109   x_origin = x->left_margin + (size - this->width) / 2;
1110   y_origin = x->bottom_margin + (size - this->length) / 2;
1111
1112   snprintf (page_size, sizeof page_size,
1113             "a,xsize=%.3f,ysize=%.3f,xorigin=%.3f,yorigin=%.3f",
1114             (double) size / PSUS, (double) size / PSUS,
1115             (double) x_origin / PSUS, (double) y_origin / PSUS);
1116
1117   ch->pl_params = pl_newplparams ();
1118   pl_setplparam (ch->pl_params, "PAGESIZE", page_size);
1119   ch->lp = pl_newpl_r ("ps", NULL, ch->file, stderr, ch->pl_params);
1120 #endif
1121 }
1122
1123 static void
1124 ps_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
1125 {
1126 #ifndef NO_CHARTS
1127   struct ps_driver_ext *x = this->ext;
1128   char buf[BUFSIZ];
1129   static int doc_num = 0;
1130
1131   outp_eject_page (this);
1132   fprintf (x->file,
1133            "/sp save def\n"
1134            "%d %d translate 1000 dup scale\n"
1135            "userdict begin\n"
1136            "/showpage { } def\n"
1137            "0 setgray 0 setlinecap 1 setlinewidth\n"
1138            "0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath clear\n"
1139            "%%%%BeginDocument: %d\n",
1140            -x->left_margin, -x->bottom_margin,
1141            doc_num++);
1142
1143   rewind (ch->file);
1144   while (fwrite (buf, 1, fread (buf, 1, sizeof buf, ch->file), x->file))
1145     continue;
1146   fclose (ch->file);
1147
1148   fputs ("%%%%EndDocument\n"
1149          "end\n"
1150          "sp restore\n",
1151          x->file);
1152   outp_close_page (this);
1153 #endif
1154 }
1155 \f
1156 static void embed_font (struct outp_driver *this, struct font *font);
1157 static void reencode_font (struct outp_driver *this, struct font *font);
1158
1159 /* Loads and returns the font for STRING, which has the format
1160    "AFM,PFA,ENC", where AFM is the AFM file's name, PFA is the
1161    PFA or PFB file's name, and ENC is the encoding file's name.
1162    PFA and ENC are optional.
1163    Returns a null pointer if unsuccessful. */
1164 static struct font *
1165 load_font (const char *string_)
1166 {
1167   char *string = xstrdup (string_);
1168   struct font *font;
1169   char *position = string;
1170   char *token;
1171   char *afm_file_name;
1172
1173   font = xmalloc (sizeof *font);
1174   font->metrics = NULL;
1175   font->embed_fn = NULL;
1176   font->encoding_fn = NULL;
1177
1178   token = strsep (&position, ",");
1179   if (token == NULL)
1180     {
1181       error (0, 0, _("\"%s\": bad font specification"), string);
1182       goto error;
1183     }
1184
1185   /* Read AFM file. */
1186   afm_file_name = find_ps_file (token);
1187   if (afm_file_name == NULL)
1188     {
1189       error (0, 0, _("could not find AFM file \"%s\""), token);
1190       goto error;
1191     }
1192   font->metrics = afm_open (afm_file_name);
1193   free (afm_file_name);
1194   if (font->metrics == NULL)
1195     goto error;
1196
1197   /* Find font file to embed. */
1198   token = strsep (&position, ",");
1199   if (token != NULL && *token != '\0')
1200     {
1201       font->embed_fn = find_ps_file (token);
1202       if (font->embed_fn == NULL)
1203         error (0, 0, _("could not find font \"%s\""), token);
1204     }
1205
1206   /* Find encoding. */
1207   token = strsep (&position, ",");
1208   if (token != NULL && *token == '\0')
1209     {
1210       font->encoding_fn = find_ps_file (token);
1211       if (font->encoding_fn == NULL)
1212         error (0, 0, _("could not find encoding \"%s\""), token);
1213     }
1214
1215   free (string);
1216   return font;
1217
1218  error:
1219   free (string);
1220   free_font (font);
1221   return NULL;
1222 }
1223
1224 /* Frees FONT. */
1225 static void
1226 free_font (struct font *font)
1227 {
1228   if (font != NULL)
1229     {
1230       afm_close (font->metrics);
1231       free (font->embed_fn);
1232       free (font->encoding_fn);
1233       free (font);
1234     }
1235 }
1236
1237 /* Emits PostScript code to embed FONT (if necessary), scale it
1238    to the proper size, re-encode it (if necessary), and store the
1239    resulting font as an object named F#, where INDEX is
1240    substituted for #. */
1241 static void
1242 setup_font (struct outp_driver *this, struct font *font, int index)
1243 {
1244   struct ps_driver_ext *x = this->ext;
1245   char *ps_name;
1246
1247   if (font->embed_fn != NULL)
1248     embed_font (this, font);
1249   else
1250     fprintf (x->file, "%%%%IncludeResource: font %s\n",
1251              afm_get_findfont_name (font->metrics));
1252
1253   ps_name = quote_ps_name (afm_get_findfont_name (font->metrics));
1254   fprintf (x->file, "%s findfont %d scalefont\n", ps_name, this->font_height);
1255   free (ps_name);
1256
1257   if (font->encoding_fn != NULL)
1258     reencode_font (this, font);
1259
1260   fprintf (x->file, "/F%d ED\n", index);
1261 }
1262
1263 /* Copies up to COPY_BYTES bytes from SRC to DST, stopping at
1264    end-of-file or on error. */
1265 static void
1266 copy_bytes_literally (FILE *src, FILE *dst, unsigned long copy_bytes)
1267 {
1268   while (copy_bytes > 0)
1269     {
1270       char buffer[BUFSIZ];
1271       unsigned long chunk_bytes = MIN (copy_bytes, sizeof buffer);
1272       size_t read_bytes = fread (buffer, 1, chunk_bytes, src);
1273       size_t write_bytes = fwrite (buffer, 1, read_bytes, dst);
1274       if (write_bytes != chunk_bytes)
1275         break;
1276       copy_bytes -= chunk_bytes;
1277     }
1278 }
1279
1280 /* Copies up to COPY_BYTES bytes from SRC to DST, stopping at
1281    end-of-file or on error.  The bytes are translated into
1282    hexadecimal during copying and broken into lines with
1283    new-line characters. */
1284 static void
1285 copy_bytes_as_hex (FILE *src, FILE *dst, unsigned long copy_bytes)
1286 {
1287   unsigned long i;
1288
1289   for (i = 0; i < copy_bytes; i++)
1290     {
1291       int c = getc (src);
1292       if (c == EOF)
1293         break;
1294       if (i > 0 && i % 36 == 0)
1295         putc ('\n', dst);
1296       fprintf (dst, "%02X", c);
1297     }
1298   putc ('\n', dst);
1299 }
1300
1301 /* Embeds the given FONT into THIS driver's output stream. */
1302 static void
1303 embed_font (struct outp_driver *this, struct font *font)
1304 {
1305   struct ps_driver_ext *x = this->ext;
1306   FILE *file;
1307   int c;
1308
1309   file = fopen (font->embed_fn, "rb");
1310   if (file == NULL)
1311     {
1312       error (errno, 0, _("cannot open font file \"%s\""), font->embed_fn);
1313       return;
1314     }
1315
1316   fprintf (x->file, "%%%%BeginResource: font %s\n",
1317            afm_get_findfont_name (font->metrics));
1318
1319   c = getc (file);
1320   ungetc (c, file);
1321   if (c != 128)
1322     {
1323       /* PFA file.  Copy literally. */
1324       copy_bytes_literally (file, x->file, ULONG_MAX);
1325     }
1326   else
1327     {
1328       /* PFB file.  Translate as specified in Adobe Technical
1329          Note #5040. */
1330       while ((c = getc (file)) == 128)
1331         {
1332           int type;
1333           unsigned long length;
1334
1335           type = getc (file);
1336           if (type == 3)
1337             break;
1338
1339           length = getc (file);
1340           length |= (unsigned long) getc (file) << 8;
1341           length |= (unsigned long) getc (file) << 16;
1342           length |= (unsigned long) getc (file) << 24;
1343
1344           if (type == 1)
1345             copy_bytes_literally (file, x->file, length);
1346           else if (type == 2)
1347             copy_bytes_as_hex (file, x->file, length);
1348           else
1349             break;
1350         }
1351     }
1352   if (freaderror (file))
1353     error (errno, 0, _("reading font file \"%s\""), font->embed_fn);
1354   fputs ("%%EndResource\n", x->file);
1355 }
1356
1357 /* Re-encodes FONT according to the specified encoding. */
1358 static void
1359 reencode_font (struct outp_driver *this, struct font *font)
1360 {
1361   struct ps_driver_ext *x = this->ext;
1362
1363   struct string line;
1364
1365   int line_number;
1366   FILE *file;
1367
1368   char *tab[256];
1369
1370   int i;
1371
1372   file = fopen (font->encoding_fn, "r");
1373   if (file == NULL)
1374     {
1375       error (errno, 0, _("cannot open font encoding file \"%s\""),
1376              font->encoding_fn);
1377       return;
1378     }
1379
1380   for (i = 0; i < 256; i++)
1381     tab[i] = NULL;
1382
1383   line_number = 0;
1384
1385   ds_init (&line, 0);
1386   while (ds_get_config_line (file, &line, &line_number))
1387     {
1388       char *pschar, *code;
1389       char *save_ptr, *tail;
1390       int code_val;
1391
1392       if (ds_is_empty (&line) == 0)
1393         continue;
1394
1395       pschar = strtok_r (ds_c_str (&line), " \t\r\n", &save_ptr);
1396       code = strtok_r (NULL, " \t\r\n", &save_ptr);
1397       if (pschar == NULL || code == NULL)
1398         continue;
1399
1400       code_val = strtol (code, &tail, 0);
1401       if (*tail)
1402         {
1403           error_at_line (0, 0, font->encoding_fn, line_number,
1404                          _("invalid numeric format"));
1405           continue;
1406         }
1407       if (code_val < 0 || code_val > 255)
1408         continue;
1409       if (tab[code_val] != 0)
1410         free (tab[code_val]);
1411       tab[code_val] = xstrdup (pschar);
1412     }
1413   ds_destroy (&line);
1414
1415   fputs ("[", x->file);
1416   for (i = 0; i < 256; i++)
1417     {
1418       char *name = quote_ps_name (tab[i] != NULL ? tab[i] : ".notdef");
1419       fprintf (x->file, "%s\n", name);
1420       free (name);
1421       free (tab[i]);
1422     }
1423   fputs ("] RF\n", x->file);
1424
1425   if (freaderror (file) != 0)
1426     error (errno, 0, "closing Postscript encoding \"%s\"", font->encoding_fn);
1427 }
1428
1429 /* PostScript driver class. */
1430 struct outp_class postscript_class =
1431 {
1432   "postscript",
1433   0,
1434
1435   ps_open_driver,
1436   ps_close_driver,
1437
1438   ps_open_page,
1439   ps_close_page,
1440
1441   ps_submit,
1442
1443   ps_line,
1444   ps_text_metrics,
1445   ps_text_draw,
1446
1447   ps_chart_initialise,
1448   ps_chart_finalise
1449 };