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