cairo: Render group_open_items.
[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/message.h"
24 #include "libpspp/pool.h"
25 #include "libpspp/start-date.h"
26 #include "libpspp/str.h"
27 #include "libpspp/string-map.h"
28 #include "libpspp/version.h"
29 #include "data/file-handle-def.h"
30 #include "output/cairo-chart.h"
31 #include "output/chart-item-provider.h"
32 #include "output/charts/boxplot.h"
33 #include "output/charts/np-plot.h"
34 #include "output/charts/piechart.h"
35 #include "output/charts/barchart.h"
36 #include "output/charts/plot-hist.h"
37 #include "output/charts/roc-chart.h"
38 #include "output/charts/spreadlevel-plot.h"
39 #include "output/charts/scree.h"
40 #include "output/charts/scatterplot.h"
41 #include "output/driver-provider.h"
42 #include "output/group-item.h"
43 #include "output/message-item.h"
44 #include "output/options.h"
45 #include "output/render.h"
46 #include "output/tab.h"
47 #include "output/table-item.h"
48 #include "output/table.h"
49 #include "output/text-item.h"
50
51 #include <cairo/cairo-pdf.h>
52 #include <cairo/cairo-ps.h>
53 #include <cairo/cairo-svg.h>
54 #include <cairo/cairo.h>
55 #include <math.h>
56 #include <pango/pango-font.h>
57 #include <pango/pango-layout.h>
58 #include <pango/pango.h>
59 #include <pango/pangocairo.h>
60 #include <stdlib.h>
61
62 #include "gl/c-ctype.h"
63 #include "gl/c-strcase.h"
64 #include "gl/intprops.h"
65 #include "gl/minmax.h"
66 #include "gl/xalloc.h"
67
68 #include "gettext.h"
69 #define _(msgid) gettext (msgid)
70
71 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
72 #define H TABLE_HORZ
73 #define V TABLE_VERT
74
75 /* The unit used for internal measurements is inch/(72 * XR_POINT).
76    (Thus, XR_POINT units represent one point.) */
77 #define XR_POINT PANGO_SCALE
78
79 /* Conversions to and from points. */
80 static double
81 xr_to_pt (int x)
82 {
83   return x / (double) XR_POINT;
84 }
85
86 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
87 static int
88 px_to_xr (int x)
89 {
90   return x * (PANGO_SCALE * 72 / 96);
91 }
92
93 /* Dimensions for drawing lines in tables. */
94 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
95 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
96
97 /* Output types. */
98 enum xr_output_type
99   {
100     XR_PDF,
101     XR_PS,
102     XR_SVG
103   };
104
105 /* Cairo fonts. */
106 enum xr_font_type
107   {
108     XR_FONT_PROPORTIONAL,
109     XR_FONT_EMPHASIS,
110     XR_FONT_FIXED,
111     XR_N_FONTS
112   };
113
114 /* A font for use with Cairo. */
115 struct xr_font
116   {
117     PangoFontDescription *desc;
118     PangoLayout *layout;
119   };
120
121 /* An output item whose rendering is in progress. */
122 struct xr_render_fsm
123   {
124     /* Renders as much of itself as it can on the current page.  Returns true
125        if rendering is complete, false if the output item needs another
126        page. */
127     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
128
129     /* Destroys the output item. */
130     void (*destroy) (struct xr_render_fsm *);
131   };
132
133 /* Cairo output driver. */
134 struct xr_driver
135   {
136     struct output_driver driver;
137
138     /* User parameters. */
139     struct xr_font fonts[XR_N_FONTS];
140
141     int width;                  /* Page width minus margins. */
142     int length;                 /* Page length minus margins and header. */
143
144     int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
145     int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
146     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
147     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
148
149     int line_space;             /* Space between lines. */
150     int line_width;             /* Width of lines. */
151
152     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
153
154     struct xr_color bg;    /* Background color */
155     struct xr_color fg;    /* Foreground color */
156
157     /* Internal state. */
158     struct render_params *params;
159     int char_width, char_height;
160     char *command_name;
161     char *title;
162     char *subtitle;
163     cairo_t *cairo;
164     int page_number;            /* Current page number. */
165     int x, y;
166     struct xr_render_fsm *fsm;
167     int nest;
168   };
169
170 static const struct output_driver_class cairo_driver_class;
171
172 static void xr_driver_destroy_fsm (struct xr_driver *);
173 static void xr_driver_run_fsm (struct xr_driver *);
174
175 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
176                           enum render_line_style styles[TABLE_N_AXES][2],
177                           struct cell_color colors[TABLE_N_AXES][2]);
178 static void xr_measure_cell_width (void *, const struct table_cell *,
179                                    int *min, int *max);
180 static int xr_measure_cell_height (void *, const struct table_cell *,
181                                    int width);
182 static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
183                           int bb[TABLE_N_AXES][2],
184                           int spill[TABLE_N_AXES][2],
185                           int clip[TABLE_N_AXES][2]);
186 static int xr_adjust_break (void *, const struct table_cell *,
187                             int width, int height);
188
189 static struct xr_render_fsm *xr_render_output_item (
190   struct xr_driver *, const struct output_item *);
191 \f
192 /* Output driver basics. */
193
194 static struct xr_driver *
195 xr_driver_cast (struct output_driver *driver)
196 {
197   assert (driver->class == &cairo_driver_class);
198   return UP_CAST (driver, struct xr_driver, driver);
199 }
200
201 static struct driver_option *
202 opt (struct output_driver *d, struct string_map *options, const char *key,
203      const char *default_value)
204 {
205   return driver_option_get (d, options, key, default_value);
206 }
207
208 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
209    Currently, the input string must be of the form "#RRRRGGGGBBBB"
210    Future implementations might allow things like "yellow" and
211    "sky-blue-ultra-brown"
212 */
213 void
214 parse_color (struct output_driver *d, struct string_map *options,
215              const char *key, const char *default_value,
216              struct xr_color *color)
217 {
218   int red, green, blue;
219   char *string = parse_string (opt (d, options, key, default_value));
220
221   if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
222     {
223       /* If the parsed option string fails, then try the default value */
224       if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
225         {
226           /* ... and if that fails set everything to zero */
227           red = green = blue = 0;
228         }
229     }
230
231   free (string);
232
233   /* Convert 16 bit ints to float */
234   color->red = red / (double) 0xFFFF;
235   color->green = green / (double) 0xFFFF;
236   color->blue = blue / (double) 0xFFFF;
237 }
238
239 static PangoFontDescription *
240 parse_font (const char *font, int default_size, bool bold, bool italic)
241 {
242   if (!c_strcasecmp (font, "Monospaced"))
243     font = "Monospace";
244
245   PangoFontDescription *desc = pango_font_description_from_string (font);
246   if (desc == NULL)
247     return NULL;
248
249   /* If the font description didn't include an explicit font size, then set it
250      to DEFAULT_SIZE, which is in inch/72000 units. */
251   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
252     pango_font_description_set_size (desc,
253                                      (default_size / 1000.0) * PANGO_SCALE);
254
255   pango_font_description_set_weight (desc, (bold
256                                             ? PANGO_WEIGHT_BOLD
257                                             : PANGO_WEIGHT_NORMAL));
258   pango_font_description_set_style (desc, (italic
259                                            ? PANGO_STYLE_ITALIC
260                                            : PANGO_STYLE_NORMAL));
261
262   return desc;
263 }
264
265 static PangoFontDescription *
266 parse_font_option (struct output_driver *d, struct string_map *options,
267                    const char *key, const char *default_value,
268                    int default_size, bool bold, bool italic)
269 {
270   char *string = parse_string (opt (d, options, key, default_value));
271   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
272   if (!desc)
273     {
274       msg (MW, _("`%s': bad font specification"), string);
275
276       /* Fall back to DEFAULT_VALUE, which had better be a valid font
277          description. */
278       desc = parse_font (default_value, default_size, bold, italic);
279       assert (desc != NULL);
280     }
281   free (string);
282
283   return desc;
284 }
285
286 static void
287 apply_options (struct xr_driver *xr, struct string_map *o)
288 {
289   struct output_driver *d = &xr->driver;
290
291   /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
292   int left_margin, right_margin;
293   int top_margin, bottom_margin;
294   int paper_width, paper_length;
295   int font_size;
296   int min_break[TABLE_N_AXES];
297
298   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
299   const double scale = XR_POINT / 1000.;
300
301   int i;
302
303   for (i = 0; i < XR_N_FONTS; i++)
304     {
305       struct xr_font *font = &xr->fonts[i];
306
307       if (font->desc != NULL)
308         pango_font_description_free (font->desc);
309     }
310
311   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
312   xr->fonts[XR_FONT_FIXED].desc = parse_font_option
313     (d, o, "fixed-font", "monospace", font_size, false, false);
314   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font_option (
315     d, o, "prop-font", "sans serif", font_size, false, false);
316   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option (
317     d, o, "emph-font", "sans serif", font_size, false, true);
318
319   xr->page_number = 0;
320
321   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
322   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
323
324   /* Get dimensions.  */
325   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
326   left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
327   right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
328   top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
329   bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
330
331   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
332   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
333
334   /* Convert to inch/(XR_POINT * 72). */
335   xr->left_margin = left_margin * scale;
336   xr->right_margin = right_margin * scale;
337   xr->top_margin = top_margin * scale;
338   xr->bottom_margin = bottom_margin * scale;
339   xr->width = (paper_width - left_margin - right_margin) * scale;
340   xr->length = (paper_length - top_margin - bottom_margin) * scale;
341   xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
342   xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
343 }
344
345 static struct xr_driver *
346 xr_allocate (const char *name, int device_type, struct string_map *o)
347 {
348   struct xr_driver *xr = xzalloc (sizeof *xr);
349   struct output_driver *d = &xr->driver;
350
351   output_driver_init (d, &cairo_driver_class, name, device_type);
352
353   apply_options (xr, o);
354
355   return xr;
356 }
357
358 static int
359 pango_to_xr (int pango)
360 {
361   return (XR_POINT != PANGO_SCALE
362           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
363           : pango);
364 }
365
366 static int
367 xr_to_pango (int xr)
368 {
369   return (XR_POINT != PANGO_SCALE
370           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
371           : xr);
372 }
373
374 static bool
375 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
376 {
377   int i;
378
379   xr->cairo = cairo;
380
381   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
382
383   xr->char_width = 0;
384   xr->char_height = 0;
385   for (i = 0; i < XR_N_FONTS; i++)
386     {
387       struct xr_font *font = &xr->fonts[i];
388       int char_width, char_height;
389
390       font->layout = pango_cairo_create_layout (cairo);
391       pango_layout_set_font_description (font->layout, font->desc);
392
393       pango_layout_set_text (font->layout, "0", 1);
394       pango_layout_get_size (font->layout, &char_width, &char_height);
395       xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
396       xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
397     }
398
399   if (xr->params == NULL)
400     {
401       xr->params = xmalloc (sizeof *xr->params);
402       xr->params->draw_line = xr_draw_line;
403       xr->params->measure_cell_width = xr_measure_cell_width;
404       xr->params->measure_cell_height = xr_measure_cell_height;
405       xr->params->adjust_break = xr_adjust_break;
406       xr->params->draw_cell = xr_draw_cell;
407       xr->params->aux = xr;
408       xr->params->size[H] = xr->width;
409       xr->params->size[V] = xr->length;
410       xr->params->font_size[H] = xr->char_width;
411       xr->params->font_size[V] = xr->char_height;
412
413       int lw = XR_LINE_WIDTH;
414       int ls = XR_LINE_SPACE;
415       for (i = 0; i < TABLE_N_AXES; i++)
416         {
417           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
418           xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw;
419           xr->params->line_widths[i][RENDER_LINE_DASHED] = lw;
420           xr->params->line_widths[i][RENDER_LINE_THICK] = lw * 2;
421           xr->params->line_widths[i][RENDER_LINE_THIN] = lw / 2;
422           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls;
423         }
424
425       for (i = 0; i < TABLE_N_AXES; i++)
426         xr->params->min_break[i] = xr->min_break[i];
427       xr->params->supports_margins = true;
428     }
429
430   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
431
432   return true;
433 }
434
435 static struct output_driver *
436 xr_create (const char *file_name, enum settings_output_devices device_type,
437            struct string_map *o, enum xr_output_type file_type)
438 {
439   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
440   struct xr_driver *xr;
441   cairo_surface_t *surface;
442   cairo_status_t status;
443   double width_pt, length_pt;
444
445   xr = xr_allocate (file_name, device_type, o);
446
447   width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
448   length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
449   if (file_type == XR_PDF)
450     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
451   else if (file_type == XR_PS)
452     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
453   else if (file_type == XR_SVG)
454     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
455   else
456     NOT_REACHED ();
457
458   status = cairo_surface_status (surface);
459   if (status != CAIRO_STATUS_SUCCESS)
460     {
461       msg (ME, _("error opening output file `%s': %s"),
462              file_name, cairo_status_to_string (status));
463       cairo_surface_destroy (surface);
464       goto error;
465     }
466
467   xr->cairo = cairo_create (surface);
468   cairo_surface_destroy (surface);
469
470   if (!xr_set_cairo (xr, xr->cairo))
471     goto error;
472
473   cairo_save (xr->cairo);
474   xr_driver_next_page (xr, xr->cairo);
475
476   if (xr->width / xr->char_width < MIN_WIDTH)
477     {
478       msg (ME, _("The defined page is not wide enough to hold at least %d "
479                      "characters in the default font.  In fact, there's only "
480                      "room for %d characters."),
481              MIN_WIDTH,
482              xr->width / xr->char_width);
483       goto error;
484     }
485
486   if (xr->length / xr->char_height < MIN_LENGTH)
487     {
488       msg (ME, _("The defined page is not long enough to hold at least %d "
489                      "lines in the default font.  In fact, there's only "
490                      "room for %d lines."),
491              MIN_LENGTH,
492              xr->length / xr->char_height);
493       goto error;
494     }
495
496   return &xr->driver;
497
498  error:
499   output_driver_destroy (&xr->driver);
500   return NULL;
501 }
502
503 static struct output_driver *
504 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
505                struct string_map *o)
506 {
507   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
508   fh_unref (fh);
509   return od ;
510 }
511
512 static struct output_driver *
513 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
514                struct string_map *o)
515 {
516   struct output_driver *od =  xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
517   fh_unref (fh);
518   return od ;
519 }
520
521 static struct output_driver *
522 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
523                struct string_map *o)
524 {
525   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
526   fh_unref (fh);
527   return od ;
528 }
529
530 static void
531 xr_destroy (struct output_driver *driver)
532 {
533   struct xr_driver *xr = xr_driver_cast (driver);
534   size_t i;
535
536   xr_driver_destroy_fsm (xr);
537
538   if (xr->cairo != NULL)
539     {
540       cairo_status_t status;
541
542       cairo_surface_finish (cairo_get_target (xr->cairo));
543       status = cairo_status (xr->cairo);
544       if (status != CAIRO_STATUS_SUCCESS)
545         msg (ME, _("error drawing output for %s driver: %s"),
546                output_driver_get_name (driver),
547                cairo_status_to_string (status));
548       cairo_destroy (xr->cairo);
549     }
550
551   for (i = 0; i < XR_N_FONTS; i++)
552     {
553       struct xr_font *font = &xr->fonts[i];
554
555       if (font->desc != NULL)
556         pango_font_description_free (font->desc);
557       if (font->layout != NULL)
558         g_object_unref (font->layout);
559     }
560
561   free (xr->params);
562   free (xr);
563 }
564
565 static void
566 xr_flush (struct output_driver *driver)
567 {
568   struct xr_driver *xr = xr_driver_cast (driver);
569
570   cairo_surface_flush (cairo_get_target (xr->cairo));
571 }
572
573 static void
574 xr_submit (struct output_driver *driver, const struct output_item *output_item)
575 {
576   struct xr_driver *xr = xr_driver_cast (driver);
577
578   xr_driver_output_item (xr, output_item);
579   while (xr_driver_need_new_page (xr))
580     {
581       cairo_restore (xr->cairo);
582       cairo_show_page (xr->cairo);
583       cairo_save (xr->cairo);
584       xr_driver_next_page (xr, xr->cairo);
585     }
586 }
587 \f
588 /* Functions for rendering a series of output items to a series of Cairo
589    contexts, with pagination.
590
591    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
592    its underlying implementation.
593
594    See the big comment in cairo.h for intended usage. */
595
596 /* Gives new page CAIRO to XR for output. */
597 void
598 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
599 {
600   cairo_save (cairo);
601   cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
602   cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
603   cairo_fill (cairo);
604   cairo_restore (cairo);
605
606   cairo_translate (cairo,
607                    xr_to_pt (xr->left_margin),
608                    xr_to_pt (xr->top_margin));
609
610   xr->page_number++;
611   xr->cairo = cairo;
612   xr->x = xr->y = 0;
613   xr_driver_run_fsm (xr);
614 }
615
616 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
617    rendering a previous output item, that is, only if xr_driver_need_new_page()
618    returns false. */
619 void
620 xr_driver_output_item (struct xr_driver *xr,
621                        const struct output_item *output_item)
622 {
623   assert (xr->fsm == NULL);
624   xr->fsm = xr_render_output_item (xr, output_item);
625   xr_driver_run_fsm (xr);
626 }
627
628 /* Returns true if XR is in the middle of rendering an output item and needs a
629    new page to be appended using xr_driver_next_page() to make progress,
630    otherwise false. */
631 bool
632 xr_driver_need_new_page (const struct xr_driver *xr)
633 {
634   return xr->fsm != NULL;
635 }
636
637 /* Returns true if the current page doesn't have any content yet. */
638 bool
639 xr_driver_is_page_blank (const struct xr_driver *xr)
640 {
641   return xr->y == 0;
642 }
643
644 static void
645 xr_driver_destroy_fsm (struct xr_driver *xr)
646 {
647   if (xr->fsm != NULL)
648     {
649       xr->fsm->destroy (xr->fsm);
650       xr->fsm = NULL;
651     }
652 }
653
654 static void
655 xr_driver_run_fsm (struct xr_driver *xr)
656 {
657   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
658     xr_driver_destroy_fsm (xr);
659 }
660 \f
661 static void
662 xr_layout_cell (struct xr_driver *, const struct table_cell *,
663                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
664                 int *width, int *height, int *brk);
665
666 static void
667 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
668            const struct cell_color *color)
669 {
670   cairo_new_path (xr->cairo);
671   cairo_set_source_rgb (xr->cairo,
672                         color->r / 255.0, color->g / 255.0, color->b / 255.0);
673   cairo_set_line_width (
674     xr->cairo,
675     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
676               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
677               : XR_LINE_WIDTH));
678   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
679   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
680   cairo_stroke (xr->cairo);
681 }
682
683 static void UNUSED
684 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
685 {
686   cairo_new_path (xr->cairo);
687   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
688   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
689   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
690   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
691   cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
692   cairo_close_path (xr->cairo);
693   cairo_stroke (xr->cairo);
694 }
695
696 static void
697 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
698 {
699   cairo_new_path (xr->cairo);
700   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
701   cairo_rectangle (xr->cairo,
702                    xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
703                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
704   cairo_fill (xr->cairo);
705 }
706
707 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
708    shortening it to X0...X1 if SHORTEN is true.
709    Draws a horizontal line X1...X3 at Y if RIGHT says so,
710    shortening it to X2...X3 if SHORTEN is true. */
711 static void
712 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
713            enum render_line_style left, enum render_line_style right,
714            const struct cell_color *left_color,
715            const struct cell_color *right_color,
716            bool shorten)
717 {
718   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
719       && cell_color_equal (left_color, right_color))
720     dump_line (xr, x0, y, x3, y, left, left_color);
721   else
722     {
723       if (left != RENDER_LINE_NONE)
724         dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
725       if (right != RENDER_LINE_NONE)
726         dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
727     }
728 }
729
730 /* Draws a vertical line Y0...Y2 at X if TOP says so,
731    shortening it to Y0...Y1 if SHORTEN is true.
732    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
733    shortening it to Y2...Y3 if SHORTEN is true. */
734 static void
735 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
736            enum render_line_style top, enum render_line_style bottom,
737            const struct cell_color *top_color,
738            const struct cell_color *bottom_color,
739            bool shorten)
740 {
741   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
742       && cell_color_equal (top_color, bottom_color))
743     dump_line (xr, x, y0, x, y3, top, top_color);
744   else
745     {
746       if (top != RENDER_LINE_NONE)
747         dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
748       if (bottom != RENDER_LINE_NONE)
749         dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
750     }
751 }
752
753 static void
754 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
755               enum render_line_style styles[TABLE_N_AXES][2],
756               struct cell_color colors[TABLE_N_AXES][2])
757 {
758   const int x0 = bb[H][0];
759   const int y0 = bb[V][0];
760   const int x3 = bb[H][1];
761   const int y3 = bb[V][1];
762   const int top = styles[H][0];
763   const int bottom = styles[H][1];
764
765   int start_side = render_direction_rtl();
766   int end_side = !start_side;
767   const int start_of_line = styles[V][start_side];
768   const int end_of_line   = styles[V][end_side];
769   const struct cell_color *top_color = &colors[H][0];
770   const struct cell_color *bottom_color = &colors[H][1];
771   const struct cell_color *start_color = &colors[V][start_side];
772   const struct cell_color *end_color = &colors[V][end_side];
773
774   /* The algorithm here is somewhat subtle, to allow it to handle
775      all the kinds of intersections that we need.
776
777      Three additional ordinates are assigned along the x axis.  The
778      first is xc, midway between x0 and x3.  The others are x1 and
779      x2; for a single vertical line these are equal to xc, and for
780      a double vertical line they are the ordinates of the left and
781      right half of the double line.
782
783      yc, y1, and y2 are assigned similarly along the y axis.
784
785      The following diagram shows the coordinate system and output
786      for double top and bottom lines, single left line, and no
787      right line:
788
789                  x0       x1 xc  x2      x3
790                y0 ________________________
791                   |        #     #       |
792                   |        #     #       |
793                   |        #     #       |
794                   |        #     #       |
795                   |        #     #       |
796      y1 = y2 = yc |#########     #       |
797                   |        #     #       |
798                   |        #     #       |
799                   |        #     #       |
800                   |        #     #       |
801                y3 |________#_____#_______|
802   */
803   struct xr_driver *xr = xr_;
804
805   /* Offset from center of each line in a pair of double lines. */
806   int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
807
808   /* Are the lines along each axis single or double?
809      (It doesn't make sense to have different kinds of line on the
810      same axis, so we don't try to gracefully handle that case.) */
811   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
812   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
813
814   /* When horizontal lines are doubled,
815      the left-side line along y1 normally runs from x0 to x2,
816      and the right-side line along y1 from x3 to x1.
817      If the top-side line is also doubled, we shorten the y1 lines,
818      so that the left-side line runs only to x1,
819      and the right-side line only to x2.
820      Otherwise, the horizontal line at y = y1 below would cut off
821      the intersection, which looks ugly:
822                x0       x1     x2      x3
823              y0 ________________________
824                 |        #     #       |
825                 |        #     #       |
826                 |        #     #       |
827                 |        #     #       |
828              y1 |#########     ########|
829                 |                      |
830                 |                      |
831              y2 |######################|
832                 |                      |
833                 |                      |
834              y3 |______________________|
835      It is more of a judgment call when the horizontal line is
836      single.  We actually choose to cut off the line anyhow, as
837      shown in the first diagram above.
838   */
839   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
840   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
841   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
842   int horz_line_ofs = double_vert ? double_line_ofs : 0;
843   int xc = (x0 + x3) / 2;
844   int x1 = xc - horz_line_ofs;
845   int x2 = xc + horz_line_ofs;
846
847   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
848   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
849   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
850   int vert_line_ofs = double_horz ? double_line_ofs : 0;
851   int yc = (y0 + y3) / 2;
852   int y1 = yc - vert_line_ofs;
853   int y2 = yc + vert_line_ofs;
854
855   if (!double_horz)
856     horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
857                start_color, end_color, shorten_yc_line);
858   else
859     {
860       horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
861                  start_color, end_color, shorten_y1_lines);
862       horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
863                  start_color, end_color, shorten_y2_lines);
864     }
865
866   if (!double_vert)
867     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
868                shorten_xc_line);
869   else
870     {
871       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
872                  shorten_x1_lines);
873       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
874                  shorten_x2_lines);
875     }
876 }
877
878 static void
879 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
880                        int *min_width, int *max_width)
881 {
882   struct xr_driver *xr = xr_;
883   int bb[TABLE_N_AXES][2];
884   int clip[TABLE_N_AXES][2];
885   int h;
886
887   bb[H][0] = 0;
888   bb[H][1] = INT_MAX;
889   bb[V][0] = 0;
890   bb[V][1] = INT_MAX;
891   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
892   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
893
894   bb[H][1] = 1;
895   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
896
897   if (*min_width > 0)
898     *min_width += px_to_xr (cell->style->margin[H][0]
899                             + cell->style->margin[H][1]);
900   if (*max_width > 0)
901     *max_width += px_to_xr (cell->style->margin[H][0]
902                             + cell->style->margin[H][1]);
903 }
904
905 static int
906 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
907 {
908   struct xr_driver *xr = xr_;
909   int bb[TABLE_N_AXES][2];
910   int clip[TABLE_N_AXES][2];
911   int w, h;
912
913   bb[H][0] = 0;
914   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
915                                + cell->style->margin[H][1]);
916   bb[V][0] = 0;
917   bb[V][1] = INT_MAX;
918   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
919   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
920   h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]);
921   return h;
922 }
923
924 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
925
926 static void
927 xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
928               int bb[TABLE_N_AXES][2],
929               int spill[TABLE_N_AXES][2],
930               int clip[TABLE_N_AXES][2])
931 {
932   struct xr_driver *xr = xr_;
933   int w, h, brk;
934
935   cairo_save (xr->cairo);
936   int bg_clip[TABLE_N_AXES][2];
937   for (int axis = 0; axis < TABLE_N_AXES; axis++)
938     {
939       bg_clip[axis][0] = clip[axis][0];
940       if (bb[axis][0] == clip[axis][0])
941         bg_clip[axis][0] -= spill[axis][0];
942
943       bg_clip[axis][1] = clip[axis][1];
944       if (bb[axis][1] == clip[axis][1])
945         bg_clip[axis][1] += spill[axis][1];
946     }
947   xr_clip (xr, bg_clip);
948   cairo_set_source_rgb (xr->cairo,
949                         cell->style->bg[color_idx].r / 255.,
950                         cell->style->bg[color_idx].g / 255.,
951                         cell->style->bg[color_idx].b / 255.);
952   fill_rectangle (xr,
953                   bb[H][0] - spill[H][0],
954                   bb[V][0] - spill[V][0],
955                   bb[H][1] + spill[H][1],
956                   bb[V][1] + spill[V][1]);
957   cairo_restore (xr->cairo);
958
959   cairo_save (xr->cairo);
960   cairo_set_source_rgb (xr->cairo,
961                         cell->style->fg[color_idx].r / 255.,
962                         cell->style->fg[color_idx].g / 255.,
963                         cell->style->fg[color_idx].b / 255.);
964
965   for (int axis = 0; axis < TABLE_N_AXES; axis++)
966     {
967       bb[axis][0] += px_to_xr (cell->style->margin[axis][0]);
968       bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]);
969     }
970   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
971     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
972   cairo_restore (xr->cairo);
973 }
974
975 static int
976 xr_adjust_break (void *xr_, const struct table_cell *cell,
977                  int width, int height)
978 {
979   struct xr_driver *xr = xr_;
980   int bb[TABLE_N_AXES][2];
981   int clip[TABLE_N_AXES][2];
982   int w, h, brk;
983
984   if (xr_measure_cell_height (xr_, cell, width) < height)
985     return -1;
986
987   bb[H][0] = 0;
988   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
989                                + cell->style->margin[H][1]);
990   if (bb[H][1] <= 0)
991     return 0;
992   bb[V][0] = 0;
993   bb[V][1] = height - px_to_xr (cell->style->margin[V][0]
994                                 + cell->style->margin[V][1]);
995   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
996   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
997   return brk;
998 }
999 \f
1000 static void
1001 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
1002 {
1003   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
1004     {
1005       double x0 = xr_to_pt (clip[H][0] + xr->x);
1006       double y0 = xr_to_pt (clip[V][0] + xr->y);
1007       double x1 = xr_to_pt (clip[H][1] + xr->x);
1008       double y1 = xr_to_pt (clip[V][1] + xr->y);
1009
1010       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
1011       cairo_clip (xr->cairo);
1012     }
1013 }
1014
1015 static void
1016 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
1017 {
1018   attr->start_index = start_index;
1019   pango_attr_list_insert (list, attr);
1020 }
1021
1022 static int
1023 xr_layout_cell_text (struct xr_driver *xr,
1024                      const struct cell_contents *contents,
1025                      const struct cell_style *style,
1026                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1027                      int *widthp, int *brk)
1028 {
1029   unsigned int options = contents->options;
1030   int w, h;
1031
1032   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1033                           : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
1034                           : &xr->fonts[XR_FONT_PROPORTIONAL]);
1035   struct xr_font local_font;
1036   if (style->font)
1037     {
1038       PangoFontDescription *desc = parse_font (
1039         style->font,
1040         style->font_size ? style->font_size * 1000 * 72 / 128 : 10000,
1041         style->bold, style->italic);
1042       if (desc)
1043         {
1044           PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
1045           pango_layout_set_font_description (layout, desc);
1046
1047           local_font.desc = desc;
1048           local_font.layout = layout;
1049           font = &local_font;
1050         }
1051     }
1052
1053   int footnote_adjustment;
1054   if (contents->n_footnotes == 0)
1055     footnote_adjustment = 0;
1056   else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT)
1057     {
1058       PangoAttrList *attrs;
1059
1060       const char *marker = contents->footnotes[0]->marker;
1061       pango_layout_set_text (font->layout, marker, strlen (marker));
1062
1063       attrs = pango_attr_list_new ();
1064       pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
1065       pango_layout_set_attributes (font->layout, attrs);
1066       pango_attr_list_unref (attrs);
1067
1068       pango_layout_get_size (font->layout, &w, &h);
1069       footnote_adjustment = MIN (w, px_to_xr (style->margin[H][1]));
1070     }
1071   else
1072     footnote_adjustment = px_to_xr (style->margin[H][1]);
1073
1074   struct string tmp = DS_EMPTY_INITIALIZER;
1075   const char *text = contents->text;
1076
1077   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
1078      Pango's implementation of it): it will break after a period or a comma
1079      that precedes a digit, e.g. in ".000" it will break after the period.
1080      This code looks for such a situation and inserts a U+2060 WORD JOINER
1081      to prevent the break.
1082
1083      This isn't necessary when the decimal point is between two digits
1084      (e.g. "0.000" won't be broken) or when the display width is not limited so
1085      that word wrapping won't happen.
1086
1087      It isn't necessary to look for more than one period or comma, as would
1088      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
1089      are present then there will always be a digit on both sides of every
1090      period and comma. */
1091   if (bb[H][1] != INT_MAX)
1092     {
1093       const char *decimal = text + strcspn (text, ".,");
1094       if (decimal[0]
1095           && c_isdigit (decimal[1])
1096           && (decimal == text || !c_isdigit (decimal[-1])))
1097         {
1098           ds_extend (&tmp, strlen (text) + 16);
1099           ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1));
1100           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
1101           ds_put_cstr (&tmp, decimal + 1);
1102         }
1103     }
1104
1105   if (footnote_adjustment)
1106     {
1107       bb[H][1] += footnote_adjustment;
1108
1109       if (ds_is_empty (&tmp))
1110         {
1111           ds_extend (&tmp, strlen (text) + 16);
1112           ds_put_cstr (&tmp, text);
1113         }
1114       size_t initial_length = ds_length (&tmp);
1115
1116       cell_contents_format_footnote_markers (contents, &tmp);
1117       pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
1118
1119       PangoAttrList *attrs = pango_attr_list_new ();
1120       if (style->underline)
1121         pango_attr_list_insert (attrs, pango_attr_underline_new (
1122                                PANGO_UNDERLINE_SINGLE));
1123       add_attr_with_start (attrs, pango_attr_rise_new (7000), initial_length);
1124       add_attr_with_start (
1125         attrs, pango_attr_font_desc_new (font->desc), initial_length);
1126       pango_layout_set_attributes (font->layout, attrs);
1127       pango_attr_list_unref (attrs);
1128     }
1129   else
1130     {
1131       const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
1132       pango_layout_set_text (font->layout, content, -1);
1133
1134       if (style->underline)
1135         {
1136           PangoAttrList *attrs = pango_attr_list_new ();
1137           pango_attr_list_insert (attrs, pango_attr_underline_new (
1138                                     PANGO_UNDERLINE_SINGLE));
1139           pango_layout_set_attributes (font->layout, attrs);
1140           pango_attr_list_unref (attrs);
1141         }
1142     }
1143   ds_destroy (&tmp);
1144
1145   pango_layout_set_alignment (
1146     font->layout,
1147     ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1148      : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT
1149      : PANGO_ALIGN_CENTER));
1150   pango_layout_set_width (
1151     font->layout,
1152     bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1153   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1154
1155   if (clip[H][0] != clip[H][1])
1156     {
1157       cairo_save (xr->cairo);
1158       xr_clip (xr, clip);
1159       cairo_translate (xr->cairo,
1160                        xr_to_pt (bb[H][0] + xr->x),
1161                        xr_to_pt (bb[V][0] + xr->y));
1162       pango_cairo_show_layout (xr->cairo, font->layout);
1163
1164       /* If enabled, this draws a blue rectangle around the extents of each
1165          line of text, which can be rather useful for debugging layout
1166          issues. */
1167       if (0)
1168         {
1169           PangoLayoutIter *iter;
1170           iter = pango_layout_get_iter (font->layout);
1171           do
1172             {
1173               PangoRectangle extents;
1174
1175               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1176               cairo_save (xr->cairo);
1177               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1178               dump_rectangle (xr,
1179                               pango_to_xr (extents.x) - xr->x,
1180                               pango_to_xr (extents.y) - xr->y,
1181                               pango_to_xr (extents.x + extents.width) - xr->x,
1182                               pango_to_xr (extents.y + extents.height) - xr->y);
1183               cairo_restore (xr->cairo);
1184             }
1185           while (pango_layout_iter_next_line (iter));
1186           pango_layout_iter_free (iter);
1187         }
1188
1189       cairo_restore (xr->cairo);
1190     }
1191
1192   pango_layout_get_size (font->layout, &w, &h);
1193   w = pango_to_xr (w);
1194   h = pango_to_xr (h);
1195   if (w > *widthp)
1196     *widthp = w;
1197   if (bb[V][0] + h >= bb[V][1])
1198     {
1199       PangoLayoutIter *iter;
1200       int best UNUSED = 0;
1201
1202       /* Choose a breakpoint between lines instead of in the middle of one. */
1203       iter = pango_layout_get_iter (font->layout);
1204       do
1205         {
1206           PangoRectangle extents;
1207           int y0, y1;
1208           int bottom;
1209
1210           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1211           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1212           extents.x = pango_to_xr (extents.x);
1213           extents.y = pango_to_xr (y0);
1214           extents.width = pango_to_xr (extents.width);
1215           extents.height = pango_to_xr (y1 - y0);
1216           bottom = bb[V][0] + extents.y + extents.height;
1217           if (bottom < bb[V][1])
1218             {
1219               if (brk && clip[H][0] != clip[H][1])
1220                 best = bottom;
1221               if (brk)
1222                 *brk = bottom;
1223             }
1224           else
1225             break;
1226         }
1227       while (pango_layout_iter_next_line (iter));
1228       pango_layout_iter_free (iter);
1229
1230       /* If enabled, draws a green line across the chosen breakpoint, which can
1231          be useful for debugging issues with breaking.  */
1232       if (0)
1233         {
1234           if (best && !xr->nest)
1235             dump_line (xr, -xr->left_margin, best,
1236                        xr->width + xr->right_margin, best,
1237                        RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0));
1238         }
1239     }
1240
1241   pango_layout_set_attributes (font->layout, NULL);
1242
1243   if (font == &local_font)
1244     {
1245       g_object_unref (G_OBJECT (font->layout));
1246       pango_font_description_free (font->desc);
1247     }
1248
1249   return bb[V][0] + h;
1250 }
1251
1252 static void
1253 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1254                 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1255                 int *width, int *height, int *brk)
1256 {
1257   int bb[TABLE_N_AXES][2];
1258   size_t i;
1259
1260   *width = 0;
1261   *height = 0;
1262   if (brk)
1263     *brk = 0;
1264
1265   memcpy (bb, bb_, sizeof bb);
1266
1267   /* If enabled, draws a blue rectangle around the cell extents, which can be
1268      useful for debugging layout. */
1269   if (0)
1270     {
1271       if (clip[H][0] != clip[H][1])
1272         {
1273           int offset = (xr->nest) * XR_POINT;
1274
1275           cairo_save (xr->cairo);
1276           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1277           dump_rectangle (xr,
1278                           bb[H][0] + offset, bb[V][0] + offset,
1279                           bb[H][1] - offset, bb[V][1] - offset);
1280           cairo_restore (xr->cairo);
1281         }
1282     }
1283
1284   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1285     {
1286       const struct cell_contents *contents = &cell->contents[i];
1287
1288       if (brk)
1289         *brk = bb[V][0];
1290       if (i > 0)
1291         {
1292           bb[V][0] += xr->char_height / 2;
1293           if (bb[V][0] >= bb[V][1])
1294             break;
1295           if (brk)
1296             *brk = bb[V][0];
1297         }
1298
1299       bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip,
1300                                       width, brk);
1301     }
1302   *height = bb[V][0] - bb_[V][0];
1303 }
1304 \f
1305 struct output_driver_factory pdf_driver_factory =
1306   { "pdf", "pspp.pdf", xr_pdf_create };
1307 struct output_driver_factory ps_driver_factory =
1308   { "ps", "pspp.ps", xr_ps_create };
1309 struct output_driver_factory svg_driver_factory =
1310   { "svg", "pspp.svg", xr_svg_create };
1311
1312 static const struct output_driver_class cairo_driver_class =
1313 {
1314   "cairo",
1315   xr_destroy,
1316   xr_submit,
1317   xr_flush,
1318 };
1319 \f
1320 /* GUI rendering helpers. */
1321
1322 struct xr_rendering
1323   {
1324     struct output_item *item;
1325
1326     /* Table items. */
1327     struct render_pager *p;
1328     struct xr_driver *xr;
1329   };
1330
1331 #define CHART_WIDTH 500
1332 #define CHART_HEIGHT 375
1333
1334
1335
1336 struct xr_driver *
1337 xr_driver_create (cairo_t *cairo, struct string_map *options)
1338 {
1339   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1340   if (!xr_set_cairo (xr, cairo))
1341     {
1342       output_driver_destroy (&xr->driver);
1343       return NULL;
1344     }
1345   return xr;
1346 }
1347
1348 /* Destroy XR, which should have been created with xr_driver_create().  Any
1349    cairo_t added to XR is not destroyed, because it is owned by the client. */
1350 void
1351 xr_driver_destroy (struct xr_driver *xr)
1352 {
1353   if (xr != NULL)
1354     {
1355       xr->cairo = NULL;
1356       output_driver_destroy (&xr->driver);
1357     }
1358 }
1359
1360 static struct xr_rendering *
1361 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1362 {
1363   struct table_item *table_item;
1364   struct xr_rendering *r;
1365
1366   table_item = table_item_create (table_from_string (TAB_LEFT, text),
1367                                   NULL, NULL);
1368   r = xr_rendering_create (xr, &table_item->output_item, cr);
1369   table_item_unref (table_item);
1370
1371   return r;
1372 }
1373
1374 void
1375 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1376 {
1377   if (is_table_item (xr->item))
1378     apply_options (xr->xr, o);
1379 }
1380
1381 struct xr_rendering *
1382 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1383                      cairo_t *cr)
1384 {
1385   struct xr_rendering *r = NULL;
1386
1387   if (is_text_item (item))
1388     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1389                                   cr);
1390   else if (is_message_item (item))
1391     {
1392       const struct message_item *message_item = to_message_item (item);
1393       char *s = msg_to_string (message_item_get_msg (message_item));
1394       r = xr_rendering_create_text (xr, s, cr);
1395       free (s);
1396     }
1397   else if (is_table_item (item))
1398     {
1399       r = xzalloc (sizeof *r);
1400       r->item = output_item_ref (item);
1401       r->xr = xr;
1402       xr_set_cairo (xr, cr);
1403       r->p = render_pager_create (xr->params, to_table_item (item));
1404     }
1405   else if (is_chart_item (item))
1406     {
1407       r = xzalloc (sizeof *r);
1408       r->item = output_item_ref (item);
1409     }
1410   else if (is_group_open_item (item))
1411     r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
1412                                   cr);
1413
1414   return r;
1415 }
1416
1417 void
1418 xr_rendering_destroy (struct xr_rendering *r)
1419 {
1420   if (r)
1421     {
1422       output_item_unref (r->item);
1423       render_pager_destroy (r->p);
1424       free (r);
1425     }
1426 }
1427
1428 void
1429 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1430 {
1431   if (is_table_item (r->item))
1432     {
1433       *w = render_pager_get_size (r->p, H) / XR_POINT;
1434       *h = render_pager_get_size (r->p, V) / XR_POINT;
1435     }
1436   else
1437     {
1438       *w = CHART_WIDTH;
1439       *h = CHART_HEIGHT;
1440     }
1441 }
1442
1443 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1444                     double x, double y, double width, double height);
1445
1446 /* Draws onto CR */
1447 void
1448 xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
1449 {
1450   if (is_table_item (r->item))
1451     {
1452       struct xr_driver *xr = r->xr;
1453
1454       xr_set_cairo (xr, cr);
1455
1456       render_pager_draw (r->p);
1457
1458     }
1459   else
1460     xr_draw_chart (to_chart_item (r->item), cr,
1461                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1462 }
1463
1464 static void
1465 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1466                double x, double y, double width, double height)
1467 {
1468   struct xrchart_geometry geom;
1469
1470   cairo_save (cr);
1471   cairo_translate (cr, x, y + height);
1472   cairo_scale (cr, 1.0, -1.0);
1473   xrchart_geometry_init (cr, &geom, width, height);
1474   if (is_boxplot (chart_item))
1475     xrchart_draw_boxplot (chart_item, cr, &geom);
1476   else if (is_histogram_chart (chart_item))
1477     xrchart_draw_histogram (chart_item, cr, &geom);
1478   else if (is_np_plot_chart (chart_item))
1479     xrchart_draw_np_plot (chart_item, cr, &geom);
1480   else if (is_piechart (chart_item))
1481     xrchart_draw_piechart (chart_item, cr, &geom);
1482   else if (is_barchart (chart_item))
1483     xrchart_draw_barchart (chart_item, cr, &geom);
1484   else if (is_roc_chart (chart_item))
1485     xrchart_draw_roc (chart_item, cr, &geom);
1486   else if (is_scree (chart_item))
1487     xrchart_draw_scree (chart_item, cr, &geom);
1488   else if (is_spreadlevel_plot_chart (chart_item))
1489     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1490   else if (is_scatterplot_chart (chart_item))
1491     xrchart_draw_scatterplot (chart_item, cr, &geom);
1492   else
1493     NOT_REACHED ();
1494   xrchart_geometry_free (cr, &geom);
1495
1496   cairo_restore (cr);
1497 }
1498
1499 char *
1500 xr_draw_png_chart (const struct chart_item *item,
1501                    const char *file_name_template, int number,
1502                    const struct xr_color *fg,
1503                    const struct xr_color *bg
1504                    )
1505 {
1506   const int width = 640;
1507   const int length = 480;
1508
1509   cairo_surface_t *surface;
1510   cairo_status_t status;
1511   const char *number_pos;
1512   char *file_name;
1513   cairo_t *cr;
1514
1515   number_pos = strchr (file_name_template, '#');
1516   if (number_pos != NULL)
1517     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1518                            file_name_template, number, number_pos + 1);
1519   else
1520     file_name = xstrdup (file_name_template);
1521
1522   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1523   cr = cairo_create (surface);
1524
1525   cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1526   cairo_paint (cr);
1527
1528   cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1529
1530   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1531
1532   status = cairo_surface_write_to_png (surface, file_name);
1533   if (status != CAIRO_STATUS_SUCCESS)
1534     msg (ME, _("error writing output file `%s': %s"),
1535            file_name, cairo_status_to_string (status));
1536
1537   cairo_destroy (cr);
1538   cairo_surface_destroy (surface);
1539
1540   return file_name;
1541 }
1542 \f
1543 struct xr_table_state
1544   {
1545     struct xr_render_fsm fsm;
1546     struct table_item *table_item;
1547     struct render_pager *p;
1548   };
1549
1550 static bool
1551 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1552 {
1553   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1554
1555   while (render_pager_has_next (ts->p))
1556     {
1557       int used;
1558
1559       used = render_pager_draw_next (ts->p, xr->length - xr->y);
1560       if (!used)
1561         {
1562           assert (xr->y > 0);
1563           return true;
1564         }
1565       else
1566         xr->y += used;
1567     }
1568   return false;
1569 }
1570
1571 static void
1572 xr_table_destroy (struct xr_render_fsm *fsm)
1573 {
1574   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1575
1576   table_item_unref (ts->table_item);
1577   render_pager_destroy (ts->p);
1578   free (ts);
1579 }
1580
1581 static struct xr_render_fsm *
1582 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1583 {
1584   struct xr_table_state *ts;
1585
1586   ts = xmalloc (sizeof *ts);
1587   ts->fsm.render = xr_table_render;
1588   ts->fsm.destroy = xr_table_destroy;
1589   ts->table_item = table_item_ref (table_item);
1590
1591   if (xr->y > 0)
1592     xr->y += xr->char_height;
1593
1594   ts->p = render_pager_create (xr->params, table_item);
1595
1596   return &ts->fsm;
1597 }
1598 \f
1599 struct xr_chart_state
1600   {
1601     struct xr_render_fsm fsm;
1602     struct chart_item *chart_item;
1603   };
1604
1605 static bool
1606 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1607 {
1608   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1609
1610   const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
1611
1612   if (xr->y > xr->length - chart_height)
1613     return true;
1614
1615   if (xr->cairo != NULL)
1616     {
1617       xr_draw_chart (cs->chart_item, xr->cairo,
1618                      0.0,
1619                      xr_to_pt (xr->y),
1620                      xr_to_pt (xr->width),
1621                      xr_to_pt (chart_height));
1622     }
1623   xr->y += chart_height;
1624
1625   return false;
1626 }
1627
1628 static void
1629 xr_chart_destroy (struct xr_render_fsm *fsm)
1630 {
1631   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1632
1633   chart_item_unref (cs->chart_item);
1634   free (cs);
1635 }
1636
1637 static struct xr_render_fsm *
1638 xr_render_chart (const struct chart_item *chart_item)
1639 {
1640   struct xr_chart_state *cs;
1641
1642   cs = xmalloc (sizeof *cs);
1643   cs->fsm.render = xr_chart_render;
1644   cs->fsm.destroy = xr_chart_destroy;
1645   cs->chart_item = chart_item_ref (chart_item);
1646
1647   return &cs->fsm;
1648 }
1649 \f
1650 static bool
1651 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1652 {
1653   return xr->y > 0;
1654 }
1655
1656 static void
1657 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1658 {
1659   /* Nothing to do. */
1660 }
1661
1662 static struct xr_render_fsm *
1663 xr_render_eject (void)
1664 {
1665   static struct xr_render_fsm eject_renderer =
1666     {
1667       xr_eject_render,
1668       xr_eject_destroy
1669     };
1670
1671   return &eject_renderer;
1672 }
1673 \f
1674 static struct xr_render_fsm *
1675 xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
1676 {
1677   struct tab_table *tab = tab_create (1, 1);
1678
1679   struct cell_style *style = pool_alloc (tab->container, sizeof *style);
1680   *style = (struct cell_style) CELL_STYLE_INITIALIZER;
1681   if (item->font)
1682     style->font = pool_strdup (tab->container, item->font);
1683   style->font_size = item->font_size;
1684   style->bold = item->bold;
1685   style->italic = item->italic;
1686   style->underline = item->underline;
1687   tab->styles[0] = style;
1688
1689   tab_text (tab, 0, 0, TAB_LEFT, text_item_get_text (item));
1690   struct table_item *table_item = table_item_create (&tab->table, NULL, NULL);
1691   struct xr_render_fsm *fsm = xr_render_table (xr, table_item);
1692   table_item_unref (table_item);
1693
1694   return fsm;
1695 }
1696
1697 static struct xr_render_fsm *
1698 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1699 {
1700   enum text_item_type type = text_item_get_type (text_item);
1701
1702   switch (type)
1703     {
1704     case TEXT_ITEM_PAGE_TITLE:
1705       break;
1706
1707     case TEXT_ITEM_BLANK_LINE:
1708       if (xr->y > 0)
1709         xr->y += xr->char_height;
1710       break;
1711
1712     case TEXT_ITEM_EJECT_PAGE:
1713       if (xr->y > 0)
1714         return xr_render_eject ();
1715       break;
1716
1717     default:
1718       return xr_create_text_renderer (xr, text_item);
1719     }
1720
1721   return NULL;
1722 }
1723
1724 static struct xr_render_fsm *
1725 xr_render_message (struct xr_driver *xr,
1726                    const struct message_item *message_item)
1727 {
1728   char *s = msg_to_string (message_item_get_msg (message_item));
1729   struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s);
1730   free (s);
1731   struct xr_render_fsm *fsm = xr_create_text_renderer (xr, item);
1732   text_item_unref (item);
1733
1734   return fsm;
1735 }
1736
1737 static struct xr_render_fsm *
1738 xr_render_output_item (struct xr_driver *xr,
1739                        const struct output_item *output_item)
1740 {
1741   if (is_table_item (output_item))
1742     return xr_render_table (xr, to_table_item (output_item));
1743   else if (is_chart_item (output_item))
1744     return xr_render_chart (to_chart_item (output_item));
1745   else if (is_text_item (output_item))
1746     return xr_render_text (xr, to_text_item (output_item));
1747   else if (is_message_item (output_item))
1748     return xr_render_message (xr, to_message_item (output_item));
1749   else
1750     return NULL;
1751 }