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