output: Add support for alternating row colors.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 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/cast.h"
23 #include "libpspp/message.h"
24 #include "libpspp/pool.h"
25 #include "libpspp/start-date.h"
26 #include "libpspp/str.h"
27 #include "libpspp/string-map.h"
28 #include "libpspp/version.h"
29 #include "data/file-handle-def.h"
30 #include "output/cairo-chart.h"
31 #include "output/chart-item-provider.h"
32 #include "output/charts/boxplot.h"
33 #include "output/charts/np-plot.h"
34 #include "output/charts/piechart.h"
35 #include "output/charts/barchart.h"
36 #include "output/charts/plot-hist.h"
37 #include "output/charts/roc-chart.h"
38 #include "output/charts/spreadlevel-plot.h"
39 #include "output/charts/scree.h"
40 #include "output/charts/scatterplot.h"
41 #include "output/driver-provider.h"
42 #include "output/message-item.h"
43 #include "output/options.h"
44 #include "output/render.h"
45 #include "output/tab.h"
46 #include "output/table-item.h"
47 #include "output/table.h"
48 #include "output/text-item.h"
49
50 #include <cairo/cairo-pdf.h>
51 #include <cairo/cairo-ps.h>
52 #include <cairo/cairo-svg.h>
53 #include <cairo/cairo.h>
54 #include <math.h>
55 #include <pango/pango-font.h>
56 #include <pango/pango-layout.h>
57 #include <pango/pango.h>
58 #include <pango/pangocairo.h>
59 #include <stdlib.h>
60
61 #include "gl/c-strcase.h"
62 #include "gl/intprops.h"
63 #include "gl/minmax.h"
64 #include "gl/xalloc.h"
65
66 #include "gettext.h"
67 #define _(msgid) gettext (msgid)
68
69 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
70 #define H TABLE_HORZ
71 #define V TABLE_VERT
72
73 /* The unit used for internal measurements is inch/(72 * XR_POINT). */
74 #define XR_POINT PANGO_SCALE
75
76 /* Conversions to and from points. */
77 static double
78 xr_to_pt (int x)
79 {
80   return x / (double) XR_POINT;
81 }
82
83 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
84 static int
85 px_to_xr (int x)
86 {
87   return x * (PANGO_SCALE * 72 / 96);
88 }
89
90 /* Output types. */
91 enum xr_output_type
92   {
93     XR_PDF,
94     XR_PS,
95     XR_SVG
96   };
97
98 /* Cairo fonts. */
99 enum xr_font_type
100   {
101     XR_FONT_PROPORTIONAL,
102     XR_FONT_EMPHASIS,
103     XR_FONT_FIXED,
104     XR_N_FONTS
105   };
106
107 /* A font for use with Cairo. */
108 struct xr_font
109   {
110     PangoFontDescription *desc;
111     PangoLayout *layout;
112   };
113
114 /* An output item whose rendering is in progress. */
115 struct xr_render_fsm
116   {
117     /* Renders as much of itself as it can on the current page.  Returns true
118        if rendering is complete, false if the output item needs another
119        page. */
120     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
121
122     /* Destroys the output item. */
123     void (*destroy) (struct xr_render_fsm *);
124   };
125
126 /* Cairo output driver. */
127 struct xr_driver
128   {
129     struct output_driver driver;
130
131     /* User parameters. */
132     struct xr_font fonts[XR_N_FONTS];
133
134     int width;                  /* Page width minus margins. */
135     int length;                 /* Page length minus margins and header. */
136
137     int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
138     int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
139     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
140     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
141
142     int line_space;             /* Space between lines. */
143     int line_width;             /* Width of lines. */
144
145     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
146
147     struct xr_color bg;    /* Background color */
148     struct xr_color fg;    /* Foreground color */
149
150     /* Internal state. */
151     struct render_params *params;
152     int char_width, char_height;
153     char *command_name;
154     char *title;
155     char *subtitle;
156     cairo_t *cairo;
157     int page_number;            /* Current page number. */
158     int x, y;
159     struct xr_render_fsm *fsm;
160     int nest;
161   };
162
163 static const struct output_driver_class cairo_driver_class;
164
165 static void xr_driver_destroy_fsm (struct xr_driver *);
166 static void xr_driver_run_fsm (struct xr_driver *);
167
168 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
169                           enum render_line_style styles[TABLE_N_AXES][2],
170                           struct cell_color colors[TABLE_N_AXES][2]);
171 static void xr_measure_cell_width (void *, const struct table_cell *,
172                                    int *min, int *max);
173 static int xr_measure_cell_height (void *, const struct table_cell *,
174                                    int width);
175 static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
176                           int bb[TABLE_N_AXES][2],
177                           int spill[TABLE_N_AXES][2],
178                           int clip[TABLE_N_AXES][2]);
179 static int xr_adjust_break (void *, const struct table_cell *,
180                             int width, int height);
181
182 static struct xr_render_fsm *xr_render_output_item (
183   struct xr_driver *, const struct output_item *);
184 \f
185 /* Output driver basics. */
186
187 static struct xr_driver *
188 xr_driver_cast (struct output_driver *driver)
189 {
190   assert (driver->class == &cairo_driver_class);
191   return UP_CAST (driver, struct xr_driver, driver);
192 }
193
194 static struct driver_option *
195 opt (struct output_driver *d, struct string_map *options, const char *key,
196      const char *default_value)
197 {
198   return driver_option_get (d, options, key, default_value);
199 }
200
201 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
202    Currently, the input string must be of the form "#RRRRGGGGBBBB"
203    Future implementations might allow things like "yellow" and
204    "sky-blue-ultra-brown"
205 */
206 void
207 parse_color (struct output_driver *d, struct string_map *options,
208              const char *key, const char *default_value,
209              struct xr_color *color)
210 {
211   int red, green, blue;
212   char *string = parse_string (opt (d, options, key, default_value));
213
214   if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
215     {
216       /* If the parsed option string fails, then try the default value */
217       if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
218         {
219           /* ... and if that fails set everything to zero */
220           red = green = blue = 0;
221         }
222     }
223
224   free (string);
225
226   /* Convert 16 bit ints to float */
227   color->red = red / (double) 0xFFFF;
228   color->green = green / (double) 0xFFFF;
229   color->blue = blue / (double) 0xFFFF;
230 }
231
232 static PangoFontDescription *
233 parse_font (const char *font, int default_size, bool bold, bool italic)
234 {
235   if (!c_strcasecmp (font, "Monospaced"))
236     font = "Monospace";
237
238   PangoFontDescription *desc = pango_font_description_from_string (font);
239   if (desc == NULL)
240     return NULL;
241
242   /* If the font description didn't include an explicit font size, then set it
243      to DEFAULT_SIZE, which is in inch/72000 units. */
244   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
245     pango_font_description_set_size (desc,
246                                      (default_size / 1000.0) * PANGO_SCALE);
247
248   pango_font_description_set_weight (desc, (bold
249                                             ? PANGO_WEIGHT_BOLD
250                                             : PANGO_WEIGHT_NORMAL));
251   pango_font_description_set_style (desc, (italic
252                                            ? PANGO_STYLE_ITALIC
253                                            : PANGO_STYLE_NORMAL));
254
255   return desc;
256 }
257
258 static PangoFontDescription *
259 parse_font_option (struct output_driver *d, struct string_map *options,
260                    const char *key, const char *default_value,
261                    int default_size, bool bold, bool italic)
262 {
263   char *string = parse_string (opt (d, options, key, default_value));
264   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
265   if (!desc)
266     {
267       msg (MW, _("`%s': bad font specification"), string);
268
269       /* Fall back to DEFAULT_VALUE, which had better be a valid font
270          description. */
271       desc = parse_font (default_value, default_size, bold, italic);
272       assert (desc != NULL);
273     }
274   free (string);
275
276   return desc;
277 }
278
279 static void
280 apply_options (struct xr_driver *xr, struct string_map *o)
281 {
282   struct output_driver *d = &xr->driver;
283
284   /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
285   int left_margin, right_margin;
286   int top_margin, bottom_margin;
287   int paper_width, paper_length;
288   int font_size;
289   int min_break[TABLE_N_AXES];
290
291   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
292   const double scale = XR_POINT / 1000.;
293
294   int i;
295
296   for (i = 0; i < XR_N_FONTS; i++)
297     {
298       struct xr_font *font = &xr->fonts[i];
299
300       if (font->desc != NULL)
301         pango_font_description_free (font->desc);
302     }
303
304   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
305   xr->fonts[XR_FONT_FIXED].desc = parse_font_option
306     (d, o, "fixed-font", "monospace", font_size, false, false);
307   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font_option (
308     d, o, "prop-font", "sans serif", font_size, false, false);
309   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option (
310     d, o, "emph-font", "sans serif", font_size, false, true);
311
312   xr->line_space = XR_POINT;
313   xr->line_width = XR_POINT / 2;
314   xr->page_number = 0;
315
316   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
317   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
318
319   /* Get dimensions.  */
320   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
321   left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
322   right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
323   top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
324   bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
325
326   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
327   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
328
329   /* Convert to inch/(XR_POINT * 72). */
330   xr->left_margin = left_margin * scale;
331   xr->right_margin = right_margin * scale;
332   xr->top_margin = top_margin * scale;
333   xr->bottom_margin = bottom_margin * scale;
334   xr->width = (paper_width - left_margin - right_margin) * scale;
335   xr->length = (paper_length - top_margin - bottom_margin) * scale;
336   xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
337   xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
338 }
339
340 static struct xr_driver *
341 xr_allocate (const char *name, int device_type, struct string_map *o)
342 {
343   struct xr_driver *xr = xzalloc (sizeof *xr);
344   struct output_driver *d = &xr->driver;
345
346   output_driver_init (d, &cairo_driver_class, name, device_type);
347
348   apply_options (xr, o);
349
350   return xr;
351 }
352
353 static int
354 pango_to_xr (int pango)
355 {
356   return (XR_POINT != PANGO_SCALE
357           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
358           : pango);
359 }
360
361 static int
362 xr_to_pango (int xr)
363 {
364   return (XR_POINT != PANGO_SCALE
365           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
366           : xr);
367 }
368
369 static bool
370 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
371 {
372   int i;
373
374   xr->cairo = cairo;
375
376   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
377
378   xr->char_width = 0;
379   xr->char_height = 0;
380   for (i = 0; i < XR_N_FONTS; i++)
381     {
382       struct xr_font *font = &xr->fonts[i];
383       int char_width, char_height;
384
385       font->layout = pango_cairo_create_layout (cairo);
386       pango_layout_set_font_description (font->layout, font->desc);
387
388       pango_layout_set_text (font->layout, "0", 1);
389       pango_layout_get_size (font->layout, &char_width, &char_height);
390       xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
391       xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
392     }
393
394   if (xr->params == NULL)
395     {
396       xr->params = xmalloc (sizeof *xr->params);
397       xr->params->draw_line = xr_draw_line;
398       xr->params->measure_cell_width = xr_measure_cell_width;
399       xr->params->measure_cell_height = xr_measure_cell_height;
400       xr->params->adjust_break = xr_adjust_break;
401       xr->params->draw_cell = xr_draw_cell;
402       xr->params->aux = xr;
403       xr->params->size[H] = xr->width;
404       xr->params->size[V] = xr->length;
405       xr->params->font_size[H] = xr->char_width;
406       xr->params->font_size[V] = xr->char_height;
407
408       int lw = xr->line_width;
409       int ls = xr->line_space;
410       for (i = 0; i < TABLE_N_AXES; i++)
411         {
412           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
413           xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw;
414           xr->params->line_widths[i][RENDER_LINE_DASHED] = lw;
415           xr->params->line_widths[i][RENDER_LINE_THICK] = lw * 2;
416           xr->params->line_widths[i][RENDER_LINE_THIN] = lw / 2;
417           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls;
418         }
419
420       for (i = 0; i < TABLE_N_AXES; i++)
421         xr->params->min_break[i] = xr->min_break[i];
422       xr->params->supports_margins = true;
423     }
424
425   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
426
427   return true;
428 }
429
430 static struct output_driver *
431 xr_create (const char *file_name, enum settings_output_devices device_type,
432            struct string_map *o, enum xr_output_type file_type)
433 {
434   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
435   struct xr_driver *xr;
436   cairo_surface_t *surface;
437   cairo_status_t status;
438   double width_pt, length_pt;
439
440   xr = xr_allocate (file_name, device_type, o);
441
442   width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
443   length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
444   if (file_type == XR_PDF)
445     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
446   else if (file_type == XR_PS)
447     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
448   else if (file_type == XR_SVG)
449     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
450   else
451     NOT_REACHED ();
452
453   status = cairo_surface_status (surface);
454   if (status != CAIRO_STATUS_SUCCESS)
455     {
456       msg (ME, _("error opening output file `%s': %s"),
457              file_name, cairo_status_to_string (status));
458       cairo_surface_destroy (surface);
459       goto error;
460     }
461
462   xr->cairo = cairo_create (surface);
463   cairo_surface_destroy (surface);
464
465   if (!xr_set_cairo (xr, xr->cairo))
466     goto error;
467
468   cairo_save (xr->cairo);
469   xr_driver_next_page (xr, xr->cairo);
470
471   if (xr->width / xr->char_width < MIN_WIDTH)
472     {
473       msg (ME, _("The defined page is not wide enough to hold at least %d "
474                      "characters in the default font.  In fact, there's only "
475                      "room for %d characters."),
476              MIN_WIDTH,
477              xr->width / xr->char_width);
478       goto error;
479     }
480
481   if (xr->length / xr->char_height < MIN_LENGTH)
482     {
483       msg (ME, _("The defined page is not long enough to hold at least %d "
484                      "lines in the default font.  In fact, there's only "
485                      "room for %d lines."),
486              MIN_LENGTH,
487              xr->length / xr->char_height);
488       goto error;
489     }
490
491   return &xr->driver;
492
493  error:
494   output_driver_destroy (&xr->driver);
495   return NULL;
496 }
497
498 static struct output_driver *
499 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
500                struct string_map *o)
501 {
502   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
503   fh_unref (fh);
504   return od ;
505 }
506
507 static struct output_driver *
508 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
509                struct string_map *o)
510 {
511   struct output_driver *od =  xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
512   fh_unref (fh);
513   return od ;
514 }
515
516 static struct output_driver *
517 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
518                struct string_map *o)
519 {
520   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
521   fh_unref (fh);
522   return od ;
523 }
524
525 static void
526 xr_destroy (struct output_driver *driver)
527 {
528   struct xr_driver *xr = xr_driver_cast (driver);
529   size_t i;
530
531   xr_driver_destroy_fsm (xr);
532
533   if (xr->cairo != NULL)
534     {
535       cairo_status_t status;
536
537       cairo_surface_finish (cairo_get_target (xr->cairo));
538       status = cairo_status (xr->cairo);
539       if (status != CAIRO_STATUS_SUCCESS)
540         msg (ME, _("error drawing output for %s driver: %s"),
541                output_driver_get_name (driver),
542                cairo_status_to_string (status));
543       cairo_destroy (xr->cairo);
544     }
545
546   for (i = 0; i < XR_N_FONTS; i++)
547     {
548       struct xr_font *font = &xr->fonts[i];
549
550       if (font->desc != NULL)
551         pango_font_description_free (font->desc);
552       if (font->layout != NULL)
553         g_object_unref (font->layout);
554     }
555
556   free (xr->params);
557   free (xr);
558 }
559
560 static void
561 xr_flush (struct output_driver *driver)
562 {
563   struct xr_driver *xr = xr_driver_cast (driver);
564
565   cairo_surface_flush (cairo_get_target (xr->cairo));
566 }
567
568 static void
569 xr_submit (struct output_driver *driver, const struct output_item *output_item)
570 {
571   struct xr_driver *xr = xr_driver_cast (driver);
572
573   xr_driver_output_item (xr, output_item);
574   while (xr_driver_need_new_page (xr))
575     {
576       cairo_restore (xr->cairo);
577       cairo_show_page (xr->cairo);
578       cairo_save (xr->cairo);
579       xr_driver_next_page (xr, xr->cairo);
580     }
581 }
582 \f
583 /* Functions for rendering a series of output items to a series of Cairo
584    contexts, with pagination.
585
586    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
587    its underlying implementation.
588
589    See the big comment in cairo.h for intended usage. */
590
591 /* Gives new page CAIRO to XR for output. */
592 void
593 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
594 {
595   cairo_save (cairo);
596   cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
597   cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
598   cairo_fill (cairo);
599   cairo_restore (cairo);
600
601   cairo_translate (cairo,
602                    xr_to_pt (xr->left_margin),
603                    xr_to_pt (xr->top_margin));
604
605   xr->page_number++;
606   xr->cairo = cairo;
607   xr->x = xr->y = 0;
608   xr_driver_run_fsm (xr);
609 }
610
611 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
612    rendering a previous output item, that is, only if xr_driver_need_new_page()
613    returns false. */
614 void
615 xr_driver_output_item (struct xr_driver *xr,
616                        const struct output_item *output_item)
617 {
618   assert (xr->fsm == NULL);
619   xr->fsm = xr_render_output_item (xr, output_item);
620   xr_driver_run_fsm (xr);
621 }
622
623 /* Returns true if XR is in the middle of rendering an output item and needs a
624    new page to be appended using xr_driver_next_page() to make progress,
625    otherwise false. */
626 bool
627 xr_driver_need_new_page (const struct xr_driver *xr)
628 {
629   return xr->fsm != NULL;
630 }
631
632 /* Returns true if the current page doesn't have any content yet. */
633 bool
634 xr_driver_is_page_blank (const struct xr_driver *xr)
635 {
636   return xr->y == 0;
637 }
638
639 static void
640 xr_driver_destroy_fsm (struct xr_driver *xr)
641 {
642   if (xr->fsm != NULL)
643     {
644       xr->fsm->destroy (xr->fsm);
645       xr->fsm = NULL;
646     }
647 }
648
649 static void
650 xr_driver_run_fsm (struct xr_driver *xr)
651 {
652   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
653     xr_driver_destroy_fsm (xr);
654 }
655 \f
656 static void
657 xr_layout_cell (struct xr_driver *, const struct table_cell *,
658                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
659                 int *width, int *height, int *brk);
660
661 static void
662 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
663            const struct cell_color *color)
664 {
665   cairo_new_path (xr->cairo);
666   cairo_set_source_rgb (xr->cairo,
667                         color->r / 255.0, color->g / 255.0, color->b / 255.0);
668   cairo_set_line_width (
669     xr->cairo,
670     xr_to_pt (style == RENDER_LINE_THICK ? xr->line_width * 2
671               : style == RENDER_LINE_THIN ? xr->line_width / 2
672               : xr->line_width));
673   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
674   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
675   cairo_stroke (xr->cairo);
676 }
677
678 static void UNUSED
679 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
680 {
681   cairo_new_path (xr->cairo);
682   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
683   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
684   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
685   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
686   cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
687   cairo_close_path (xr->cairo);
688   cairo_stroke (xr->cairo);
689 }
690
691 static void
692 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
693 {
694   cairo_new_path (xr->cairo);
695   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
696   cairo_rectangle (xr->cairo,
697                    xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
698                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
699   cairo_fill (xr->cairo);
700 }
701
702 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
703    shortening it to X0...X1 if SHORTEN is true.
704    Draws a horizontal line X1...X3 at Y if RIGHT says so,
705    shortening it to X2...X3 if SHORTEN is true. */
706 static void
707 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
708            enum render_line_style left, enum render_line_style right,
709            const struct cell_color *left_color,
710            const struct cell_color *right_color,
711            bool shorten)
712 {
713   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
714       && cell_color_equal (left_color, right_color))
715     dump_line (xr, x0, y, x3, y, left, left_color);
716   else
717     {
718       if (left != RENDER_LINE_NONE)
719         dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
720       if (right != RENDER_LINE_NONE)
721         dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
722     }
723 }
724
725 /* Draws a vertical line Y0...Y2 at X if TOP says so,
726    shortening it to Y0...Y1 if SHORTEN is true.
727    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
728    shortening it to Y2...Y3 if SHORTEN is true. */
729 static void
730 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
731            enum render_line_style top, enum render_line_style bottom,
732            const struct cell_color *top_color,
733            const struct cell_color *bottom_color,
734            bool shorten)
735 {
736   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
737       && cell_color_equal (top_color, bottom_color))
738     dump_line (xr, x, y0, x, y3, top, top_color);
739   else
740     {
741       if (top != RENDER_LINE_NONE)
742         dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
743       if (bottom != RENDER_LINE_NONE)
744         dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
745     }
746 }
747
748 static void
749 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
750               enum render_line_style styles[TABLE_N_AXES][2],
751               struct cell_color colors[TABLE_N_AXES][2])
752 {
753   const int x0 = bb[H][0];
754   const int y0 = bb[V][0];
755   const int x3 = bb[H][1];
756   const int y3 = bb[V][1];
757   const int top = styles[H][0];
758   const int bottom = styles[H][1];
759
760   int start_side = render_direction_rtl();
761   int end_side = !start_side;
762   const int start_of_line = styles[V][start_side];
763   const int end_of_line   = styles[V][end_side];
764   const struct cell_color *top_color = &colors[H][0];
765   const struct cell_color *bottom_color = &colors[H][1];
766   const struct cell_color *start_color = &colors[V][start_side];
767   const struct cell_color *end_color = &colors[V][end_side];
768
769   /* The algorithm here is somewhat subtle, to allow it to handle
770      all the kinds of intersections that we need.
771
772      Three additional ordinates are assigned along the x axis.  The
773      first is xc, midway between x0 and x3.  The others are x1 and
774      x2; for a single vertical line these are equal to xc, and for
775      a double vertical line they are the ordinates of the left and
776      right half of the double line.
777
778      yc, y1, and y2 are assigned similarly along the y axis.
779
780      The following diagram shows the coordinate system and output
781      for double top and bottom lines, single left line, and no
782      right line:
783
784                  x0       x1 xc  x2      x3
785                y0 ________________________
786                   |        #     #       |
787                   |        #     #       |
788                   |        #     #       |
789                   |        #     #       |
790                   |        #     #       |
791      y1 = y2 = yc |#########     #       |
792                   |        #     #       |
793                   |        #     #       |
794                   |        #     #       |
795                   |        #     #       |
796                y3 |________#_____#_______|
797   */
798   struct xr_driver *xr = xr_;
799
800   /* Offset from center of each line in a pair of double lines. */
801   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
802
803   /* Are the lines along each axis single or double?
804      (It doesn't make sense to have different kinds of line on the
805      same axis, so we don't try to gracefully handle that case.) */
806   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
807   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
808
809   /* When horizontal lines are doubled,
810      the left-side line along y1 normally runs from x0 to x2,
811      and the right-side line along y1 from x3 to x1.
812      If the top-side line is also doubled, we shorten the y1 lines,
813      so that the left-side line runs only to x1,
814      and the right-side line only to x2.
815      Otherwise, the horizontal line at y = y1 below would cut off
816      the intersection, which looks ugly:
817                x0       x1     x2      x3
818              y0 ________________________
819                 |        #     #       |
820                 |        #     #       |
821                 |        #     #       |
822                 |        #     #       |
823              y1 |#########     ########|
824                 |                      |
825                 |                      |
826              y2 |######################|
827                 |                      |
828                 |                      |
829              y3 |______________________|
830      It is more of a judgment call when the horizontal line is
831      single.  We actually choose to cut off the line anyhow, as
832      shown in the first diagram above.
833   */
834   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
835   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
836   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
837   int horz_line_ofs = double_vert ? double_line_ofs : 0;
838   int xc = (x0 + x3) / 2;
839   int x1 = xc - horz_line_ofs;
840   int x2 = xc + horz_line_ofs;
841
842   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
843   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
844   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
845   int vert_line_ofs = double_horz ? double_line_ofs : 0;
846   int yc = (y0 + y3) / 2;
847   int y1 = yc - vert_line_ofs;
848   int y2 = yc + vert_line_ofs;
849
850   if (!double_horz)
851     horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
852                start_color, end_color, shorten_yc_line);
853   else
854     {
855       horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
856                  start_color, end_color, shorten_y1_lines);
857       horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
858                  start_color, end_color, shorten_y2_lines);
859     }
860
861   if (!double_vert)
862     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
863                shorten_xc_line);
864   else
865     {
866       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
867                  shorten_x1_lines);
868       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
869                  shorten_x2_lines);
870     }
871 }
872
873 static void
874 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
875                        int *min_width, int *max_width)
876 {
877   struct xr_driver *xr = xr_;
878   int bb[TABLE_N_AXES][2];
879   int clip[TABLE_N_AXES][2];
880   int h;
881
882   bb[H][0] = 0;
883   bb[H][1] = INT_MAX;
884   bb[V][0] = 0;
885   bb[V][1] = INT_MAX;
886   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
887   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
888
889   bb[H][1] = 1;
890   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
891
892   if (*min_width > 0)
893     *min_width += px_to_xr (cell->style->margin[H][0]
894                             + cell->style->margin[H][1]);
895   if (*max_width > 0)
896     *max_width += px_to_xr (cell->style->margin[H][0]
897                             + cell->style->margin[H][1]);
898 }
899
900 static int
901 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
902 {
903   struct xr_driver *xr = xr_;
904   int bb[TABLE_N_AXES][2];
905   int clip[TABLE_N_AXES][2];
906   int w, h;
907
908   bb[H][0] = 0;
909   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
910                                + cell->style->margin[H][1]);
911   bb[V][0] = 0;
912   bb[V][1] = INT_MAX;
913   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
914   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
915   h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]);
916   return h;
917 }
918
919 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
920
921 static void
922 xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
923               int bb[TABLE_N_AXES][2],
924               int spill[TABLE_N_AXES][2],
925               int clip[TABLE_N_AXES][2])
926 {
927   struct xr_driver *xr = xr_;
928   int w, h, brk;
929
930   cairo_save (xr->cairo);
931   int bg_clip[TABLE_N_AXES][2];
932   for (int axis = 0; axis < TABLE_N_AXES; axis++)
933     {
934       bg_clip[axis][0] = clip[axis][0];
935       if (bb[axis][0] == clip[axis][0])
936         bg_clip[axis][0] -= spill[axis][0];
937
938       bg_clip[axis][1] = clip[axis][1];
939       if (bb[axis][1] == clip[axis][1])
940         bg_clip[axis][1] += spill[axis][1];
941     }
942   xr_clip (xr, bg_clip);
943   cairo_set_source_rgb (xr->cairo,
944                         cell->style->bg[color_idx].r / 255.,
945                         cell->style->bg[color_idx].g / 255.,
946                         cell->style->bg[color_idx].b / 255.);
947   fill_rectangle (xr,
948                   bb[H][0] - spill[H][0],
949                   bb[V][0] - spill[V][0],
950                   bb[H][1] + spill[H][1],
951                   bb[V][1] + spill[V][1]);
952   cairo_restore (xr->cairo);
953
954   cairo_save (xr->cairo);
955   cairo_set_source_rgb (xr->cairo,
956                         cell->style->fg[color_idx].r / 255.,
957                         cell->style->fg[color_idx].g / 255.,
958                         cell->style->fg[color_idx].b / 255.);
959
960   for (int axis = 0; axis < TABLE_N_AXES; axis++)
961     {
962       bb[axis][0] += px_to_xr (cell->style->margin[axis][0]);
963       bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]);
964     }
965   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
966     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
967   cairo_restore (xr->cairo);
968 }
969
970 static int
971 xr_adjust_break (void *xr_, const struct table_cell *cell,
972                  int width, int height)
973 {
974   struct xr_driver *xr = xr_;
975   int bb[TABLE_N_AXES][2];
976   int clip[TABLE_N_AXES][2];
977   int w, h, brk;
978
979   if (xr_measure_cell_height (xr_, cell, width) < height)
980     return -1;
981
982   bb[H][0] = 0;
983   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
984                                + cell->style->margin[H][1]);
985   if (bb[H][1] <= 0)
986     return 0;
987   bb[V][0] = 0;
988   bb[V][1] = height - px_to_xr (cell->style->margin[V][0]
989                                 + cell->style->margin[V][1]);
990   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
991   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
992   return brk;
993 }
994 \f
995 static void
996 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
997 {
998   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
999     {
1000       double x0 = xr_to_pt (clip[H][0] + xr->x);
1001       double y0 = xr_to_pt (clip[V][0] + xr->y);
1002       double x1 = xr_to_pt (clip[H][1] + xr->x);
1003       double y1 = xr_to_pt (clip[V][1] + xr->y);
1004
1005       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
1006       cairo_clip (xr->cairo);
1007     }
1008 }
1009
1010 static void
1011 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
1012 {
1013   attr->start_index = start_index;
1014   pango_attr_list_insert (list, attr);
1015 }
1016
1017 static int
1018 xr_layout_cell_text (struct xr_driver *xr,
1019                      const struct cell_contents *contents,
1020                      const struct cell_style *style,
1021                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1022                      int *widthp, int *brk)
1023 {
1024   unsigned int options = contents->options;
1025   size_t length;
1026   int w, h;
1027
1028   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1029                           : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
1030                           : &xr->fonts[XR_FONT_PROPORTIONAL]);
1031   struct xr_font local_font;
1032   if (style->font)
1033     {
1034       PangoFontDescription *desc = parse_font (
1035         style->font,
1036         style->font_size ? style->font_size * 1000 * 72 / 128 : 10000,
1037         style->bold, style->italic);
1038       if (desc)
1039         {
1040           PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
1041           pango_layout_set_font_description (layout, desc);
1042
1043           local_font.desc = desc;
1044           local_font.layout = layout;
1045           font = &local_font;
1046         }
1047     }
1048
1049   int footnote_adjustment;
1050   if (contents->n_footnotes == 0)
1051     footnote_adjustment = 0;
1052   else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT)
1053     {
1054       PangoAttrList *attrs;
1055
1056       const char *marker = contents->footnotes[0]->marker;
1057       pango_layout_set_text (font->layout, marker, strlen (marker));
1058
1059       attrs = pango_attr_list_new ();
1060       pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
1061       pango_layout_set_attributes (font->layout, attrs);
1062       pango_attr_list_unref (attrs);
1063
1064       pango_layout_get_size (font->layout, &w, &h);
1065       footnote_adjustment = MIN (w, px_to_xr (style->margin[H][1]));
1066     }
1067   else
1068     footnote_adjustment = px_to_xr (style->margin[H][1]);
1069
1070   length = strlen (contents->text);
1071   if (footnote_adjustment)
1072     {
1073       PangoAttrList *attrs;
1074       struct string s;
1075
1076       bb[H][1] += footnote_adjustment;
1077
1078       ds_init_empty (&s);
1079       ds_extend (&s, length + contents->n_footnotes * 10);
1080       ds_put_cstr (&s, contents->text);
1081       cell_contents_format_footnote_markers (contents, &s);
1082       pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
1083       ds_destroy (&s);
1084
1085       attrs = pango_attr_list_new ();
1086       if (style->underline)
1087         pango_attr_list_insert (attrs, pango_attr_underline_new (
1088                                PANGO_UNDERLINE_SINGLE));
1089       add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
1090       add_attr_with_start (
1091         attrs, pango_attr_font_desc_new (font->desc), length);
1092       pango_layout_set_attributes (font->layout, attrs);
1093       pango_attr_list_unref (attrs);
1094     }
1095   else
1096     {
1097       pango_layout_set_text (font->layout, contents->text, -1);
1098
1099       if (style->underline)
1100         {
1101           PangoAttrList *attrs = pango_attr_list_new ();
1102           pango_attr_list_insert (attrs, pango_attr_underline_new (
1103                                     PANGO_UNDERLINE_SINGLE));
1104           pango_layout_set_attributes (font->layout, attrs);
1105           pango_attr_list_unref (attrs);
1106         }
1107     }
1108
1109   pango_layout_set_alignment (
1110     font->layout,
1111     ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1112      : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT
1113      : PANGO_ALIGN_CENTER));
1114   pango_layout_set_width (
1115     font->layout,
1116     bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1117   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1118
1119   if (clip[H][0] != clip[H][1])
1120     {
1121       cairo_save (xr->cairo);
1122       xr_clip (xr, clip);
1123       cairo_translate (xr->cairo,
1124                        xr_to_pt (bb[H][0] + xr->x),
1125                        xr_to_pt (bb[V][0] + xr->y));
1126       pango_cairo_show_layout (xr->cairo, font->layout);
1127
1128       /* If enabled, this draws a blue rectangle around the extents of each
1129          line of text, which can be rather useful for debugging layout
1130          issues. */
1131       if (0)
1132         {
1133           PangoLayoutIter *iter;
1134           iter = pango_layout_get_iter (font->layout);
1135           do
1136             {
1137               PangoRectangle extents;
1138
1139               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1140               cairo_save (xr->cairo);
1141               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1142               dump_rectangle (xr,
1143                               pango_to_xr (extents.x) - xr->x,
1144                               pango_to_xr (extents.y) - xr->y,
1145                               pango_to_xr (extents.x + extents.width) - xr->x,
1146                               pango_to_xr (extents.y + extents.height) - xr->y);
1147               cairo_restore (xr->cairo);
1148             }
1149           while (pango_layout_iter_next_line (iter));
1150           pango_layout_iter_free (iter);
1151         }
1152
1153       cairo_restore (xr->cairo);
1154     }
1155
1156   pango_layout_get_size (font->layout, &w, &h);
1157   w = pango_to_xr (w);
1158   h = pango_to_xr (h);
1159   if (w > *widthp)
1160     *widthp = w;
1161   if (bb[V][0] + h >= bb[V][1])
1162     {
1163       PangoLayoutIter *iter;
1164       int best UNUSED = 0;
1165
1166       /* Choose a breakpoint between lines instead of in the middle of one. */
1167       iter = pango_layout_get_iter (font->layout);
1168       do
1169         {
1170           PangoRectangle extents;
1171           int y0, y1;
1172           int bottom;
1173
1174           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1175           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1176           extents.x = pango_to_xr (extents.x);
1177           extents.y = pango_to_xr (y0);
1178           extents.width = pango_to_xr (extents.width);
1179           extents.height = pango_to_xr (y1 - y0);
1180           bottom = bb[V][0] + extents.y + extents.height;
1181           if (bottom < bb[V][1])
1182             {
1183               if (brk && clip[H][0] != clip[H][1])
1184                 best = bottom;
1185               if (brk)
1186                 *brk = bottom;
1187             }
1188           else
1189             break;
1190         }
1191       while (pango_layout_iter_next_line (iter));
1192       pango_layout_iter_free (iter);
1193
1194       /* If enabled, draws a green line across the chosen breakpoint, which can
1195          be useful for debugging issues with breaking.  */
1196       if (0)
1197         {
1198           if (best && !xr->nest)
1199             dump_line (xr, -xr->left_margin, best,
1200                        xr->width + xr->right_margin, best,
1201                        RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0));
1202         }
1203     }
1204
1205   pango_layout_set_attributes (font->layout, NULL);
1206
1207   if (font == &local_font)
1208     {
1209       g_object_unref (G_OBJECT (font->layout));
1210       pango_font_description_free (font->desc);
1211     }
1212
1213   return bb[V][0] + h;
1214 }
1215
1216 static void
1217 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1218                 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1219                 int *width, int *height, int *brk)
1220 {
1221   int bb[TABLE_N_AXES][2];
1222   size_t i;
1223
1224   *width = 0;
1225   *height = 0;
1226   if (brk)
1227     *brk = 0;
1228
1229   memcpy (bb, bb_, sizeof bb);
1230
1231   /* If enabled, draws a blue rectangle around the cell extents, which can be
1232      useful for debugging layout. */
1233   if (0)
1234     {
1235       if (clip[H][0] != clip[H][1])
1236         {
1237           int offset = (xr->nest) * XR_POINT;
1238
1239           cairo_save (xr->cairo);
1240           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1241           dump_rectangle (xr,
1242                           bb[H][0] + offset, bb[V][0] + offset,
1243                           bb[H][1] - offset, bb[V][1] - offset);
1244           cairo_restore (xr->cairo);
1245         }
1246     }
1247
1248   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1249     {
1250       const struct cell_contents *contents = &cell->contents[i];
1251
1252       if (brk)
1253         *brk = bb[V][0];
1254       if (i > 0)
1255         {
1256           bb[V][0] += xr->char_height / 2;
1257           if (bb[V][0] >= bb[V][1])
1258             break;
1259           if (brk)
1260             *brk = bb[V][0];
1261         }
1262
1263       bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip,
1264                                       width, brk);
1265     }
1266   *height = bb[V][0] - bb_[V][0];
1267 }
1268 \f
1269 struct output_driver_factory pdf_driver_factory =
1270   { "pdf", "pspp.pdf", xr_pdf_create };
1271 struct output_driver_factory ps_driver_factory =
1272   { "ps", "pspp.ps", xr_ps_create };
1273 struct output_driver_factory svg_driver_factory =
1274   { "svg", "pspp.svg", xr_svg_create };
1275
1276 static const struct output_driver_class cairo_driver_class =
1277 {
1278   "cairo",
1279   xr_destroy,
1280   xr_submit,
1281   xr_flush,
1282 };
1283 \f
1284 /* GUI rendering helpers. */
1285
1286 struct xr_rendering
1287   {
1288     struct output_item *item;
1289
1290     /* Table items. */
1291     struct render_pager *p;
1292     struct xr_driver *xr;
1293   };
1294
1295 #define CHART_WIDTH 500
1296 #define CHART_HEIGHT 375
1297
1298
1299
1300 struct xr_driver *
1301 xr_driver_create (cairo_t *cairo, struct string_map *options)
1302 {
1303   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1304   if (!xr_set_cairo (xr, cairo))
1305     {
1306       output_driver_destroy (&xr->driver);
1307       return NULL;
1308     }
1309   return xr;
1310 }
1311
1312 /* Destroy XR, which should have been created with xr_driver_create().  Any
1313    cairo_t added to XR is not destroyed, because it is owned by the client. */
1314 void
1315 xr_driver_destroy (struct xr_driver *xr)
1316 {
1317   if (xr != NULL)
1318     {
1319       xr->cairo = NULL;
1320       output_driver_destroy (&xr->driver);
1321     }
1322 }
1323
1324 static struct xr_rendering *
1325 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1326 {
1327   struct table_item *table_item;
1328   struct xr_rendering *r;
1329
1330   table_item = table_item_create (table_from_string (TAB_LEFT, text),
1331                                   NULL, NULL);
1332   r = xr_rendering_create (xr, &table_item->output_item, cr);
1333   table_item_unref (table_item);
1334
1335   return r;
1336 }
1337
1338 void
1339 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1340 {
1341   if (is_table_item (xr->item))
1342     apply_options (xr->xr, o);
1343 }
1344
1345 struct xr_rendering *
1346 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1347                      cairo_t *cr)
1348 {
1349   struct xr_rendering *r = NULL;
1350
1351   if (is_text_item (item))
1352     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1353                                   cr);
1354   else if (is_message_item (item))
1355     {
1356       const struct message_item *message_item = to_message_item (item);
1357       const struct msg *msg = message_item_get_msg (message_item);
1358       char *s = msg_to_string (msg, NULL);
1359       r = xr_rendering_create_text (xr, s, cr);
1360       free (s);
1361     }
1362   else if (is_table_item (item))
1363     {
1364       r = xzalloc (sizeof *r);
1365       r->item = output_item_ref (item);
1366       r->xr = xr;
1367       xr_set_cairo (xr, cr);
1368       r->p = render_pager_create (xr->params, to_table_item (item));
1369     }
1370   else if (is_chart_item (item))
1371     {
1372       r = xzalloc (sizeof *r);
1373       r->item = output_item_ref (item);
1374     }
1375
1376   return r;
1377 }
1378
1379 void
1380 xr_rendering_destroy (struct xr_rendering *r)
1381 {
1382   if (r)
1383     {
1384       output_item_unref (r->item);
1385       render_pager_destroy (r->p);
1386       free (r);
1387     }
1388 }
1389
1390 void
1391 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1392 {
1393   if (is_table_item (r->item))
1394     {
1395       *w = render_pager_get_size (r->p, H) / XR_POINT;
1396       *h = render_pager_get_size (r->p, V) / XR_POINT;
1397     }
1398   else
1399     {
1400       *w = CHART_WIDTH;
1401       *h = CHART_HEIGHT;
1402     }
1403 }
1404
1405 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1406                     double x, double y, double width, double height);
1407
1408 /* Draws onto CR */
1409 void
1410 xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
1411 {
1412   if (is_table_item (r->item))
1413     {
1414       struct xr_driver *xr = r->xr;
1415
1416       xr_set_cairo (xr, cr);
1417
1418       render_pager_draw (r->p);
1419
1420     }
1421   else
1422     xr_draw_chart (to_chart_item (r->item), cr,
1423                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1424 }
1425
1426 static void
1427 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1428                double x, double y, double width, double height)
1429 {
1430   struct xrchart_geometry geom;
1431
1432   cairo_save (cr);
1433   cairo_translate (cr, x, y + height);
1434   cairo_scale (cr, 1.0, -1.0);
1435   xrchart_geometry_init (cr, &geom, width, height);
1436   if (is_boxplot (chart_item))
1437     xrchart_draw_boxplot (chart_item, cr, &geom);
1438   else if (is_histogram_chart (chart_item))
1439     xrchart_draw_histogram (chart_item, cr, &geom);
1440   else if (is_np_plot_chart (chart_item))
1441     xrchart_draw_np_plot (chart_item, cr, &geom);
1442   else if (is_piechart (chart_item))
1443     xrchart_draw_piechart (chart_item, cr, &geom);
1444   else if (is_barchart (chart_item))
1445     xrchart_draw_barchart (chart_item, cr, &geom);
1446   else if (is_roc_chart (chart_item))
1447     xrchart_draw_roc (chart_item, cr, &geom);
1448   else if (is_scree (chart_item))
1449     xrchart_draw_scree (chart_item, cr, &geom);
1450   else if (is_spreadlevel_plot_chart (chart_item))
1451     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1452   else if (is_scatterplot_chart (chart_item))
1453     xrchart_draw_scatterplot (chart_item, cr, &geom);
1454   else
1455     NOT_REACHED ();
1456   xrchart_geometry_free (cr, &geom);
1457
1458   cairo_restore (cr);
1459 }
1460
1461 char *
1462 xr_draw_png_chart (const struct chart_item *item,
1463                    const char *file_name_template, int number,
1464                    const struct xr_color *fg,
1465                    const struct xr_color *bg
1466                    )
1467 {
1468   const int width = 640;
1469   const int length = 480;
1470
1471   cairo_surface_t *surface;
1472   cairo_status_t status;
1473   const char *number_pos;
1474   char *file_name;
1475   cairo_t *cr;
1476
1477   number_pos = strchr (file_name_template, '#');
1478   if (number_pos != NULL)
1479     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1480                            file_name_template, number, number_pos + 1);
1481   else
1482     file_name = xstrdup (file_name_template);
1483
1484   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1485   cr = cairo_create (surface);
1486
1487   cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1488   cairo_paint (cr);
1489
1490   cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1491
1492   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1493
1494   status = cairo_surface_write_to_png (surface, file_name);
1495   if (status != CAIRO_STATUS_SUCCESS)
1496     msg (ME, _("error writing output file `%s': %s"),
1497            file_name, cairo_status_to_string (status));
1498
1499   cairo_destroy (cr);
1500   cairo_surface_destroy (surface);
1501
1502   return file_name;
1503 }
1504 \f
1505 struct xr_table_state
1506   {
1507     struct xr_render_fsm fsm;
1508     struct table_item *table_item;
1509     struct render_pager *p;
1510   };
1511
1512 static bool
1513 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1514 {
1515   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1516
1517   while (render_pager_has_next (ts->p))
1518     {
1519       int used;
1520
1521       used = render_pager_draw_next (ts->p, xr->length - xr->y);
1522       if (!used)
1523         {
1524           assert (xr->y > 0);
1525           return true;
1526         }
1527       else
1528         xr->y += used;
1529     }
1530   return false;
1531 }
1532
1533 static void
1534 xr_table_destroy (struct xr_render_fsm *fsm)
1535 {
1536   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1537
1538   table_item_unref (ts->table_item);
1539   render_pager_destroy (ts->p);
1540   free (ts);
1541 }
1542
1543 static struct xr_render_fsm *
1544 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1545 {
1546   struct xr_table_state *ts;
1547
1548   ts = xmalloc (sizeof *ts);
1549   ts->fsm.render = xr_table_render;
1550   ts->fsm.destroy = xr_table_destroy;
1551   ts->table_item = table_item_ref (table_item);
1552
1553   if (xr->y > 0)
1554     xr->y += xr->char_height;
1555
1556   ts->p = render_pager_create (xr->params, table_item);
1557
1558   return &ts->fsm;
1559 }
1560 \f
1561 struct xr_chart_state
1562   {
1563     struct xr_render_fsm fsm;
1564     struct chart_item *chart_item;
1565   };
1566
1567 static bool
1568 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1569 {
1570   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1571
1572   const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
1573
1574   if (xr->y > xr->length - chart_height)
1575     return true;
1576
1577   if (xr->cairo != NULL)
1578     {
1579       xr_draw_chart (cs->chart_item, xr->cairo,
1580                      0.0,
1581                      xr_to_pt (xr->y),
1582                      xr_to_pt (xr->width),
1583                      xr_to_pt (chart_height));
1584     }
1585   xr->y += chart_height;
1586
1587   return false;
1588 }
1589
1590 static void
1591 xr_chart_destroy (struct xr_render_fsm *fsm)
1592 {
1593   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1594
1595   chart_item_unref (cs->chart_item);
1596   free (cs);
1597 }
1598
1599 static struct xr_render_fsm *
1600 xr_render_chart (const struct chart_item *chart_item)
1601 {
1602   struct xr_chart_state *cs;
1603
1604   cs = xmalloc (sizeof *cs);
1605   cs->fsm.render = xr_chart_render;
1606   cs->fsm.destroy = xr_chart_destroy;
1607   cs->chart_item = chart_item_ref (chart_item);
1608
1609   return &cs->fsm;
1610 }
1611 \f
1612 static bool
1613 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1614 {
1615   return xr->y > 0;
1616 }
1617
1618 static void
1619 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1620 {
1621   /* Nothing to do. */
1622 }
1623
1624 static struct xr_render_fsm *
1625 xr_render_eject (void)
1626 {
1627   static struct xr_render_fsm eject_renderer =
1628     {
1629       xr_eject_render,
1630       xr_eject_destroy
1631     };
1632
1633   return &eject_renderer;
1634 }
1635 \f
1636 static struct xr_render_fsm *
1637 xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
1638 {
1639   struct tab_table *tab = tab_create (1, 1);
1640
1641   struct cell_style *style = pool_alloc (tab->container, sizeof *style);
1642   *style = (struct cell_style) CELL_STYLE_INITIALIZER;
1643   if (item->font)
1644     style->font = pool_strdup (tab->container, item->font);
1645   style->font_size = item->font_size;
1646   style->bold = item->bold;
1647   style->italic = item->italic;
1648   style->underline = item->underline;
1649   tab->styles[0] = style;
1650
1651   tab_text (tab, 0, 0, TAB_LEFT, text_item_get_text (item));
1652   struct table_item *table_item = table_item_create (&tab->table, NULL, NULL);
1653   struct xr_render_fsm *fsm = xr_render_table (xr, table_item);
1654   table_item_unref (table_item);
1655
1656   return fsm;
1657 }
1658
1659 static struct xr_render_fsm *
1660 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1661 {
1662   enum text_item_type type = text_item_get_type (text_item);
1663   const char *text = text_item_get_text (text_item);
1664
1665   switch (type)
1666     {
1667     case TEXT_ITEM_TITLE:
1668       free (xr->title);
1669       xr->title = xstrdup (text);
1670       break;
1671
1672     case TEXT_ITEM_SUBTITLE:
1673       free (xr->subtitle);
1674       xr->subtitle = xstrdup (text);
1675       break;
1676
1677     case TEXT_ITEM_COMMAND_CLOSE:
1678       break;
1679
1680     case TEXT_ITEM_BLANK_LINE:
1681       if (xr->y > 0)
1682         xr->y += xr->char_height;
1683       break;
1684
1685     case TEXT_ITEM_EJECT_PAGE:
1686       if (xr->y > 0)
1687         return xr_render_eject ();
1688       break;
1689
1690     default:
1691       return xr_create_text_renderer (xr, text_item);
1692     }
1693
1694   return NULL;
1695 }
1696
1697 static struct xr_render_fsm *
1698 xr_render_message (struct xr_driver *xr,
1699                    const struct message_item *message_item)
1700 {
1701   const struct msg *msg = message_item_get_msg (message_item);
1702   char *s = msg_to_string (msg, message_item->command_name);
1703   struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s);
1704   free (s);
1705   struct xr_render_fsm *fsm = xr_create_text_renderer (xr, item);
1706   text_item_unref (item);
1707
1708   return fsm;
1709 }
1710
1711 static struct xr_render_fsm *
1712 xr_render_output_item (struct xr_driver *xr,
1713                        const struct output_item *output_item)
1714 {
1715   if (is_table_item (output_item))
1716     return xr_render_table (xr, to_table_item (output_item));
1717   else if (is_chart_item (output_item))
1718     return xr_render_chart (to_chart_item (output_item));
1719   else if (is_text_item (output_item))
1720     return xr_render_text (xr, to_text_item (output_item));
1721   else if (is_message_item (output_item))
1722     return xr_render_message (xr, to_message_item (output_item));
1723   else
1724     return NULL;
1725 }