output: Implement very rudimentary graphical GUI output for PSPPIRE.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 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 <output/cairo.h>
20
21 #include <libpspp/assertion.h>
22 #include <libpspp/start-date.h>
23 #include <libpspp/version.h>
24 #include <output/afm.h>
25 #include <output/chart.h>
26 #include <output/manager.h>
27 #include <output/output.h>
28
29 #include <cairo/cairo-pdf.h>
30 #include <cairo/cairo-ps.h>
31 #include <cairo/cairo-svg.h>
32 #include <cairo/cairo.h>
33 #include <pango/pango-font.h>
34 #include <pango/pango-layout.h>
35 #include <pango/pango.h>
36 #include <pango/pangocairo.h>
37 #include <stdlib.h>
38
39 #include "error.h"
40 #include "intprops.h"
41 #include "minmax.h"
42 #include "xalloc.h"
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 /* Cairo driver options: (defaults listed first)
48
49    output-file="pspp.pdf"
50    output-type=pdf|ps|png|svg
51    paper-size=letter (see "papersize" file)
52    orientation=portrait|landscape
53    headers=on|off
54
55    left-margin=0.5in
56    right-margin=0.5in
57    top-margin=0.5in
58    bottom-margin=0.5in
59
60    prop-font=Times-Roman
61    emph-font=Times-Italic
62    fixed-font=Courier
63    font-size=10000
64
65    line-gutter=1pt
66    line-spacing=1pt
67    line-width=0.5pt
68  */
69
70 /* Measurements as we present to the rest of PSPP. */
71 #define XR_POINT PANGO_SCALE
72 #define XR_INCH (XR_POINT * 72)
73
74 /* Conversions to and from points. */
75 static double
76 xr_to_pt (int x)
77 {
78   return x / (double) XR_POINT;
79 }
80
81 static int
82 pt_to_xr (double x)
83 {
84   return x * XR_POINT + 0.5;
85 }
86
87 /* Output types. */
88 enum xr_output_type
89   {
90     XR_PDF,
91     XR_PS,
92     XR_SVG
93   };
94
95 /* A font for use with Cairo. */
96 struct xr_font
97   {
98     char *string;
99     PangoFontDescription *desc;
100     PangoLayout *layout;
101     PangoFontMetrics *metrics;
102   };
103
104 /* Cairo output driver extension record. */
105 struct xr_driver_ext
106   {
107     cairo_t *cairo;
108     struct xr_font fonts[OUTP_FONT_CNT];
109
110     bool draw_headers;          /* Draw headers at top of page? */
111     int page_number;            /* Current page number. */
112
113     int line_gutter;            /* Space around lines. */
114     int line_space;             /* Space between lines. */
115     int line_width;             /* Width of lines. */
116   };
117
118 struct xr_driver_options
119   {
120     struct outp_driver *driver;
121
122     char *file_name;            /* Output file name. */
123     enum xr_output_type file_type; /* Type of output file. */
124
125
126     bool portrait;              /* Portrait mode? */
127
128     int paper_width;            /* Width of paper before dropping margins. */
129     int paper_length;           /* Length of paper before dropping margins. */
130     int left_margin;            /* Left margin in XR units. */
131     int right_margin;           /* Right margin in XR units. */
132     int top_margin;             /* Top margin in XR units. */
133     int bottom_margin;          /* Bottom margin in XR units. */
134   };
135
136 static bool handle_option (void *options, const char *key,
137                            const struct string *val);
138 static void draw_headers (struct outp_driver *this);
139
140 static bool load_font (struct outp_driver *this, struct xr_font *);
141 static void free_font (struct xr_font *);
142 static int text_width (struct outp_driver *, const char *, enum outp_font);
143 \f
144 /* Driver initialization. */
145
146 static struct outp_driver *
147 xr_allocate (const char *name, int types)
148 {
149   struct outp_driver *this;
150   struct xr_driver_ext *x;
151   size_t i;
152
153   this = outp_allocate_driver (&cairo_class, name, types);
154   this->width = this->length = 0;
155   this->font_height = XR_POINT * 10;
156   this->ext = x = xzalloc (sizeof *x);
157   x->cairo = NULL;
158   x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
159   x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
160   x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
161   for (i = 0; i < OUTP_FONT_CNT; i++)
162     {
163       struct xr_font *font = &x->fonts[i];
164       font->desc = NULL;
165       font->metrics = NULL;
166       font->layout = NULL;
167     }
168   x->draw_headers = true;
169   x->page_number = 0;
170   x->line_gutter = XR_POINT;
171   x->line_space = XR_POINT;
172   x->line_width = XR_POINT / 2;
173
174   return this;
175 }
176
177 static bool
178 xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
179 {
180   struct xr_driver_ext *x = this->ext;
181   int i;
182
183   x->cairo = cairo;
184
185   cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
186
187   for (i = 0; i < OUTP_FONT_CNT; i++)
188     if (!load_font (this, &x->fonts[i]))
189       return false;
190
191   this->fixed_width = text_width (this, "0", OUTP_FIXED);
192   this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
193
194   this->horiz_line_width[OUTP_L_NONE] = 0;
195   this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
196   this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
197                                            + 2 * x->line_width);
198   memcpy (this->vert_line_width, this->horiz_line_width,
199           sizeof this->vert_line_width);
200
201   return true;
202 }
203
204 struct outp_driver *
205 xr_create_driver (cairo_t *cairo)
206 {
207   struct outp_driver *this;
208
209   this = xr_allocate ("cairo", 0);
210   this->width = INT_MAX / 8;
211   this->length = INT_MAX / 8;
212   if (!xr_set_cairo (this, cairo))
213     {
214       this->class->close_driver (this);
215       outp_free_driver (this);
216       return NULL;
217     }
218   return this;
219 }
220
221 static bool
222 xr_open_driver (const char *name, int types, struct substring option_string)
223 {
224   struct outp_driver *this;
225   struct xr_driver_ext *x;
226   struct xr_driver_options options;
227   cairo_surface_t *surface;
228   cairo_status_t status;
229   double width_pt, length_pt;
230
231   this = xr_allocate (name, types);
232   x = this->ext;
233
234   options.driver = this;
235   options.file_name = xstrdup ("pspp.pdf");
236   options.file_type = XR_PDF;
237   options.portrait = true;
238   outp_get_paper_size ("", &options.paper_width, &options.paper_length);
239   options.left_margin = XR_INCH / 2;
240   options.right_margin = XR_INCH / 2;
241   options.top_margin = XR_INCH / 2;
242   options.bottom_margin = XR_INCH / 2;
243
244   outp_parse_options (this->name, option_string, handle_option, &options);
245
246   width_pt = options.paper_width / 1000.0;
247   length_pt = options.paper_length / 1000.0;
248   if (options.portrait)
249     {
250       this->width = pt_to_xr (width_pt);
251       this->length = pt_to_xr (length_pt);
252     }
253   else
254     {
255       this->width = pt_to_xr (width_pt);
256       this->length = pt_to_xr (length_pt);
257     }
258   if (x->draw_headers)
259     options.top_margin += 3 * this->font_height;
260   this->width -= options.left_margin + options.right_margin;
261   this->length -= options.top_margin + options.bottom_margin;
262
263   if (options.file_type == XR_PDF)
264     surface = cairo_pdf_surface_create (options.file_name,
265                                         width_pt, length_pt);
266   else if (options.file_type == XR_PS)
267     surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
268   else if (options.file_type == XR_SVG)
269     surface = cairo_svg_surface_create (options.file_name,
270                                         width_pt, length_pt);
271   else
272     NOT_REACHED ();
273
274   status = cairo_surface_status (surface);
275   if (status != CAIRO_STATUS_SUCCESS)
276     {
277       error (0, 0, _("opening output file \"%s\": %s"),
278              options.file_name, cairo_status_to_string (status));
279       cairo_surface_destroy (surface);
280       goto error;
281     }
282
283   x->cairo = cairo_create (surface);
284   cairo_surface_destroy (surface);
285
286   cairo_translate (x->cairo,
287                    xr_to_pt (options.left_margin),
288                    xr_to_pt (options.top_margin));
289
290   if (this->length / this->font_height < 15)
291     {
292       error (0, 0, _("The defined page is not long "
293                      "enough to hold margins and headers, plus least 15 "
294                      "lines of the default fonts.  In fact, there's only "
295                      "room for %d lines."),
296              this->length / this->font_height);
297       goto error;
298     }
299
300   if (!xr_set_cairo (this, x->cairo))
301     goto error;
302
303   outp_register_driver (this);
304   free (options.file_name);
305   return true;
306
307  error:
308   this->class->close_driver (this);
309   outp_free_driver (this);
310   free (options.file_name);
311   return false;
312 }
313
314 static bool
315 xr_close_driver (struct outp_driver *this)
316 {
317   struct xr_driver_ext *x = this->ext;
318   bool ok = true;
319   size_t i;
320
321   if (x->cairo != NULL)
322     {
323       cairo_status_t status;
324
325       cairo_surface_finish (cairo_get_target (x->cairo));
326       status = cairo_status (x->cairo);
327       if (status != CAIRO_STATUS_SUCCESS)
328         error (0, 0, _("error writing output file for %s driver: %s"),
329                this->name, cairo_status_to_string (status));
330       cairo_destroy (x->cairo);
331     }
332
333   for (i = 0; i < OUTP_FONT_CNT; i++)
334     free_font (&x->fonts[i]);
335   free (x);
336
337   return ok;
338 }
339
340 /* Generic option types. */
341 enum
342 {
343   output_file_arg,
344   output_type_arg,
345   paper_size_arg,
346   orientation_arg,
347   line_style_arg,
348   boolean_arg,
349   dimension_arg,
350   string_arg,
351   nonneg_int_arg
352 };
353
354 /* All the options that the Cairo driver supports. */
355 static const struct outp_option option_tab[] =
356 {
357   {"output-file",               output_file_arg,0},
358   {"output-type",               output_type_arg,0},
359   {"paper-size",                paper_size_arg, 0},
360   {"orientation",               orientation_arg,0},
361
362   {"headers",                   boolean_arg,    1},
363
364   {"prop-font",                 string_arg,     OUTP_PROPORTIONAL},
365   {"emph-font",                 string_arg,     OUTP_EMPHASIS},
366   {"fixed-font",                string_arg,     OUTP_FIXED},
367
368   {"left-margin",               dimension_arg,  0},
369   {"right-margin",              dimension_arg,  1},
370   {"top-margin",                dimension_arg,  2},
371   {"bottom-margin",             dimension_arg,  3},
372   {"font-size",                 dimension_arg,  4},
373   {"line-width",                dimension_arg,  5},
374   {"line-gutter",               dimension_arg,  6},
375   {"line-width",                dimension_arg,  7},
376   {NULL, 0, 0},
377 };
378
379 static bool
380 handle_option (void *options_, const char *key, const struct string *val)
381 {
382   struct xr_driver_options *options = options_;
383   struct outp_driver *this = options->driver;
384   struct xr_driver_ext *x = this->ext;
385   int subcat;
386   char *value = ds_cstr (val);
387
388   switch (outp_match_keyword (key, option_tab, &subcat))
389     {
390     case -1:
391       error (0, 0,
392              _("unknown configuration parameter `%s' for Cairo device "
393                "driver"), key);
394       break;
395     case output_file_arg:
396       free (options->file_name);
397       options->file_name = xstrdup (value);
398       break;
399     case output_type_arg:
400       if (!strcmp (value, "pdf"))
401         options->file_type = XR_PDF;
402       else if (!strcmp (value, "ps"))
403         options->file_type = XR_PS;
404       else if (!strcmp (value, "svg"))
405         options->file_type = XR_SVG;
406       else
407         {
408           error (0, 0, _("unknown Cairo output type \"%s\""), value);
409           return false;
410         }
411       break;
412     case paper_size_arg:
413       outp_get_paper_size (value,
414                            &options->paper_width, &options->paper_length);
415       break;
416     case orientation_arg:
417       if (!strcmp (value, "portrait"))
418         options->portrait = true;
419       else if (!strcmp (value, "landscape"))
420         options->portrait = false;
421       else
422         error (0, 0, _("unknown orientation `%s' (valid orientations are "
423                        "`portrait' and `landscape')"), value);
424       break;
425     case boolean_arg:
426       if (!strcmp (value, "on") || !strcmp (value, "true")
427           || !strcmp (value, "yes") || atoi (value))
428         x->draw_headers = true;
429       else if (!strcmp (value, "off") || !strcmp (value, "false")
430                || !strcmp (value, "no") || !strcmp (value, "0"))
431         x->draw_headers = false;
432       else
433         {
434           error (0, 0, _("boolean value expected for %s"), key);
435           return false;
436         }
437       break;
438     case dimension_arg:
439       {
440         int dimension = outp_evaluate_dimension (value);
441
442         if (dimension <= 0)
443           break;
444         switch (subcat)
445           {
446           case 0:
447             options->left_margin = dimension;
448             break;
449           case 1:
450             options->right_margin = dimension;
451             break;
452           case 2:
453             options->top_margin = dimension;
454             break;
455           case 3:
456             options->bottom_margin = dimension;
457             break;
458           case 4:
459             this->font_height = dimension;
460             break;
461           case 5:
462             x->line_width = dimension;
463             break;
464           case 6:
465             x->line_gutter = dimension;
466             break;
467           case 7:
468             x->line_width = dimension;
469             break;
470           default:
471             NOT_REACHED ();
472           }
473       }
474       break;
475     case string_arg:
476       free (x->fonts[subcat].string);
477       x->fonts[subcat].string = ds_xstrdup (val);
478       break;
479     default:
480       NOT_REACHED ();
481     }
482
483   return true;
484 }
485 \f
486 /* Basic file operations. */
487
488 static void
489 xr_open_page (struct outp_driver *this)
490 {
491   struct xr_driver_ext *x = this->ext;
492
493   x->page_number++;
494
495   if (x->draw_headers)
496     draw_headers (this);
497 }
498
499 static void
500 xr_close_page (struct outp_driver *this)
501 {
502   struct xr_driver_ext *x = this->ext;
503   cairo_show_page (x->cairo);
504 }
505
506 static void
507 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
508 {
509   switch (s->type)
510     {
511     case SOM_CHART:
512       break;
513     default:
514       NOT_REACHED ();
515     }
516 }
517 \f
518 /* Draws a line from (x0,y0) to (x1,y1). */
519 static void
520 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
521 {
522   struct xr_driver_ext *x = this->ext;
523   cairo_new_path (x->cairo);
524   cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
525   cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
526   cairo_stroke (x->cairo);
527 }
528
529 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
530    shortening it to X0...X1 if SHORTEN is true.
531    Draws a horizontal line X1...X3 at Y if RIGHT says so,
532    shortening it to X2...X3 if SHORTEN is true. */
533 static void
534 horz_line (struct outp_driver *this,
535            int x0, int x1, int x2, int x3, int y,
536            enum outp_line_style left, enum outp_line_style right,
537            bool shorten)
538 {
539   if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
540     dump_line (this, x0, y, x3, y);
541   else
542     {
543       if (left != OUTP_L_NONE)
544         dump_line (this, x0, y, shorten ? x1 : x2, y);
545       if (right != OUTP_L_NONE)
546         dump_line (this, shorten ? x2 : x1, y, x3, y);
547     }
548 }
549
550 /* Draws a vertical line Y0...Y2 at X if TOP says so,
551    shortening it to Y0...Y1 if SHORTEN is true.
552    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
553    shortening it to Y2...Y3 if SHORTEN is true. */
554 static void
555 vert_line (struct outp_driver *this,
556            int y0, int y1, int y2, int y3, int x,
557            enum outp_line_style top, enum outp_line_style bottom,
558            bool shorten)
559 {
560   if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
561     dump_line (this, x, y0, x, y3);
562   else
563     {
564       if (top != OUTP_L_NONE)
565         dump_line (this, x, y0, x, shorten ? y1 : y2);
566       if (bottom != OUTP_L_NONE)
567         dump_line (this, x, shorten ? y2 : y1, x, y3);
568     }
569 }
570
571 /* Draws a generalized intersection of lines in the rectangle
572    (X0,Y0)-(X3,Y3).  The line coming from the top to the center
573    is of style TOP, from left to center of style LEFT, from
574    bottom to center of style BOTTOM, and from right to center of
575    style RIGHT. */
576 static void
577 xr_line (struct outp_driver *this,
578          int x0, int y0, int x3, int y3,
579          enum outp_line_style top, enum outp_line_style left,
580          enum outp_line_style bottom, enum outp_line_style right)
581 {
582   /* The algorithm here is somewhat subtle, to allow it to handle
583      all the kinds of intersections that we need.
584
585      Three additional ordinates are assigned along the x axis.  The
586      first is xc, midway between x0 and x3.  The others are x1 and
587      x2; for a single vertical line these are equal to xc, and for
588      a double vertical line they are the ordinates of the left and
589      right half of the double line.
590
591      yc, y1, and y2 are assigned similarly along the y axis.
592
593      The following diagram shows the coordinate system and output
594      for double top and bottom lines, single left line, and no
595      right line:
596
597                  x0       x1 xc  x2      x3
598                y0 ________________________
599                   |        #     #       |
600                   |        #     #       |
601                   |        #     #       |
602                   |        #     #       |
603                   |        #     #       |
604      y1 = y2 = yc |#########     #       |
605                   |        #     #       |
606                   |        #     #       |
607                   |        #     #       |
608                   |        #     #       |
609                y3 |________#_____#_______|
610   */
611   struct xr_driver_ext *ext = this->ext;
612
613   /* Offset from center of each line in a pair of double lines. */
614   int double_line_ofs = (ext->line_space + ext->line_width) / 2;
615
616   /* Are the lines along each axis single or double?
617      (It doesn't make sense to have different kinds of line on the
618      same axis, so we don't try to gracefully handle that case.) */
619   bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
620   bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
621
622   /* When horizontal lines are doubled,
623      the left-side line along y1 normally runs from x0 to x2,
624      and the right-side line along y1 from x3 to x1.
625      If the top-side line is also doubled, we shorten the y1 lines,
626      so that the left-side line runs only to x1,
627      and the right-side line only to x2.
628      Otherwise, the horizontal line at y = y1 below would cut off
629      the intersection, which looks ugly:
630                x0       x1     x2      x3
631              y0 ________________________
632                 |        #     #       |
633                 |        #     #       |
634                 |        #     #       |
635                 |        #     #       |
636              y1 |#########     ########|
637                 |                      |
638                 |                      |
639              y2 |######################|
640                 |                      |
641                 |                      |
642              y3 |______________________|
643      It is more of a judgment call when the horizontal line is
644      single.  We actually choose to cut off the line anyhow, as
645      shown in the first diagram above.
646   */
647   bool shorten_y1_lines = top == OUTP_L_DOUBLE;
648   bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
649   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
650   int horz_line_ofs = double_vert ? double_line_ofs : 0;
651   int xc = (x0 + x3) / 2;
652   int x1 = xc - horz_line_ofs;
653   int x2 = xc + horz_line_ofs;
654
655   bool shorten_x1_lines = left == OUTP_L_DOUBLE;
656   bool shorten_x2_lines = right == OUTP_L_DOUBLE;
657   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
658   int vert_line_ofs = double_horz ? double_line_ofs : 0;
659   int yc = (y0 + y3) / 2;
660   int y1 = yc - vert_line_ofs;
661   int y2 = yc + vert_line_ofs;
662
663   if (!double_horz)
664     horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
665   else
666     {
667       horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
668       horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
669     }
670
671   if (!double_vert)
672     vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
673   else
674     {
675       vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
676       vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
677     }
678 }
679
680 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
681    and with the given JUSTIFICATION for THIS driver. */
682 static int
683 draw_text (struct outp_driver *this,
684            const char *string, int x, int y, int max_width,
685            enum outp_justification justification)
686 {
687   struct outp_text text;
688   int width;
689
690   text.font = OUTP_PROPORTIONAL;
691   text.justification = justification;
692   text.string = ss_cstr (string);
693   text.h = max_width;
694   text.v = this->font_height;
695   text.x = x;
696   text.y = y;
697   this->class->text_metrics (this, &text, &width, NULL);
698   this->class->text_draw (this, &text);
699   return width;
700 }
701
702 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
703    and with the given JUSTIFICATION for THIS driver. */
704 static int
705 text_width (struct outp_driver *this, const char *string, enum outp_font font)
706 {
707   struct outp_text text;
708   int width;
709
710   text.font = font;
711   text.justification = OUTP_LEFT;
712   text.string = ss_cstr (string);
713   text.h = INT_MAX;
714   text.v = this->font_height;
715   text.x = 0;
716   text.y = 0;
717   this->class->text_metrics (this, &text, &width, NULL);
718   return width;
719 }
720
721 /* Writes LEFT left-justified and RIGHT right-justified within
722    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
723 static void
724 draw_header_line (struct outp_driver *this,
725                   const char *left, const char *right,
726                   int x0, int x1, int y)
727 {
728   int right_width = 0;
729   if (right != NULL)
730     right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
731                    + this->prop_em_width);
732   if (left != NULL)
733     draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
734 }
735
736 /* Draw top of page headers for THIS driver. */
737 static void
738 draw_headers (struct outp_driver *this)
739 {
740   struct xr_driver_ext *ext = this->ext;
741   char *r1, *r2;
742   int x0, x1;
743   int y;
744
745   y = -3 * this->font_height;
746   x0 = this->prop_em_width;
747   x1 = this->width - this->prop_em_width;
748
749   /* Draw box. */
750   cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
751                    xr_to_pt (2 * (this->font_height
752                                   + ext->line_width + ext->line_gutter)));
753   cairo_save (ext->cairo);
754   cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
755   cairo_fill_preserve (ext->cairo);
756   cairo_restore (ext->cairo);
757   cairo_stroke (ext->cairo);
758
759   y += ext->line_width + ext->line_gutter;
760
761   r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
762   r2 = xasprintf ("%s - %s", version, host_system);
763
764   draw_header_line (this, outp_title, r1, x0, x1, y);
765   y += this->font_height;
766
767   draw_header_line (this, outp_subtitle, r2, x0, x1, y);
768
769   free (r1);
770   free (r2);
771 }
772 \f
773 /* Format TEXT on THIS driver.
774    If DRAW is nonzero, draw the text.
775    The width of the widest line is stored into *WIDTH, if WIDTH
776    is nonnull.
777    The total height of the text written is stored into *HEIGHT,
778    if HEIGHT is nonnull. */
779 static void
780 text (struct outp_driver *this, const struct outp_text *text, bool draw,
781       int *width, int *height)
782 {
783   struct xr_driver_ext *ext = this->ext;
784   struct xr_font *font = &ext->fonts[text->font];
785
786   pango_layout_set_text (font->layout,
787                          text->string.string, text->string.length);
788   pango_layout_set_alignment (
789     font->layout,
790     (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
791      : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
792      : PANGO_ALIGN_CENTER));
793   pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
794   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
795   /* XXX need to limit number of lines to those that fit in text->v. */
796
797   if (draw)
798     {
799       int x = text->x;
800       if (text->justification != OUTP_LEFT && text->h != INT_MAX)
801         {
802           int w, h, excess;
803           pango_layout_get_size (font->layout, &w, &h);
804           excess = text->h - w;
805           if (excess > 0)
806             {
807               if (text->justification == OUTP_CENTER)
808                 x += excess / 2;
809               else
810                 x += excess;
811             }
812         }
813       cairo_save (ext->cairo);
814       cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
815       pango_cairo_show_layout (ext->cairo, font->layout);
816       cairo_restore (ext->cairo);
817     }
818
819   if (width != NULL || height != NULL)
820     {
821       int w, h;
822       pango_layout_get_size (font->layout, &w, &h);
823       if (width != NULL)
824         *width = w;
825       if (height != NULL)
826         *height = h;
827     }
828 }
829
830 static void
831 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
832                  int *width, int *height)
833 {
834   text (this, t, false, width, height);
835 }
836
837 static void
838 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
839 {
840   text (this, t, true, NULL, NULL);
841 }
842 \f
843 static void
844 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
845 {
846 #ifdef NO_CHARTS
847   ch->lp = NULL;
848 #else
849   /* XXX libplot doesn't support Cairo yet. */
850 #endif
851 }
852
853 static void
854 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
855 {
856 #ifndef NO_CHARTS
857   /* XXX libplot doesn't support Cairo yet. */
858 #endif
859 }
860 \f
861 /* Attempts to load FONT, initializing its other members based on
862    its 'string' member and the information in THIS.  Returns true
863    if successful, otherwise false. */
864 static bool
865 load_font (struct outp_driver *this, struct xr_font *font)
866 {
867   struct xr_driver_ext *x = this->ext;
868   PangoContext *context;
869   PangoLanguage *language;
870
871   font->desc = pango_font_description_from_string (font->string);
872   if (font->desc == NULL)
873     {
874       error (0, 0, _("\"%s\": bad font specification"), font->string);
875       return false;
876     }
877   pango_font_description_set_absolute_size (font->desc, this->font_height);
878
879   font->layout = pango_cairo_create_layout (x->cairo);
880   pango_layout_set_font_description (font->layout, font->desc);
881
882   language = pango_language_get_default ();
883   context = pango_layout_get_context (font->layout);
884   font->metrics = pango_context_get_metrics (context, font->desc, language);
885
886   return true;
887 }
888
889 /* Frees FONT. */
890 static void
891 free_font (struct xr_font *font)
892 {
893   free (font->string);
894   if (font->desc != NULL)
895     pango_font_description_free (font->desc);
896   pango_font_metrics_unref (font->metrics);
897   g_object_unref (font->layout);
898 }
899
900 /* Cairo driver class. */
901 const struct outp_class cairo_class =
902 {
903   "cairo",
904   0,
905
906   xr_open_driver,
907   xr_close_driver,
908
909   xr_open_page,
910   xr_close_page,
911   NULL,
912
913   xr_submit,
914
915   xr_line,
916   xr_text_metrics,
917   xr_text_draw,
918
919   xr_chart_initialise,
920   xr_chart_finalise
921 };