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