cairo: Fix values of prop_em_width, fixed_width.
[pspp-builds.git] / 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 static int text_width (struct outp_driver *, const char *, enum outp_font);
121 \f
122 /* Driver initialization. */
123
124 static bool
125 xr_open_driver (struct outp_driver *this, struct substring options)
126 {
127   cairo_surface_t *surface;
128   cairo_status_t status;
129   struct xr_driver_ext *x;
130   double width_pt, length_pt;
131   size_t i;
132
133   this->width = this->length = 0;
134   this->font_height = XR_POINT * 10;
135
136   this->ext = x = xmalloc (sizeof *x);
137   x->file_name = xstrdup ("pspp.pdf");
138   x->file_type = XR_PDF;
139   x->draw_headers = true;
140   x->page_number = 0;
141   x->portrait = true;
142   outp_get_paper_size ("", &x->paper_width, &x->paper_length);
143   x->left_margin = XR_INCH / 2;
144   x->right_margin = XR_INCH / 2;
145   x->top_margin = XR_INCH / 2;
146   x->bottom_margin = XR_INCH / 2;
147   x->line_gutter = XR_POINT;
148   x->line_space = XR_POINT;
149   x->line_width = XR_POINT / 2;
150   x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
151   x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
152   x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
153   for (i = 0; i < OUTP_FONT_CNT; i++)
154     {
155       struct xr_font *font = &x->fonts[i];
156       font->desc = NULL;
157       font->metrics = NULL;
158       font->layout = NULL;
159     }
160
161   outp_parse_options (options, handle_option, this);
162
163   if (x->portrait)
164     {
165       this->width = x->paper_width;
166       this->length = x->paper_length;
167     }
168   else
169     {
170       this->width = x->paper_length;
171       this->length = x->paper_width;
172     }
173   if (x->draw_headers)
174     x->top_margin += 3 * this->font_height;
175   this->width -= x->left_margin + x->right_margin;
176   this->length -= x->top_margin + x->bottom_margin;
177
178   width_pt = x->paper_width / (double) XR_POINT;
179   length_pt = x->paper_length / (double) XR_POINT;
180   if (x->file_type == XR_PDF)
181     surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
182   else if (x->file_type == XR_PS)
183     surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
184   else if (x->file_type == XR_SVG)
185     surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
186   else
187     NOT_REACHED ();
188
189   status = cairo_surface_status (surface);
190   if (status != CAIRO_STATUS_SUCCESS)
191     {
192       error (0, 0, _("opening output file \"%s\": %s"),
193              x->file_name, cairo_status_to_string (status));
194       cairo_surface_destroy (surface);
195       goto error;
196     }
197
198   x->cairo = cairo_create (surface);
199   cairo_surface_destroy (surface);
200
201   cairo_scale (x->cairo, 1.0 / PANGO_SCALE, 1.0 / PANGO_SCALE);
202   cairo_translate (x->cairo, x->left_margin, x->top_margin);
203   cairo_set_line_width (x->cairo, x->line_width);
204
205   for (i = 0; i < OUTP_FONT_CNT; i++)
206     if (!load_font (this, &x->fonts[i]))
207       goto error;
208
209   if (this->length / this->font_height < 15)
210     {
211       error (0, 0, _("The defined page is not long "
212                      "enough to hold margins and headers, plus least 15 "
213                      "lines of the default fonts.  In fact, there's only "
214                      "room for %d lines."),
215              this->length / this->font_height);
216       goto error;
217     }
218
219   this->fixed_width = text_width (this, "0", OUTP_FIXED);
220   this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
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 STRING at location (X,Y) trimmed to the given MAX_WIDTH
624    and with the given JUSTIFICATION for THIS driver. */
625 static int
626 text_width (struct outp_driver *this, const char *string, enum outp_font font)
627 {
628   struct outp_text text;
629   int width;
630
631   text.font = font;
632   text.justification = OUTP_LEFT;
633   text.string = ss_cstr (string);
634   text.h = INT_MAX;
635   text.v = this->font_height;
636   text.x = 0;
637   text.y = 0;
638   this->class->text_metrics (this, &text, &width, NULL);
639   return width;
640 }
641
642 /* Writes LEFT left-justified and RIGHT right-justified within
643    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
644 static void
645 draw_header_line (struct outp_driver *this,
646                   const char *left, const char *right,
647                   int x0, int x1, int y)
648 {
649   int right_width = 0;
650   if (right != NULL)
651     right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
652                    + this->prop_em_width);
653   if (left != NULL)
654     draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
655 }
656
657 /* Draw top of page headers for THIS driver. */
658 static void
659 draw_headers (struct outp_driver *this)
660 {
661   struct xr_driver_ext *ext = this->ext;
662   char *r1, *r2;
663   int x0, x1;
664   int y;
665
666   y = -3 * this->font_height;
667   x0 = this->prop_em_width;
668   x1 = this->width - this->prop_em_width;
669
670   /* Draw box. */
671   cairo_rectangle (ext->cairo, 0, y, this->width,
672                    2 * (this->font_height
673                         + ext->line_width + ext->line_gutter));
674   cairo_save (ext->cairo);
675   cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
676   cairo_fill_preserve (ext->cairo);
677   cairo_restore (ext->cairo);
678   cairo_stroke (ext->cairo);
679
680   y += ext->line_width + ext->line_gutter;
681
682   r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
683   r2 = xasprintf ("%s - %s", version, host_system);
684
685   draw_header_line (this, outp_title, r1, x0, x1, y);
686   y += this->font_height;
687
688   draw_header_line (this, outp_subtitle, r2, x0, x1, y);
689
690   free (r1);
691   free (r2);
692 }
693 \f
694 /* Format TEXT on THIS driver.
695    If DRAW is nonzero, draw the text.
696    The width of the widest line is stored into *WIDTH, if WIDTH
697    is nonnull.
698    The total height of the text written is stored into *HEIGHT,
699    if HEIGHT is nonnull. */
700 static void
701 text (struct outp_driver *this, const struct outp_text *text, bool draw,
702       int *width, int *height)
703 {
704   struct xr_driver_ext *ext = this->ext;
705   struct xr_font *font = &ext->fonts[text->font];
706
707   pango_layout_set_text (font->layout,
708                          text->string.string, text->string.length);
709   pango_layout_set_alignment (
710     font->layout,
711     (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
712      : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
713      : PANGO_ALIGN_CENTER));
714   pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
715   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
716   /* XXX need to limit number of lines to those that fit in text->v. */
717
718   if (draw)
719     {
720       int x = text->x;
721       if (text->justification != OUTP_LEFT && text->h != INT_MAX)
722         {
723           int w, h, excess;
724           pango_layout_get_size (font->layout, &w, &h);
725           excess = text->h - w;
726           if (excess > 0)
727             {
728               if (text->justification == OUTP_CENTER)
729                 x += excess / 2;
730               else
731                 x += excess;
732             }
733         }
734       cairo_save (ext->cairo);
735       cairo_translate (ext->cairo, text->x, text->y);
736       cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
737       pango_cairo_show_layout (ext->cairo, font->layout);
738       cairo_restore (ext->cairo);
739       pango_cairo_update_layout (ext->cairo, font->layout);
740     }
741
742   if (width != NULL || height != NULL)
743     {
744       int w, h;
745       pango_layout_get_size (font->layout, &w, &h);
746       if (width != NULL)
747         *width = w;
748       if (height != NULL)
749         *height = h;
750     }
751 }
752
753 static void
754 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
755                  int *width, int *height)
756 {
757   text (this, t, false, width, height);
758 }
759
760 static void
761 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
762 {
763   assert (this->page_open);
764   text (this, t, true, NULL, NULL);
765 }
766 \f
767 static void
768 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
769 {
770 #ifdef NO_CHARTS
771   ch->lp = NULL;
772 #else
773   /* XXX libplot doesn't support Cairo yet. */
774 #endif
775 }
776
777 static void
778 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
779 {
780 #ifndef NO_CHARTS
781   /* XXX libplot doesn't support Cairo yet. */
782 #endif
783 }
784 \f
785 /* Attempts to load FONT, initializing its other members based on
786    its 'string' member and the information in THIS.  Returns true
787    if successful, otherwise false. */
788 static bool
789 load_font (struct outp_driver *this, struct xr_font *font)
790 {
791   struct xr_driver_ext *x = this->ext;
792   PangoContext *context;
793   PangoLanguage *language;
794
795   font->desc = pango_font_description_from_string (font->string);
796   if (font->desc == NULL)
797     {
798       error (0, 0, _("\"%s\": bad font specification"), font->string);
799       return false;
800     }
801   pango_font_description_set_absolute_size (font->desc, this->font_height);
802
803   font->layout = pango_cairo_create_layout (x->cairo);
804       pango_cairo_update_layout (x->cairo, font->layout);
805   pango_layout_set_font_description (font->layout, font->desc);
806
807   language = pango_language_get_default ();
808   context = pango_layout_get_context (font->layout);
809   font->metrics = pango_context_get_metrics (context, font->desc, language);
810
811   return true;
812 }
813
814 /* Frees FONT. */
815 static void
816 free_font (struct xr_font *font)
817 {
818   free (font->string);
819   if (font->desc != NULL)
820     pango_font_description_free (font->desc);
821   pango_font_metrics_unref (font->metrics);
822   g_object_unref (font->layout);
823 }
824
825 /* Cairo driver class. */
826 const struct outp_class cairo_class =
827 {
828   "cairo",
829   0,
830
831   xr_open_driver,
832   xr_close_driver,
833
834   xr_open_page,
835   xr_close_page,
836   NULL,
837
838   xr_submit,
839
840   xr_line,
841   xr_text_metrics,
842   xr_text_draw,
843
844   xr_chart_initialise,
845   xr_chart_finalise
846 };