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