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