output: Add support for Pango markup of fonts and styles.
[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       xr->params->rtl = render_direction_rtl ();
429     }
430
431   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
432
433   return true;
434 }
435
436 static struct output_driver *
437 xr_create (const char *file_name, enum settings_output_devices device_type,
438            struct string_map *o, enum xr_output_type file_type)
439 {
440   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
441   struct xr_driver *xr;
442   cairo_surface_t *surface;
443   cairo_status_t status;
444   double width_pt, length_pt;
445
446   xr = xr_allocate (file_name, device_type, o);
447
448   width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
449   length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
450   if (file_type == XR_PDF)
451     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
452   else if (file_type == XR_PS)
453     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
454   else if (file_type == XR_SVG)
455     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
456   else
457     NOT_REACHED ();
458
459   status = cairo_surface_status (surface);
460   if (status != CAIRO_STATUS_SUCCESS)
461     {
462       msg (ME, _("error opening output file `%s': %s"),
463              file_name, cairo_status_to_string (status));
464       cairo_surface_destroy (surface);
465       goto error;
466     }
467
468   xr->cairo = cairo_create (surface);
469   cairo_surface_destroy (surface);
470
471   if (!xr_set_cairo (xr, xr->cairo))
472     goto error;
473
474   cairo_save (xr->cairo);
475   xr_driver_next_page (xr, xr->cairo);
476
477   if (xr->width / xr->char_width < MIN_WIDTH)
478     {
479       msg (ME, _("The defined page is not wide enough to hold at least %d "
480                      "characters in the default font.  In fact, there's only "
481                      "room for %d characters."),
482              MIN_WIDTH,
483              xr->width / xr->char_width);
484       goto error;
485     }
486
487   if (xr->length / xr->char_height < MIN_LENGTH)
488     {
489       msg (ME, _("The defined page is not long enough to hold at least %d "
490                      "lines in the default font.  In fact, there's only "
491                      "room for %d lines."),
492              MIN_LENGTH,
493              xr->length / xr->char_height);
494       goto error;
495     }
496
497   return &xr->driver;
498
499  error:
500   output_driver_destroy (&xr->driver);
501   return NULL;
502 }
503
504 static struct output_driver *
505 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
506                struct string_map *o)
507 {
508   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
509   fh_unref (fh);
510   return od ;
511 }
512
513 static struct output_driver *
514 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
515                struct string_map *o)
516 {
517   struct output_driver *od =  xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
518   fh_unref (fh);
519   return od ;
520 }
521
522 static struct output_driver *
523 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
524                struct string_map *o)
525 {
526   struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
527   fh_unref (fh);
528   return od ;
529 }
530
531 static void
532 xr_destroy (struct output_driver *driver)
533 {
534   struct xr_driver *xr = xr_driver_cast (driver);
535   size_t i;
536
537   xr_driver_destroy_fsm (xr);
538
539   if (xr->cairo != NULL)
540     {
541       cairo_status_t status;
542
543       cairo_surface_finish (cairo_get_target (xr->cairo));
544       status = cairo_status (xr->cairo);
545       if (status != CAIRO_STATUS_SUCCESS)
546         msg (ME, _("error drawing output for %s driver: %s"),
547                output_driver_get_name (driver),
548                cairo_status_to_string (status));
549       cairo_destroy (xr->cairo);
550     }
551
552   for (i = 0; i < XR_N_FONTS; i++)
553     {
554       struct xr_font *font = &xr->fonts[i];
555
556       if (font->desc != NULL)
557         pango_font_description_free (font->desc);
558       if (font->layout != NULL)
559         g_object_unref (font->layout);
560     }
561
562   free (xr->params);
563   free (xr);
564 }
565
566 static void
567 xr_flush (struct output_driver *driver)
568 {
569   struct xr_driver *xr = xr_driver_cast (driver);
570
571   cairo_surface_flush (cairo_get_target (xr->cairo));
572 }
573
574 static void
575 xr_submit (struct output_driver *driver, const struct output_item *output_item)
576 {
577   struct xr_driver *xr = xr_driver_cast (driver);
578
579   xr_driver_output_item (xr, output_item);
580   while (xr_driver_need_new_page (xr))
581     {
582       cairo_restore (xr->cairo);
583       cairo_show_page (xr->cairo);
584       cairo_save (xr->cairo);
585       xr_driver_next_page (xr, xr->cairo);
586     }
587 }
588 \f
589 /* Functions for rendering a series of output items to a series of Cairo
590    contexts, with pagination.
591
592    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
593    its underlying implementation.
594
595    See the big comment in cairo.h for intended usage. */
596
597 /* Gives new page CAIRO to XR for output. */
598 void
599 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
600 {
601   cairo_save (cairo);
602   cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
603   cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
604   cairo_fill (cairo);
605   cairo_restore (cairo);
606
607   cairo_translate (cairo,
608                    xr_to_pt (xr->left_margin),
609                    xr_to_pt (xr->top_margin));
610
611   xr->page_number++;
612   xr->cairo = cairo;
613   xr->x = xr->y = 0;
614   xr_driver_run_fsm (xr);
615 }
616
617 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
618    rendering a previous output item, that is, only if xr_driver_need_new_page()
619    returns false. */
620 void
621 xr_driver_output_item (struct xr_driver *xr,
622                        const struct output_item *output_item)
623 {
624   assert (xr->fsm == NULL);
625   xr->fsm = xr_render_output_item (xr, output_item);
626   xr_driver_run_fsm (xr);
627 }
628
629 /* Returns true if XR is in the middle of rendering an output item and needs a
630    new page to be appended using xr_driver_next_page() to make progress,
631    otherwise false. */
632 bool
633 xr_driver_need_new_page (const struct xr_driver *xr)
634 {
635   return xr->fsm != NULL;
636 }
637
638 /* Returns true if the current page doesn't have any content yet. */
639 bool
640 xr_driver_is_page_blank (const struct xr_driver *xr)
641 {
642   return xr->y == 0;
643 }
644
645 static void
646 xr_driver_destroy_fsm (struct xr_driver *xr)
647 {
648   if (xr->fsm != NULL)
649     {
650       xr->fsm->destroy (xr->fsm);
651       xr->fsm = NULL;
652     }
653 }
654
655 static void
656 xr_driver_run_fsm (struct xr_driver *xr)
657 {
658   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
659     xr_driver_destroy_fsm (xr);
660 }
661 \f
662 static void
663 xr_layout_cell (struct xr_driver *, const struct table_cell *,
664                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
665                 int *width, int *height, int *brk);
666
667 static void
668 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
669            const struct cell_color *color)
670 {
671   cairo_new_path (xr->cairo);
672   cairo_set_source_rgb (xr->cairo,
673                         color->r / 255.0, color->g / 255.0, color->b / 255.0);
674   cairo_set_line_width (
675     xr->cairo,
676     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
677               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
678               : XR_LINE_WIDTH));
679   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
680   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
681   cairo_stroke (xr->cairo);
682 }
683
684 static void UNUSED
685 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
686 {
687   cairo_new_path (xr->cairo);
688   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
689   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
690   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
691   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
692   cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
693   cairo_close_path (xr->cairo);
694   cairo_stroke (xr->cairo);
695 }
696
697 static void
698 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
699 {
700   cairo_new_path (xr->cairo);
701   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
702   cairo_rectangle (xr->cairo,
703                    xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
704                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
705   cairo_fill (xr->cairo);
706 }
707
708 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
709    shortening it to X0...X1 if SHORTEN is true.
710    Draws a horizontal line X1...X3 at Y if RIGHT says so,
711    shortening it to X2...X3 if SHORTEN is true. */
712 static void
713 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
714            enum render_line_style left, enum render_line_style right,
715            const struct cell_color *left_color,
716            const struct cell_color *right_color,
717            bool shorten)
718 {
719   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
720       && cell_color_equal (left_color, right_color))
721     dump_line (xr, x0, y, x3, y, left, left_color);
722   else
723     {
724       if (left != RENDER_LINE_NONE)
725         dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
726       if (right != RENDER_LINE_NONE)
727         dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
728     }
729 }
730
731 /* Draws a vertical line Y0...Y2 at X if TOP says so,
732    shortening it to Y0...Y1 if SHORTEN is true.
733    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
734    shortening it to Y2...Y3 if SHORTEN is true. */
735 static void
736 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
737            enum render_line_style top, enum render_line_style bottom,
738            const struct cell_color *top_color,
739            const struct cell_color *bottom_color,
740            bool shorten)
741 {
742   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
743       && cell_color_equal (top_color, bottom_color))
744     dump_line (xr, x, y0, x, y3, top, top_color);
745   else
746     {
747       if (top != RENDER_LINE_NONE)
748         dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
749       if (bottom != RENDER_LINE_NONE)
750         dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
751     }
752 }
753
754 static void
755 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
756               enum render_line_style styles[TABLE_N_AXES][2],
757               struct cell_color colors[TABLE_N_AXES][2])
758 {
759   const int x0 = bb[H][0];
760   const int y0 = bb[V][0];
761   const int x3 = bb[H][1];
762   const int y3 = bb[V][1];
763   const int top = styles[H][0];
764   const int bottom = styles[H][1];
765
766   int start_side = render_direction_rtl();
767   int end_side = !start_side;
768   const int start_of_line = styles[V][start_side];
769   const int end_of_line   = styles[V][end_side];
770   const struct cell_color *top_color = &colors[H][0];
771   const struct cell_color *bottom_color = &colors[H][1];
772   const struct cell_color *start_color = &colors[V][start_side];
773   const struct cell_color *end_color = &colors[V][end_side];
774
775   /* The algorithm here is somewhat subtle, to allow it to handle
776      all the kinds of intersections that we need.
777
778      Three additional ordinates are assigned along the x axis.  The
779      first is xc, midway between x0 and x3.  The others are x1 and
780      x2; for a single vertical line these are equal to xc, and for
781      a double vertical line they are the ordinates of the left and
782      right half of the double line.
783
784      yc, y1, and y2 are assigned similarly along the y axis.
785
786      The following diagram shows the coordinate system and output
787      for double top and bottom lines, single left line, and no
788      right line:
789
790                  x0       x1 xc  x2      x3
791                y0 ________________________
792                   |        #     #       |
793                   |        #     #       |
794                   |        #     #       |
795                   |        #     #       |
796                   |        #     #       |
797      y1 = y2 = yc |#########     #       |
798                   |        #     #       |
799                   |        #     #       |
800                   |        #     #       |
801                   |        #     #       |
802                y3 |________#_____#_______|
803   */
804   struct xr_driver *xr = xr_;
805
806   /* Offset from center of each line in a pair of double lines. */
807   int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
808
809   /* Are the lines along each axis single or double?
810      (It doesn't make sense to have different kinds of line on the
811      same axis, so we don't try to gracefully handle that case.) */
812   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
813   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
814
815   /* When horizontal lines are doubled,
816      the left-side line along y1 normally runs from x0 to x2,
817      and the right-side line along y1 from x3 to x1.
818      If the top-side line is also doubled, we shorten the y1 lines,
819      so that the left-side line runs only to x1,
820      and the right-side line only to x2.
821      Otherwise, the horizontal line at y = y1 below would cut off
822      the intersection, which looks ugly:
823                x0       x1     x2      x3
824              y0 ________________________
825                 |        #     #       |
826                 |        #     #       |
827                 |        #     #       |
828                 |        #     #       |
829              y1 |#########     ########|
830                 |                      |
831                 |                      |
832              y2 |######################|
833                 |                      |
834                 |                      |
835              y3 |______________________|
836      It is more of a judgment call when the horizontal line is
837      single.  We actually choose to cut off the line anyhow, as
838      shown in the first diagram above.
839   */
840   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
841   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
842   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
843   int horz_line_ofs = double_vert ? double_line_ofs : 0;
844   int xc = (x0 + x3) / 2;
845   int x1 = xc - horz_line_ofs;
846   int x2 = xc + horz_line_ofs;
847
848   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
849   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
850   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
851   int vert_line_ofs = double_horz ? double_line_ofs : 0;
852   int yc = (y0 + y3) / 2;
853   int y1 = yc - vert_line_ofs;
854   int y2 = yc + vert_line_ofs;
855
856   if (!double_horz)
857     horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
858                start_color, end_color, shorten_yc_line);
859   else
860     {
861       horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
862                  start_color, end_color, shorten_y1_lines);
863       horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
864                  start_color, end_color, shorten_y2_lines);
865     }
866
867   if (!double_vert)
868     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
869                shorten_xc_line);
870   else
871     {
872       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
873                  shorten_x1_lines);
874       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
875                  shorten_x2_lines);
876     }
877 }
878
879 static void
880 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
881                        int *min_width, int *max_width)
882 {
883   struct xr_driver *xr = xr_;
884   int bb[TABLE_N_AXES][2];
885   int clip[TABLE_N_AXES][2];
886   int h;
887
888   bb[H][0] = 0;
889   bb[H][1] = INT_MAX;
890   bb[V][0] = 0;
891   bb[V][1] = INT_MAX;
892   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
893   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
894
895   bb[H][1] = 1;
896   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
897
898   if (*min_width > 0)
899     *min_width += px_to_xr (cell->style->margin[H][0]
900                             + cell->style->margin[H][1]);
901   if (*max_width > 0)
902     *max_width += px_to_xr (cell->style->margin[H][0]
903                             + cell->style->margin[H][1]);
904 }
905
906 static int
907 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
908 {
909   struct xr_driver *xr = xr_;
910   int bb[TABLE_N_AXES][2];
911   int clip[TABLE_N_AXES][2];
912   int w, h;
913
914   bb[H][0] = 0;
915   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
916                                + cell->style->margin[H][1]);
917   bb[V][0] = 0;
918   bb[V][1] = INT_MAX;
919   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
920   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
921   h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]);
922   return h;
923 }
924
925 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
926
927 static void
928 xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
929               int bb[TABLE_N_AXES][2],
930               int spill[TABLE_N_AXES][2],
931               int clip[TABLE_N_AXES][2])
932 {
933   struct xr_driver *xr = xr_;
934   int w, h, brk;
935
936   cairo_save (xr->cairo);
937   int bg_clip[TABLE_N_AXES][2];
938   for (int axis = 0; axis < TABLE_N_AXES; axis++)
939     {
940       bg_clip[axis][0] = clip[axis][0];
941       if (bb[axis][0] == clip[axis][0])
942         bg_clip[axis][0] -= spill[axis][0];
943
944       bg_clip[axis][1] = clip[axis][1];
945       if (bb[axis][1] == clip[axis][1])
946         bg_clip[axis][1] += spill[axis][1];
947     }
948   xr_clip (xr, bg_clip);
949   cairo_set_source_rgb (xr->cairo,
950                         cell->style->bg[color_idx].r / 255.,
951                         cell->style->bg[color_idx].g / 255.,
952                         cell->style->bg[color_idx].b / 255.);
953   fill_rectangle (xr,
954                   bb[H][0] - spill[H][0],
955                   bb[V][0] - spill[V][0],
956                   bb[H][1] + spill[H][1],
957                   bb[V][1] + spill[V][1]);
958   cairo_restore (xr->cairo);
959
960   cairo_save (xr->cairo);
961   cairo_set_source_rgb (xr->cairo,
962                         cell->style->fg[color_idx].r / 255.,
963                         cell->style->fg[color_idx].g / 255.,
964                         cell->style->fg[color_idx].b / 255.);
965
966   for (int axis = 0; axis < TABLE_N_AXES; axis++)
967     {
968       bb[axis][0] += px_to_xr (cell->style->margin[axis][0]);
969       bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]);
970     }
971   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
972     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
973   cairo_restore (xr->cairo);
974 }
975
976 static int
977 xr_adjust_break (void *xr_, const struct table_cell *cell,
978                  int width, int height)
979 {
980   struct xr_driver *xr = xr_;
981   int bb[TABLE_N_AXES][2];
982   int clip[TABLE_N_AXES][2];
983   int w, h, brk;
984
985   if (xr_measure_cell_height (xr_, cell, width) < height)
986     return -1;
987
988   bb[H][0] = 0;
989   bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
990                                + cell->style->margin[H][1]);
991   if (bb[H][1] <= 0)
992     return 0;
993   bb[V][0] = 0;
994   bb[V][1] = height - px_to_xr (cell->style->margin[V][0]
995                                 + cell->style->margin[V][1]);
996   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
997   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
998   return brk;
999 }
1000 \f
1001 static void
1002 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
1003 {
1004   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
1005     {
1006       double x0 = xr_to_pt (clip[H][0] + xr->x);
1007       double y0 = xr_to_pt (clip[V][0] + xr->y);
1008       double x1 = xr_to_pt (clip[H][1] + xr->x);
1009       double y1 = xr_to_pt (clip[V][1] + xr->y);
1010
1011       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
1012       cairo_clip (xr->cairo);
1013     }
1014 }
1015
1016 static void
1017 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
1018 {
1019   attr->start_index = start_index;
1020   pango_attr_list_insert (list, attr);
1021 }
1022
1023 static void
1024 markup_escape (const char *in, struct string *out)
1025 {
1026   for (int c = *in++; c; c = *in++)
1027     switch (c)
1028       {
1029       case '&':
1030         ds_put_cstr (out, "&amp;");
1031         break;
1032       case '<':
1033         ds_put_cstr (out, "&lt;");
1034         break;
1035       case '>':
1036         ds_put_cstr (out, "&gt;");
1037         break;
1038       default:
1039         ds_put_byte (out, c);
1040         break;
1041       }
1042 }
1043
1044 static int
1045 xr_layout_cell_text (struct xr_driver *xr,
1046                      const struct cell_contents *contents,
1047                      const struct cell_style *style,
1048                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1049                      int *widthp, int *brk)
1050 {
1051   unsigned int options = contents->options;
1052   int w, h;
1053
1054   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1055                           : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
1056                           : &xr->fonts[XR_FONT_PROPORTIONAL]);
1057   struct xr_font local_font;
1058   if (style->font)
1059     {
1060       PangoFontDescription *desc = parse_font (
1061         style->font,
1062         style->font_size ? style->font_size * 1000 * 72 / 128 : 10000,
1063         style->bold, style->italic);
1064       if (desc)
1065         {
1066           PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
1067           pango_layout_set_font_description (layout, desc);
1068
1069           local_font.desc = desc;
1070           local_font.layout = layout;
1071           font = &local_font;
1072         }
1073     }
1074
1075   int footnote_adjustment;
1076   if (contents->n_footnotes == 0)
1077     footnote_adjustment = 0;
1078   else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT)
1079     {
1080       PangoAttrList *attrs;
1081
1082       const char *marker = contents->footnotes[0]->marker;
1083       pango_layout_set_text (font->layout, marker, strlen (marker));
1084
1085       attrs = pango_attr_list_new ();
1086       pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
1087       pango_layout_set_attributes (font->layout, attrs);
1088       pango_attr_list_unref (attrs);
1089
1090       pango_layout_get_size (font->layout, &w, &h);
1091       footnote_adjustment = MIN (w, px_to_xr (style->margin[H][1]));
1092     }
1093   else
1094     footnote_adjustment = px_to_xr (style->margin[H][1]);
1095
1096   struct string tmp = DS_EMPTY_INITIALIZER;
1097   const char *text = contents->text;
1098
1099   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
1100      Pango's implementation of it): it will break after a period or a comma
1101      that precedes a digit, e.g. in ".000" it will break after the period.
1102      This code looks for such a situation and inserts a U+2060 WORD JOINER
1103      to prevent the break.
1104
1105      This isn't necessary when the decimal point is between two digits
1106      (e.g. "0.000" won't be broken) or when the display width is not limited so
1107      that word wrapping won't happen.
1108
1109      It isn't necessary to look for more than one period or comma, as would
1110      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
1111      are present then there will always be a digit on both sides of every
1112      period and comma. */
1113   if (bb[H][1] != INT_MAX)
1114     {
1115       const char *decimal = text + strcspn (text, ".,");
1116       if (decimal[0]
1117           && c_isdigit (decimal[1])
1118           && (decimal == text || !c_isdigit (decimal[-1])))
1119         {
1120           ds_extend (&tmp, strlen (text) + 16);
1121           ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1));
1122           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
1123           ds_put_cstr (&tmp, decimal + 1);
1124         }
1125     }
1126
1127   if (footnote_adjustment)
1128     {
1129       bb[H][1] += footnote_adjustment;
1130
1131       if (ds_is_empty (&tmp))
1132         {
1133           ds_extend (&tmp, strlen (text) + 16);
1134           ds_put_cstr (&tmp, text);
1135         }
1136       size_t initial_length = ds_length (&tmp);
1137
1138       for (size_t i = 0; i < contents->n_footnotes; i++)
1139         {
1140           if (i)
1141             ds_put_byte (&tmp, ',');
1142
1143           const char *marker = contents->footnotes[i]->marker;
1144           if (options & TAB_MARKUP)
1145             markup_escape (marker, &tmp);
1146           else
1147             ds_put_cstr (&tmp, marker);
1148         }
1149
1150       if (options & TAB_MARKUP)
1151         pango_layout_set_markup (font->layout,
1152                                  ds_cstr (&tmp), ds_length (&tmp));
1153       else
1154         pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
1155
1156       PangoAttrList *attrs = pango_attr_list_new ();
1157       if (style->underline)
1158         pango_attr_list_insert (attrs, pango_attr_underline_new (
1159                                PANGO_UNDERLINE_SINGLE));
1160       add_attr_with_start (attrs, pango_attr_rise_new (7000), initial_length);
1161       add_attr_with_start (
1162         attrs, pango_attr_font_desc_new (font->desc), initial_length);
1163       pango_layout_set_attributes (font->layout, attrs);
1164       pango_attr_list_unref (attrs);
1165     }
1166   else
1167     {
1168       const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
1169       if (options & TAB_MARKUP)
1170         pango_layout_set_markup (font->layout, content, -1);
1171       else
1172         pango_layout_set_text (font->layout, content, -1);
1173
1174       if (style->underline)
1175         {
1176           PangoAttrList *attrs = pango_attr_list_new ();
1177           pango_attr_list_insert (attrs, pango_attr_underline_new (
1178                                     PANGO_UNDERLINE_SINGLE));
1179           pango_layout_set_attributes (font->layout, attrs);
1180           pango_attr_list_unref (attrs);
1181         }
1182     }
1183   ds_destroy (&tmp);
1184
1185   pango_layout_set_alignment (
1186     font->layout,
1187     ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1188      : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT
1189      : PANGO_ALIGN_CENTER));
1190   pango_layout_set_width (
1191     font->layout,
1192     bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1193   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1194
1195   if (clip[H][0] != clip[H][1])
1196     {
1197       cairo_save (xr->cairo);
1198       xr_clip (xr, clip);
1199       cairo_translate (xr->cairo,
1200                        xr_to_pt (bb[H][0] + xr->x),
1201                        xr_to_pt (bb[V][0] + xr->y));
1202       pango_cairo_show_layout (xr->cairo, font->layout);
1203
1204       /* If enabled, this draws a blue rectangle around the extents of each
1205          line of text, which can be rather useful for debugging layout
1206          issues. */
1207       if (0)
1208         {
1209           PangoLayoutIter *iter;
1210           iter = pango_layout_get_iter (font->layout);
1211           do
1212             {
1213               PangoRectangle extents;
1214
1215               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1216               cairo_save (xr->cairo);
1217               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1218               dump_rectangle (xr,
1219                               pango_to_xr (extents.x) - xr->x,
1220                               pango_to_xr (extents.y) - xr->y,
1221                               pango_to_xr (extents.x + extents.width) - xr->x,
1222                               pango_to_xr (extents.y + extents.height) - xr->y);
1223               cairo_restore (xr->cairo);
1224             }
1225           while (pango_layout_iter_next_line (iter));
1226           pango_layout_iter_free (iter);
1227         }
1228
1229       cairo_restore (xr->cairo);
1230     }
1231
1232   pango_layout_get_size (font->layout, &w, &h);
1233   w = pango_to_xr (w);
1234   h = pango_to_xr (h);
1235   if (w > *widthp)
1236     *widthp = w;
1237   if (bb[V][0] + h >= bb[V][1])
1238     {
1239       PangoLayoutIter *iter;
1240       int best UNUSED = 0;
1241
1242       /* Choose a breakpoint between lines instead of in the middle of one. */
1243       iter = pango_layout_get_iter (font->layout);
1244       do
1245         {
1246           PangoRectangle extents;
1247           int y0, y1;
1248           int bottom;
1249
1250           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1251           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1252           extents.x = pango_to_xr (extents.x);
1253           extents.y = pango_to_xr (y0);
1254           extents.width = pango_to_xr (extents.width);
1255           extents.height = pango_to_xr (y1 - y0);
1256           bottom = bb[V][0] + extents.y + extents.height;
1257           if (bottom < bb[V][1])
1258             {
1259               if (brk && clip[H][0] != clip[H][1])
1260                 best = bottom;
1261               if (brk)
1262                 *brk = bottom;
1263             }
1264           else
1265             break;
1266         }
1267       while (pango_layout_iter_next_line (iter));
1268       pango_layout_iter_free (iter);
1269
1270       /* If enabled, draws a green line across the chosen breakpoint, which can
1271          be useful for debugging issues with breaking.  */
1272       if (0)
1273         {
1274           if (best && !xr->nest)
1275             dump_line (xr, -xr->left_margin, best,
1276                        xr->width + xr->right_margin, best,
1277                        RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0));
1278         }
1279     }
1280
1281   pango_layout_set_attributes (font->layout, NULL);
1282
1283   if (font == &local_font)
1284     {
1285       g_object_unref (G_OBJECT (font->layout));
1286       pango_font_description_free (font->desc);
1287     }
1288
1289   return bb[V][0] + h;
1290 }
1291
1292 static void
1293 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1294                 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1295                 int *width, int *height, int *brk)
1296 {
1297   int bb[TABLE_N_AXES][2];
1298   size_t i;
1299
1300   *width = 0;
1301   *height = 0;
1302   if (brk)
1303     *brk = 0;
1304
1305   memcpy (bb, bb_, sizeof bb);
1306
1307   /* If enabled, draws a blue rectangle around the cell extents, which can be
1308      useful for debugging layout. */
1309   if (0)
1310     {
1311       if (clip[H][0] != clip[H][1])
1312         {
1313           int offset = (xr->nest) * XR_POINT;
1314
1315           cairo_save (xr->cairo);
1316           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1317           dump_rectangle (xr,
1318                           bb[H][0] + offset, bb[V][0] + offset,
1319                           bb[H][1] - offset, bb[V][1] - offset);
1320           cairo_restore (xr->cairo);
1321         }
1322     }
1323
1324   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1325     {
1326       const struct cell_contents *contents = &cell->contents[i];
1327
1328       if (brk)
1329         *brk = bb[V][0];
1330       if (i > 0)
1331         {
1332           bb[V][0] += xr->char_height / 2;
1333           if (bb[V][0] >= bb[V][1])
1334             break;
1335           if (brk)
1336             *brk = bb[V][0];
1337         }
1338
1339       bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip,
1340                                       width, brk);
1341     }
1342   *height = bb[V][0] - bb_[V][0];
1343 }
1344 \f
1345 struct output_driver_factory pdf_driver_factory =
1346   { "pdf", "pspp.pdf", xr_pdf_create };
1347 struct output_driver_factory ps_driver_factory =
1348   { "ps", "pspp.ps", xr_ps_create };
1349 struct output_driver_factory svg_driver_factory =
1350   { "svg", "pspp.svg", xr_svg_create };
1351
1352 static const struct output_driver_class cairo_driver_class =
1353 {
1354   "cairo",
1355   xr_destroy,
1356   xr_submit,
1357   xr_flush,
1358 };
1359 \f
1360 /* GUI rendering helpers. */
1361
1362 struct xr_rendering
1363   {
1364     struct output_item *item;
1365
1366     /* Table items. */
1367     struct render_pager *p;
1368     struct xr_driver *xr;
1369   };
1370
1371 #define CHART_WIDTH 500
1372 #define CHART_HEIGHT 375
1373
1374
1375
1376 struct xr_driver *
1377 xr_driver_create (cairo_t *cairo, struct string_map *options)
1378 {
1379   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1380   if (!xr_set_cairo (xr, cairo))
1381     {
1382       output_driver_destroy (&xr->driver);
1383       return NULL;
1384     }
1385   return xr;
1386 }
1387
1388 /* Destroy XR, which should have been created with xr_driver_create().  Any
1389    cairo_t added to XR is not destroyed, because it is owned by the client. */
1390 void
1391 xr_driver_destroy (struct xr_driver *xr)
1392 {
1393   if (xr != NULL)
1394     {
1395       xr->cairo = NULL;
1396       output_driver_destroy (&xr->driver);
1397     }
1398 }
1399
1400 static struct xr_rendering *
1401 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1402 {
1403   struct table_item *table_item;
1404   struct xr_rendering *r;
1405
1406   table_item = table_item_create (table_from_string (TAB_LEFT, text),
1407                                   NULL, NULL);
1408   r = xr_rendering_create (xr, &table_item->output_item, cr);
1409   table_item_unref (table_item);
1410
1411   return r;
1412 }
1413
1414 void
1415 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1416 {
1417   if (is_table_item (xr->item))
1418     apply_options (xr->xr, o);
1419 }
1420
1421 struct xr_rendering *
1422 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1423                      cairo_t *cr)
1424 {
1425   struct xr_rendering *r = NULL;
1426
1427   if (is_text_item (item))
1428     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1429                                   cr);
1430   else if (is_message_item (item))
1431     {
1432       const struct message_item *message_item = to_message_item (item);
1433       char *s = msg_to_string (message_item_get_msg (message_item));
1434       r = xr_rendering_create_text (xr, s, cr);
1435       free (s);
1436     }
1437   else if (is_table_item (item))
1438     {
1439       r = xzalloc (sizeof *r);
1440       r->item = output_item_ref (item);
1441       r->xr = xr;
1442       xr_set_cairo (xr, cr);
1443       r->p = render_pager_create (xr->params, to_table_item (item));
1444     }
1445   else if (is_chart_item (item))
1446     {
1447       r = xzalloc (sizeof *r);
1448       r->item = output_item_ref (item);
1449     }
1450   else if (is_group_open_item (item))
1451     r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
1452                                   cr);
1453
1454   return r;
1455 }
1456
1457 void
1458 xr_rendering_destroy (struct xr_rendering *r)
1459 {
1460   if (r)
1461     {
1462       output_item_unref (r->item);
1463       render_pager_destroy (r->p);
1464       free (r);
1465     }
1466 }
1467
1468 void
1469 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1470 {
1471   if (is_table_item (r->item))
1472     {
1473       *w = render_pager_get_size (r->p, H) / XR_POINT;
1474       *h = render_pager_get_size (r->p, V) / XR_POINT;
1475     }
1476   else
1477     {
1478       *w = CHART_WIDTH;
1479       *h = CHART_HEIGHT;
1480     }
1481 }
1482
1483 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1484                     double x, double y, double width, double height);
1485
1486 /* Draws onto CR */
1487 void
1488 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1489                    int x0, int y0, int x1, int y1)
1490 {
1491   if (is_table_item (r->item))
1492     {
1493       struct xr_driver *xr = r->xr;
1494
1495       xr_set_cairo (xr, cr);
1496
1497       render_pager_draw_region (r->p, x0 * XR_POINT, y0 * XR_POINT,
1498                                 (x1 - x0) * XR_POINT, (y1 - y0) * XR_POINT);
1499     }
1500   else
1501     xr_draw_chart (to_chart_item (r->item), cr,
1502                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1503 }
1504
1505 static void
1506 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1507                double x, double y, double width, double height)
1508 {
1509   struct xrchart_geometry geom;
1510
1511   cairo_save (cr);
1512   cairo_translate (cr, x, y + height);
1513   cairo_scale (cr, 1.0, -1.0);
1514   xrchart_geometry_init (cr, &geom, width, height);
1515   if (is_boxplot (chart_item))
1516     xrchart_draw_boxplot (chart_item, cr, &geom);
1517   else if (is_histogram_chart (chart_item))
1518     xrchart_draw_histogram (chart_item, cr, &geom);
1519   else if (is_np_plot_chart (chart_item))
1520     xrchart_draw_np_plot (chart_item, cr, &geom);
1521   else if (is_piechart (chart_item))
1522     xrchart_draw_piechart (chart_item, cr, &geom);
1523   else if (is_barchart (chart_item))
1524     xrchart_draw_barchart (chart_item, cr, &geom);
1525   else if (is_roc_chart (chart_item))
1526     xrchart_draw_roc (chart_item, cr, &geom);
1527   else if (is_scree (chart_item))
1528     xrchart_draw_scree (chart_item, cr, &geom);
1529   else if (is_spreadlevel_plot_chart (chart_item))
1530     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1531   else if (is_scatterplot_chart (chart_item))
1532     xrchart_draw_scatterplot (chart_item, cr, &geom);
1533   else
1534     NOT_REACHED ();
1535   xrchart_geometry_free (cr, &geom);
1536
1537   cairo_restore (cr);
1538 }
1539
1540 char *
1541 xr_draw_png_chart (const struct chart_item *item,
1542                    const char *file_name_template, int number,
1543                    const struct xr_color *fg,
1544                    const struct xr_color *bg
1545                    )
1546 {
1547   const int width = 640;
1548   const int length = 480;
1549
1550   cairo_surface_t *surface;
1551   cairo_status_t status;
1552   const char *number_pos;
1553   char *file_name;
1554   cairo_t *cr;
1555
1556   number_pos = strchr (file_name_template, '#');
1557   if (number_pos != NULL)
1558     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1559                            file_name_template, number, number_pos + 1);
1560   else
1561     file_name = xstrdup (file_name_template);
1562
1563   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1564   cr = cairo_create (surface);
1565
1566   cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1567   cairo_paint (cr);
1568
1569   cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1570
1571   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1572
1573   status = cairo_surface_write_to_png (surface, file_name);
1574   if (status != CAIRO_STATUS_SUCCESS)
1575     msg (ME, _("error writing output file `%s': %s"),
1576            file_name, cairo_status_to_string (status));
1577
1578   cairo_destroy (cr);
1579   cairo_surface_destroy (surface);
1580
1581   return file_name;
1582 }
1583 \f
1584 struct xr_table_state
1585   {
1586     struct xr_render_fsm fsm;
1587     struct render_pager *p;
1588   };
1589
1590 static bool
1591 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1592 {
1593   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1594
1595   while (render_pager_has_next (ts->p))
1596     {
1597       int used;
1598
1599       used = render_pager_draw_next (ts->p, xr->length - xr->y);
1600       if (!used)
1601         {
1602           assert (xr->y > 0);
1603           return true;
1604         }
1605       else
1606         xr->y += used;
1607     }
1608   return false;
1609 }
1610
1611 static void
1612 xr_table_destroy (struct xr_render_fsm *fsm)
1613 {
1614   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1615
1616   render_pager_destroy (ts->p);
1617   free (ts);
1618 }
1619
1620 static struct xr_render_fsm *
1621 xr_render_table (struct xr_driver *xr, struct table_item *table_item)
1622 {
1623   struct xr_table_state *ts;
1624
1625   ts = xmalloc (sizeof *ts);
1626   ts->fsm.render = xr_table_render;
1627   ts->fsm.destroy = xr_table_destroy;
1628
1629   if (xr->y > 0)
1630     xr->y += xr->char_height;
1631
1632   ts->p = render_pager_create (xr->params, table_item);
1633   table_item_unref (table_item);
1634
1635   return &ts->fsm;
1636 }
1637 \f
1638 struct xr_chart_state
1639   {
1640     struct xr_render_fsm fsm;
1641     struct chart_item *chart_item;
1642   };
1643
1644 static bool
1645 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1646 {
1647   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1648
1649   const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
1650
1651   if (xr->y > xr->length - chart_height)
1652     return true;
1653
1654   if (xr->cairo != NULL)
1655     {
1656       xr_draw_chart (cs->chart_item, xr->cairo,
1657                      0.0,
1658                      xr_to_pt (xr->y),
1659                      xr_to_pt (xr->width),
1660                      xr_to_pt (chart_height));
1661     }
1662   xr->y += chart_height;
1663
1664   return false;
1665 }
1666
1667 static void
1668 xr_chart_destroy (struct xr_render_fsm *fsm)
1669 {
1670   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1671
1672   chart_item_unref (cs->chart_item);
1673   free (cs);
1674 }
1675
1676 static struct xr_render_fsm *
1677 xr_render_chart (const struct chart_item *chart_item)
1678 {
1679   struct xr_chart_state *cs;
1680
1681   cs = xmalloc (sizeof *cs);
1682   cs->fsm.render = xr_chart_render;
1683   cs->fsm.destroy = xr_chart_destroy;
1684   cs->chart_item = chart_item_ref (chart_item);
1685
1686   return &cs->fsm;
1687 }
1688 \f
1689 static bool
1690 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1691 {
1692   return xr->y > 0;
1693 }
1694
1695 static void
1696 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1697 {
1698   /* Nothing to do. */
1699 }
1700
1701 static struct xr_render_fsm *
1702 xr_render_eject (void)
1703 {
1704   static struct xr_render_fsm eject_renderer =
1705     {
1706       xr_eject_render,
1707       xr_eject_destroy
1708     };
1709
1710   return &eject_renderer;
1711 }
1712 \f
1713 static struct xr_render_fsm *
1714 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1715 {
1716   enum text_item_type type = text_item_get_type (text_item);
1717
1718   switch (type)
1719     {
1720     case TEXT_ITEM_PAGE_TITLE:
1721       break;
1722
1723     case TEXT_ITEM_BLANK_LINE:
1724       if (xr->y > 0)
1725         xr->y += xr->char_height;
1726       break;
1727
1728     case TEXT_ITEM_EJECT_PAGE:
1729       if (xr->y > 0)
1730         return xr_render_eject ();
1731       break;
1732
1733     default:
1734       return xr_render_table (
1735         xr, text_item_to_table_item (text_item_ref (text_item)));
1736     }
1737
1738   return NULL;
1739 }
1740
1741 static struct xr_render_fsm *
1742 xr_render_message (struct xr_driver *xr,
1743                    const struct message_item *message_item)
1744 {
1745   char *s = msg_to_string (message_item_get_msg (message_item));
1746   struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s);
1747   free (s);
1748   return xr_render_table (xr, text_item_to_table_item (item));
1749 }
1750
1751 static struct xr_render_fsm *
1752 xr_render_output_item (struct xr_driver *xr,
1753                        const struct output_item *output_item)
1754 {
1755   if (is_table_item (output_item))
1756     return xr_render_table (xr, table_item_ref (to_table_item (output_item)));
1757   else if (is_chart_item (output_item))
1758     return xr_render_chart (to_chart_item (output_item));
1759   else if (is_text_item (output_item))
1760     return xr_render_text (xr, to_text_item (output_item));
1761   else if (is_message_item (output_item))
1762     return xr_render_message (xr, to_message_item (output_item));
1763   else
1764     return NULL;
1765 }