Add support for reading and writing SPV files.
[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/hash-functions.h"
24 #include "libpspp/message.h"
25 #include "libpspp/pool.h"
26 #include "libpspp/start-date.h"
27 #include "libpspp/str.h"
28 #include "libpspp/string-map.h"
29 #include "libpspp/version.h"
30 #include "data/file-handle-def.h"
31 #include "output/cairo-chart.h"
32 #include "output/chart-item-provider.h"
33 #include "output/charts/boxplot.h"
34 #include "output/charts/np-plot.h"
35 #include "output/charts/piechart.h"
36 #include "output/charts/barchart.h"
37 #include "output/charts/plot-hist.h"
38 #include "output/charts/roc-chart.h"
39 #include "output/charts/spreadlevel-plot.h"
40 #include "output/charts/scree.h"
41 #include "output/charts/scatterplot.h"
42 #include "output/driver-provider.h"
43 #include "output/group-item.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/page-setup-item.h"
47 #include "output/pivot-table.h"
48 #include "output/render.h"
49 #include "output/table-item.h"
50 #include "output/table.h"
51 #include "output/text-item.h"
52
53 #include <cairo/cairo-pdf.h>
54 #include <cairo/cairo-ps.h>
55 #include <cairo/cairo-svg.h>
56 #include <cairo/cairo.h>
57 #include <inttypes.h>
58 #include <math.h>
59 #include <pango/pango-font.h>
60 #include <pango/pango-layout.h>
61 #include <pango/pango.h>
62 #include <pango/pangocairo.h>
63 #include <stdlib.h>
64
65 #include "gl/c-ctype.h"
66 #include "gl/c-strcase.h"
67 #include "gl/intprops.h"
68 #include "gl/minmax.h"
69 #include "gl/xalloc.h"
70
71 #include "gettext.h"
72 #define _(msgid) gettext (msgid)
73
74 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
75 #define H TABLE_HORZ
76 #define V TABLE_VERT
77
78 /* The unit used for internal measurements is inch/(72 * XR_POINT).
79    (Thus, XR_POINT units represent one point.) */
80 #define XR_POINT PANGO_SCALE
81
82 /* Conversions to and from points. */
83 static double
84 xr_to_pt (int x)
85 {
86   return x / (double) XR_POINT;
87 }
88
89 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
90 static int
91 px_to_xr (int x)
92 {
93   return x * (PANGO_SCALE * 72 / 96);
94 }
95
96 /* Dimensions for drawing lines in tables. */
97 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
98 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
99
100 /* Output types. */
101 enum xr_output_type
102   {
103     XR_PDF,
104     XR_PS,
105     XR_SVG
106   };
107
108 /* Cairo fonts. */
109 enum xr_font_type
110   {
111     XR_FONT_PROPORTIONAL,
112     XR_FONT_EMPHASIS,
113     XR_FONT_FIXED,
114     XR_N_FONTS
115   };
116
117 /* A font for use with Cairo. */
118 struct xr_font
119   {
120     PangoFontDescription *desc;
121     PangoLayout *layout;
122   };
123
124 /* An output item whose rendering is in progress. */
125 struct xr_render_fsm
126   {
127     /* Renders as much of itself as it can on the current page.  Returns true
128        if rendering is complete, false if the output item needs another
129        page. */
130     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
131
132     /* Destroys the output item. */
133     void (*destroy) (struct xr_render_fsm *);
134   };
135
136 /* Cairo output driver. */
137 struct xr_driver
138   {
139     struct output_driver driver;
140
141     /* User parameters. */
142     struct xr_font fonts[XR_N_FONTS];
143
144     int width;                  /* Page width minus margins. */
145     int length;                 /* Page length minus margins and header. */
146
147     int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
148     int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
149     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
150     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
151
152     int line_space;             /* Space between lines. */
153     int line_width;             /* Width of lines. */
154
155     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
156     int object_spacing;         /* Space between output objects. */
157
158     struct cell_color bg;       /* Background color */
159     struct cell_color fg;       /* Foreground color */
160
161     int initial_page_number;
162
163     struct page_heading headings[2]; /* Top and bottom headings. */
164     int headings_height[2];
165
166     /* Internal state. */
167     struct render_params *params;
168     int char_width, char_height;
169     char *command_name;
170     char *title;
171     char *subtitle;
172     cairo_t *cairo;
173     cairo_surface_t *surface;
174     int page_number;            /* Current page number. */
175     int x, y;
176     struct xr_render_fsm *fsm;
177     int nest;
178     struct string_map heading_vars;
179   };
180
181 static const struct output_driver_class cairo_driver_class;
182
183 static void xr_driver_destroy_fsm (struct xr_driver *);
184 static void xr_driver_run_fsm (struct xr_driver *);
185
186 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
187                           enum render_line_style styles[TABLE_N_AXES][2],
188                           struct cell_color colors[TABLE_N_AXES][2]);
189 static void xr_measure_cell_width (void *, const struct table_cell *,
190                                    int *min, int *max);
191 static int xr_measure_cell_height (void *, const struct table_cell *,
192                                    int width);
193 static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
194                           int bb[TABLE_N_AXES][2],
195                           int spill[TABLE_N_AXES][2],
196                           int clip[TABLE_N_AXES][2]);
197 static int xr_adjust_break (void *, const struct table_cell *,
198                             int width, int height);
199
200 static struct xr_render_fsm *xr_render_output_item (
201   struct xr_driver *, const struct output_item *);
202 \f
203 /* Output driver basics. */
204
205 static struct xr_driver *
206 xr_driver_cast (struct output_driver *driver)
207 {
208   assert (driver->class == &cairo_driver_class);
209   return UP_CAST (driver, struct xr_driver, driver);
210 }
211
212 static struct driver_option *
213 opt (struct output_driver *d, struct string_map *options, const char *key,
214      const char *default_value)
215 {
216   return driver_option_get (d, options, key, default_value);
217 }
218
219 static int
220 lookup_color_name (const char *s)
221 {
222   struct color
223     {
224       struct hmap_node hmap_node;
225       const char *name;
226       int code;
227     };
228
229   static struct color colors[] =
230     {
231       { .name = "aliceblue", .code = 0xf0f8ff },
232       { .name = "antiquewhite", .code = 0xfaebd7 },
233       { .name = "aqua", .code = 0x00ffff },
234       { .name = "aquamarine", .code = 0x7fffd4 },
235       { .name = "azure", .code = 0xf0ffff },
236       { .name = "beige", .code = 0xf5f5dc },
237       { .name = "bisque", .code = 0xffe4c4 },
238       { .name = "black", .code = 0x000000 },
239       { .name = "blanchedalmond", .code = 0xffebcd },
240       { .name = "blue", .code = 0x0000ff },
241       { .name = "blueviolet", .code = 0x8a2be2 },
242       { .name = "brown", .code = 0xa52a2a },
243       { .name = "burlywood", .code = 0xdeb887 },
244       { .name = "cadetblue", .code = 0x5f9ea0 },
245       { .name = "chartreuse", .code = 0x7fff00 },
246       { .name = "chocolate", .code = 0xd2691e },
247       { .name = "coral", .code = 0xff7f50 },
248       { .name = "cornflowerblue", .code = 0x6495ed },
249       { .name = "cornsilk", .code = 0xfff8dc },
250       { .name = "crimson", .code = 0xdc143c },
251       { .name = "cyan", .code = 0x00ffff },
252       { .name = "darkblue", .code = 0x00008b },
253       { .name = "darkcyan", .code = 0x008b8b },
254       { .name = "darkgoldenrod", .code = 0xb8860b },
255       { .name = "darkgray", .code = 0xa9a9a9 },
256       { .name = "darkgreen", .code = 0x006400 },
257       { .name = "darkgrey", .code = 0xa9a9a9 },
258       { .name = "darkkhaki", .code = 0xbdb76b },
259       { .name = "darkmagenta", .code = 0x8b008b },
260       { .name = "darkolivegreen", .code = 0x556b2f },
261       { .name = "darkorange", .code = 0xff8c00 },
262       { .name = "darkorchid", .code = 0x9932cc },
263       { .name = "darkred", .code = 0x8b0000 },
264       { .name = "darksalmon", .code = 0xe9967a },
265       { .name = "darkseagreen", .code = 0x8fbc8f },
266       { .name = "darkslateblue", .code = 0x483d8b },
267       { .name = "darkslategray", .code = 0x2f4f4f },
268       { .name = "darkslategrey", .code = 0x2f4f4f },
269       { .name = "darkturquoise", .code = 0x00ced1 },
270       { .name = "darkviolet", .code = 0x9400d3 },
271       { .name = "deeppink", .code = 0xff1493 },
272       { .name = "deepskyblue", .code = 0x00bfff },
273       { .name = "dimgray", .code = 0x696969 },
274       { .name = "dimgrey", .code = 0x696969 },
275       { .name = "dodgerblue", .code = 0x1e90ff },
276       { .name = "firebrick", .code = 0xb22222 },
277       { .name = "floralwhite", .code = 0xfffaf0 },
278       { .name = "forestgreen", .code = 0x228b22 },
279       { .name = "fuchsia", .code = 0xff00ff },
280       { .name = "gainsboro", .code = 0xdcdcdc },
281       { .name = "ghostwhite", .code = 0xf8f8ff },
282       { .name = "gold", .code = 0xffd700 },
283       { .name = "goldenrod", .code = 0xdaa520 },
284       { .name = "gray", .code = 0x808080 },
285       { .name = "green", .code = 0x008000 },
286       { .name = "greenyellow", .code = 0xadff2f },
287       { .name = "grey", .code = 0x808080 },
288       { .name = "honeydew", .code = 0xf0fff0 },
289       { .name = "hotpink", .code = 0xff69b4 },
290       { .name = "indianred", .code = 0xcd5c5c },
291       { .name = "indigo", .code = 0x4b0082 },
292       { .name = "ivory", .code = 0xfffff0 },
293       { .name = "khaki", .code = 0xf0e68c },
294       { .name = "lavender", .code = 0xe6e6fa },
295       { .name = "lavenderblush", .code = 0xfff0f5 },
296       { .name = "lawngreen", .code = 0x7cfc00 },
297       { .name = "lemonchiffon", .code = 0xfffacd },
298       { .name = "lightblue", .code = 0xadd8e6 },
299       { .name = "lightcoral", .code = 0xf08080 },
300       { .name = "lightcyan", .code = 0xe0ffff },
301       { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
302       { .name = "lightgray", .code = 0xd3d3d3 },
303       { .name = "lightgreen", .code = 0x90ee90 },
304       { .name = "lightgrey", .code = 0xd3d3d3 },
305       { .name = "lightpink", .code = 0xffb6c1 },
306       { .name = "lightsalmon", .code = 0xffa07a },
307       { .name = "lightseagreen", .code = 0x20b2aa },
308       { .name = "lightskyblue", .code = 0x87cefa },
309       { .name = "lightslategray", .code = 0x778899 },
310       { .name = "lightslategrey", .code = 0x778899 },
311       { .name = "lightsteelblue", .code = 0xb0c4de },
312       { .name = "lightyellow", .code = 0xffffe0 },
313       { .name = "lime", .code = 0x00ff00 },
314       { .name = "limegreen", .code = 0x32cd32 },
315       { .name = "linen", .code = 0xfaf0e6 },
316       { .name = "magenta", .code = 0xff00ff },
317       { .name = "maroon", .code = 0x800000 },
318       { .name = "mediumaquamarine", .code = 0x66cdaa },
319       { .name = "mediumblue", .code = 0x0000cd },
320       { .name = "mediumorchid", .code = 0xba55d3 },
321       { .name = "mediumpurple", .code = 0x9370db },
322       { .name = "mediumseagreen", .code = 0x3cb371 },
323       { .name = "mediumslateblue", .code = 0x7b68ee },
324       { .name = "mediumspringgreen", .code = 0x00fa9a },
325       { .name = "mediumturquoise", .code = 0x48d1cc },
326       { .name = "mediumvioletred", .code = 0xc71585 },
327       { .name = "midnightblue", .code = 0x191970 },
328       { .name = "mintcream", .code = 0xf5fffa },
329       { .name = "mistyrose", .code = 0xffe4e1 },
330       { .name = "moccasin", .code = 0xffe4b5 },
331       { .name = "navajowhite", .code = 0xffdead },
332       { .name = "navy", .code = 0x000080 },
333       { .name = "oldlace", .code = 0xfdf5e6 },
334       { .name = "olive", .code = 0x808000 },
335       { .name = "olivedrab", .code = 0x6b8e23 },
336       { .name = "orange", .code = 0xffa500 },
337       { .name = "orangered", .code = 0xff4500 },
338       { .name = "orchid", .code = 0xda70d6 },
339       { .name = "palegoldenrod", .code = 0xeee8aa },
340       { .name = "palegreen", .code = 0x98fb98 },
341       { .name = "paleturquoise", .code = 0xafeeee },
342       { .name = "palevioletred", .code = 0xdb7093 },
343       { .name = "papayawhip", .code = 0xffefd5 },
344       { .name = "peachpuff", .code = 0xffdab9 },
345       { .name = "peru", .code = 0xcd853f },
346       { .name = "pink", .code = 0xffc0cb },
347       { .name = "plum", .code = 0xdda0dd },
348       { .name = "powderblue", .code = 0xb0e0e6 },
349       { .name = "purple", .code = 0x800080 },
350       { .name = "red", .code = 0xff0000 },
351       { .name = "rosybrown", .code = 0xbc8f8f },
352       { .name = "royalblue", .code = 0x4169e1 },
353       { .name = "saddlebrown", .code = 0x8b4513 },
354       { .name = "salmon", .code = 0xfa8072 },
355       { .name = "sandybrown", .code = 0xf4a460 },
356       { .name = "seagreen", .code = 0x2e8b57 },
357       { .name = "seashell", .code = 0xfff5ee },
358       { .name = "sienna", .code = 0xa0522d },
359       { .name = "silver", .code = 0xc0c0c0 },
360       { .name = "skyblue", .code = 0x87ceeb },
361       { .name = "slateblue", .code = 0x6a5acd },
362       { .name = "slategray", .code = 0x708090 },
363       { .name = "slategrey", .code = 0x708090 },
364       { .name = "snow", .code = 0xfffafa },
365       { .name = "springgreen", .code = 0x00ff7f },
366       { .name = "steelblue", .code = 0x4682b4 },
367       { .name = "tan", .code = 0xd2b48c },
368       { .name = "teal", .code = 0x008080 },
369       { .name = "thistle", .code = 0xd8bfd8 },
370       { .name = "tomato", .code = 0xff6347 },
371       { .name = "turquoise", .code = 0x40e0d0 },
372       { .name = "violet", .code = 0xee82ee },
373       { .name = "wheat", .code = 0xf5deb3 },
374       { .name = "white", .code = 0xffffff },
375       { .name = "whitesmoke", .code = 0xf5f5f5 },
376       { .name = "yellow", .code = 0xffff00 },
377       { .name = "yellowgreen", .code = 0x9acd32 },
378     };
379
380   static struct hmap color_table = HMAP_INITIALIZER (color_table);
381
382   if (hmap_is_empty (&color_table))
383     for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
384       hmap_insert (&color_table, &colors[i].hmap_node,
385                    hash_string (colors[i].name, 0));
386
387   const struct color *color;
388   HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
389                            hash_string (s, 0), &color_table)
390     if (!strcmp (color->name, s))
391       return color->code;
392   return -1;
393 }
394
395 static bool
396 parse_color__ (const char *s, struct cell_color *color)
397 {
398   /* #rrrrggggbbbb */
399   uint16_t r16, g16, b16;
400   int len;
401   if (sscanf (s, "#%4"SCNx16"%4"SCNx16"%4"SCNx16"%n",
402               &r16, &g16, &b16, &len) == 3
403       && len == 13
404       && !s[len])
405     {
406       color->r = r16 >> 8;
407       color->g = g16 >> 8;
408       color->b = b16 >> 8;
409       return true;
410     }
411
412   /* #rrggbb */
413   uint8_t r, g, b;
414   if (sscanf (s, "#%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
415       && len == 7
416       && !s[len])
417     {
418       color->r = r;
419       color->g = g;
420       color->b = b;
421       return true;
422     }
423
424   /* rrggbb */
425   if (sscanf (s, "%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
426       && len == 6
427       && !s[len])
428     {
429       color->r = r;
430       color->g = g;
431       color->b = b;
432       return true;
433     }
434
435   /* rgb(r,g,b) */
436   if (sscanf (s, "rgb (%"SCNi8" , %"SCNi8" , %"SCNi8" ) %n",
437               &r, &g, &b, &len) == 3
438       && !s[len])
439     {
440       color->r = r;
441       color->g = g;
442       color->b = b;
443       return true;
444     }
445
446   /* rgba(r,g,b,a), ignoring a. */
447   if (sscanf (s, "rgba (%"SCNi8" , %"SCNi8" , %"SCNi8", %*f ) %n",
448               &r, &g, &b, &len) == 3
449       && !s[len])
450     {
451       color->r = r;
452       color->g = g;
453       color->b = b;
454       return true;
455     }
456
457   int code = lookup_color_name (s);
458   if (code >= 0)
459     {
460       color->r = code >> 16;
461       color->g = code >> 8;
462       color->b = code;
463       return true;
464     }
465
466   return false;
467 }
468
469 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
470    Currently, the input string must be of the form "#RRRRGGGGBBBB"
471    Future implementations might allow things like "yellow" and
472    "sky-blue-ultra-brown"
473 */
474 void
475 parse_color (struct output_driver *d, struct string_map *options,
476              const char *key, const char *default_value,
477              struct cell_color *color)
478 {
479   char *string = parse_string (opt (d, options, key, default_value));
480   if (!parse_color__ (string, color) && !parse_color__ (default_value, color))
481     *color = (struct cell_color) CELL_COLOR_BLACK;
482   free (string);
483 }
484
485 static PangoFontDescription *
486 parse_font (const char *font, int default_size, bool bold, bool italic)
487 {
488   if (!c_strcasecmp (font, "Monospaced"))
489     font = "Monospace";
490
491   PangoFontDescription *desc = pango_font_description_from_string (font);
492   if (desc == NULL)
493     return NULL;
494
495   /* If the font description didn't include an explicit font size, then set it
496      to DEFAULT_SIZE, which is in inch/72000 units. */
497   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
498     pango_font_description_set_size (desc,
499                                      (default_size / 1000.0) * PANGO_SCALE);
500
501   pango_font_description_set_weight (desc, (bold
502                                             ? PANGO_WEIGHT_BOLD
503                                             : PANGO_WEIGHT_NORMAL));
504   pango_font_description_set_style (desc, (italic
505                                            ? PANGO_STYLE_ITALIC
506                                            : PANGO_STYLE_NORMAL));
507
508   return desc;
509 }
510
511 static PangoFontDescription *
512 parse_font_option (struct output_driver *d, struct string_map *options,
513                    const char *key, const char *default_value,
514                    int default_size, bool bold, bool italic)
515 {
516   char *string = parse_string (opt (d, options, key, default_value));
517   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
518   if (!desc)
519     {
520       msg (MW, _("`%s': bad font specification"), string);
521
522       /* Fall back to DEFAULT_VALUE, which had better be a valid font
523          description. */
524       desc = parse_font (default_value, default_size, bold, italic);
525       assert (desc != NULL);
526     }
527   free (string);
528
529   return desc;
530 }
531
532 static void
533 apply_options (struct xr_driver *xr, struct string_map *o)
534 {
535   struct output_driver *d = &xr->driver;
536
537   /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
538   int left_margin, right_margin;
539   int top_margin, bottom_margin;
540   int paper_width, paper_length;
541   int font_size;
542   int min_break[TABLE_N_AXES];
543
544   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
545   const double scale = XR_POINT / 1000.;
546
547   int i;
548
549   for (i = 0; i < XR_N_FONTS; i++)
550     {
551       struct xr_font *font = &xr->fonts[i];
552
553       if (font->desc != NULL)
554         pango_font_description_free (font->desc);
555     }
556
557   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
558   xr->fonts[XR_FONT_FIXED].desc = parse_font_option
559     (d, o, "fixed-font", "monospace", font_size, false, false);
560   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font_option (
561     d, o, "prop-font", "sans serif", font_size, false, false);
562   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option (
563     d, o, "emph-font", "sans serif", font_size, false, true);
564
565   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
566   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
567
568   /* Get dimensions.  */
569   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
570   left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
571   right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
572   top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
573   bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
574
575   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
576   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
577
578   int object_spacing = (parse_dimension (opt (d, o, "object-spacing", NULL))
579                         * scale);
580
581   /* Convert to inch/(XR_POINT * 72). */
582   xr->left_margin = left_margin * scale;
583   xr->right_margin = right_margin * scale;
584   xr->top_margin = top_margin * scale;
585   xr->bottom_margin = bottom_margin * scale;
586   xr->width = (paper_width - left_margin - right_margin) * scale;
587   xr->length = (paper_length - top_margin - bottom_margin) * scale;
588   xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
589   xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
590   xr->object_spacing = object_spacing >= 0 ? object_spacing : XR_POINT * 12;
591
592   /* There are no headings so headings_height can stay 0. */
593 }
594
595 static struct xr_driver *
596 xr_allocate (const char *name, int device_type, struct string_map *o)
597 {
598   struct xr_driver *xr = xzalloc (sizeof *xr);
599   struct output_driver *d = &xr->driver;
600
601   output_driver_init (d, &cairo_driver_class, name, device_type);
602
603   string_map_init (&xr->heading_vars);
604
605   apply_options (xr, o);
606
607   return xr;
608 }
609
610 static int
611 pango_to_xr (int pango)
612 {
613   return (XR_POINT != PANGO_SCALE
614           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
615           : pango);
616 }
617
618 static int
619 xr_to_pango (int xr)
620 {
621   return (XR_POINT != PANGO_SCALE
622           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
623           : xr);
624 }
625
626 static void
627 xr_measure_fonts (cairo_t *cairo, const struct xr_font fonts[XR_N_FONTS],
628                   int *char_width, int *char_height)
629 {
630   *char_width = 0;
631   *char_height = 0;
632   for (int i = 0; i < XR_N_FONTS; i++)
633     {
634       PangoLayout *layout = pango_cairo_create_layout (cairo);
635       pango_layout_set_font_description (layout, fonts[i].desc);
636
637       pango_layout_set_text (layout, "0", 1);
638
639       int cw, ch;
640       pango_layout_get_size (layout, &cw, &ch);
641       *char_width = MAX (*char_width, pango_to_xr (cw));
642       *char_height = MAX (*char_height, pango_to_xr (ch));
643
644       g_object_unref (G_OBJECT (layout));
645     }
646 }
647
648 static int
649 get_layout_height (PangoLayout *layout)
650 {
651   int w, h;
652   pango_layout_get_size (layout, &w, &h);
653   return h;
654 }
655
656 static int
657 xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font,
658                         const struct page_heading *ph, int page_number,
659                         int width, bool draw, int base_y)
660 {
661   PangoLayout *layout = pango_cairo_create_layout (cairo);
662   pango_layout_set_font_description (layout, font);
663
664   int y = 0;
665   for (size_t i = 0; i < ph->n; i++)
666     {
667       const struct page_paragraph *pp = &ph->paragraphs[i];
668
669       char *markup = output_driver_substitute_heading_vars (pp->markup,
670                                                             page_number);
671       pango_layout_set_markup (layout, markup, -1);
672       free (markup);
673
674       pango_layout_set_alignment (
675         layout,
676         (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
677          : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER
678          : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT
679          : PANGO_ALIGN_RIGHT));
680       pango_layout_set_width (layout, xr_to_pango (width));
681       if (draw)
682         {
683           cairo_save (cairo);
684           cairo_translate (cairo, 0, xr_to_pt (y + base_y));
685           pango_cairo_show_layout (cairo, layout);
686           cairo_restore (cairo);
687         }
688
689       y += pango_to_xr (get_layout_height (layout));
690     }
691
692   g_object_unref (G_OBJECT (layout));
693
694   return y;
695 }
696
697 static int
698 xr_measure_headings (cairo_surface_t *surface,
699                      const PangoFontDescription *font,
700                      const struct page_heading headings[2],
701                      int width, int object_spacing, int height[2])
702 {
703   cairo_t *cairo = cairo_create (surface);
704   int total = 0;
705   for (int i = 0; i < 2; i++)
706     {
707       int h = xr_render_page_heading (cairo, font, &headings[i], -1,
708                                       width, false, 0);
709
710       /* If the top heading is nonempty, add some space below it. */
711       if (h && i == 0)
712         h += object_spacing;
713
714       if (height)
715         height[i] = h;
716       total += h;
717     }
718   cairo_destroy (cairo);
719   return total;
720 }
721
722 static bool
723 xr_check_fonts (cairo_surface_t *surface,
724                 const struct xr_font fonts[XR_N_FONTS],
725                 int usable_width, int usable_length)
726 {
727   cairo_t *cairo = cairo_create (surface);
728   int char_width, char_height;
729   xr_measure_fonts (cairo, fonts, &char_width, &char_height);
730   cairo_destroy (cairo);
731
732   bool ok = true;
733   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
734   if (usable_width / char_width < MIN_WIDTH)
735     {
736       msg (ME, _("The defined page is not wide enough to hold at least %d "
737                  "characters in the default font.  In fact, there's only "
738                  "room for %d characters."),
739            MIN_WIDTH, usable_width / char_width);
740       ok = false;
741     }
742   if (usable_length / char_height < MIN_LENGTH)
743     {
744       msg (ME, _("The defined page is not long enough to hold at least %d "
745                  "lines in the default font.  In fact, there's only "
746                  "room for %d lines."),
747            MIN_LENGTH, usable_length / char_height);
748       ok = false;
749     }
750   return ok;
751 }
752
753 static void
754 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
755 {
756   xr->cairo = cairo;
757
758   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
759
760   xr_measure_fonts (xr->cairo, xr->fonts, &xr->char_width, &xr->char_height);
761
762   for (int i = 0; i < XR_N_FONTS; i++)
763     {
764       struct xr_font *font = &xr->fonts[i];
765       font->layout = pango_cairo_create_layout (cairo);
766       pango_layout_set_font_description (font->layout, font->desc);
767     }
768
769   if (xr->params == NULL)
770     {
771       xr->params = xmalloc (sizeof *xr->params);
772       xr->params->draw_line = xr_draw_line;
773       xr->params->measure_cell_width = xr_measure_cell_width;
774       xr->params->measure_cell_height = xr_measure_cell_height;
775       xr->params->adjust_break = xr_adjust_break;
776       xr->params->draw_cell = xr_draw_cell;
777       xr->params->aux = xr;
778       xr->params->size[H] = xr->width;
779       xr->params->size[V] = xr->length;
780       xr->params->font_size[H] = xr->char_width;
781       xr->params->font_size[V] = xr->char_height;
782
783       int lw = XR_LINE_WIDTH;
784       int ls = XR_LINE_SPACE;
785       for (int i = 0; i < TABLE_N_AXES; i++)
786         {
787           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
788           xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw;
789           xr->params->line_widths[i][RENDER_LINE_DASHED] = lw;
790           xr->params->line_widths[i][RENDER_LINE_THICK] = lw * 2;
791           xr->params->line_widths[i][RENDER_LINE_THIN] = lw / 2;
792           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls;
793         }
794
795       for (int i = 0; i < TABLE_N_AXES; i++)
796         xr->params->min_break[i] = xr->min_break[i];
797       xr->params->supports_margins = true;
798       xr->params->rtl = render_direction_rtl ();
799     }
800
801   cairo_set_source_rgb (xr->cairo,
802                         xr->fg.r / 255.0, xr->fg.g / 255.0, xr->fg.b / 255.0);
803 }
804
805 static struct output_driver *
806 xr_create (const char *file_name, enum settings_output_devices device_type,
807            struct string_map *o, enum xr_output_type file_type)
808 {
809   struct xr_driver *xr;
810   cairo_status_t status;
811   double width_pt, length_pt;
812
813   xr = xr_allocate (file_name, device_type, o);
814
815   width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
816   length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
817   if (file_type == XR_PDF)
818     xr->surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
819   else if (file_type == XR_PS)
820     xr->surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
821   else if (file_type == XR_SVG)
822     xr->surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
823   else
824     NOT_REACHED ();
825
826   status = cairo_surface_status (xr->surface);
827   if (status != CAIRO_STATUS_SUCCESS)
828     {
829       msg (ME, _("error opening output file `%s': %s"),
830              file_name, cairo_status_to_string (status));
831       goto error;
832     }
833
834   if (!xr_check_fonts (xr->surface, xr->fonts, xr->width, xr->length))
835     goto error;
836
837   return &xr->driver;
838
839  error:
840   output_driver_destroy (&xr->driver);
841   return NULL;
842 }
843
844 static struct output_driver *
845 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
846                struct string_map *o)
847 {
848   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
849   fh_unref (fh);
850   return od ;
851 }
852
853 static struct output_driver *
854 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
855                struct string_map *o)
856 {
857   struct output_driver *od =  xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
858   fh_unref (fh);
859   return od ;
860 }
861
862 static struct output_driver *
863 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
864                struct string_map *o)
865 {
866   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
867   fh_unref (fh);
868   return od ;
869 }
870
871 static void
872 xr_destroy (struct output_driver *driver)
873 {
874   struct xr_driver *xr = xr_driver_cast (driver);
875   size_t i;
876
877   xr_driver_destroy_fsm (xr);
878
879   if (xr->cairo != NULL)
880     {
881       cairo_surface_finish (xr->surface);
882       cairo_status_t status = cairo_status (xr->cairo);
883       if (status != CAIRO_STATUS_SUCCESS)
884         msg (ME, _("error drawing output for %s driver: %s"),
885                output_driver_get_name (driver),
886                cairo_status_to_string (status));
887       cairo_surface_destroy (xr->surface);
888
889       cairo_destroy (xr->cairo);
890     }
891
892   for (i = 0; i < XR_N_FONTS; i++)
893     {
894       struct xr_font *font = &xr->fonts[i];
895
896       if (font->desc != NULL)
897         pango_font_description_free (font->desc);
898       if (font->layout != NULL)
899         g_object_unref (font->layout);
900     }
901
902   free (xr->params);
903   free (xr);
904 }
905
906 static void
907 xr_flush (struct output_driver *driver)
908 {
909   struct xr_driver *xr = xr_driver_cast (driver);
910
911   cairo_surface_flush (cairo_get_target (xr->cairo));
912 }
913
914 static void
915 xr_update_page_setup (struct output_driver *driver,
916                       const struct page_setup *ps)
917 {
918   struct xr_driver *xr = xr_driver_cast (driver);
919
920   xr->initial_page_number = ps->initial_page_number;
921   xr->object_spacing = ps->object_spacing * 72 * XR_POINT;
922
923   if (xr->cairo)
924     return;
925
926   int usable[TABLE_N_AXES];
927   for (int i = 0; i < 2; i++)
928     usable[i] = (ps->paper[i]
929                  - (ps->margins[i][0] + ps->margins[i][1])) * 72 * XR_POINT;
930
931   int headings_height[2];
932   usable[V] -= xr_measure_headings (
933     xr->surface, xr->fonts[XR_FONT_PROPORTIONAL].desc, ps->headings,
934     usable[H], xr->object_spacing, headings_height);
935
936   enum table_axis h = ps->orientation == PAGE_LANDSCAPE;
937   enum table_axis v = !h;
938   if (!xr_check_fonts (xr->surface, xr->fonts, usable[h], usable[v]))
939     return;
940
941   for (int i = 0; i < 2; i++)
942     {
943       page_heading_uninit (&xr->headings[i]);
944       page_heading_copy (&xr->headings[i], &ps->headings[i]);
945       xr->headings_height[i] = headings_height[i];
946     }
947   xr->width = usable[h];
948   xr->length = usable[v];
949   xr->left_margin = ps->margins[h][0] * 72 * XR_POINT;
950   xr->right_margin = ps->margins[h][1] * 72 * XR_POINT;
951   xr->top_margin = ps->margins[v][0] * 72 * XR_POINT;
952   xr->bottom_margin = ps->margins[v][1] * 72 * XR_POINT;
953   cairo_pdf_surface_set_size (xr->surface,
954                               ps->paper[h] * 72.0, ps->paper[v] * 72.0);
955 }
956
957 static void
958 xr_submit (struct output_driver *driver, const struct output_item *output_item)
959 {
960   struct xr_driver *xr = xr_driver_cast (driver);
961
962   if (is_page_setup_item (output_item))
963     {
964       xr_update_page_setup (driver,
965                             to_page_setup_item (output_item)->page_setup);
966       return;
967     }
968
969   if (!xr->cairo)
970     {
971       xr->page_number = xr->initial_page_number - 1;
972       xr_set_cairo (xr, cairo_create (xr->surface));
973       cairo_save (xr->cairo);
974       xr_driver_next_page (xr, xr->cairo);
975     }
976
977   xr_driver_output_item (xr, output_item);
978   while (xr_driver_need_new_page (xr))
979     {
980       cairo_restore (xr->cairo);
981       cairo_show_page (xr->cairo);
982       cairo_save (xr->cairo);
983       xr_driver_next_page (xr, xr->cairo);
984     }
985 }
986 \f
987 /* Functions for rendering a series of output items to a series of Cairo
988    contexts, with pagination.
989
990    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
991    its underlying implementation.
992
993    See the big comment in cairo.h for intended usage. */
994
995 /* Gives new page CAIRO to XR for output. */
996 void
997 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
998 {
999   cairo_save (cairo);
1000   cairo_set_source_rgb (cairo,
1001                         xr->bg.r / 255.0, xr->bg.g / 255.0, xr->bg.b / 255.0);
1002   cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
1003   cairo_fill (cairo);
1004   cairo_restore (cairo);
1005
1006   cairo_translate (cairo,
1007                    xr_to_pt (xr->left_margin),
1008                    xr_to_pt (xr->top_margin + xr->headings_height[0]));
1009
1010   xr->page_number++;
1011   xr->cairo = cairo;
1012   xr->x = xr->y = 0;
1013
1014   xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc,
1015                           &xr->headings[0], xr->page_number, xr->width, true,
1016                           -xr->headings_height[0]);
1017   xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc,
1018                           &xr->headings[1], xr->page_number, xr->width, true,
1019                           xr->length);
1020
1021   xr_driver_run_fsm (xr);
1022 }
1023
1024 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
1025    rendering a previous output item, that is, only if xr_driver_need_new_page()
1026    returns false. */
1027 void
1028 xr_driver_output_item (struct xr_driver *xr,
1029                        const struct output_item *output_item)
1030 {
1031   assert (xr->fsm == NULL);
1032   xr->fsm = xr_render_output_item (xr, output_item);
1033   xr_driver_run_fsm (xr);
1034 }
1035
1036 /* Returns true if XR is in the middle of rendering an output item and needs a
1037    new page to be appended using xr_driver_next_page() to make progress,
1038    otherwise false. */
1039 bool
1040 xr_driver_need_new_page (const struct xr_driver *xr)
1041 {
1042   return xr->fsm != NULL;
1043 }
1044
1045 /* Returns true if the current page doesn't have any content yet. */
1046 bool
1047 xr_driver_is_page_blank (const struct xr_driver *xr)
1048 {
1049   return xr->y == 0;
1050 }
1051
1052 static void
1053 xr_driver_destroy_fsm (struct xr_driver *xr)
1054 {
1055   if (xr->fsm != NULL)
1056     {
1057       xr->fsm->destroy (xr->fsm);
1058       xr->fsm = NULL;
1059     }
1060 }
1061
1062 static void
1063 xr_driver_run_fsm (struct xr_driver *xr)
1064 {
1065   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
1066     xr_driver_destroy_fsm (xr);
1067 }
1068 \f
1069 static void
1070 xr_layout_cell (struct xr_driver *, const struct table_cell *,
1071                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1072                 int *width, int *height, int *brk);
1073
1074 static void
1075 set_source_rgba (cairo_t *cairo, const struct cell_color *color)
1076 {
1077   cairo_set_source_rgba (cairo,
1078                          color->r / 255., color->g / 255., color->b / 255.,
1079                          color->alpha / 255.);
1080 }
1081
1082 static void
1083 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
1084            const struct cell_color *color)
1085 {
1086   cairo_new_path (xr->cairo);
1087   set_source_rgba (xr->cairo, color);
1088   cairo_set_line_width (
1089     xr->cairo,
1090     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
1091               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
1092               : XR_LINE_WIDTH));
1093   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
1094   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
1095   cairo_stroke (xr->cairo);
1096 }
1097
1098 static void UNUSED
1099 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
1100 {
1101   cairo_new_path (xr->cairo);
1102   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
1103   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
1104   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
1105   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
1106   cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
1107   cairo_close_path (xr->cairo);
1108   cairo_stroke (xr->cairo);
1109 }
1110
1111 static void
1112 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
1113 {
1114   cairo_new_path (xr->cairo);
1115   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
1116   cairo_rectangle (xr->cairo,
1117                    xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
1118                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
1119   cairo_fill (xr->cairo);
1120 }
1121
1122 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
1123    shortening it to X0...X1 if SHORTEN is true.
1124    Draws a horizontal line X1...X3 at Y if RIGHT says so,
1125    shortening it to X2...X3 if SHORTEN is true. */
1126 static void
1127 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
1128            enum render_line_style left, enum render_line_style right,
1129            const struct cell_color *left_color,
1130            const struct cell_color *right_color,
1131            bool shorten)
1132 {
1133   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
1134       && cell_color_equal (left_color, right_color))
1135     dump_line (xr, x0, y, x3, y, left, left_color);
1136   else
1137     {
1138       if (left != RENDER_LINE_NONE)
1139         dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
1140       if (right != RENDER_LINE_NONE)
1141         dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
1142     }
1143 }
1144
1145 /* Draws a vertical line Y0...Y2 at X if TOP says so,
1146    shortening it to Y0...Y1 if SHORTEN is true.
1147    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
1148    shortening it to Y2...Y3 if SHORTEN is true. */
1149 static void
1150 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
1151            enum render_line_style top, enum render_line_style bottom,
1152            const struct cell_color *top_color,
1153            const struct cell_color *bottom_color,
1154            bool shorten)
1155 {
1156   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
1157       && cell_color_equal (top_color, bottom_color))
1158     dump_line (xr, x, y0, x, y3, top, top_color);
1159   else
1160     {
1161       if (top != RENDER_LINE_NONE)
1162         dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
1163       if (bottom != RENDER_LINE_NONE)
1164         dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
1165     }
1166 }
1167
1168 static void
1169 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
1170               enum render_line_style styles[TABLE_N_AXES][2],
1171               struct cell_color colors[TABLE_N_AXES][2])
1172 {
1173   const int x0 = bb[H][0];
1174   const int y0 = bb[V][0];
1175   const int x3 = bb[H][1];
1176   const int y3 = bb[V][1];
1177   const int top = styles[H][0];
1178   const int bottom = styles[H][1];
1179
1180   int start_side = render_direction_rtl();
1181   int end_side = !start_side;
1182   const int start_of_line = styles[V][start_side];
1183   const int end_of_line   = styles[V][end_side];
1184   const struct cell_color *top_color = &colors[H][0];
1185   const struct cell_color *bottom_color = &colors[H][1];
1186   const struct cell_color *start_color = &colors[V][start_side];
1187   const struct cell_color *end_color = &colors[V][end_side];
1188
1189   /* The algorithm here is somewhat subtle, to allow it to handle
1190      all the kinds of intersections that we need.
1191
1192      Three additional ordinates are assigned along the x axis.  The
1193      first is xc, midway between x0 and x3.  The others are x1 and
1194      x2; for a single vertical line these are equal to xc, and for
1195      a double vertical line they are the ordinates of the left and
1196      right half of the double line.
1197
1198      yc, y1, and y2 are assigned similarly along the y axis.
1199
1200      The following diagram shows the coordinate system and output
1201      for double top and bottom lines, single left line, and no
1202      right line:
1203
1204                  x0       x1 xc  x2      x3
1205                y0 ________________________
1206                   |        #     #       |
1207                   |        #     #       |
1208                   |        #     #       |
1209                   |        #     #       |
1210                   |        #     #       |
1211      y1 = y2 = yc |#########     #       |
1212                   |        #     #       |
1213                   |        #     #       |
1214                   |        #     #       |
1215                   |        #     #       |
1216                y3 |________#_____#_______|
1217   */
1218   struct xr_driver *xr = xr_;
1219
1220   /* Offset from center of each line in a pair of double lines. */
1221   int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
1222
1223   /* Are the lines along each axis single or double?
1224      (It doesn't make sense to have different kinds of line on the
1225      same axis, so we don't try to gracefully handle that case.) */
1226   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
1227   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
1228
1229   /* When horizontal lines are doubled,
1230      the left-side line along y1 normally runs from x0 to x2,
1231      and the right-side line along y1 from x3 to x1.
1232      If the top-side line is also doubled, we shorten the y1 lines,
1233      so that the left-side line runs only to x1,
1234      and the right-side line only to x2.
1235      Otherwise, the horizontal line at y = y1 below would cut off
1236      the intersection, which looks ugly:
1237                x0       x1     x2      x3
1238              y0 ________________________
1239                 |        #     #       |
1240                 |        #     #       |
1241                 |        #     #       |
1242                 |        #     #       |
1243              y1 |#########     ########|
1244                 |                      |
1245                 |                      |
1246              y2 |######################|
1247                 |                      |
1248                 |                      |
1249              y3 |______________________|
1250      It is more of a judgment call when the horizontal line is
1251      single.  We actually choose to cut off the line anyhow, as
1252      shown in the first diagram above.
1253   */
1254   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
1255   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
1256   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
1257   int horz_line_ofs = double_vert ? double_line_ofs : 0;
1258   int xc = (x0 + x3) / 2;
1259   int x1 = xc - horz_line_ofs;
1260   int x2 = xc + horz_line_ofs;
1261
1262   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
1263   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
1264   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
1265   int vert_line_ofs = double_horz ? double_line_ofs : 0;
1266   int yc = (y0 + y3) / 2;
1267   int y1 = yc - vert_line_ofs;
1268   int y2 = yc + vert_line_ofs;
1269
1270   if (!double_horz)
1271     horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
1272                start_color, end_color, shorten_yc_line);
1273   else
1274     {
1275       horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
1276                  start_color, end_color, shorten_y1_lines);
1277       horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
1278                  start_color, end_color, shorten_y2_lines);
1279     }
1280
1281   if (!double_vert)
1282     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
1283                shorten_xc_line);
1284   else
1285     {
1286       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
1287                  shorten_x1_lines);
1288       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
1289                  shorten_x2_lines);
1290     }
1291 }
1292
1293 static void
1294 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
1295                        int *min_width, int *max_width)
1296 {
1297   struct xr_driver *xr = xr_;
1298   int bb[TABLE_N_AXES][2];
1299   int clip[TABLE_N_AXES][2];
1300   int h;
1301
1302   bb[H][0] = 0;
1303   bb[H][1] = INT_MAX;
1304   bb[V][0] = 0;
1305   bb[V][1] = INT_MAX;
1306   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1307   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
1308
1309   bb[H][1] = 1;
1310   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
1311
1312   if (*min_width > 0)
1313     *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
1314                             + cell->style->cell_style.margin[H][1]);
1315   if (*max_width > 0)
1316     *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
1317                             + cell->style->cell_style.margin[H][1]);
1318 }
1319
1320 static int
1321 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
1322 {
1323   struct xr_driver *xr = xr_;
1324   int bb[TABLE_N_AXES][2];
1325   int clip[TABLE_N_AXES][2];
1326   int w, h;
1327
1328   bb[H][0] = 0;
1329   bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
1330                                + cell->style->cell_style.margin[H][1]);
1331   bb[V][0] = 0;
1332   bb[V][1] = INT_MAX;
1333   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1334   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
1335   h += px_to_xr (cell->style->cell_style.margin[V][0]
1336                  + cell->style->cell_style.margin[V][1]);
1337   return h;
1338 }
1339
1340 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
1341
1342 static void
1343 xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
1344               int bb[TABLE_N_AXES][2],
1345               int spill[TABLE_N_AXES][2],
1346               int clip[TABLE_N_AXES][2])
1347 {
1348   struct xr_driver *xr = xr_;
1349   int w, h, brk;
1350
1351   cairo_save (xr->cairo);
1352   int bg_clip[TABLE_N_AXES][2];
1353   for (int axis = 0; axis < TABLE_N_AXES; axis++)
1354     {
1355       bg_clip[axis][0] = clip[axis][0];
1356       if (bb[axis][0] == clip[axis][0])
1357         bg_clip[axis][0] -= spill[axis][0];
1358
1359       bg_clip[axis][1] = clip[axis][1];
1360       if (bb[axis][1] == clip[axis][1])
1361         bg_clip[axis][1] += spill[axis][1];
1362     }
1363   xr_clip (xr, bg_clip);
1364   set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]);
1365   fill_rectangle (xr,
1366                   bb[H][0] - spill[H][0],
1367                   bb[V][0] - spill[V][0],
1368                   bb[H][1] + spill[H][1],
1369                   bb[V][1] + spill[V][1]);
1370   cairo_restore (xr->cairo);
1371
1372   cairo_save (xr->cairo);
1373   set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
1374
1375   for (int axis = 0; axis < TABLE_N_AXES; axis++)
1376     {
1377       bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
1378       bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
1379     }
1380   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
1381     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
1382   cairo_restore (xr->cairo);
1383 }
1384
1385 static int
1386 xr_adjust_break (void *xr_, const struct table_cell *cell,
1387                  int width, int height)
1388 {
1389   struct xr_driver *xr = xr_;
1390   int bb[TABLE_N_AXES][2];
1391   int clip[TABLE_N_AXES][2];
1392   int w, h, brk;
1393
1394   if (xr_measure_cell_height (xr_, cell, width) < height)
1395     return -1;
1396
1397   bb[H][0] = 0;
1398   bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
1399                                + cell->style->cell_style.margin[H][1]);
1400   if (bb[H][1] <= 0)
1401     return 0;
1402   bb[V][0] = 0;
1403   bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
1404                                 + cell->style->cell_style.margin[V][1]);
1405   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1406   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
1407   return brk;
1408 }
1409 \f
1410 static void
1411 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
1412 {
1413   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
1414     {
1415       double x0 = xr_to_pt (clip[H][0] + xr->x);
1416       double y0 = xr_to_pt (clip[V][0] + xr->y);
1417       double x1 = xr_to_pt (clip[H][1] + xr->x);
1418       double y1 = xr_to_pt (clip[V][1] + xr->y);
1419
1420       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
1421       cairo_clip (xr->cairo);
1422     }
1423 }
1424
1425 static void
1426 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
1427 {
1428   attr->start_index = start_index;
1429   pango_attr_list_insert (list, attr);
1430 }
1431
1432 static void
1433 markup_escape (const char *in, struct string *out)
1434 {
1435   for (int c = *in++; c; c = *in++)
1436     switch (c)
1437       {
1438       case '&':
1439         ds_put_cstr (out, "&amp;");
1440         break;
1441       case '<':
1442         ds_put_cstr (out, "&lt;");
1443         break;
1444       case '>':
1445         ds_put_cstr (out, "&gt;");
1446         break;
1447       default:
1448         ds_put_byte (out, c);
1449         break;
1450       }
1451 }
1452
1453 static int
1454 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
1455 {
1456   int size[TABLE_N_AXES];
1457   pango_layout_get_size (layout, &size[H], &size[V]);
1458   return size[axis];
1459 }
1460
1461 static int
1462 xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell,
1463                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1464                      int *widthp, int *brk)
1465 {
1466   const struct font_style *font_style = &cell->style->font_style;
1467   const struct cell_style *cell_style = &cell->style->cell_style;
1468   unsigned int options = cell->options;
1469
1470   enum table_axis X = options & TAB_ROTATE ? V : H;
1471   enum table_axis Y = !X;
1472   int R = options & TAB_ROTATE ? 0 : 1;
1473
1474   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1475                           : &xr->fonts[XR_FONT_PROPORTIONAL]);
1476   struct xr_font local_font;
1477   if (font_style->typeface)
1478     {
1479       PangoFontDescription *desc = parse_font (
1480         font_style->typeface,
1481         font_style->size ? font_style->size * 1000 * 72 / 128 : 10000,
1482         font_style->bold, font_style->italic);
1483       if (desc)
1484         {
1485           PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
1486           pango_layout_set_font_description (layout, desc);
1487
1488           local_font.desc = desc;
1489           local_font.layout = layout;
1490           font = &local_font;
1491         }
1492     }
1493
1494   const char *text = cell->text;
1495   enum table_halign halign = table_halign_interpret (
1496     cell_style->halign, cell->options & TAB_NUMERIC);
1497   if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
1498     {
1499       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
1500
1501       const char *decimal = strrchr (text, cell_style->decimal_char);
1502       if (decimal)
1503         {
1504           pango_layout_set_text (font->layout, decimal, strlen (decimal));
1505           pango_layout_set_width (font->layout, -1);
1506           margin_adjustment += get_layout_dimension (font->layout, H);
1507         }
1508
1509       if (margin_adjustment < 0)
1510         bb[H][1] += margin_adjustment;
1511     }
1512
1513   struct string tmp = DS_EMPTY_INITIALIZER;
1514
1515   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
1516      Pango's implementation of it): it will break after a period or a comma
1517      that precedes a digit, e.g. in ".000" it will break after the period.
1518      This code looks for such a situation and inserts a U+2060 WORD JOINER
1519      to prevent the break.
1520
1521      This isn't necessary when the decimal point is between two digits
1522      (e.g. "0.000" won't be broken) or when the display width is not limited so
1523      that word wrapping won't happen.
1524
1525      It isn't necessary to look for more than one period or comma, as would
1526      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
1527      are present then there will always be a digit on both sides of every
1528      period and comma. */
1529   if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
1530     {
1531       const char *decimal = text + strcspn (text, ".,");
1532       if (decimal[0]
1533           && c_isdigit (decimal[1])
1534           && (decimal == text || !c_isdigit (decimal[-1])))
1535         {
1536           ds_extend (&tmp, strlen (text) + 16);
1537           ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1));
1538           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
1539           ds_put_cstr (&tmp, decimal + 1);
1540         }
1541     }
1542
1543   if (cell->n_footnotes)
1544     {
1545       int footnote_adjustment;
1546       if (cell->n_footnotes == 1 && halign == TABLE_HALIGN_RIGHT)
1547         {
1548           const char *marker = cell->footnotes[0]->marker;
1549           pango_layout_set_text (font->layout, marker, strlen (marker));
1550
1551           PangoAttrList *attrs = pango_attr_list_new ();
1552           pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
1553           pango_attr_list_insert (attrs, pango_attr_rise_new (3000));
1554           pango_layout_set_attributes (font->layout, attrs);
1555           pango_attr_list_unref (attrs);
1556
1557           int w = get_layout_dimension (font->layout, X);
1558           int right_margin = px_to_xr (cell_style->margin[X][R]);
1559           footnote_adjustment = MIN (w, right_margin);
1560
1561           pango_layout_set_attributes (font->layout, NULL);
1562         }
1563       else
1564         footnote_adjustment = px_to_xr (cell_style->margin[X][R]);
1565
1566       if (R)
1567         bb[X][R] += footnote_adjustment;
1568       else
1569         bb[X][R] -= footnote_adjustment;
1570
1571       if (ds_is_empty (&tmp))
1572         {
1573           ds_extend (&tmp, strlen (text) + 16);
1574           ds_put_cstr (&tmp, text);
1575         }
1576       size_t initial_length = ds_length (&tmp);
1577
1578       for (size_t i = 0; i < cell->n_footnotes; i++)
1579         {
1580           if (i)
1581             ds_put_byte (&tmp, ',');
1582
1583           const char *marker = cell->footnotes[i]->marker;
1584           if (options & TAB_MARKUP)
1585             markup_escape (marker, &tmp);
1586           else
1587             ds_put_cstr (&tmp, marker);
1588         }
1589
1590       if (options & TAB_MARKUP)
1591         pango_layout_set_markup (font->layout,
1592                                  ds_cstr (&tmp), ds_length (&tmp));
1593       else
1594         pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
1595
1596       PangoAttrList *attrs = pango_attr_list_new ();
1597       if (font_style->underline)
1598         pango_attr_list_insert (attrs, pango_attr_underline_new (
1599                                PANGO_UNDERLINE_SINGLE));
1600       add_attr_with_start (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL), initial_length);
1601       add_attr_with_start (attrs, pango_attr_rise_new (3000), initial_length);
1602       add_attr_with_start (
1603         attrs, pango_attr_font_desc_new (font->desc), initial_length);
1604       pango_layout_set_attributes (font->layout, attrs);
1605       pango_attr_list_unref (attrs);
1606     }
1607   else
1608     {
1609       const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
1610       if (options & TAB_MARKUP)
1611         pango_layout_set_markup (font->layout, content, -1);
1612       else
1613         pango_layout_set_text (font->layout, content, -1);
1614
1615       if (font_style->underline)
1616         {
1617           PangoAttrList *attrs = pango_attr_list_new ();
1618           pango_attr_list_insert (attrs, pango_attr_underline_new (
1619                                     PANGO_UNDERLINE_SINGLE));
1620           pango_layout_set_attributes (font->layout, attrs);
1621           pango_attr_list_unref (attrs);
1622         }
1623     }
1624   ds_destroy (&tmp);
1625
1626   pango_layout_set_alignment (font->layout,
1627                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
1628                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
1629                                : PANGO_ALIGN_CENTER));
1630   pango_layout_set_width (
1631     font->layout,
1632     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
1633   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1634
1635   if (clip[H][0] != clip[H][1])
1636     {
1637       cairo_save (xr->cairo);
1638       if (!(options & TAB_ROTATE))
1639         xr_clip (xr, clip);
1640       if (options & TAB_ROTATE)
1641         {
1642           cairo_translate (xr->cairo,
1643                            xr_to_pt (bb[H][0] + xr->x),
1644                            xr_to_pt (bb[V][1] + xr->y));
1645           cairo_rotate (xr->cairo, -M_PI_2);
1646         }
1647       else
1648         cairo_translate (xr->cairo,
1649                          xr_to_pt (bb[H][0] + xr->x),
1650                          xr_to_pt (bb[V][0] + xr->y));
1651       pango_cairo_show_layout (xr->cairo, font->layout);
1652
1653       /* If enabled, this draws a blue rectangle around the extents of each
1654          line of text, which can be rather useful for debugging layout
1655          issues. */
1656       if (0)
1657         {
1658           PangoLayoutIter *iter;
1659           iter = pango_layout_get_iter (font->layout);
1660           do
1661             {
1662               PangoRectangle extents;
1663
1664               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1665               cairo_save (xr->cairo);
1666               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1667               dump_rectangle (xr,
1668                               pango_to_xr (extents.x) - xr->x,
1669                               pango_to_xr (extents.y) - xr->y,
1670                               pango_to_xr (extents.x + extents.width) - xr->x,
1671                               pango_to_xr (extents.y + extents.height) - xr->y);
1672               cairo_restore (xr->cairo);
1673             }
1674           while (pango_layout_iter_next_line (iter));
1675           pango_layout_iter_free (iter);
1676         }
1677
1678       cairo_restore (xr->cairo);
1679     }
1680
1681   int size[TABLE_N_AXES];
1682   pango_layout_get_size (font->layout, &size[H], &size[V]);
1683   int w = pango_to_xr (size[X]);
1684   int h = pango_to_xr (size[Y]);
1685   if (w > *widthp)
1686     *widthp = w;
1687   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
1688     {
1689       PangoLayoutIter *iter;
1690       int best UNUSED = 0;
1691
1692       /* Choose a breakpoint between lines instead of in the middle of one. */
1693       iter = pango_layout_get_iter (font->layout);
1694       do
1695         {
1696           PangoRectangle extents;
1697           int y0, y1;
1698           int bottom;
1699
1700           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1701           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1702           extents.x = pango_to_xr (extents.x);
1703           extents.y = pango_to_xr (y0);
1704           extents.width = pango_to_xr (extents.width);
1705           extents.height = pango_to_xr (y1 - y0);
1706           bottom = bb[V][0] + extents.y + extents.height;
1707           if (bottom < bb[V][1])
1708             {
1709               if (brk && clip[H][0] != clip[H][1])
1710                 best = bottom;
1711               if (brk)
1712                 *brk = bottom;
1713             }
1714           else
1715             break;
1716         }
1717       while (pango_layout_iter_next_line (iter));
1718       pango_layout_iter_free (iter);
1719
1720       /* If enabled, draws a green line across the chosen breakpoint, which can
1721          be useful for debugging issues with breaking.  */
1722       if (0)
1723         {
1724           if (best && !xr->nest)
1725             dump_line (xr, -xr->left_margin, best,
1726                        xr->width + xr->right_margin, best,
1727                        RENDER_LINE_SINGLE,
1728                        &(struct cell_color) CELL_COLOR (0, 255, 0));
1729         }
1730     }
1731
1732   pango_layout_set_attributes (font->layout, NULL);
1733
1734   if (font == &local_font)
1735     {
1736       g_object_unref (G_OBJECT (font->layout));
1737       pango_font_description_free (font->desc);
1738     }
1739
1740   return h;
1741 }
1742
1743 static void
1744 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1745                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1746                 int *width, int *height, int *brk)
1747 {
1748   *width = 0;
1749   *height = 0;
1750
1751   /* If enabled, draws a blue rectangle around the cell extents, which can be
1752      useful for debugging layout. */
1753   if (0)
1754     {
1755       if (clip[H][0] != clip[H][1])
1756         {
1757           int offset = (xr->nest) * XR_POINT;
1758
1759           cairo_save (xr->cairo);
1760           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1761           dump_rectangle (xr,
1762                           bb[H][0] + offset, bb[V][0] + offset,
1763                           bb[H][1] - offset, bb[V][1] - offset);
1764           cairo_restore (xr->cairo);
1765         }
1766     }
1767
1768   if (brk)
1769     *brk = bb[V][0];
1770   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
1771 }
1772 \f
1773 struct output_driver_factory pdf_driver_factory =
1774   { "pdf", "pspp.pdf", xr_pdf_create };
1775 struct output_driver_factory ps_driver_factory =
1776   { "ps", "pspp.ps", xr_ps_create };
1777 struct output_driver_factory svg_driver_factory =
1778   { "svg", "pspp.svg", xr_svg_create };
1779
1780 static const struct output_driver_class cairo_driver_class =
1781 {
1782   "cairo",
1783   xr_destroy,
1784   xr_submit,
1785   xr_flush,
1786 };
1787 \f
1788 /* GUI rendering helpers. */
1789
1790 struct xr_rendering
1791   {
1792     struct output_item *item;
1793
1794     /* Table items. */
1795     struct render_pager *p;
1796     struct xr_driver *xr;
1797   };
1798
1799 #define CHART_WIDTH 500
1800 #define CHART_HEIGHT 375
1801
1802
1803
1804 struct xr_driver *
1805 xr_driver_create (cairo_t *cairo, struct string_map *options)
1806 {
1807   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1808   xr_set_cairo (xr, cairo);
1809   return xr;
1810 }
1811
1812 /* Destroy XR, which should have been created with xr_driver_create().  Any
1813    cairo_t added to XR is not destroyed, because it is owned by the client. */
1814 void
1815 xr_driver_destroy (struct xr_driver *xr)
1816 {
1817   if (xr != NULL)
1818     {
1819       xr->cairo = NULL;
1820       output_driver_destroy (&xr->driver);
1821     }
1822 }
1823
1824 static struct xr_rendering *
1825 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1826 {
1827   struct pivot_table *pt = pivot_table_create_for_text (
1828     NULL, pivot_value_new_user_text (text, -1));
1829   struct table_item *table_item = table_item_create (pt);
1830   struct xr_rendering *r = xr_rendering_create (xr, &table_item->output_item,
1831                                                 cr);
1832   table_item_unref (table_item);
1833
1834   return r;
1835 }
1836
1837 void
1838 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1839 {
1840   if (is_table_item (xr->item))
1841     apply_options (xr->xr, o);
1842 }
1843
1844 struct xr_rendering *
1845 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1846                      cairo_t *cr)
1847 {
1848   struct xr_rendering *r = NULL;
1849
1850   if (is_text_item (item))
1851     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1852                                   cr);
1853   else if (is_message_item (item))
1854     {
1855       const struct message_item *message_item = to_message_item (item);
1856       char *s = msg_to_string (message_item_get_msg (message_item));
1857       r = xr_rendering_create_text (xr, s, cr);
1858       free (s);
1859     }
1860   else if (is_table_item (item))
1861     {
1862       r = xzalloc (sizeof *r);
1863       r->item = output_item_ref (item);
1864       r->xr = xr;
1865       xr_set_cairo (xr, cr);
1866       r->p = render_pager_create (xr->params, to_table_item (item));
1867     }
1868   else if (is_chart_item (item))
1869     {
1870       r = xzalloc (sizeof *r);
1871       r->item = output_item_ref (item);
1872     }
1873   else if (is_group_open_item (item))
1874     r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
1875                                   cr);
1876
1877   return r;
1878 }
1879
1880 void
1881 xr_rendering_destroy (struct xr_rendering *r)
1882 {
1883   if (r)
1884     {
1885       output_item_unref (r->item);
1886       render_pager_destroy (r->p);
1887       free (r);
1888     }
1889 }
1890
1891 void
1892 xr_rendering_measure (struct xr_rendering *r, int *wp, int *hp)
1893 {
1894   int w, h;
1895
1896   if (is_table_item (r->item))
1897     {
1898       w = render_pager_get_size (r->p, H) / XR_POINT;
1899       h = render_pager_get_size (r->p, V) / XR_POINT;
1900     }
1901   else
1902     {
1903       w = CHART_WIDTH;
1904       h = CHART_HEIGHT;
1905     }
1906
1907   if (wp)
1908     *wp = w;
1909   if (hp)
1910     *hp = h;
1911 }
1912
1913 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1914                     double x, double y, double width, double height);
1915
1916 /* Draws onto CR */
1917 void
1918 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1919                    int x0, int y0, int x1, int y1)
1920 {
1921   if (is_table_item (r->item))
1922     {
1923       struct xr_driver *xr = r->xr;
1924
1925       xr_set_cairo (xr, cr);
1926
1927       render_pager_draw_region (r->p, x0 * XR_POINT, y0 * XR_POINT,
1928                                 (x1 - x0) * XR_POINT, (y1 - y0) * XR_POINT);
1929     }
1930   else
1931     xr_draw_chart (to_chart_item (r->item), cr,
1932                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1933 }
1934
1935 static void
1936 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1937                double x, double y, double width, double height)
1938 {
1939   struct xrchart_geometry geom;
1940
1941   cairo_save (cr);
1942   cairo_translate (cr, x, y + height);
1943   cairo_scale (cr, 1.0, -1.0);
1944   xrchart_geometry_init (cr, &geom, width, height);
1945   if (is_boxplot (chart_item))
1946     xrchart_draw_boxplot (chart_item, cr, &geom);
1947   else if (is_histogram_chart (chart_item))
1948     xrchart_draw_histogram (chart_item, cr, &geom);
1949   else if (is_np_plot_chart (chart_item))
1950     xrchart_draw_np_plot (chart_item, cr, &geom);
1951   else if (is_piechart (chart_item))
1952     xrchart_draw_piechart (chart_item, cr, &geom);
1953   else if (is_barchart (chart_item))
1954     xrchart_draw_barchart (chart_item, cr, &geom);
1955   else if (is_roc_chart (chart_item))
1956     xrchart_draw_roc (chart_item, cr, &geom);
1957   else if (is_scree (chart_item))
1958     xrchart_draw_scree (chart_item, cr, &geom);
1959   else if (is_spreadlevel_plot_chart (chart_item))
1960     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1961   else if (is_scatterplot_chart (chart_item))
1962     xrchart_draw_scatterplot (chart_item, cr, &geom);
1963   else
1964     NOT_REACHED ();
1965   xrchart_geometry_free (cr, &geom);
1966
1967   cairo_restore (cr);
1968 }
1969
1970 char *
1971 xr_draw_png_chart (const struct chart_item *item,
1972                    const char *file_name_template, int number,
1973                    const struct cell_color *fg,
1974                    const struct cell_color *bg)
1975 {
1976   const int width = 640;
1977   const int length = 480;
1978
1979   cairo_surface_t *surface;
1980   cairo_status_t status;
1981   const char *number_pos;
1982   char *file_name;
1983   cairo_t *cr;
1984
1985   number_pos = strchr (file_name_template, '#');
1986   if (number_pos != NULL)
1987     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1988                            file_name_template, number, number_pos + 1);
1989   else
1990     file_name = xstrdup (file_name_template);
1991
1992   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1993   cr = cairo_create (surface);
1994
1995   cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0);
1996   cairo_paint (cr);
1997
1998   cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0);
1999
2000   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
2001
2002   status = cairo_surface_write_to_png (surface, file_name);
2003   if (status != CAIRO_STATUS_SUCCESS)
2004     msg (ME, _("error writing output file `%s': %s"),
2005            file_name, cairo_status_to_string (status));
2006
2007   cairo_destroy (cr);
2008   cairo_surface_destroy (surface);
2009
2010   return file_name;
2011 }
2012 \f
2013 struct xr_table_state
2014   {
2015     struct xr_render_fsm fsm;
2016     struct render_pager *p;
2017   };
2018
2019 static bool
2020 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
2021 {
2022   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
2023
2024   while (render_pager_has_next (ts->p))
2025     {
2026       int used;
2027
2028       used = render_pager_draw_next (ts->p, xr->length - xr->y);
2029       if (!used)
2030         {
2031           assert (xr->y > 0);
2032           return true;
2033         }
2034       else
2035         xr->y += used;
2036     }
2037   return false;
2038 }
2039
2040 static void
2041 xr_table_destroy (struct xr_render_fsm *fsm)
2042 {
2043   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
2044
2045   render_pager_destroy (ts->p);
2046   free (ts);
2047 }
2048
2049 static struct xr_render_fsm *
2050 xr_render_table (struct xr_driver *xr, struct table_item *table_item)
2051 {
2052   struct xr_table_state *ts;
2053
2054   ts = xmalloc (sizeof *ts);
2055   ts->fsm.render = xr_table_render;
2056   ts->fsm.destroy = xr_table_destroy;
2057
2058   if (xr->y > 0)
2059     xr->y += xr->char_height;
2060
2061   ts->p = render_pager_create (xr->params, table_item);
2062   table_item_unref (table_item);
2063
2064   return &ts->fsm;
2065 }
2066 \f
2067 struct xr_chart_state
2068   {
2069     struct xr_render_fsm fsm;
2070     struct chart_item *chart_item;
2071   };
2072
2073 static bool
2074 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
2075 {
2076   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
2077
2078   const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
2079
2080   if (xr->y > xr->length - chart_height)
2081     return true;
2082
2083   if (xr->cairo != NULL)
2084     {
2085       xr_draw_chart (cs->chart_item, xr->cairo,
2086                      0.0,
2087                      xr_to_pt (xr->y),
2088                      xr_to_pt (xr->width),
2089                      xr_to_pt (chart_height));
2090     }
2091   xr->y += chart_height;
2092
2093   return false;
2094 }
2095
2096 static void
2097 xr_chart_destroy (struct xr_render_fsm *fsm)
2098 {
2099   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
2100
2101   chart_item_unref (cs->chart_item);
2102   free (cs);
2103 }
2104
2105 static struct xr_render_fsm *
2106 xr_render_chart (const struct chart_item *chart_item)
2107 {
2108   struct xr_chart_state *cs;
2109
2110   cs = xmalloc (sizeof *cs);
2111   cs->fsm.render = xr_chart_render;
2112   cs->fsm.destroy = xr_chart_destroy;
2113   cs->chart_item = chart_item_ref (chart_item);
2114
2115   return &cs->fsm;
2116 }
2117 \f
2118 static bool
2119 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
2120 {
2121   return xr->y > 0;
2122 }
2123
2124 static void
2125 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
2126 {
2127   /* Nothing to do. */
2128 }
2129
2130 static struct xr_render_fsm *
2131 xr_render_eject (void)
2132 {
2133   static struct xr_render_fsm eject_renderer =
2134     {
2135       xr_eject_render,
2136       xr_eject_destroy
2137     };
2138
2139   return &eject_renderer;
2140 }
2141 \f
2142 static struct xr_render_fsm *
2143 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
2144 {
2145   enum text_item_type type = text_item_get_type (text_item);
2146   const char *text = text_item_get_text (text_item);
2147
2148   switch (type)
2149     {
2150     case TEXT_ITEM_PAGE_TITLE:
2151       string_map_replace (&xr->heading_vars, "PageTitle", text);
2152       break;
2153
2154     case TEXT_ITEM_EJECT_PAGE:
2155       if (xr->y > 0)
2156         return xr_render_eject ();
2157       break;
2158
2159     default:
2160       return xr_render_table (
2161         xr, text_item_to_table_item (text_item_ref (text_item)));
2162     }
2163
2164   return NULL;
2165 }
2166
2167 static struct xr_render_fsm *
2168 xr_render_message (struct xr_driver *xr,
2169                    const struct message_item *message_item)
2170 {
2171   char *s = msg_to_string (message_item_get_msg (message_item));
2172   struct text_item *item = text_item_create (TEXT_ITEM_LOG, s);
2173   free (s);
2174   return xr_render_table (xr, text_item_to_table_item (item));
2175 }
2176
2177 static struct xr_render_fsm *
2178 xr_render_output_item (struct xr_driver *xr,
2179                        const struct output_item *output_item)
2180 {
2181   if (is_table_item (output_item))
2182     return xr_render_table (xr, table_item_ref (to_table_item (output_item)));
2183   else if (is_chart_item (output_item))
2184     return xr_render_chart (to_chart_item (output_item));
2185   else if (is_text_item (output_item))
2186     return xr_render_text (xr, to_text_item (output_item));
2187   else if (is_message_item (output_item))
2188     return xr_render_message (xr, to_message_item (output_item));
2189   else
2190     return NULL;
2191 }