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