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