cairo: Add .5pt gutter around table rules.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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.  CAIRO may be null to skip actually
567    rendering the page (which might be useful to find out how many pages an
568    output document has without actually rendering it). */
569 void
570 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
571 {
572   if (cairo != NULL)
573     {
574       cairo_save (cairo);
575       cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
576       cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
577       cairo_fill (cairo);
578       cairo_restore (cairo);
579
580       cairo_translate (cairo,
581                        xr_to_pt (xr->left_margin),
582                        xr_to_pt (xr->top_margin));
583     }
584
585   xr->page_number++;
586   xr->cairo = cairo;
587   xr->x = xr->y = 0;
588   xr_driver_run_fsm (xr);
589 }
590
591 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
592    rendering a previous output item, that is, only if xr_driver_need_new_page()
593    returns false. */
594 void
595 xr_driver_output_item (struct xr_driver *xr,
596                        const struct output_item *output_item)
597 {
598   assert (xr->fsm == NULL);
599   xr->fsm = xr_render_output_item (xr, output_item);
600   xr_driver_run_fsm (xr);
601 }
602
603 /* Returns true if XR is in the middle of rendering an output item and needs a
604    new page to be appended using xr_driver_next_page() to make progress,
605    otherwise false. */
606 bool
607 xr_driver_need_new_page (const struct xr_driver *xr)
608 {
609   return xr->fsm != NULL;
610 }
611
612 /* Returns true if the current page doesn't have any content yet. */
613 bool
614 xr_driver_is_page_blank (const struct xr_driver *xr)
615 {
616   return xr->y == 0;
617 }
618
619 static void
620 xr_driver_destroy_fsm (struct xr_driver *xr)
621 {
622   if (xr->fsm != NULL)
623     {
624       xr->fsm->destroy (xr->fsm);
625       xr->fsm = NULL;
626     }
627 }
628
629 static void
630 xr_driver_run_fsm (struct xr_driver *xr)
631 {
632   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
633     xr_driver_destroy_fsm (xr);
634 }
635 \f
636 static void
637 xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx,
638                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
639                 int *width, int *height, int *brk);
640
641 static void
642 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
643 {
644   cairo_new_path (xr->cairo);
645   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
646   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
647   cairo_stroke (xr->cairo);
648 }
649
650 static void UNUSED
651 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
652 {
653   cairo_new_path (xr->cairo);
654   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
655   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
656   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
657   cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
658   cairo_close_path (xr->cairo);
659   cairo_stroke (xr->cairo);
660 }
661
662 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
663    shortening it to X0...X1 if SHORTEN is true.
664    Draws a horizontal line X1...X3 at Y if RIGHT says so,
665    shortening it to X2...X3 if SHORTEN is true. */
666 static void
667 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
668            enum render_line_style left, enum render_line_style right,
669            bool shorten)
670 {
671   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
672     dump_line (xr, x0, y, x3, y);
673   else
674     {
675       if (left != RENDER_LINE_NONE)
676         dump_line (xr, x0, y, shorten ? x1 : x2, y);
677       if (right != RENDER_LINE_NONE)
678         dump_line (xr, shorten ? x2 : x1, y, x3, y);
679     }
680 }
681
682 /* Draws a vertical line Y0...Y2 at X if TOP says so,
683    shortening it to Y0...Y1 if SHORTEN is true.
684    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
685    shortening it to Y2...Y3 if SHORTEN is true. */
686 static void
687 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
688            enum render_line_style top, enum render_line_style bottom,
689            bool shorten)
690 {
691   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
692     dump_line (xr, x, y0, x, y3);
693   else
694     {
695       if (top != RENDER_LINE_NONE)
696         dump_line (xr, x, y0, x, shorten ? y1 : y2);
697       if (bottom != RENDER_LINE_NONE)
698         dump_line (xr, x, shorten ? y2 : y1, x, y3);
699     }
700 }
701
702 static void
703 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
704               enum render_line_style styles[TABLE_N_AXES][2])
705 {
706   const int x0 = bb[H][0];
707   const int y0 = bb[V][0];
708   const int x3 = bb[H][1];
709   const int y3 = bb[V][1];
710   const int top = styles[H][0];
711   const int left = styles[V][0];
712   const int bottom = styles[H][1];
713   const int right = styles[V][1];
714
715   /* The algorithm here is somewhat subtle, to allow it to handle
716      all the kinds of intersections that we need.
717
718      Three additional ordinates are assigned along the x axis.  The
719      first is xc, midway between x0 and x3.  The others are x1 and
720      x2; for a single vertical line these are equal to xc, and for
721      a double vertical line they are the ordinates of the left and
722      right half of the double line.
723
724      yc, y1, and y2 are assigned similarly along the y axis.
725
726      The following diagram shows the coordinate system and output
727      for double top and bottom lines, single left line, and no
728      right line:
729
730                  x0       x1 xc  x2      x3
731                y0 ________________________
732                   |        #     #       |
733                   |        #     #       |
734                   |        #     #       |
735                   |        #     #       |
736                   |        #     #       |
737      y1 = y2 = yc |#########     #       |
738                   |        #     #       |
739                   |        #     #       |
740                   |        #     #       |
741                   |        #     #       |
742                y3 |________#_____#_______|
743   */
744   struct xr_driver *xr = xr_;
745
746   /* Offset from center of each line in a pair of double lines. */
747   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
748
749   /* Are the lines along each axis single or double?
750      (It doesn't make sense to have different kinds of line on the
751      same axis, so we don't try to gracefully handle that case.) */
752   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
753   bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
754
755   /* When horizontal lines are doubled,
756      the left-side line along y1 normally runs from x0 to x2,
757      and the right-side line along y1 from x3 to x1.
758      If the top-side line is also doubled, we shorten the y1 lines,
759      so that the left-side line runs only to x1,
760      and the right-side line only to x2.
761      Otherwise, the horizontal line at y = y1 below would cut off
762      the intersection, which looks ugly:
763                x0       x1     x2      x3
764              y0 ________________________
765                 |        #     #       |
766                 |        #     #       |
767                 |        #     #       |
768                 |        #     #       |
769              y1 |#########     ########|
770                 |                      |
771                 |                      |
772              y2 |######################|
773                 |                      |
774                 |                      |
775              y3 |______________________|
776      It is more of a judgment call when the horizontal line is
777      single.  We actually choose to cut off the line anyhow, as
778      shown in the first diagram above.
779   */
780   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
781   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
782   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
783   int horz_line_ofs = double_vert ? double_line_ofs : 0;
784   int xc = (x0 + x3) / 2;
785   int x1 = xc - horz_line_ofs;
786   int x2 = xc + horz_line_ofs;
787
788   bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
789   bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
790   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
791   int vert_line_ofs = double_horz ? double_line_ofs : 0;
792   int yc = (y0 + y3) / 2;
793   int y1 = yc - vert_line_ofs;
794   int y2 = yc + vert_line_ofs;
795
796   if (!double_horz)
797     horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
798   else
799     {
800       horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
801       horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
802     }
803
804   if (!double_vert)
805     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
806   else
807     {
808       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
809       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
810     }
811 }
812
813 static void
814 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
815                        int footnote_idx, int *min_width, int *max_width)
816 {
817   struct xr_driver *xr = xr_;
818   int bb[TABLE_N_AXES][2];
819   int clip[TABLE_N_AXES][2];
820   int h;
821
822   bb[H][0] = 0;
823   bb[H][1] = INT_MAX;
824   bb[V][0] = 0;
825   bb[V][1] = INT_MAX;
826   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
827   xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL);
828
829   bb[H][1] = 1;
830   xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL);
831
832   if (*min_width > 0)
833     *min_width += xr->cell_margin * 2;
834   if (*max_width > 0)
835     *max_width += xr->cell_margin * 2;
836 }
837
838 static int
839 xr_measure_cell_height (void *xr_, const struct table_cell *cell,
840                         int footnote_idx, int width)
841 {
842   struct xr_driver *xr = xr_;
843   int bb[TABLE_N_AXES][2];
844   int clip[TABLE_N_AXES][2];
845   int w, h;
846
847   bb[H][0] = 0;
848   bb[H][1] = width - xr->cell_margin * 2;
849   if (bb[H][1] <= 0)
850     return 0;
851   bb[V][0] = 0;
852   bb[V][1] = INT_MAX;
853   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
854   xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL);
855   return h;
856 }
857
858 static void
859 xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx,
860               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
861 {
862   struct xr_driver *xr = xr_;
863   int w, h, brk;
864
865   bb[H][0] += xr->cell_margin;
866   bb[H][1] -= xr->cell_margin;
867   if (bb[H][0] >= bb[H][1])
868     return;
869   xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
870 }
871
872 static int
873 xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx,
874                  int width, int height)
875 {
876   struct xr_driver *xr = xr_;
877   int bb[TABLE_N_AXES][2];
878   int clip[TABLE_N_AXES][2];
879   int w, h, brk;
880
881   if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height)
882     return -1;
883
884   bb[H][0] = 0;
885   bb[H][1] = width - 2 * xr->cell_margin;
886   if (bb[H][1] <= 0)
887     return 0;
888   bb[V][0] = 0;
889   bb[V][1] = height;
890   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
891   xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
892   return brk;
893 }
894 \f
895 static void
896 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
897 {
898   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
899     {
900       double x0 = xr_to_pt (clip[H][0] + xr->x);
901       double y0 = xr_to_pt (clip[V][0] + xr->y);
902       double x1 = xr_to_pt (clip[H][1] + xr->x);
903       double y1 = xr_to_pt (clip[V][1] + xr->y);
904
905       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
906       cairo_clip (xr->cairo);
907     }
908 }
909
910 static void
911 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
912 {
913   attr->start_index = start_index;
914   pango_attr_list_insert (list, attr);
915 }
916
917 static int
918 xr_layout_cell_text (struct xr_driver *xr,
919                      const struct cell_contents *contents, int footnote_idx,
920                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
921                      int y, int *widthp, int *brk)
922 {
923   unsigned int options = contents->options;
924   struct xr_font *font;
925   bool merge_footnotes;
926   size_t length;
927   int w, h;
928
929   if (contents->n_footnotes == 0)
930     merge_footnotes = false;
931   else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT)
932     {
933       PangoAttrList *attrs;
934       char marker[16];
935
936       font = &xr->fonts[XR_FONT_MARKER];
937
938       str_format_26adic (footnote_idx + 1, false, marker, sizeof marker);
939       pango_layout_set_text (font->layout, marker, strlen (marker));
940
941       attrs = pango_attr_list_new ();
942       pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
943       pango_layout_set_attributes (font->layout, attrs);
944       pango_attr_list_unref (attrs);
945
946       pango_layout_get_size (font->layout, &w, &h);
947       merge_footnotes = w > xr->cell_margin;
948       if (!merge_footnotes && clip[H][0] != clip[H][1])
949         {
950           cairo_save (xr->cairo);
951           xr_clip (xr, clip);
952           cairo_translate (xr->cairo,
953                            xr_to_pt (bb[H][1] + xr->x),
954                            xr_to_pt (y + xr->y));
955           pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
956           pango_layout_set_width (font->layout, -1);
957           pango_cairo_show_layout (xr->cairo, font->layout);
958           cairo_restore (xr->cairo);
959         }
960
961       pango_layout_set_attributes (font->layout, NULL);
962     }
963   else
964     merge_footnotes = true;
965
966   font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
967           : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
968           : &xr->fonts[XR_FONT_PROPORTIONAL]);
969
970   length = strlen (contents->text);
971   if (merge_footnotes)
972     {
973       PangoAttrList *attrs;
974       struct string s;
975       size_t i;
976
977       bb[H][1] += xr->cell_margin;
978
979       ds_init_empty (&s);
980       ds_extend (&s, length + contents->n_footnotes * 10);
981       ds_put_cstr (&s, contents->text);
982       for (i = 0; i < contents->n_footnotes; i++)
983         {
984           char marker[16];
985
986           if (i > 0)
987             ds_put_byte (&s, ',');
988           str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker);
989           ds_put_cstr (&s, marker);
990         }
991       pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
992       ds_destroy (&s);
993
994       attrs = pango_attr_list_new ();
995       add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
996       add_attr_with_start (
997         attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
998       pango_layout_set_attributes (font->layout, attrs);
999       pango_attr_list_unref (attrs);
1000     }
1001   else
1002     pango_layout_set_text (font->layout, contents->text, -1);
1003
1004   pango_layout_set_alignment (
1005     font->layout,
1006     ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1007      : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
1008      : PANGO_ALIGN_CENTER));
1009   pango_layout_set_width (
1010     font->layout,
1011     bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1012   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1013
1014   if (clip[H][0] != clip[H][1])
1015     {
1016       cairo_save (xr->cairo);
1017       xr_clip (xr, clip);
1018       cairo_translate (xr->cairo,
1019                        xr_to_pt (bb[H][0] + xr->x),
1020                        xr_to_pt (y + xr->y));
1021       pango_cairo_show_layout (xr->cairo, font->layout);
1022
1023       /* If enabled, this draws a blue rectangle around the extents of each
1024          line of text, which can be rather useful for debugging layout
1025          issues. */
1026       if (0)
1027         {
1028           PangoLayoutIter *iter;
1029           iter = pango_layout_get_iter (font->layout);
1030           do
1031             {
1032               PangoRectangle extents;
1033
1034               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1035               cairo_save (xr->cairo);
1036               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1037               dump_rectangle (xr,
1038                               pango_to_xr (extents.x) - xr->x,
1039                               pango_to_xr (extents.y) - xr->y,
1040                               pango_to_xr (extents.x + extents.width) - xr->x,
1041                               pango_to_xr (extents.y + extents.height) - xr->y);
1042               cairo_restore (xr->cairo);
1043             }
1044           while (pango_layout_iter_next_line (iter));
1045           pango_layout_iter_free (iter);
1046         }
1047
1048       cairo_restore (xr->cairo);
1049     }
1050
1051   pango_layout_get_size (font->layout, &w, &h);
1052   w = pango_to_xr (w);
1053   h = pango_to_xr (h);
1054   if (w > *widthp)
1055     *widthp = w;
1056   if (y + h >= bb[V][1])
1057     {
1058       PangoLayoutIter *iter;
1059       int best UNUSED = 0;
1060
1061       /* Choose a breakpoint between lines instead of in the middle of one. */
1062       iter = pango_layout_get_iter (font->layout);
1063       do
1064         {
1065           PangoRectangle extents;
1066           int y0, y1;
1067           int bottom;
1068
1069           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1070           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1071           extents.x = pango_to_xr (extents.x);
1072           extents.y = pango_to_xr (y0);
1073           extents.width = pango_to_xr (extents.width);
1074           extents.height = pango_to_xr (y1 - y0);
1075           bottom = y + extents.y + extents.height;
1076           if (bottom < bb[V][1])
1077             {
1078               if (brk && clip[H][0] != clip[H][1])
1079                 best = bottom;
1080               *brk = bottom;
1081             }
1082           else
1083             break;
1084         }
1085       while (pango_layout_iter_next_line (iter));
1086
1087       /* If enabled, draws a green line across the chosen breakpoint, which can
1088          be useful for debugging issues with breaking.  */
1089       if (0)
1090         {
1091           if (best && !xr->nest)
1092             {
1093               cairo_save (xr->cairo);
1094               cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1095               dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
1096               cairo_restore (xr->cairo);
1097             }
1098         }
1099     }
1100
1101   pango_layout_set_attributes (font->layout, NULL);
1102   return y + h;
1103 }
1104
1105 static int
1106 xr_layout_cell_subtable (struct xr_driver *xr,
1107                          const struct cell_contents *contents,
1108                          int footnote_idx UNUSED,
1109                          int bb[TABLE_N_AXES][2],
1110                          int clip[TABLE_N_AXES][2], int *widthp, int *brk)
1111 {
1112   int single_width, double_width;
1113   struct render_params params;
1114   struct render_pager *p;
1115   int r[TABLE_N_AXES][2];
1116   int width, height;
1117   int i;
1118
1119   params.draw_line = xr_draw_line;
1120   params.measure_cell_width = xr_measure_cell_width;
1121   params.measure_cell_height = xr_measure_cell_height;
1122   params.adjust_break = NULL;
1123   params.draw_cell = xr_draw_cell;
1124   params.aux = xr;
1125   params.size[H] = bb[H][1] - bb[H][0];
1126   params.size[V] = bb[V][1] - bb[V][0];
1127   params.font_size[H] = xr->char_width;
1128   params.font_size[V] = xr->char_height;
1129
1130   single_width = 2 * xr->line_gutter + xr->line_width;
1131   double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
1132   for (i = 0; i < TABLE_N_AXES; i++)
1133     {
1134       params.line_widths[i][RENDER_LINE_NONE] = 0;
1135       params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
1136       params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
1137     }
1138
1139   xr->nest++;
1140   p = render_pager_create (&params, contents->table);
1141   width = render_pager_get_size (p, H);
1142   height = render_pager_get_size (p, V);
1143   if (bb[V][0] + height >= bb[V][1])
1144     *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
1145
1146   /* r = intersect(bb, clip) - bb. */
1147   for (i = 0; i < TABLE_N_AXES; i++)
1148     {
1149       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
1150       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
1151     }
1152
1153   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
1154     {
1155       unsigned int alignment = contents->options & TAB_ALIGNMENT;
1156       int save_x = xr->x;
1157
1158       cairo_save (xr->cairo);
1159       xr_clip (xr, clip);
1160       xr->x += bb[H][0];
1161       if (alignment == TAB_RIGHT)
1162         xr->x += params.size[H] - width;
1163       else if (alignment == TAB_CENTER)
1164         xr->x += (params.size[H] - width) / 2;
1165       xr->y += bb[V][0];
1166       render_pager_draw_region (p, r[H][0], r[V][0],
1167                                 r[H][1] - r[H][0], r[V][1] - r[V][0]);
1168       xr->y -= bb[V][0];
1169       xr->x = save_x;
1170       cairo_restore (xr->cairo);
1171     }
1172   render_pager_destroy (p);
1173   xr->nest--;
1174
1175   if (width > *widthp)
1176     *widthp = width;
1177   return bb[V][0] + height;
1178 }
1179
1180 static void
1181 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1182                 int footnote_idx,
1183                 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1184                 int *width, int *height, int *brk)
1185 {
1186   int bb[TABLE_N_AXES][2];
1187   size_t i;
1188
1189   *width = 0;
1190   *height = 0;
1191   if (brk)
1192     *brk = 0;
1193
1194   memcpy (bb, bb_, sizeof bb);
1195
1196   /* If enabled, draws a blue rectangle around the cell extents, which can be
1197      useful for debugging layout. */
1198   if (0)
1199     {
1200       if (clip[H][0] != clip[H][1])
1201         {
1202           int offset = (xr->nest) * XR_POINT;
1203
1204           cairo_save (xr->cairo);
1205           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1206           dump_rectangle (xr,
1207                           bb[H][0] + offset, bb[V][0] + offset,
1208                           bb[H][1] - offset, bb[V][1] - offset);
1209           cairo_restore (xr->cairo);
1210         }
1211     }
1212
1213   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1214     {
1215       const struct cell_contents *contents = &cell->contents[i];
1216
1217       if (brk)
1218         *brk = bb[V][0];
1219       if (i > 0)
1220         {
1221           bb[V][0] += xr->char_height / 2;
1222           if (bb[V][0] >= bb[V][1])
1223             break;
1224           if (brk)
1225             *brk = bb[V][0];
1226         }
1227
1228       if (contents->text)
1229         bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip,
1230                                         bb[V][0], width, brk);
1231       else
1232         bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx,
1233                                             bb, clip, width, brk);
1234       footnote_idx += contents->n_footnotes;
1235     }
1236   *height = bb[V][0] - bb_[V][0];
1237 }
1238 \f
1239 struct output_driver_factory pdf_driver_factory =
1240   { "pdf", "pspp.pdf", xr_pdf_create };
1241 struct output_driver_factory ps_driver_factory =
1242   { "ps", "pspp.ps", xr_ps_create };
1243 struct output_driver_factory svg_driver_factory =
1244   { "svg", "pspp.svg", xr_svg_create };
1245
1246 static const struct output_driver_class cairo_driver_class =
1247 {
1248   "cairo",
1249   xr_destroy,
1250   xr_submit,
1251   xr_flush,
1252 };
1253 \f
1254 /* GUI rendering helpers. */
1255
1256 struct xr_rendering
1257   {
1258     struct output_item *item;
1259
1260     /* Table items. */
1261     struct render_pager *p;
1262     struct xr_driver *xr;
1263   };
1264
1265 #define CHART_WIDTH 500
1266 #define CHART_HEIGHT 375
1267
1268
1269
1270 struct xr_driver *
1271 xr_driver_create (cairo_t *cairo, struct string_map *options)
1272 {
1273   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1274   if (!xr_set_cairo (xr, cairo))
1275     {
1276       output_driver_destroy (&xr->driver);
1277       return NULL;
1278     }
1279   return xr;
1280 }
1281
1282 /* Destroy XR, which should have been created with xr_driver_create().  Any
1283    cairo_t added to XR is not destroyed, because it is owned by the client. */
1284 void
1285 xr_driver_destroy (struct xr_driver *xr)
1286 {
1287   if (xr != NULL)
1288     {
1289       xr->cairo = NULL;
1290       output_driver_destroy (&xr->driver);
1291     }
1292 }
1293
1294 static struct xr_rendering *
1295 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1296 {
1297   struct table_item *table_item;
1298   struct xr_rendering *r;
1299
1300   table_item = table_item_create (table_from_string (TAB_LEFT, text),
1301                                   NULL, NULL);
1302   r = xr_rendering_create (xr, &table_item->output_item, cr);
1303   table_item_unref (table_item);
1304
1305   return r;
1306 }
1307
1308 void 
1309 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1310 {
1311   if (is_table_item (xr->item))
1312     apply_options (xr->xr, o);
1313 }
1314
1315 struct xr_rendering *
1316 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1317                      cairo_t *cr)
1318 {
1319   struct xr_rendering *r = NULL;
1320
1321   if (is_text_item (item))
1322     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1323                                   cr);
1324   else if (is_message_item (item))
1325     {
1326       const struct message_item *message_item = to_message_item (item);
1327       const struct msg *msg = message_item_get_msg (message_item);
1328       char *s = msg_to_string (msg, NULL);
1329       r = xr_rendering_create_text (xr, s, cr);
1330       free (s);
1331     }
1332   else if (is_table_item (item))
1333     {
1334       r = xzalloc (sizeof *r);
1335       r->item = output_item_ref (item);
1336       r->xr = xr;
1337       xr_set_cairo (xr, cr);
1338       r->p = render_pager_create (xr->params, to_table_item (item));
1339     }
1340   else if (is_chart_item (item))
1341     {
1342       r = xzalloc (sizeof *r);
1343       r->item = output_item_ref (item);
1344     }
1345
1346   return r;
1347 }
1348
1349 void
1350 xr_rendering_destroy (struct xr_rendering *r)
1351 {
1352   if (r)
1353     {
1354       output_item_unref (r->item);
1355       render_pager_destroy (r->p);
1356       free (r);
1357     }
1358 }
1359
1360 void
1361 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1362 {
1363   if (is_table_item (r->item))
1364     {
1365       *w = render_pager_get_size (r->p, H) / XR_POINT;
1366       *h = render_pager_get_size (r->p, V) / XR_POINT;
1367     }
1368   else
1369     {
1370       *w = CHART_WIDTH;
1371       *h = CHART_HEIGHT;
1372     }
1373 }
1374
1375 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1376                     double x, double y, double width, double height);
1377
1378 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1379    and possibly some additional parts. */
1380 void
1381 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1382                    int x, int y, int w, int h)
1383 {
1384   if (is_table_item (r->item))
1385     {
1386       struct xr_driver *xr = r->xr;
1387
1388       xr_set_cairo (xr, cr);
1389
1390       xr->y = 0;
1391       render_pager_draw_region (r->p,
1392                                 x * XR_POINT, y * XR_POINT,
1393                                 w * XR_POINT, h * XR_POINT);
1394     }
1395   else
1396     xr_draw_chart (to_chart_item (r->item), cr,
1397                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1398 }
1399
1400 static void
1401 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1402                double x, double y, double width, double height)
1403 {
1404   struct xrchart_geometry geom;
1405
1406   cairo_save (cr);
1407   cairo_translate (cr, x, y + height);
1408   cairo_scale (cr, 1.0, -1.0);
1409   xrchart_geometry_init (cr, &geom, width, height);
1410   if (is_boxplot (chart_item))
1411     xrchart_draw_boxplot (chart_item, cr, &geom);
1412   else if (is_histogram_chart (chart_item))
1413     xrchart_draw_histogram (chart_item, cr, &geom);
1414   else if (is_np_plot_chart (chart_item))
1415     xrchart_draw_np_plot (chart_item, cr, &geom);
1416   else if (is_piechart (chart_item))
1417     xrchart_draw_piechart (chart_item, cr, &geom);
1418   else if (is_roc_chart (chart_item))
1419     xrchart_draw_roc (chart_item, cr, &geom);
1420   else if (is_scree (chart_item))
1421     xrchart_draw_scree (chart_item, cr, &geom);
1422   else if (is_spreadlevel_plot_chart (chart_item))
1423     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1424   else if (is_scatterplot_chart (chart_item))
1425     xrchart_draw_scatterplot (chart_item, cr, &geom);
1426   else
1427     NOT_REACHED ();
1428   xrchart_geometry_free (cr, &geom);
1429
1430   cairo_restore (cr);
1431 }
1432
1433 char *
1434 xr_draw_png_chart (const struct chart_item *item,
1435                    const char *file_name_template, int number,
1436                    const struct xr_color *fg,
1437                    const struct xr_color *bg
1438                    )
1439 {
1440   const int width = 640;
1441   const int length = 480;
1442
1443   cairo_surface_t *surface;
1444   cairo_status_t status;
1445   const char *number_pos;
1446   char *file_name;
1447   cairo_t *cr;
1448
1449   number_pos = strchr (file_name_template, '#');
1450   if (number_pos != NULL)
1451     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1452                            file_name_template, number, number_pos + 1);
1453   else
1454     file_name = xstrdup (file_name_template);
1455
1456   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1457   cr = cairo_create (surface);
1458
1459   cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1460   cairo_paint (cr);
1461
1462   cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1463
1464   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1465
1466   status = cairo_surface_write_to_png (surface, file_name);
1467   if (status != CAIRO_STATUS_SUCCESS)
1468     msg (ME, _("error writing output file `%s': %s"),
1469            file_name, cairo_status_to_string (status));
1470
1471   cairo_destroy (cr);
1472   cairo_surface_destroy (surface);
1473
1474   return file_name;
1475 }
1476 \f
1477 struct xr_table_state
1478   {
1479     struct xr_render_fsm fsm;
1480     struct table_item *table_item;
1481     struct render_pager *p;
1482   };
1483
1484 static bool
1485 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1486 {
1487   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1488
1489   while (render_pager_has_next (ts->p))
1490     {
1491       int used;
1492
1493       used = render_pager_draw_next (ts->p, xr->length - xr->y);
1494       if (!used)
1495         {
1496           assert (xr->y > 0);
1497           return true;
1498         }
1499       else
1500         xr->y += used;
1501     }
1502   return false;
1503 }
1504
1505 static void
1506 xr_table_destroy (struct xr_render_fsm *fsm)
1507 {
1508   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1509
1510   table_item_unref (ts->table_item);
1511   render_pager_destroy (ts->p);
1512   free (ts);
1513 }
1514
1515 static struct xr_render_fsm *
1516 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1517 {
1518   struct xr_table_state *ts;
1519
1520   ts = xmalloc (sizeof *ts);
1521   ts->fsm.render = xr_table_render;
1522   ts->fsm.destroy = xr_table_destroy;
1523   ts->table_item = table_item_ref (table_item);
1524
1525   if (xr->y > 0)
1526     xr->y += xr->char_height;
1527
1528   ts->p = render_pager_create (xr->params, table_item);
1529
1530   return &ts->fsm;
1531 }
1532 \f
1533 struct xr_chart_state
1534   {
1535     struct xr_render_fsm fsm;
1536     struct chart_item *chart_item;
1537   };
1538
1539 static bool
1540 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1541 {
1542   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1543
1544   if (xr->y > 0)
1545     return true;
1546
1547   if (xr->cairo != NULL)
1548     xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1549                    xr_to_pt (xr->width), xr_to_pt (xr->length));
1550   xr->y = xr->length;
1551
1552   return false;
1553 }
1554
1555 static void
1556 xr_chart_destroy (struct xr_render_fsm *fsm)
1557 {
1558   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1559
1560   chart_item_unref (cs->chart_item);
1561   free (cs);
1562 }
1563
1564 static struct xr_render_fsm *
1565 xr_render_chart (const struct chart_item *chart_item)
1566 {
1567   struct xr_chart_state *cs;
1568
1569   cs = xmalloc (sizeof *cs);
1570   cs->fsm.render = xr_chart_render;
1571   cs->fsm.destroy = xr_chart_destroy;
1572   cs->chart_item = chart_item_ref (chart_item);
1573
1574   return &cs->fsm;
1575 }
1576 \f
1577 static bool
1578 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1579 {
1580   return xr->y > 0;
1581 }
1582
1583 static void
1584 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1585 {
1586   /* Nothing to do. */
1587 }
1588
1589 static struct xr_render_fsm *
1590 xr_render_eject (void)
1591 {
1592   static struct xr_render_fsm eject_renderer =
1593     {
1594       xr_eject_render,
1595       xr_eject_destroy
1596     };
1597
1598   return &eject_renderer;
1599 }
1600 \f
1601 static struct xr_render_fsm *
1602 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1603 {
1604   struct table_item *table_item;
1605   struct xr_render_fsm *fsm;
1606
1607   table_item = table_item_create (table_from_string (TAB_LEFT, text),
1608                                   NULL, NULL);
1609   fsm = xr_render_table (xr, table_item);
1610   table_item_unref (table_item);
1611
1612   return fsm;
1613 }
1614
1615 static struct xr_render_fsm *
1616 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1617 {
1618   enum text_item_type type = text_item_get_type (text_item);
1619   const char *text = text_item_get_text (text_item);
1620
1621   switch (type)
1622     {
1623     case TEXT_ITEM_TITLE:
1624       free (xr->title);
1625       xr->title = xstrdup (text);
1626       break;
1627
1628     case TEXT_ITEM_SUBTITLE:
1629       free (xr->subtitle);
1630       xr->subtitle = xstrdup (text);
1631       break;
1632
1633     case TEXT_ITEM_COMMAND_CLOSE:
1634       break;
1635
1636     case TEXT_ITEM_BLANK_LINE:
1637       if (xr->y > 0)
1638         xr->y += xr->char_height;
1639       break;
1640
1641     case TEXT_ITEM_EJECT_PAGE:
1642       if (xr->y > 0)
1643         return xr_render_eject ();
1644       break;
1645
1646     default:
1647       return xr_create_text_renderer (xr, text);
1648     }
1649
1650   return NULL;
1651 }
1652
1653 static struct xr_render_fsm *
1654 xr_render_message (struct xr_driver *xr,
1655                    const struct message_item *message_item)
1656 {
1657   const struct msg *msg = message_item_get_msg (message_item);
1658   struct xr_render_fsm *fsm;
1659   char *s;
1660
1661   s = msg_to_string (msg, xr->command_name);
1662   fsm = xr_create_text_renderer (xr, s);
1663   free (s);
1664
1665   return fsm;
1666 }
1667
1668 static struct xr_render_fsm *
1669 xr_render_output_item (struct xr_driver *xr,
1670                        const struct output_item *output_item)
1671 {
1672   if (is_table_item (output_item))
1673     return xr_render_table (xr, to_table_item (output_item));
1674   else if (is_chart_item (output_item))
1675     return xr_render_chart (to_chart_item (output_item));
1676   else if (is_text_item (output_item))
1677     return xr_render_text (xr, to_text_item (output_item));
1678   else if (is_message_item (output_item))
1679     return xr_render_message (xr, to_message_item (output_item));
1680   else
1681     return NULL;
1682 }