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