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