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