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