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