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