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