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