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