101735418142f9e63b41bb29fbe2caa451461982
[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 = 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_rise_new (7000));
1553           pango_layout_set_attributes (font->layout, attrs);
1554           pango_attr_list_unref (attrs);
1555
1556           int w = get_layout_dimension (font->layout, X);
1557           int right_margin = px_to_xr (cell_style->margin[X][R]);
1558           footnote_adjustment = MIN (w, right_margin);
1559
1560           pango_layout_set_attributes (font->layout, NULL);
1561         }
1562       else
1563         footnote_adjustment = px_to_xr (cell_style->margin[X][R]);
1564
1565       if (R)
1566         bb[X][R] += footnote_adjustment;
1567       else
1568         bb[X][R] -= footnote_adjustment;
1569
1570       if (ds_is_empty (&tmp))
1571         {
1572           ds_extend (&tmp, strlen (text) + 16);
1573           ds_put_cstr (&tmp, text);
1574         }
1575       size_t initial_length = ds_length (&tmp);
1576
1577       for (size_t i = 0; i < cell->n_footnotes; i++)
1578         {
1579           if (i)
1580             ds_put_byte (&tmp, ',');
1581
1582           const char *marker = cell->footnotes[i]->marker;
1583           if (options & TAB_MARKUP)
1584             markup_escape (marker, &tmp);
1585           else
1586             ds_put_cstr (&tmp, marker);
1587         }
1588
1589       if (options & TAB_MARKUP)
1590         pango_layout_set_markup (font->layout,
1591                                  ds_cstr (&tmp), ds_length (&tmp));
1592       else
1593         pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
1594
1595       PangoAttrList *attrs = pango_attr_list_new ();
1596       if (font_style->underline)
1597         pango_attr_list_insert (attrs, pango_attr_underline_new (
1598                                PANGO_UNDERLINE_SINGLE));
1599       add_attr_with_start (attrs, pango_attr_rise_new (7000), initial_length);
1600       add_attr_with_start (
1601         attrs, pango_attr_font_desc_new (font->desc), initial_length);
1602       pango_layout_set_attributes (font->layout, attrs);
1603       pango_attr_list_unref (attrs);
1604     }
1605   else
1606     {
1607       const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
1608       if (options & TAB_MARKUP)
1609         pango_layout_set_markup (font->layout, content, -1);
1610       else
1611         pango_layout_set_text (font->layout, content, -1);
1612
1613       if (font_style->underline)
1614         {
1615           PangoAttrList *attrs = pango_attr_list_new ();
1616           pango_attr_list_insert (attrs, pango_attr_underline_new (
1617                                     PANGO_UNDERLINE_SINGLE));
1618           pango_layout_set_attributes (font->layout, attrs);
1619           pango_attr_list_unref (attrs);
1620         }
1621     }
1622   ds_destroy (&tmp);
1623
1624   pango_layout_set_alignment (font->layout,
1625                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
1626                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
1627                                : PANGO_ALIGN_CENTER));
1628   pango_layout_set_width (
1629     font->layout,
1630     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
1631   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1632
1633   if (clip[H][0] != clip[H][1])
1634     {
1635       cairo_save (xr->cairo);
1636       if (!(options & TAB_ROTATE))
1637         xr_clip (xr, clip);
1638       if (options & TAB_ROTATE)
1639         {
1640           cairo_translate (xr->cairo,
1641                            xr_to_pt (bb[H][0] + xr->x),
1642                            xr_to_pt (bb[V][1] + xr->y));
1643           cairo_rotate (xr->cairo, -M_PI_2);
1644         }
1645       else
1646         cairo_translate (xr->cairo,
1647                          xr_to_pt (bb[H][0] + xr->x),
1648                          xr_to_pt (bb[V][0] + xr->y));
1649       pango_cairo_show_layout (xr->cairo, font->layout);
1650
1651       /* If enabled, this draws a blue rectangle around the extents of each
1652          line of text, which can be rather useful for debugging layout
1653          issues. */
1654       if (0)
1655         {
1656           PangoLayoutIter *iter;
1657           iter = pango_layout_get_iter (font->layout);
1658           do
1659             {
1660               PangoRectangle extents;
1661
1662               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1663               cairo_save (xr->cairo);
1664               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1665               dump_rectangle (xr,
1666                               pango_to_xr (extents.x) - xr->x,
1667                               pango_to_xr (extents.y) - xr->y,
1668                               pango_to_xr (extents.x + extents.width) - xr->x,
1669                               pango_to_xr (extents.y + extents.height) - xr->y);
1670               cairo_restore (xr->cairo);
1671             }
1672           while (pango_layout_iter_next_line (iter));
1673           pango_layout_iter_free (iter);
1674         }
1675
1676       cairo_restore (xr->cairo);
1677     }
1678
1679   int size[TABLE_N_AXES];
1680   pango_layout_get_size (font->layout, &size[H], &size[V]);
1681   int w = pango_to_xr (size[X]);
1682   int h = pango_to_xr (size[Y]);
1683   if (w > *widthp)
1684     *widthp = w;
1685   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
1686     {
1687       PangoLayoutIter *iter;
1688       int best UNUSED = 0;
1689
1690       /* Choose a breakpoint between lines instead of in the middle of one. */
1691       iter = pango_layout_get_iter (font->layout);
1692       do
1693         {
1694           PangoRectangle extents;
1695           int y0, y1;
1696           int bottom;
1697
1698           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1699           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1700           extents.x = pango_to_xr (extents.x);
1701           extents.y = pango_to_xr (y0);
1702           extents.width = pango_to_xr (extents.width);
1703           extents.height = pango_to_xr (y1 - y0);
1704           bottom = bb[V][0] + extents.y + extents.height;
1705           if (bottom < bb[V][1])
1706             {
1707               if (brk && clip[H][0] != clip[H][1])
1708                 best = bottom;
1709               if (brk)
1710                 *brk = bottom;
1711             }
1712           else
1713             break;
1714         }
1715       while (pango_layout_iter_next_line (iter));
1716       pango_layout_iter_free (iter);
1717
1718       /* If enabled, draws a green line across the chosen breakpoint, which can
1719          be useful for debugging issues with breaking.  */
1720       if (0)
1721         {
1722           if (best && !xr->nest)
1723             dump_line (xr, -xr->left_margin, best,
1724                        xr->width + xr->right_margin, best,
1725                        RENDER_LINE_SINGLE,
1726                        &(struct cell_color) CELL_COLOR (0, 255, 0));
1727         }
1728     }
1729
1730   pango_layout_set_attributes (font->layout, NULL);
1731
1732   if (font == &local_font)
1733     {
1734       g_object_unref (G_OBJECT (font->layout));
1735       pango_font_description_free (font->desc);
1736     }
1737
1738   return h;
1739 }
1740
1741 static void
1742 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1743                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1744                 int *width, int *height, int *brk)
1745 {
1746   *width = 0;
1747   *height = 0;
1748
1749   /* If enabled, draws a blue rectangle around the cell extents, which can be
1750      useful for debugging layout. */
1751   if (0)
1752     {
1753       if (clip[H][0] != clip[H][1])
1754         {
1755           int offset = (xr->nest) * XR_POINT;
1756
1757           cairo_save (xr->cairo);
1758           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1759           dump_rectangle (xr,
1760                           bb[H][0] + offset, bb[V][0] + offset,
1761                           bb[H][1] - offset, bb[V][1] - offset);
1762           cairo_restore (xr->cairo);
1763         }
1764     }
1765
1766   if (brk)
1767     *brk = bb[V][0];
1768   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
1769 }
1770 \f
1771 struct output_driver_factory pdf_driver_factory =
1772   { "pdf", "pspp.pdf", xr_pdf_create };
1773 struct output_driver_factory ps_driver_factory =
1774   { "ps", "pspp.ps", xr_ps_create };
1775 struct output_driver_factory svg_driver_factory =
1776   { "svg", "pspp.svg", xr_svg_create };
1777
1778 static const struct output_driver_class cairo_driver_class =
1779 {
1780   "cairo",
1781   xr_destroy,
1782   xr_submit,
1783   xr_flush,
1784 };
1785 \f
1786 /* GUI rendering helpers. */
1787
1788 struct xr_rendering
1789   {
1790     struct output_item *item;
1791
1792     /* Table items. */
1793     struct render_pager *p;
1794     struct xr_driver *xr;
1795   };
1796
1797 #define CHART_WIDTH 500
1798 #define CHART_HEIGHT 375
1799
1800
1801
1802 struct xr_driver *
1803 xr_driver_create (cairo_t *cairo, struct string_map *options)
1804 {
1805   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1806   xr_set_cairo (xr, cairo);
1807   return xr;
1808 }
1809
1810 /* Destroy XR, which should have been created with xr_driver_create().  Any
1811    cairo_t added to XR is not destroyed, because it is owned by the client. */
1812 void
1813 xr_driver_destroy (struct xr_driver *xr)
1814 {
1815   if (xr != NULL)
1816     {
1817       xr->cairo = NULL;
1818       output_driver_destroy (&xr->driver);
1819     }
1820 }
1821
1822 static struct xr_rendering *
1823 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1824 {
1825   struct pivot_table *pt = pivot_table_create_for_text (
1826     NULL, pivot_value_new_user_text (text, -1));
1827   struct table_item *table_item = table_item_create (pt);
1828   struct xr_rendering *r = xr_rendering_create (xr, &table_item->output_item,
1829                                                 cr);
1830   table_item_unref (table_item);
1831
1832   return r;
1833 }
1834
1835 void
1836 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1837 {
1838   if (is_table_item (xr->item))
1839     apply_options (xr->xr, o);
1840 }
1841
1842 struct xr_rendering *
1843 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1844                      cairo_t *cr)
1845 {
1846   struct xr_rendering *r = NULL;
1847
1848   if (is_text_item (item))
1849     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1850                                   cr);
1851   else if (is_message_item (item))
1852     {
1853       const struct message_item *message_item = to_message_item (item);
1854       char *s = msg_to_string (message_item_get_msg (message_item));
1855       r = xr_rendering_create_text (xr, s, cr);
1856       free (s);
1857     }
1858   else if (is_table_item (item))
1859     {
1860       r = xzalloc (sizeof *r);
1861       r->item = output_item_ref (item);
1862       r->xr = xr;
1863       xr_set_cairo (xr, cr);
1864       r->p = render_pager_create (xr->params, to_table_item (item));
1865     }
1866   else if (is_chart_item (item))
1867     {
1868       r = xzalloc (sizeof *r);
1869       r->item = output_item_ref (item);
1870     }
1871   else if (is_group_open_item (item))
1872     r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
1873                                   cr);
1874
1875   return r;
1876 }
1877
1878 void
1879 xr_rendering_destroy (struct xr_rendering *r)
1880 {
1881   if (r)
1882     {
1883       output_item_unref (r->item);
1884       render_pager_destroy (r->p);
1885       free (r);
1886     }
1887 }
1888
1889 void
1890 xr_rendering_measure (struct xr_rendering *r, int *wp, int *hp)
1891 {
1892   int w, h;
1893
1894   if (is_table_item (r->item))
1895     {
1896       w = render_pager_get_size (r->p, H) / XR_POINT;
1897       h = render_pager_get_size (r->p, V) / XR_POINT;
1898     }
1899   else
1900     {
1901       w = CHART_WIDTH;
1902       h = CHART_HEIGHT;
1903     }
1904
1905   if (wp)
1906     *wp = w;
1907   if (hp)
1908     *hp = h;
1909 }
1910
1911 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1912                     double x, double y, double width, double height);
1913
1914 /* Draws onto CR */
1915 void
1916 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1917                    int x0, int y0, int x1, int y1)
1918 {
1919   if (is_table_item (r->item))
1920     {
1921       struct xr_driver *xr = r->xr;
1922
1923       xr_set_cairo (xr, cr);
1924
1925       render_pager_draw_region (r->p, x0 * XR_POINT, y0 * XR_POINT,
1926                                 (x1 - x0) * XR_POINT, (y1 - y0) * XR_POINT);
1927     }
1928   else
1929     xr_draw_chart (to_chart_item (r->item), cr,
1930                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1931 }
1932
1933 static void
1934 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1935                double x, double y, double width, double height)
1936 {
1937   struct xrchart_geometry geom;
1938
1939   cairo_save (cr);
1940   cairo_translate (cr, x, y + height);
1941   cairo_scale (cr, 1.0, -1.0);
1942   xrchart_geometry_init (cr, &geom, width, height);
1943   if (is_boxplot (chart_item))
1944     xrchart_draw_boxplot (chart_item, cr, &geom);
1945   else if (is_histogram_chart (chart_item))
1946     xrchart_draw_histogram (chart_item, cr, &geom);
1947   else if (is_np_plot_chart (chart_item))
1948     xrchart_draw_np_plot (chart_item, cr, &geom);
1949   else if (is_piechart (chart_item))
1950     xrchart_draw_piechart (chart_item, cr, &geom);
1951   else if (is_barchart (chart_item))
1952     xrchart_draw_barchart (chart_item, cr, &geom);
1953   else if (is_roc_chart (chart_item))
1954     xrchart_draw_roc (chart_item, cr, &geom);
1955   else if (is_scree (chart_item))
1956     xrchart_draw_scree (chart_item, cr, &geom);
1957   else if (is_spreadlevel_plot_chart (chart_item))
1958     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1959   else if (is_scatterplot_chart (chart_item))
1960     xrchart_draw_scatterplot (chart_item, cr, &geom);
1961   else
1962     NOT_REACHED ();
1963   xrchart_geometry_free (cr, &geom);
1964
1965   cairo_restore (cr);
1966 }
1967
1968 char *
1969 xr_draw_png_chart (const struct chart_item *item,
1970                    const char *file_name_template, int number,
1971                    const struct cell_color *fg,
1972                    const struct cell_color *bg)
1973 {
1974   const int width = 640;
1975   const int length = 480;
1976
1977   cairo_surface_t *surface;
1978   cairo_status_t status;
1979   const char *number_pos;
1980   char *file_name;
1981   cairo_t *cr;
1982
1983   number_pos = strchr (file_name_template, '#');
1984   if (number_pos != NULL)
1985     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1986                            file_name_template, number, number_pos + 1);
1987   else
1988     file_name = xstrdup (file_name_template);
1989
1990   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1991   cr = cairo_create (surface);
1992
1993   cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0);
1994   cairo_paint (cr);
1995
1996   cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0);
1997
1998   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1999
2000   status = cairo_surface_write_to_png (surface, file_name);
2001   if (status != CAIRO_STATUS_SUCCESS)
2002     msg (ME, _("error writing output file `%s': %s"),
2003            file_name, cairo_status_to_string (status));
2004
2005   cairo_destroy (cr);
2006   cairo_surface_destroy (surface);
2007
2008   return file_name;
2009 }
2010 \f
2011 struct xr_table_state
2012   {
2013     struct xr_render_fsm fsm;
2014     struct render_pager *p;
2015   };
2016
2017 static bool
2018 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
2019 {
2020   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
2021
2022   while (render_pager_has_next (ts->p))
2023     {
2024       int used;
2025
2026       used = render_pager_draw_next (ts->p, xr->length - xr->y);
2027       if (!used)
2028         {
2029           assert (xr->y > 0);
2030           return true;
2031         }
2032       else
2033         xr->y += used;
2034     }
2035   return false;
2036 }
2037
2038 static void
2039 xr_table_destroy (struct xr_render_fsm *fsm)
2040 {
2041   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
2042
2043   render_pager_destroy (ts->p);
2044   free (ts);
2045 }
2046
2047 static struct xr_render_fsm *
2048 xr_render_table (struct xr_driver *xr, struct table_item *table_item)
2049 {
2050   struct xr_table_state *ts;
2051
2052   ts = xmalloc (sizeof *ts);
2053   ts->fsm.render = xr_table_render;
2054   ts->fsm.destroy = xr_table_destroy;
2055
2056   if (xr->y > 0)
2057     xr->y += xr->char_height;
2058
2059   ts->p = render_pager_create (xr->params, table_item);
2060   table_item_unref (table_item);
2061
2062   return &ts->fsm;
2063 }
2064 \f
2065 struct xr_chart_state
2066   {
2067     struct xr_render_fsm fsm;
2068     struct chart_item *chart_item;
2069   };
2070
2071 static bool
2072 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
2073 {
2074   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
2075
2076   const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
2077
2078   if (xr->y > xr->length - chart_height)
2079     return true;
2080
2081   if (xr->cairo != NULL)
2082     {
2083       xr_draw_chart (cs->chart_item, xr->cairo,
2084                      0.0,
2085                      xr_to_pt (xr->y),
2086                      xr_to_pt (xr->width),
2087                      xr_to_pt (chart_height));
2088     }
2089   xr->y += chart_height;
2090
2091   return false;
2092 }
2093
2094 static void
2095 xr_chart_destroy (struct xr_render_fsm *fsm)
2096 {
2097   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
2098
2099   chart_item_unref (cs->chart_item);
2100   free (cs);
2101 }
2102
2103 static struct xr_render_fsm *
2104 xr_render_chart (const struct chart_item *chart_item)
2105 {
2106   struct xr_chart_state *cs;
2107
2108   cs = xmalloc (sizeof *cs);
2109   cs->fsm.render = xr_chart_render;
2110   cs->fsm.destroy = xr_chart_destroy;
2111   cs->chart_item = chart_item_ref (chart_item);
2112
2113   return &cs->fsm;
2114 }
2115 \f
2116 static bool
2117 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
2118 {
2119   return xr->y > 0;
2120 }
2121
2122 static void
2123 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
2124 {
2125   /* Nothing to do. */
2126 }
2127
2128 static struct xr_render_fsm *
2129 xr_render_eject (void)
2130 {
2131   static struct xr_render_fsm eject_renderer =
2132     {
2133       xr_eject_render,
2134       xr_eject_destroy
2135     };
2136
2137   return &eject_renderer;
2138 }
2139 \f
2140 static struct xr_render_fsm *
2141 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
2142 {
2143   enum text_item_type type = text_item_get_type (text_item);
2144   const char *text = text_item_get_text (text_item);
2145
2146   switch (type)
2147     {
2148     case TEXT_ITEM_PAGE_TITLE:
2149       string_map_replace (&xr->heading_vars, "PageTitle", text);
2150       break;
2151
2152     case TEXT_ITEM_EJECT_PAGE:
2153       if (xr->y > 0)
2154         return xr_render_eject ();
2155       break;
2156
2157     default:
2158       return xr_render_table (
2159         xr, text_item_to_table_item (text_item_ref (text_item)));
2160     }
2161
2162   return NULL;
2163 }
2164
2165 static struct xr_render_fsm *
2166 xr_render_message (struct xr_driver *xr,
2167                    const struct message_item *message_item)
2168 {
2169   char *s = msg_to_string (message_item_get_msg (message_item));
2170   struct text_item *item = text_item_create (TEXT_ITEM_LOG, s);
2171   free (s);
2172   return xr_render_table (xr, text_item_to_table_item (item));
2173 }
2174
2175 static struct xr_render_fsm *
2176 xr_render_output_item (struct xr_driver *xr,
2177                        const struct output_item *output_item)
2178 {
2179   if (is_table_item (output_item))
2180     return xr_render_table (xr, table_item_ref (to_table_item (output_item)));
2181   else if (is_chart_item (output_item))
2182     return xr_render_chart (to_chart_item (output_item));
2183   else if (is_text_item (output_item))
2184     return xr_render_text (xr, to_text_item (output_item));
2185   else if (is_message_item (output_item))
2186     return xr_render_message (xr, to_message_item (output_item));
2187   else
2188     return NULL;
2189 }