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