cairo: Fix failures to scale between cairo and pango units.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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/start-date.h"
25 #include "libpspp/str.h"
26 #include "libpspp/string-map.h"
27 #include "libpspp/version.h"
28 #include "output/cairo-chart.h"
29 #include "output/chart-item-provider.h"
30 #include "output/charts/boxplot.h"
31 #include "output/charts/np-plot.h"
32 #include "output/charts/piechart.h"
33 #include "output/charts/plot-hist.h"
34 #include "output/charts/roc-chart.h"
35 #include "output/charts/spreadlevel-plot.h"
36 #include "output/charts/scree.h"
37 #include "output/driver-provider.h"
38 #include "output/message-item.h"
39 #include "output/options.h"
40 #include "output/render.h"
41 #include "output/tab.h"
42 #include "output/table-item.h"
43 #include "output/table.h"
44 #include "output/text-item.h"
45
46 #include <cairo/cairo-pdf.h>
47 #include <cairo/cairo-ps.h>
48 #include <cairo/cairo-svg.h>
49 #include <cairo/cairo.h>
50 #include <math.h>
51 #include <pango/pango-font.h>
52 #include <pango/pango-layout.h>
53 #include <pango/pango.h>
54 #include <pango/pangocairo.h>
55 #include <stdlib.h>
56
57 #include "gl/intprops.h"
58 #include "gl/minmax.h"
59 #include "gl/xalloc.h"
60
61 #include "gettext.h"
62 #define _(msgid) gettext (msgid)
63
64 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
65 #define H TABLE_HORZ
66 #define V TABLE_VERT
67
68 /* Measurements as we present to the rest of PSPP. */
69 #define XR_POINT 1000
70
71 /* Conversions to and from points. */
72 static double
73 xr_to_pt (int x)
74 {
75   return x / (double) XR_POINT;
76 }
77
78 /* Output types. */
79 enum xr_output_type
80   {
81     XR_PDF,
82     XR_PS,
83     XR_SVG
84   };
85
86 /* Cairo fonts. */
87 enum xr_font_type
88   {
89     XR_FONT_PROPORTIONAL,
90     XR_FONT_EMPHASIS,
91     XR_FONT_FIXED,
92     XR_N_FONTS
93   };
94
95 /* A font for use with Cairo. */
96 struct xr_font
97   {
98     PangoFontDescription *desc;
99     PangoLayout *layout;
100   };
101
102 /* An output item whose rendering is in progress. */
103 struct xr_render_fsm
104   {
105     /* Renders as much of itself as it can on the current page.  Returns true
106        if rendering is complete, false if the output item needs another
107        page. */
108     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
109
110     /* Destroys the output item. */
111     void (*destroy) (struct xr_render_fsm *);
112   };
113
114 /* Cairo output driver. */
115 struct xr_driver
116   {
117     struct output_driver driver;
118
119     /* User parameters. */
120     struct xr_font fonts[XR_N_FONTS];
121
122     int width;                  /* Page width minus margins. */
123     int length;                 /* Page length minus margins and header. */
124
125     int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
126     int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
127     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
128     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
129
130     int line_gutter;            /* Space around lines. */
131     int line_space;             /* Space between lines. */
132     int line_width;             /* Width of lines. */
133
134     struct xr_color bg;    /* Background color */
135     struct xr_color fg;    /* Foreground color */
136
137     /* Internal state. */
138     struct render_params *params;
139     int char_width, char_height;
140     char *command_name;
141     char *title;
142     char *subtitle;
143     cairo_t *cairo;
144     int page_number;            /* Current page number. */
145     int y;
146     struct xr_render_fsm *fsm;
147   };
148
149 static const struct output_driver_class cairo_driver_class;
150
151 static void xr_driver_destroy_fsm (struct xr_driver *);
152 static void xr_driver_run_fsm (struct xr_driver *);
153
154 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
155                           enum render_line_style styles[TABLE_N_AXES][2]);
156 static void xr_measure_cell_width (void *, const struct table_cell *,
157                                    int *min, int *max);
158 static int xr_measure_cell_height (void *, const struct table_cell *,
159                                    int width);
160 static void xr_draw_cell (void *, const struct table_cell *,
161                           int bb[TABLE_N_AXES][2],
162                           int clip[TABLE_N_AXES][2]);
163
164 static struct xr_render_fsm *xr_render_output_item (
165   struct xr_driver *, const struct output_item *);
166 \f
167 /* Output driver basics. */
168
169 static struct xr_driver *
170 xr_driver_cast (struct output_driver *driver)
171 {
172   assert (driver->class == &cairo_driver_class);
173   return UP_CAST (driver, struct xr_driver, driver);
174 }
175
176 static struct driver_option *
177 opt (struct output_driver *d, struct string_map *options, const char *key,
178      const char *default_value)
179 {
180   return driver_option_get (d, options, key, default_value);
181 }
182
183 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
184    Currently, the input string must be of the form "#RRRRGGGGBBBB"
185    Future implementations might allow things like "yellow" and
186    "sky-blue-ultra-brown"
187 */
188 void
189 parse_color (struct output_driver *d, struct string_map *options,
190              const char *key, const char *default_value,
191              struct xr_color *color)
192 {
193   int red, green, blue;
194   char *string = parse_string (opt (d, options, key, default_value));
195
196   if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
197     {
198       /* If the parsed option string fails, then try the default value */
199       if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
200         {
201           /* ... and if that fails set everything to zero */
202           red = green = blue = 0;
203         }
204     }
205
206   free (string);
207
208   /* Convert 16 bit ints to float */
209   color->red = red / (double) 0xFFFF;
210   color->green = green / (double) 0xFFFF;
211   color->blue = blue / (double) 0xFFFF;
212 }
213
214 static PangoFontDescription *
215 parse_font (struct output_driver *d, struct string_map *options,
216             const char *key, const char *default_value,
217             int default_size)
218 {
219   PangoFontDescription *desc;
220   char *string;
221
222   /* Parse KEY as a font description. */
223   string = parse_string (opt (d, options, key, default_value));
224   desc = pango_font_description_from_string (string);
225   if (desc == NULL)
226     {
227       msg (MW, _("`%s': bad font specification"), string);
228
229       /* Fall back to DEFAULT_VALUE, which had better be a valid font
230          description. */
231       desc = pango_font_description_from_string (default_value);
232       assert (desc != NULL);
233     }
234   free (string);
235
236   /* If the font description didn't include an explicit font size, then set it
237      to DEFAULT_SIZE, which is in inch/72000 units. */
238   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
239     pango_font_description_set_size (desc,
240                                      (default_size / 1000.0) * PANGO_SCALE);
241
242   return desc;
243 }
244
245
246 static void
247 apply_options (struct xr_driver *xr, struct string_map *o)
248 {
249   struct output_driver *d = &xr->driver;
250
251   /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
252   int left_margin, right_margin;
253   int top_margin, bottom_margin;
254   int paper_width, paper_length;
255   int font_size;
256
257   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
258   const double scale = XR_POINT / 1000.;
259
260   int i;
261
262   for (i = 0; i < XR_N_FONTS; i++)
263     {
264       struct xr_font *font = &xr->fonts[i];
265
266       if (font->desc != NULL)
267         pango_font_description_free (font->desc);
268     }
269
270   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
271   xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
272                                               font_size);
273   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
274                                                      "serif", font_size);
275   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
276                                                  "serif italic", font_size);
277
278   xr->line_gutter = parse_dimension (opt (d, o, "gutter", "3pt")) * scale;
279   xr->line_space = XR_POINT;
280   xr->line_width = XR_POINT / 2;
281   xr->page_number = 0;
282
283   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
284   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
285
286   /* Get dimensions.  */
287   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
288   left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
289   right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
290   top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
291   bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
292
293   /* Convert to inch/(XR_POINT * 72). */
294   xr->left_margin = left_margin * scale;
295   xr->right_margin = right_margin * scale;
296   xr->top_margin = top_margin * scale;
297   xr->bottom_margin = bottom_margin * scale;
298   xr->width = (paper_width - left_margin - right_margin) * scale;
299   xr->length = (paper_length - top_margin - bottom_margin) * scale;
300 }
301
302 static struct xr_driver *
303 xr_allocate (const char *name, int device_type, struct string_map *o)
304 {
305   struct xr_driver *xr = xzalloc (sizeof *xr);
306   struct output_driver *d = &xr->driver;
307
308   output_driver_init (d, &cairo_driver_class, name, device_type);
309
310   apply_options (xr, o);
311
312   return xr;
313 }
314
315 static int
316 pango_to_xr (int pango)
317 {
318   return ceil (pango * (1. * XR_POINT / PANGO_SCALE));
319 }
320
321 static int
322 xr_to_pango (int xr)
323 {
324   return ceil (xr * (1. / XR_POINT * PANGO_SCALE));
325 }
326
327 static bool
328 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
329 {
330   int i;
331
332   xr->cairo = cairo;
333
334   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
335
336   xr->char_width = 0;
337   xr->char_height = 0;
338   for (i = 0; i < XR_N_FONTS; i++)
339     {
340       struct xr_font *font = &xr->fonts[i];
341       int char_width, char_height;
342
343       font->layout = pango_cairo_create_layout (cairo);
344       pango_layout_set_font_description (font->layout, font->desc);
345
346       pango_layout_set_text (font->layout, "0", 1);
347       pango_layout_get_size (font->layout, &char_width, &char_height);
348       xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
349       xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
350     }
351
352   if (xr->params == NULL)
353     {
354       int single_width, double_width;
355
356       xr->params = xmalloc (sizeof *xr->params);
357       xr->params->draw_line = xr_draw_line;
358       xr->params->measure_cell_width = xr_measure_cell_width;
359       xr->params->measure_cell_height = xr_measure_cell_height;
360       xr->params->draw_cell = xr_draw_cell;
361       xr->params->aux = xr;
362       xr->params->size[H] = xr->width;
363       xr->params->size[V] = xr->length;
364       xr->params->font_size[H] = xr->char_width;
365       xr->params->font_size[V] = xr->char_height;
366
367       single_width = 2 * xr->line_gutter + xr->line_width;
368       double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
369       for (i = 0; i < TABLE_N_AXES; i++)
370         {
371           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
372           xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
373           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
374         }
375     }
376
377   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
378
379   return true;
380 }
381
382 static struct output_driver *
383 xr_create (const char *file_name, enum settings_output_devices device_type,
384            struct string_map *o, enum xr_output_type file_type)
385 {
386   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
387   struct xr_driver *xr;
388   cairo_surface_t *surface;
389   cairo_status_t status;
390   double width_pt, length_pt;
391
392   xr = xr_allocate (file_name, device_type, o);
393
394   width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
395   length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
396   if (file_type == XR_PDF)
397     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
398   else if (file_type == XR_PS)
399     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
400   else if (file_type == XR_SVG)
401     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
402   else
403     NOT_REACHED ();
404
405   status = cairo_surface_status (surface);
406   if (status != CAIRO_STATUS_SUCCESS)
407     {
408       msg (ME, _("error opening output file `%s': %s"),
409              file_name, cairo_status_to_string (status));
410       cairo_surface_destroy (surface);
411       goto error;
412     }
413
414   xr->cairo = cairo_create (surface);
415   cairo_surface_destroy (surface);
416
417   if (!xr_set_cairo (xr, xr->cairo))
418     goto error;
419
420   cairo_save (xr->cairo);
421   xr_driver_next_page (xr, xr->cairo);
422
423   if (xr->width / xr->char_width < MIN_WIDTH)
424     {
425       msg (ME, _("The defined page is not wide enough to hold at least %d "
426                      "characters in the default font.  In fact, there's only "
427                      "room for %d characters."),
428              MIN_WIDTH,
429              xr->width / xr->char_width);
430       goto error;
431     }
432
433   if (xr->length / xr->char_height < MIN_LENGTH)
434     {
435       msg (ME, _("The defined page is not long enough to hold at least %d "
436                      "lines in the default font.  In fact, there's only "
437                      "room for %d lines."),
438              MIN_LENGTH,
439              xr->length / xr->char_height);
440       goto error;
441     }
442
443   return &xr->driver;
444
445  error:
446   output_driver_destroy (&xr->driver);
447   return NULL;
448 }
449
450 static struct output_driver *
451 xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
452                struct string_map *o)
453 {
454   return xr_create (file_name, device_type, o, XR_PDF);
455 }
456
457 static struct output_driver *
458 xr_ps_create (const char *file_name, enum settings_output_devices device_type,
459                struct string_map *o)
460 {
461   return xr_create (file_name, device_type, o, XR_PS);
462 }
463
464 static struct output_driver *
465 xr_svg_create (const char *file_name, enum settings_output_devices device_type,
466                struct string_map *o)
467 {
468   return xr_create (file_name, device_type, o, XR_SVG);
469 }
470
471 static void
472 xr_destroy (struct output_driver *driver)
473 {
474   struct xr_driver *xr = xr_driver_cast (driver);
475   size_t i;
476
477   xr_driver_destroy_fsm (xr);
478
479   if (xr->cairo != NULL)
480     {
481       cairo_status_t status;
482
483       cairo_surface_finish (cairo_get_target (xr->cairo));
484       status = cairo_status (xr->cairo);
485       if (status != CAIRO_STATUS_SUCCESS)
486         msg (ME, _("error drawing output for %s driver: %s"),
487                output_driver_get_name (driver),
488                cairo_status_to_string (status));
489       cairo_destroy (xr->cairo);
490     }
491
492   free (xr->command_name);
493   for (i = 0; i < XR_N_FONTS; i++)
494     {
495       struct xr_font *font = &xr->fonts[i];
496
497       if (font->desc != NULL)
498         pango_font_description_free (font->desc);
499       if (font->layout != NULL)
500         g_object_unref (font->layout);
501     }
502
503   free (xr->params);
504   free (xr);
505 }
506
507 static void
508 xr_flush (struct output_driver *driver)
509 {
510   struct xr_driver *xr = xr_driver_cast (driver);
511
512   cairo_surface_flush (cairo_get_target (xr->cairo));
513 }
514
515 static void
516 xr_init_caption_cell (const char *caption, struct table_cell *cell)
517 {
518   cell->contents = caption;
519   cell->options = TAB_LEFT;
520   cell->destructor = NULL;
521 }
522
523 static struct render_page *
524 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
525                       int *caption_widthp, int *caption_heightp)
526 {
527   const char *caption = table_item_get_caption (item);
528
529   if (caption != NULL)
530     {
531       /* XXX doesn't do well with very large captions */
532       int min_width, max_width;
533       struct table_cell cell;
534
535       xr_init_caption_cell (caption, &cell);
536
537       xr_measure_cell_width (xr, &cell, &min_width, &max_width);
538       *caption_widthp = MIN (max_width, xr->width);
539       *caption_heightp = xr_measure_cell_height (xr, &cell, *caption_widthp);
540     }
541   else
542     *caption_heightp = 0;
543
544   return render_page_create (xr->params, table_item_get_table (item));
545 }
546
547 static void
548 xr_submit (struct output_driver *driver, const struct output_item *output_item)
549 {
550   struct xr_driver *xr = xr_driver_cast (driver);
551
552   output_driver_track_current_command (output_item, &xr->command_name);
553
554   xr_driver_output_item (xr, output_item);
555   while (xr_driver_need_new_page (xr))
556     {
557       cairo_restore (xr->cairo);
558       cairo_show_page (xr->cairo);
559       cairo_save (xr->cairo);
560       xr_driver_next_page (xr, xr->cairo);
561     }
562 }
563 \f
564 /* Functions for rendering a series of output items to a series of Cairo
565    contexts, with pagination.
566
567    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
568    its underlying implementation.
569
570    See the big comment in cairo.h for intended usage. */
571
572 /* Gives new page CAIRO to XR for output.  CAIRO may be null to skip actually
573    rendering the page (which might be useful to find out how many pages an
574    output document has without actually rendering it). */
575 void
576 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
577 {
578   if (cairo != NULL)
579     {
580       cairo_save (cairo);
581       cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
582       cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
583       cairo_fill (cairo);
584       cairo_restore (cairo);
585
586       cairo_translate (cairo,
587                        xr_to_pt (xr->left_margin),
588                        xr_to_pt (xr->top_margin));
589     }
590
591   xr->page_number++;
592   xr->cairo = cairo;
593   xr->y = 0;
594   xr_driver_run_fsm (xr);
595 }
596
597 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
598    rendering a previous output item, that is, only if xr_driver_need_new_page()
599    returns false. */
600 void
601 xr_driver_output_item (struct xr_driver *xr,
602                        const struct output_item *output_item)
603 {
604   assert (xr->fsm == NULL);
605   xr->fsm = xr_render_output_item (xr, output_item);
606   xr_driver_run_fsm (xr);
607 }
608
609 /* Returns true if XR is in the middle of rendering an output item and needs a
610    new page to be appended using xr_driver_next_page() to make progress,
611    otherwise false. */
612 bool
613 xr_driver_need_new_page (const struct xr_driver *xr)
614 {
615   return xr->fsm != NULL;
616 }
617
618 /* Returns true if the current page doesn't have any content yet. */
619 bool
620 xr_driver_is_page_blank (const struct xr_driver *xr)
621 {
622   return xr->y == 0;
623 }
624
625 static void
626 xr_driver_destroy_fsm (struct xr_driver *xr)
627 {
628   if (xr->fsm != NULL)
629     {
630       xr->fsm->destroy (xr->fsm);
631       xr->fsm = NULL;
632     }
633 }
634
635 static void
636 xr_driver_run_fsm (struct xr_driver *xr)
637 {
638   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
639     xr_driver_destroy_fsm (xr);
640 }
641 \f
642 static void
643 xr_layout_cell (struct xr_driver *, const struct table_cell *,
644                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
645                 int *width, int *height);
646
647 static void
648 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
649 {
650   cairo_new_path (xr->cairo);
651   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
652   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
653   cairo_stroke (xr->cairo);
654 }
655
656 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
657    shortening it to X0...X1 if SHORTEN is true.
658    Draws a horizontal line X1...X3 at Y if RIGHT says so,
659    shortening it to X2...X3 if SHORTEN is true. */
660 static void
661 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
662            enum render_line_style left, enum render_line_style right,
663            bool shorten)
664 {
665   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
666     dump_line (xr, x0, y, x3, y);
667   else
668     {
669       if (left != RENDER_LINE_NONE)
670         dump_line (xr, x0, y, shorten ? x1 : x2, y);
671       if (right != RENDER_LINE_NONE)
672         dump_line (xr, shorten ? x2 : x1, y, x3, y);
673     }
674 }
675
676 /* Draws a vertical line Y0...Y2 at X if TOP says so,
677    shortening it to Y0...Y1 if SHORTEN is true.
678    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
679    shortening it to Y2...Y3 if SHORTEN is true. */
680 static void
681 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
682            enum render_line_style top, enum render_line_style bottom,
683            bool shorten)
684 {
685   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
686     dump_line (xr, x, y0, x, y3);
687   else
688     {
689       if (top != RENDER_LINE_NONE)
690         dump_line (xr, x, y0, x, shorten ? y1 : y2);
691       if (bottom != RENDER_LINE_NONE)
692         dump_line (xr, x, shorten ? y2 : y1, x, y3);
693     }
694 }
695
696 static void
697 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
698               enum render_line_style styles[TABLE_N_AXES][2])
699 {
700   const int x0 = bb[H][0];
701   const int y0 = bb[V][0];
702   const int x3 = bb[H][1];
703   const int y3 = bb[V][1];
704   const int top = styles[H][0];
705   const int left = styles[V][0];
706   const int bottom = styles[H][1];
707   const int right = styles[V][1];
708
709   /* The algorithm here is somewhat subtle, to allow it to handle
710      all the kinds of intersections that we need.
711
712      Three additional ordinates are assigned along the x axis.  The
713      first is xc, midway between x0 and x3.  The others are x1 and
714      x2; for a single vertical line these are equal to xc, and for
715      a double vertical line they are the ordinates of the left and
716      right half of the double line.
717
718      yc, y1, and y2 are assigned similarly along the y axis.
719
720      The following diagram shows the coordinate system and output
721      for double top and bottom lines, single left line, and no
722      right line:
723
724                  x0       x1 xc  x2      x3
725                y0 ________________________
726                   |        #     #       |
727                   |        #     #       |
728                   |        #     #       |
729                   |        #     #       |
730                   |        #     #       |
731      y1 = y2 = yc |#########     #       |
732                   |        #     #       |
733                   |        #     #       |
734                   |        #     #       |
735                   |        #     #       |
736                y3 |________#_____#_______|
737   */
738   struct xr_driver *xr = xr_;
739
740   /* Offset from center of each line in a pair of double lines. */
741   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
742
743   /* Are the lines along each axis single or double?
744      (It doesn't make sense to have different kinds of line on the
745      same axis, so we don't try to gracefully handle that case.) */
746   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
747   bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
748
749   /* When horizontal lines are doubled,
750      the left-side line along y1 normally runs from x0 to x2,
751      and the right-side line along y1 from x3 to x1.
752      If the top-side line is also doubled, we shorten the y1 lines,
753      so that the left-side line runs only to x1,
754      and the right-side line only to x2.
755      Otherwise, the horizontal line at y = y1 below would cut off
756      the intersection, which looks ugly:
757                x0       x1     x2      x3
758              y0 ________________________
759                 |        #     #       |
760                 |        #     #       |
761                 |        #     #       |
762                 |        #     #       |
763              y1 |#########     ########|
764                 |                      |
765                 |                      |
766              y2 |######################|
767                 |                      |
768                 |                      |
769              y3 |______________________|
770      It is more of a judgment call when the horizontal line is
771      single.  We actually choose to cut off the line anyhow, as
772      shown in the first diagram above.
773   */
774   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
775   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
776   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
777   int horz_line_ofs = double_vert ? double_line_ofs : 0;
778   int xc = (x0 + x3) / 2;
779   int x1 = xc - horz_line_ofs;
780   int x2 = xc + horz_line_ofs;
781
782   bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
783   bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
784   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
785   int vert_line_ofs = double_horz ? double_line_ofs : 0;
786   int yc = (y0 + y3) / 2;
787   int y1 = yc - vert_line_ofs;
788   int y2 = yc + vert_line_ofs;
789
790   if (!double_horz)
791     horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
792   else
793     {
794       horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
795       horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
796     }
797
798   if (!double_vert)
799     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
800   else
801     {
802       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
803       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
804     }
805 }
806
807 static void
808 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
809                        int *min_width, int *max_width)
810 {
811   struct xr_driver *xr = xr_;
812   int bb[TABLE_N_AXES][2];
813   int clip[TABLE_N_AXES][2];
814   int h;
815
816   bb[H][0] = 0;
817   bb[H][1] = INT_MAX;
818   bb[V][0] = 0;
819   bb[V][1] = INT_MAX;
820   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
821   xr_layout_cell (xr, cell, bb, clip, max_width, &h);
822
823   bb[H][1] = 1;
824   xr_layout_cell (xr, cell, bb, clip, min_width, &h);
825 }
826
827 static int
828 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
829 {
830   struct xr_driver *xr = xr_;
831   int bb[TABLE_N_AXES][2];
832   int clip[TABLE_N_AXES][2];
833   int w, h;
834
835   bb[H][0] = 0;
836   bb[H][1] = width;
837   bb[V][0] = 0;
838   bb[V][1] = INT_MAX;
839   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
840   xr_layout_cell (xr, cell, bb, clip, &w, &h);
841   return h;
842 }
843
844 static void
845 xr_draw_cell (void *xr_, const struct table_cell *cell,
846               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
847 {
848   struct xr_driver *xr = xr_;
849   int w, h;
850
851   xr_layout_cell (xr, cell, bb, clip, &w, &h);
852 }
853 \f
854 static void
855 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
856                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
857                 int *width, int *height)
858 {
859   struct xr_font *font;
860   int w, h;
861
862   font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
863           : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
864           : &xr->fonts[XR_FONT_PROPORTIONAL]);
865
866   pango_layout_set_text (font->layout, cell->contents, -1);
867
868   pango_layout_set_alignment (
869     font->layout,
870     ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
871      : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
872      : PANGO_ALIGN_CENTER));
873   pango_layout_set_width (
874     font->layout,
875     bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
876   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
877
878   if (clip[H][0] != clip[H][1])
879     {
880       cairo_save (xr->cairo);
881
882       if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
883         {
884           double x0 = xr_to_pt (clip[H][0]);
885           double y0 = xr_to_pt (clip[V][0] + xr->y);
886           double x1 = xr_to_pt (clip[H][1]);
887           double y1 = xr_to_pt (clip[V][1] + xr->y);
888
889           cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
890           cairo_clip (xr->cairo);
891         }
892
893       cairo_translate (xr->cairo,
894                        xr_to_pt (bb[H][0]),
895                        xr_to_pt (bb[V][0] + xr->y));
896       pango_cairo_show_layout (xr->cairo, font->layout);
897       cairo_restore (xr->cairo);
898     }
899
900   pango_layout_get_size (font->layout, &w, &h);
901   *width = pango_to_xr (w);
902   *height = pango_to_xr (h);
903 }
904
905 static void
906 xr_draw_title (struct xr_driver *xr, const char *title,
907                int title_width, int title_height)
908 {
909   struct table_cell cell;
910   int bb[TABLE_N_AXES][2];
911
912   xr_init_caption_cell (title, &cell);
913   bb[H][0] = 0;
914   bb[H][1] = title_width;
915   bb[V][0] = 0;
916   bb[V][1] = title_height;
917   xr_draw_cell (xr, &cell, bb, bb);
918 }
919 \f
920 struct output_driver_factory pdf_driver_factory =
921   { "pdf", "pspp.pdf", xr_pdf_create };
922 struct output_driver_factory ps_driver_factory =
923   { "ps", "pspp.ps", xr_ps_create };
924 struct output_driver_factory svg_driver_factory =
925   { "svg", "pspp.svg", xr_svg_create };
926
927 static const struct output_driver_class cairo_driver_class =
928 {
929   "cairo",
930   xr_destroy,
931   xr_submit,
932   xr_flush,
933 };
934 \f
935 /* GUI rendering helpers. */
936
937 struct xr_rendering
938   {
939     struct output_item *item;
940
941     /* Table items. */
942     struct render_page *page;
943     struct xr_driver *xr;
944     int title_width;
945     int title_height;
946   };
947
948 #define CHART_WIDTH 500
949 #define CHART_HEIGHT 375
950
951
952
953 struct xr_driver *
954 xr_driver_create (cairo_t *cairo, struct string_map *options)
955 {
956   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
957   if (!xr_set_cairo (xr, cairo))
958     {
959       output_driver_destroy (&xr->driver);
960       return NULL;
961     }
962   return xr;
963 }
964
965 /* Destroy XR, which should have been created with xr_driver_create().  Any
966    cairo_t added to XR is not destroyed, because it is owned by the client. */
967 void
968 xr_driver_destroy (struct xr_driver *xr)
969 {
970   if (xr != NULL)
971     {
972       xr->cairo = NULL;
973       output_driver_destroy (&xr->driver);
974     }
975 }
976
977 static struct xr_rendering *
978 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
979 {
980   struct table_item *table_item;
981   struct xr_rendering *r;
982
983   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
984   r = xr_rendering_create (xr, &table_item->output_item, cr);
985   table_item_unref (table_item);
986
987   return r;
988 }
989
990 void 
991 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
992 {
993   if (is_table_item (xr->item))
994     apply_options (xr->xr, o);
995 }
996
997 struct xr_rendering *
998 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
999                      cairo_t *cr)
1000 {
1001   struct xr_rendering *r = NULL;
1002
1003   if (is_text_item (item))
1004     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1005                                   cr);
1006   else if (is_message_item (item))
1007     {
1008       const struct message_item *message_item = to_message_item (item);
1009       const struct msg *msg = message_item_get_msg (message_item);
1010       char *s = msg_to_string (msg, NULL);
1011       r = xr_rendering_create_text (xr, s, cr);
1012       free (s);
1013     }
1014   else if (is_table_item (item))
1015     {
1016       r = xzalloc (sizeof *r);
1017       r->item = output_item_ref (item);
1018       r->xr = xr;
1019       xr_set_cairo (xr, cr);
1020       r->page = xr_render_table_item (xr, to_table_item (item),
1021                                       &r->title_width, &r->title_height);
1022     }
1023   else if (is_chart_item (item))
1024     {
1025       r = xzalloc (sizeof *r);
1026       r->item = output_item_ref (item);
1027     }
1028
1029   return r;
1030 }
1031
1032 void
1033 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1034 {
1035   if (is_table_item (r->item))
1036     {
1037       int w0 = render_page_get_size (r->page, H);
1038       int w1 = r->title_width;
1039       *w = MAX (w0, w1) / XR_POINT;
1040       *h = (render_page_get_size (r->page, V) + r->title_height) / XR_POINT;
1041     }
1042   else
1043     {
1044       *w = CHART_WIDTH;
1045       *h = CHART_HEIGHT;
1046     }
1047 }
1048
1049 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1050                     double x, double y, double width, double height);
1051
1052 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1053    and possibly some additional parts. */
1054 void
1055 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1056                    int x, int y, int w, int h)
1057 {
1058   if (is_table_item (r->item))
1059     {
1060       struct xr_driver *xr = r->xr;
1061
1062       xr_set_cairo (xr, cr);
1063
1064       if (r->title_height > 0)
1065         {
1066           xr->y = 0;
1067           xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)),
1068                          r->title_width, r->title_height);
1069         }
1070
1071       xr->y = r->title_height;
1072       render_page_draw_region (r->page, 
1073                                x * XR_POINT, (y * XR_POINT) - r->title_height,
1074                                w * XR_POINT, h * XR_POINT);
1075     }
1076   else
1077     xr_draw_chart (to_chart_item (r->item), cr,
1078                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1079 }
1080
1081 static void
1082 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1083                double x, double y, double width, double height)
1084 {
1085   struct xrchart_geometry geom;
1086
1087   cairo_save (cr);
1088   cairo_translate (cr, x, y + height);
1089   cairo_scale (cr, 1.0, -1.0);
1090   xrchart_geometry_init (cr, &geom, width, height);
1091   if (is_boxplot (chart_item))
1092     xrchart_draw_boxplot (chart_item, cr, &geom);
1093   else if (is_histogram_chart (chart_item))
1094     xrchart_draw_histogram (chart_item, cr, &geom);
1095   else if (is_np_plot_chart (chart_item))
1096     xrchart_draw_np_plot (chart_item, cr, &geom);
1097   else if (is_piechart (chart_item))
1098     xrchart_draw_piechart (chart_item, cr, &geom);
1099   else if (is_roc_chart (chart_item))
1100     xrchart_draw_roc (chart_item, cr, &geom);
1101   else if (is_scree (chart_item))
1102     xrchart_draw_scree (chart_item, cr, &geom);
1103   else if (is_spreadlevel_plot_chart (chart_item))
1104     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1105   else
1106     NOT_REACHED ();
1107   xrchart_geometry_free (cr, &geom);
1108
1109   cairo_restore (cr);
1110 }
1111
1112 char *
1113 xr_draw_png_chart (const struct chart_item *item,
1114                    const char *file_name_template, int number,
1115                    const struct xr_color *fg,
1116                    const struct xr_color *bg
1117                    )
1118 {
1119   const int width = 640;
1120   const int length = 480;
1121
1122   cairo_surface_t *surface;
1123   cairo_status_t status;
1124   const char *number_pos;
1125   char *file_name;
1126   cairo_t *cr;
1127
1128   number_pos = strchr (file_name_template, '#');
1129   if (number_pos != NULL)
1130     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1131                            file_name_template, number, number_pos + 1);
1132   else
1133     file_name = xstrdup (file_name_template);
1134
1135   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1136   cr = cairo_create (surface);
1137
1138   cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1139   cairo_paint (cr);
1140
1141   cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1142
1143   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1144
1145   status = cairo_surface_write_to_png (surface, file_name);
1146   if (status != CAIRO_STATUS_SUCCESS)
1147     msg (ME, _("error writing output file `%s': %s"),
1148            file_name, cairo_status_to_string (status));
1149
1150   cairo_destroy (cr);
1151   cairo_surface_destroy (surface);
1152
1153   return file_name;
1154 }
1155 \f
1156 struct xr_table_state
1157   {
1158     struct xr_render_fsm fsm;
1159     struct table_item *table_item;
1160     struct render_break x_break;
1161     struct render_break y_break;
1162     int caption_height;
1163   };
1164
1165 static bool
1166 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1167 {
1168   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1169
1170   for (;;)
1171     {
1172       struct render_page *y_slice;
1173       int space;
1174
1175       while (!render_break_has_next (&ts->y_break))
1176         {
1177           struct render_page *x_slice;
1178
1179           render_break_destroy (&ts->y_break);
1180           if (!render_break_has_next (&ts->x_break))
1181             return false;
1182
1183           x_slice = render_break_next (&ts->x_break, xr->width);
1184           render_break_init (&ts->y_break, x_slice, V);
1185         }
1186
1187       space = xr->length - xr->y;
1188       if (render_break_next_size (&ts->y_break) > space)
1189         {
1190           assert (xr->y > 0);
1191           return true;
1192         }
1193
1194       y_slice = render_break_next (&ts->y_break, space);
1195       if (ts->caption_height)
1196         {
1197           if (xr->cairo)
1198             xr_draw_title (xr, table_item_get_caption (ts->table_item),
1199                            xr->width, ts->caption_height);
1200
1201           xr->y += ts->caption_height;
1202           ts->caption_height = 0;
1203         }
1204
1205       if (xr->cairo)
1206         render_page_draw (y_slice);
1207       xr->y += render_page_get_size (y_slice, V);
1208       render_page_unref (y_slice);
1209     }
1210 }
1211
1212 static void
1213 xr_table_destroy (struct xr_render_fsm *fsm)
1214 {
1215   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1216
1217   table_item_unref (ts->table_item);
1218   render_break_destroy (&ts->x_break);
1219   render_break_destroy (&ts->y_break);
1220   free (ts);
1221 }
1222
1223 static struct xr_render_fsm *
1224 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1225 {
1226   struct xr_table_state *ts;
1227   struct render_page *page;
1228   int caption_width;
1229
1230   ts = xmalloc (sizeof *ts);
1231   ts->fsm.render = xr_table_render;
1232   ts->fsm.destroy = xr_table_destroy;
1233   ts->table_item = table_item_ref (table_item);
1234
1235   if (xr->y > 0)
1236     xr->y += xr->char_height;
1237
1238   page = xr_render_table_item (xr, table_item,
1239                                &caption_width, &ts->caption_height);
1240   xr->params->size[V] = xr->length - ts->caption_height;
1241
1242   render_break_init (&ts->x_break, page, H);
1243   render_break_init_empty (&ts->y_break);
1244
1245   return &ts->fsm;
1246 }
1247 \f
1248 struct xr_chart_state
1249   {
1250     struct xr_render_fsm fsm;
1251     struct chart_item *chart_item;
1252   };
1253
1254 static bool
1255 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1256 {
1257   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1258
1259   if (xr->y > 0)
1260     return true;
1261
1262   if (xr->cairo != NULL)
1263     xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1264                    xr_to_pt (xr->width), xr_to_pt (xr->length));
1265   xr->y = xr->length;
1266
1267   return false;
1268 }
1269
1270 static void
1271 xr_chart_destroy (struct xr_render_fsm *fsm)
1272 {
1273   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1274
1275   chart_item_unref (cs->chart_item);
1276   free (cs);
1277 }
1278
1279 static struct xr_render_fsm *
1280 xr_render_chart (const struct chart_item *chart_item)
1281 {
1282   struct xr_chart_state *cs;
1283
1284   cs = xmalloc (sizeof *cs);
1285   cs->fsm.render = xr_chart_render;
1286   cs->fsm.destroy = xr_chart_destroy;
1287   cs->chart_item = chart_item_ref (chart_item);
1288
1289   return &cs->fsm;
1290 }
1291 \f
1292 static bool
1293 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1294 {
1295   return xr->y > 0;
1296 }
1297
1298 static void
1299 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1300 {
1301   /* Nothing to do. */
1302 }
1303
1304 static struct xr_render_fsm *
1305 xr_render_eject (void)
1306 {
1307   static struct xr_render_fsm eject_renderer =
1308     {
1309       xr_eject_render,
1310       xr_eject_destroy
1311     };
1312
1313   return &eject_renderer;
1314 }
1315 \f
1316 static struct xr_render_fsm *
1317 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1318 {
1319   struct table_item *table_item;
1320   struct xr_render_fsm *fsm;
1321
1322   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
1323   fsm = xr_render_table (xr, table_item);
1324   table_item_unref (table_item);
1325
1326   return fsm;
1327 }
1328
1329 static struct xr_render_fsm *
1330 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1331 {
1332   enum text_item_type type = text_item_get_type (text_item);
1333   const char *text = text_item_get_text (text_item);
1334
1335   switch (type)
1336     {
1337     case TEXT_ITEM_TITLE:
1338       free (xr->title);
1339       xr->title = xstrdup (text);
1340       break;
1341
1342     case TEXT_ITEM_SUBTITLE:
1343       free (xr->subtitle);
1344       xr->subtitle = xstrdup (text);
1345       break;
1346
1347     case TEXT_ITEM_COMMAND_CLOSE:
1348       break;
1349
1350     case TEXT_ITEM_BLANK_LINE:
1351       if (xr->y > 0)
1352         xr->y += xr->char_height;
1353       break;
1354
1355     case TEXT_ITEM_EJECT_PAGE:
1356       if (xr->y > 0)
1357         return xr_render_eject ();
1358       break;
1359
1360     default:
1361       return xr_create_text_renderer (xr, text);
1362     }
1363
1364   return NULL;
1365 }
1366
1367 static struct xr_render_fsm *
1368 xr_render_message (struct xr_driver *xr,
1369                    const struct message_item *message_item)
1370 {
1371   const struct msg *msg = message_item_get_msg (message_item);
1372   struct xr_render_fsm *fsm;
1373   char *s;
1374
1375   s = msg_to_string (msg, xr->command_name);
1376   fsm = xr_create_text_renderer (xr, s);
1377   free (s);
1378
1379   return fsm;
1380 }
1381
1382 static struct xr_render_fsm *
1383 xr_render_output_item (struct xr_driver *xr,
1384                        const struct output_item *output_item)
1385 {
1386   if (is_table_item (output_item))
1387     return xr_render_table (xr, to_table_item (output_item));
1388   else if (is_chart_item (output_item))
1389     return xr_render_chart (to_chart_item (output_item));
1390   else if (is_text_item (output_item))
1391     return xr_render_text (xr, to_text_item (output_item));
1392   else if (is_message_item (output_item))
1393     return xr_render_message (xr, to_message_item (output_item));
1394   else
1395     return NULL;
1396 }