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