output: Add initial support for PSPP output via Cairo.
[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
203   for (i = 0; i < OUTP_FONT_CNT; i++)
204     if (!load_font (this, &x->fonts[i]))
205       goto error;
206
207   if (this->length / this->font_height < 15)
208     {
209       error (0, 0, _("The defined page is not long "
210                      "enough to hold margins and headers, plus least 15 "
211                      "lines of the default fonts.  In fact, there's only "
212                      "room for %d lines."),
213              this->length / this->font_height);
214       goto error;
215     }
216
217   this->fixed_width = pango_font_metrics_get_approximate_char_width (
218     x->fonts[OUTP_FIXED].metrics);
219   this->prop_em_width = pango_font_metrics_get_approximate_char_width (
220       x->fonts[OUTP_PROPORTIONAL].metrics);
221
222   this->horiz_line_width[OUTP_L_NONE] = 0;
223   this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
224   this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
225                                            + 2 * x->line_width);
226   memcpy (this->vert_line_width, this->horiz_line_width,
227           sizeof this->vert_line_width);
228
229   return true;
230
231  error:
232   this->class->close_driver (this);
233   return false;
234 }
235
236 static bool
237 xr_close_driver (struct outp_driver *this)
238 {
239   struct xr_driver_ext *x = this->ext;
240   bool ok = true;
241   size_t i;
242
243   if (x->cairo != NULL)
244     {
245       cairo_status_t status;
246
247       cairo_surface_finish (cairo_get_target (x->cairo));
248       status = cairo_status (x->cairo);
249       if (status != CAIRO_STATUS_SUCCESS)
250         error (0, 0, _("writing output file \"%s\": %s"),
251                x->file_name, cairo_status_to_string (status));
252       cairo_destroy (x->cairo);
253     }
254
255   free (x->file_name);
256   for (i = 0; i < OUTP_FONT_CNT; i++)
257     free_font (&x->fonts[i]);
258   free (x);
259
260   return ok;
261 }
262
263 /* Generic option types. */
264 enum
265 {
266   output_file_arg,
267   output_type_arg,
268   paper_size_arg,
269   orientation_arg,
270   line_style_arg,
271   boolean_arg,
272   dimension_arg,
273   string_arg,
274   nonneg_int_arg
275 };
276
277 /* All the options that the Cairo driver supports. */
278 static const struct outp_option option_tab[] =
279 {
280   {"output-file",               output_file_arg,0},
281   {"output-type",               output_type_arg,0},
282   {"paper-size",                paper_size_arg, 0},
283   {"orientation",               orientation_arg,0},
284
285   {"headers",                   boolean_arg,    1},
286
287   {"prop-font",                 string_arg,     OUTP_PROPORTIONAL},
288   {"emph-font",                 string_arg,     OUTP_EMPHASIS},
289   {"fixed-font",                string_arg,     OUTP_FIXED},
290
291   {"left-margin",               dimension_arg,  0},
292   {"right-margin",              dimension_arg,  1},
293   {"top-margin",                dimension_arg,  2},
294   {"bottom-margin",             dimension_arg,  3},
295   {"font-size",                 dimension_arg,  4},
296   {"line-width",                dimension_arg,  5},
297   {"line-gutter",               dimension_arg,  6},
298   {"line-width",                dimension_arg,  7},
299   {NULL, 0, 0},
300 };
301
302 static bool
303 handle_option (struct outp_driver *this, const char *key,
304                const struct string *val)
305 {
306   struct xr_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 Cairo 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 output_type_arg:
322       if (!strcmp (value, "pdf"))
323         x->file_type = XR_PDF;
324       else if (!strcmp (value, "ps"))
325         x->file_type = XR_PS;
326       else if (!strcmp (value, "svg"))
327         x->file_type = XR_SVG;
328       else
329         {
330           error (0, 0, _("unknown Cairo output type \"%s\""), value);
331           return false;
332         }
333       break;
334     case paper_size_arg:
335       outp_get_paper_size (value, &x->paper_width, &x->paper_length);
336       break;
337     case orientation_arg:
338       if (!strcmp (value, "portrait"))
339         x->portrait = true;
340       else if (!strcmp (value, "landscape"))
341         x->portrait = false;
342       else
343         error (0, 0, _("unknown orientation `%s' (valid orientations are "
344                        "`portrait' and `landscape')"), value);
345       break;
346     case boolean_arg:
347       if (!strcmp (value, "on") || !strcmp (value, "true")
348           || !strcmp (value, "yes") || atoi (value))
349         x->draw_headers = true;
350       else if (!strcmp (value, "off") || !strcmp (value, "false")
351                || !strcmp (value, "no") || !strcmp (value, "0"))
352         x->draw_headers = false;
353       else
354         {
355           error (0, 0, _("boolean value expected for %s"), key);
356           return false;
357         }
358       break;
359     case dimension_arg:
360       {
361         int dimension = outp_evaluate_dimension (value);
362
363         if (dimension <= 0)
364           break;
365         switch (subcat)
366           {
367           case 0:
368             x->left_margin = dimension;
369             break;
370           case 1:
371             x->right_margin = dimension;
372             break;
373           case 2:
374             x->top_margin = dimension;
375             break;
376           case 3:
377             x->bottom_margin = dimension;
378             break;
379           case 4:
380             this->font_height = dimension;
381             break;
382           case 5:
383             x->line_width = dimension;
384             break;
385           case 6:
386             x->line_gutter = dimension;
387             break;
388           case 7:
389             x->line_width = dimension;
390             break;
391           default:
392             NOT_REACHED ();
393           }
394       }
395       break;
396     case string_arg:
397       free (x->fonts[subcat].string);
398       x->fonts[subcat].string = ds_xstrdup (val);
399       break;
400     default:
401       NOT_REACHED ();
402     }
403
404   return true;
405 }
406 \f
407 /* Basic file operations. */
408
409 static void
410 xr_open_page (struct outp_driver *this)
411 {
412   struct xr_driver_ext *x = this->ext;
413
414   x->page_number++;
415
416   if (x->draw_headers)
417     draw_headers (this);
418 }
419
420 static void
421 xr_close_page (struct outp_driver *this)
422 {
423   struct xr_driver_ext *x = this->ext;
424   cairo_show_page (x->cairo);
425 }
426
427 static void
428 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
429 {
430   switch (s->type)
431     {
432     case SOM_CHART:
433       break;
434     default:
435       NOT_REACHED ();
436     }
437 }
438 \f
439 /* Draws a line from (x0,y0) to (x1,y1). */
440 static void
441 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
442 {
443   struct xr_driver_ext *x = this->ext;
444   cairo_new_path (x->cairo);
445   cairo_move_to (x->cairo, x0, y0);
446   cairo_line_to (x->cairo, x1, y1);
447   cairo_stroke (x->cairo);
448 }
449
450 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
451    shortening it to X0...X1 if SHORTEN is true.
452    Draws a horizontal line X1...X3 at Y if RIGHT says so,
453    shortening it to X2...X3 if SHORTEN is true. */
454 static void
455 horz_line (struct outp_driver *this,
456            int x0, int x1, int x2, int x3, int y,
457            enum outp_line_style left, enum outp_line_style right,
458            bool shorten)
459 {
460   if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
461     dump_line (this, x0, y, x3, y);
462   else
463     {
464       if (left != OUTP_L_NONE)
465         dump_line (this, x0, y, shorten ? x1 : x2, y);
466       if (right != OUTP_L_NONE)
467         dump_line (this, shorten ? x2 : x1, y, x3, y);
468     }
469 }
470
471 /* Draws a vertical line Y0...Y2 at X if TOP says so,
472    shortening it to Y0...Y1 if SHORTEN is true.
473    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
474    shortening it to Y2...Y3 if SHORTEN is true. */
475 static void
476 vert_line (struct outp_driver *this,
477            int y0, int y1, int y2, int y3, int x,
478            enum outp_line_style top, enum outp_line_style bottom,
479            bool shorten)
480 {
481   if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
482     dump_line (this, x, y0, x, y3);
483   else
484     {
485       if (top != OUTP_L_NONE)
486         dump_line (this, x, y0, x, shorten ? y1 : y2);
487       if (bottom != OUTP_L_NONE)
488         dump_line (this, x, shorten ? y2 : y1, x, y3);
489     }
490 }
491
492 /* Draws a generalized intersection of lines in the rectangle
493    (X0,Y0)-(X3,Y3).  The line coming from the top to the center
494    is of style TOP, from left to center of style LEFT, from
495    bottom to center of style BOTTOM, and from right to center of
496    style RIGHT. */
497 static void
498 xr_line (struct outp_driver *this,
499          int x0, int y0, int x3, int y3,
500          enum outp_line_style top, enum outp_line_style left,
501          enum outp_line_style bottom, enum outp_line_style right)
502 {
503   /* The algorithm here is somewhat subtle, to allow it to handle
504      all the kinds of intersections that we need.
505
506      Three additional ordinates are assigned along the x axis.  The
507      first is xc, midway between x0 and x3.  The others are x1 and
508      x2; for a single vertical line these are equal to xc, and for
509      a double vertical line they are the ordinates of the left and
510      right half of the double line.
511
512      yc, y1, and y2 are assigned similarly along the y axis.
513
514      The following diagram shows the coordinate system and output
515      for double top and bottom lines, single left line, and no
516      right line:
517
518                  x0       x1 xc  x2      x3
519                y0 ________________________
520                   |        #     #       |
521                   |        #     #       |
522                   |        #     #       |
523                   |        #     #       |
524                   |        #     #       |
525      y1 = y2 = yc |#########     #       |
526                   |        #     #       |
527                   |        #     #       |
528                   |        #     #       |
529                   |        #     #       |
530                y3 |________#_____#_______|
531   */
532   struct xr_driver_ext *ext = this->ext;
533
534   /* Offset from center of each line in a pair of double lines. */
535   int double_line_ofs = (ext->line_space + ext->line_width) / 2;
536
537   /* Are the lines along each axis single or double?
538      (It doesn't make sense to have different kinds of line on the
539      same axis, so we don't try to gracefully handle that case.) */
540   bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
541   bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
542
543   /* When horizontal lines are doubled,
544      the left-side line along y1 normally runs from x0 to x2,
545      and the right-side line along y1 from x3 to x1.
546      If the top-side line is also doubled, we shorten the y1 lines,
547      so that the left-side line runs only to x1,
548      and the right-side line only to x2.
549      Otherwise, the horizontal line at y = y1 below would cut off
550      the intersection, which looks ugly:
551                x0       x1     x2      x3
552              y0 ________________________
553                 |        #     #       |
554                 |        #     #       |
555                 |        #     #       |
556                 |        #     #       |
557              y1 |#########     ########|
558                 |                      |
559                 |                      |
560              y2 |######################|
561                 |                      |
562                 |                      |
563              y3 |______________________|
564      It is more of a judgment call when the horizontal line is
565      single.  We actually choose to cut off the line anyhow, as
566      shown in the first diagram above.
567   */
568   bool shorten_y1_lines = top == OUTP_L_DOUBLE;
569   bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
570   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
571   int horz_line_ofs = double_vert ? double_line_ofs : 0;
572   int xc = (x0 + x3) / 2;
573   int x1 = xc - horz_line_ofs;
574   int x2 = xc + horz_line_ofs;
575
576   bool shorten_x1_lines = left == OUTP_L_DOUBLE;
577   bool shorten_x2_lines = right == OUTP_L_DOUBLE;
578   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
579   int vert_line_ofs = double_horz ? double_line_ofs : 0;
580   int yc = (y0 + y3) / 2;
581   int y1 = yc - vert_line_ofs;
582   int y2 = yc + vert_line_ofs;
583
584   if (!double_horz)
585     horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
586   else
587     {
588       horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
589       horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
590     }
591
592   if (!double_vert)
593     vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
594   else
595     {
596       vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
597       vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
598     }
599 }
600
601 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
602    and with the given JUSTIFICATION for THIS driver. */
603 static int
604 draw_text (struct outp_driver *this,
605            const char *string, int x, int y, int max_width,
606            enum outp_justification justification)
607 {
608   struct outp_text text;
609   int width;
610
611   text.font = OUTP_PROPORTIONAL;
612   text.justification = justification;
613   text.string = ss_cstr (string);
614   text.h = max_width;
615   text.v = this->font_height;
616   text.x = x;
617   text.y = y;
618   this->class->text_metrics (this, &text, &width, NULL);
619   this->class->text_draw (this, &text);
620   return width;
621 }
622
623 /* Writes LEFT left-justified and RIGHT right-justified within
624    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
625 static void
626 draw_header_line (struct outp_driver *this,
627                   const char *left, const char *right,
628                   int x0, int x1, int y)
629 {
630   int right_width = 0;
631   if (right != NULL)
632     right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
633                    + this->prop_em_width);
634   if (left != NULL)
635     draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
636 }
637
638 /* Draw top of page headers for THIS driver. */
639 static void
640 draw_headers (struct outp_driver *this)
641 {
642   struct xr_driver_ext *ext = this->ext;
643   char *r1, *r2;
644   int x0, x1;
645   int y;
646
647   y = -3 * this->font_height;
648   x0 = this->prop_em_width;
649   x1 = this->width - this->prop_em_width;
650
651   /* Draw box. */
652   /* XXX coordinates below might not be right, both in terms of
653      the Y transformation and width/height versus x2/y2. */
654   cairo_rectangle (ext->cairo, 0, y,
655                    this->width, 2 * this->font_height + ext->line_gutter);
656   cairo_save (ext->cairo);
657   cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
658   cairo_fill_preserve (ext->cairo);
659   cairo_restore (ext->cairo);
660   cairo_stroke (ext->cairo);
661
662   y += ext->line_width + ext->line_gutter;
663
664   r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
665   r2 = xasprintf ("%s - %s", version, host_system);
666
667   draw_header_line (this, outp_title, r1, x0, x1, y);
668   y += this->font_height;
669
670   draw_header_line (this, outp_subtitle, r2, x0, x1, y);
671
672   free (r1);
673   free (r2);
674 }
675 \f
676 /* Format TEXT on THIS driver.
677    If DRAW is nonzero, draw the text.
678    The width of the widest line is stored into *WIDTH, if WIDTH
679    is nonnull.
680    The total height of the text written is stored into *HEIGHT,
681    if HEIGHT is nonnull. */
682 static void
683 text (struct outp_driver *this, const struct outp_text *text, bool draw,
684       int *width, int *height)
685 {
686   struct xr_driver_ext *ext = this->ext;
687   struct xr_font *font = &ext->fonts[text->font];
688
689   pango_layout_set_text (font->layout,
690                          text->string.string, text->string.length);
691   pango_layout_set_alignment (
692     font->layout,
693     (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
694      : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
695      : PANGO_ALIGN_CENTER));
696   pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
697   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
698   /* XXX need to limit number of lines to those that fit in text->v. */
699
700   if (draw)
701     {
702       int x = text->x;
703       if (text->justification != OUTP_LEFT && text->h != INT_MAX)
704         {
705           int w, h, excess;
706           pango_layout_get_size (font->layout, &w, &h);
707           excess = text->h - w;
708           if (excess > 0)
709             {
710               if (text->justification == OUTP_CENTER)
711                 x += excess / 2;
712               else
713                 x += excess;
714             }
715         }
716       cairo_save (ext->cairo);
717       cairo_translate (ext->cairo, text->x, text->y);
718       cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
719       pango_cairo_show_layout (ext->cairo, font->layout);
720       cairo_restore (ext->cairo);
721       pango_cairo_update_layout (ext->cairo, font->layout);
722     }
723
724   if (width != NULL || height != NULL)
725     {
726       int w, h;
727       pango_layout_get_size (font->layout, &w, &h);
728       if (width != NULL)
729         *width = w;
730       if (height != NULL)
731         *height = h;
732     }
733 }
734
735 static void
736 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
737                  int *width, int *height)
738 {
739   text (this, t, false, width, height);
740 }
741
742 static void
743 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
744 {
745   assert (this->page_open);
746   text (this, t, true, NULL, NULL);
747 }
748 \f
749 static void
750 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
751 {
752 #ifdef NO_CHARTS
753   ch->lp = NULL;
754 #else
755   /* XXX libplot doesn't support Cairo yet. */
756 #endif
757 }
758
759 static void
760 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
761 {
762 #ifndef NO_CHARTS
763   /* XXX libplot doesn't support Cairo yet. */
764 #endif
765 }
766 \f
767 /* Attempts to load FONT, initializing its other members based on
768    its 'string' member and the information in THIS.  Returns true
769    if successful, otherwise false. */
770 static bool
771 load_font (struct outp_driver *this, struct xr_font *font)
772 {
773   struct xr_driver_ext *x = this->ext;
774   PangoContext *context;
775   PangoLanguage *language;
776
777   font->desc = pango_font_description_from_string (font->string);
778   if (font->desc == NULL)
779     {
780       error (0, 0, _("\"%s\": bad font specification"), font->string);
781       return false;
782     }
783   pango_font_description_set_size (font->desc, this->font_height * 5 / 6);
784
785   font->layout = pango_cairo_create_layout (x->cairo);
786   pango_layout_set_font_description (font->layout, font->desc);
787   pango_layout_set_spacing (font->layout, this->font_height / 6);
788
789   language = pango_language_get_default ();
790   context = pango_layout_get_context (font->layout);
791   font->metrics = pango_context_get_metrics (context, font->desc, language);
792
793   return true;
794 }
795
796 /* Frees FONT. */
797 static void
798 free_font (struct xr_font *font)
799 {
800   free (font->string);
801   if (font->desc != NULL)
802     pango_font_description_free (font->desc);
803   pango_font_metrics_unref (font->metrics);
804   g_object_unref (font->layout);
805 }
806
807 /* Cairo driver class. */
808 const struct outp_class cairo_class =
809 {
810   "cairo",
811   0,
812
813   xr_open_driver,
814   xr_close_driver,
815
816   xr_open_page,
817   xr_close_page,
818   NULL,
819
820   xr_submit,
821
822   xr_line,
823   xr_text_metrics,
824   xr_text_draw,
825
826   xr_chart_initialise,
827   xr_chart_finalise
828 };