perl-module: Put struct dictionary inside a wrapper "struct pspp_dict".
[pspp] / 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/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 <pango/pango-font.h>
51 #include <pango/pango-layout.h>
52 #include <pango/pango.h>
53 #include <pango/pangocairo.h>
54 #include <stdlib.h>
55
56 #include "gl/error.h"
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 PANGO_SCALE
70 #define XR_INCH (XR_POINT * 72)
71
72 /* Conversions to and from points. */
73 static double
74 xr_to_pt (int x)
75 {
76   return x / (double) XR_POINT;
77 }
78
79 /* Output types. */
80 enum xr_output_type
81   {
82     XR_PDF,
83     XR_PS,
84     XR_SVG
85   };
86
87 /* Cairo fonts. */
88 enum xr_font_type
89   {
90     XR_FONT_PROPORTIONAL,
91     XR_FONT_EMPHASIS,
92     XR_FONT_FIXED,
93     XR_N_FONTS
94   };
95
96 /* A font for use with Cairo. */
97 struct xr_font
98   {
99     PangoFontDescription *desc;
100     PangoLayout *layout;
101   };
102
103 /* An output item whose rendering is in progress. */
104 struct xr_render_fsm
105   {
106     /* Renders as much of itself as it can on the current page.  Returns true
107        if rendering is complete, false if the output item needs another
108        page. */
109     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
110
111     /* Destroys the output item. */
112     void (*destroy) (struct xr_render_fsm *);
113   };
114
115 /* Cairo output driver. */
116 struct xr_driver
117   {
118     struct output_driver driver;
119
120     /* User parameters. */
121     struct xr_font fonts[XR_N_FONTS];
122
123     int width;                  /* Page width minus margins. */
124     int length;                 /* Page length minus margins and header. */
125
126     int left_margin;            /* Left margin in XR units. */
127     int right_margin;           /* Right margin in XR units. */
128     int top_margin;             /* Top margin in XR units. */
129     int bottom_margin;          /* Bottom margin in XR units. */
130
131     int line_gutter;            /* Space around lines. */
132     int line_space;             /* Space between lines. */
133     int line_width;             /* Width of lines. */
134
135     double bg_red, bg_green, bg_blue; /* Background color */
136     double fg_red, fg_green, fg_blue; /* Foreground color */
137
138     /* Internal state. */
139     struct render_params *params;
140     int char_width, char_height;
141     char *command_name;
142     char *title;
143     char *subtitle;
144     cairo_t *cairo;
145     int page_number;            /* Current page number. */
146     int y;
147     struct xr_render_fsm *fsm;
148   };
149
150 static const struct output_driver_class cairo_driver_class;
151
152 static void xr_driver_destroy_fsm (struct xr_driver *);
153 static void xr_driver_run_fsm (struct xr_driver *);
154
155 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
156                           enum render_line_style styles[TABLE_N_AXES][2]);
157 static void xr_measure_cell_width (void *, const struct table_cell *,
158                                    int *min, int *max);
159 static int xr_measure_cell_height (void *, const struct table_cell *,
160                                    int width);
161 static void xr_draw_cell (void *, const struct table_cell *,
162                           int bb[TABLE_N_AXES][2],
163                           int clip[TABLE_N_AXES][2]);
164
165 static struct xr_render_fsm *xr_render_output_item (
166   struct xr_driver *, const struct output_item *);
167 \f
168 /* Output driver basics. */
169
170 static struct xr_driver *
171 xr_driver_cast (struct output_driver *driver)
172 {
173   assert (driver->class == &cairo_driver_class);
174   return UP_CAST (driver, struct xr_driver, driver);
175 }
176
177 static struct driver_option *
178 opt (struct output_driver *d, struct string_map *options, const char *key,
179      const char *default_value)
180 {
181   return driver_option_get (d, options, key, default_value);
182 }
183
184 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
185    Currently, the input string must be of the form "#RRRRGGGGBBBB"
186    Future implementations might allow things like "yellow" and
187    "sky-blue-ultra-brown"
188 */
189 static void
190 parse_color (struct output_driver *d, struct string_map *options,
191               const char *key, const char *default_value,
192               double *dred, double *dgreen, double *dblue)
193 {
194   int red, green, blue;
195   char *string = parse_string (opt (d, options, key, default_value));
196
197   if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
198     {
199       /* If the parsed option string fails, then try the default value */
200       if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
201         {
202           /* ... and if that fails set everything to zero */
203           red = green = blue = 0;
204         }
205     }
206
207   free (string);
208
209   /* Convert 16 bit ints to float */
210   *dred = red / (double) 0xFFFF;
211   *dgreen = green / (double) 0xFFFF;
212   *dblue = blue / (double) 0xFFFF;
213 }
214
215 static PangoFontDescription *
216 parse_font (struct output_driver *d, struct string_map *options,
217             const char *key, const char *default_value,
218             int default_points)
219 {
220   PangoFontDescription *desc;
221   char *string;
222
223   /* Parse KEY as a font description. */
224   string = parse_string (opt (d, options, key, default_value));
225   desc = pango_font_description_from_string (string);
226   if (desc == NULL)
227     {
228       error (0, 0, _("`%s': bad font specification"), string);
229
230       /* Fall back to DEFAULT_VALUE, which had better be a valid font
231          description. */
232       desc = pango_font_description_from_string (default_value);
233       assert (desc != NULL);
234     }
235   free (string);
236
237   /* If the font description didn't include an explicit font size, then set it
238      to DEFAULT_POINTS. */
239   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
240     pango_font_description_set_size (desc,
241                                      default_points / 1000.0 * PANGO_SCALE);
242
243   return desc;
244 }
245
246
247 static void
248 apply_options (struct xr_driver *xr, struct string_map *o)
249 {
250   struct output_driver *d = &xr->driver;
251
252   int paper_width, paper_length, i;
253
254   int font_points = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
255
256   for (i = 0; i < XR_N_FONTS; i++)
257     {
258       struct xr_font *font = &xr->fonts[i];
259
260       if (font->desc != NULL)
261         pango_font_description_free (font->desc);
262     }
263
264   xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
265                                               font_points);
266   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
267                                                      "serif", font_points);
268   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
269                                                  "serif italic", font_points);
270
271   xr->line_gutter = XR_POINT;
272   xr->line_space = XR_POINT;
273   xr->line_width = XR_POINT / 2;
274   xr->page_number = 0;
275
276   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg_red, &xr->bg_green, &xr->bg_blue);
277   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg_red, &xr->fg_green, &xr->fg_blue);
278
279   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
280   xr->left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
281   xr->right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
282   xr->top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
283   xr->bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
284
285   xr->width = paper_width - xr->left_margin - xr->right_margin;
286   xr->length = paper_length - xr->top_margin - xr->bottom_margin;
287 }
288
289 static struct xr_driver *
290 xr_allocate (const char *name, int device_type, struct string_map *o)
291 {
292   struct xr_driver *xr = xzalloc (sizeof *xr);
293   struct output_driver *d = &xr->driver;
294
295   output_driver_init (d, &cairo_driver_class, name, device_type);
296
297   apply_options (xr, o);
298
299   return xr;
300 }
301
302 static bool
303 xr_is_72dpi (cairo_t *cr)
304 {
305   cairo_surface_type_t type;
306   cairo_surface_t *surface;
307
308   surface = cairo_get_target (cr);
309   type = cairo_surface_get_type (surface);
310   return type == CAIRO_SURFACE_TYPE_PDF || type == CAIRO_SURFACE_TYPE_PS;
311 }
312
313 static bool
314 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
315 {
316   PangoContext *context;
317   PangoFontMap *map;
318   int i;
319
320   xr->cairo = cairo;
321
322   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
323
324   map = pango_cairo_font_map_get_default ();
325   context = pango_font_map_create_context (map);
326   if (xr_is_72dpi (cairo))
327     {
328       /* Pango seems to always scale fonts according to the DPI specified
329          in the font map, even if the surface has a real DPI.  The default
330          DPI is 96, so on a 72 DPI device fonts end up being 96/72 = 133%
331          of their desired size.  We deal with this by fixing the resolution
332          here.  Presumably there is a better solution, but what? */
333       pango_cairo_context_set_resolution (context, 72.0);
334     }
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_layout_new (context);
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, char_width);
349       xr->char_height = MAX (xr->char_height, char_height);
350     }
351
352   g_object_unref (G_OBJECT (context));
353
354   if (xr->params == NULL)
355     {
356       int single_width, double_width;
357
358       xr->params = xmalloc (sizeof *xr->params);
359       xr->params->draw_line = xr_draw_line;
360       xr->params->measure_cell_width = xr_measure_cell_width;
361       xr->params->measure_cell_height = xr_measure_cell_height;
362       xr->params->draw_cell = xr_draw_cell;
363       xr->params->aux = xr;
364       xr->params->size[H] = xr->width;
365       xr->params->size[V] = xr->length;
366       xr->params->font_size[H] = xr->char_width;
367       xr->params->font_size[V] = xr->char_height;
368
369       single_width = 2 * xr->line_gutter + xr->line_width;
370       double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
371       for (i = 0; i < TABLE_N_AXES; i++)
372         {
373           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
374           xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
375           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
376         }
377     }
378
379   cairo_set_source_rgb (xr->cairo, xr->fg_red, xr->fg_green, xr->fg_blue);
380
381   return true;
382 }
383
384 static struct output_driver *
385 xr_create (const char *file_name, enum settings_output_devices device_type,
386            struct string_map *o, enum xr_output_type file_type)
387 {
388   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
389   struct xr_driver *xr;
390   cairo_surface_t *surface;
391   cairo_status_t status;
392   double width_pt, length_pt;
393
394   xr = xr_allocate (file_name, device_type, o);
395
396   width_pt = (xr->width + xr->left_margin + xr->right_margin) / 1000.0;
397   length_pt = (xr->length + xr->top_margin + xr->bottom_margin) / 1000.0;
398   if (file_type == XR_PDF)
399     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
400   else if (file_type == XR_PS)
401     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
402   else if (file_type == XR_SVG)
403     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
404   else
405     NOT_REACHED ();
406
407   status = cairo_surface_status (surface);
408   if (status != CAIRO_STATUS_SUCCESS)
409     {
410       error (0, 0, _("error opening output file `%s': %s"),
411              file_name, cairo_status_to_string (status));
412       cairo_surface_destroy (surface);
413       goto error;
414     }
415
416   xr->cairo = cairo_create (surface);
417   cairo_surface_destroy (surface);
418
419   if (!xr_set_cairo (xr, xr->cairo))
420     goto error;
421
422   cairo_save (xr->cairo);
423   xr_driver_next_page (xr, xr->cairo);
424
425   if (xr->width / xr->char_width < MIN_WIDTH)
426     {
427       error (0, 0, _("The defined page is not wide enough to hold at least %d "
428                      "characters in the default font.  In fact, there's only "
429                      "room for %d characters."),
430              MIN_WIDTH,
431              xr->width / xr->char_width);
432       goto error;
433     }
434
435   if (xr->length / xr->char_height < MIN_LENGTH)
436     {
437       error (0, 0, _("The defined page is not long enough to hold at least %d "
438                      "lines in the default font.  In fact, there's only "
439                      "room for %d lines."),
440              MIN_LENGTH,
441              xr->length / xr->char_height);
442       goto error;
443     }
444
445   return &xr->driver;
446
447  error:
448   output_driver_destroy (&xr->driver);
449   return NULL;
450 }
451
452 static struct output_driver *
453 xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
454                struct string_map *o)
455 {
456   return xr_create (file_name, device_type, o, XR_PDF);
457 }
458
459 static struct output_driver *
460 xr_ps_create (const char *file_name, enum settings_output_devices device_type,
461                struct string_map *o)
462 {
463   return xr_create (file_name, device_type, o, XR_PS);
464 }
465
466 static struct output_driver *
467 xr_svg_create (const char *file_name, enum settings_output_devices device_type,
468                struct string_map *o)
469 {
470   return xr_create (file_name, device_type, o, XR_SVG);
471 }
472
473 static void
474 xr_destroy (struct output_driver *driver)
475 {
476   struct xr_driver *xr = xr_driver_cast (driver);
477   size_t i;
478
479   xr_driver_destroy_fsm (xr);
480
481   if (xr->cairo != NULL)
482     {
483       cairo_status_t status;
484
485       cairo_surface_finish (cairo_get_target (xr->cairo));
486       status = cairo_status (xr->cairo);
487       if (status != CAIRO_STATUS_SUCCESS)
488         error (0, 0, _("error drawing output for %s driver: %s"),
489                output_driver_get_name (driver),
490                cairo_status_to_string (status));
491       cairo_destroy (xr->cairo);
492     }
493
494   free (xr->command_name);
495   for (i = 0; i < XR_N_FONTS; i++)
496     {
497       struct xr_font *font = &xr->fonts[i];
498
499       if (font->desc != NULL)
500         pango_font_description_free (font->desc);
501       if (font->layout != NULL)
502         g_object_unref (font->layout);
503     }
504
505   free (xr->params);
506   free (xr);
507 }
508
509 static void
510 xr_flush (struct output_driver *driver)
511 {
512   struct xr_driver *xr = xr_driver_cast (driver);
513
514   cairo_surface_flush (cairo_get_target (xr->cairo));
515 }
516
517 static void
518 xr_init_caption_cell (const char *caption, struct table_cell *cell)
519 {
520   cell->contents = caption;
521   cell->options = TAB_LEFT;
522   cell->destructor = NULL;
523 }
524
525 static struct render_page *
526 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
527                       int *caption_widthp, int *caption_heightp)
528 {
529   const char *caption = table_item_get_caption (item);
530
531   if (caption != NULL)
532     {
533       /* XXX doesn't do well with very large captions */
534       int min_width, max_width;
535       struct table_cell cell;
536
537       xr_init_caption_cell (caption, &cell);
538
539       xr_measure_cell_width (xr, &cell, &min_width, &max_width);
540       *caption_widthp = MIN (max_width, xr->width);
541       *caption_heightp = xr_measure_cell_height (xr, &cell, *caption_widthp);
542     }
543   else
544     *caption_heightp = 0;
545
546   return render_page_create (xr->params, table_item_get_table (item));
547 }
548
549 static void
550 xr_submit (struct output_driver *driver, const struct output_item *output_item)
551 {
552   struct xr_driver *xr = xr_driver_cast (driver);
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                 PangoWrapMode, 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, PANGO_WRAP_WORD, max_width, &h);
822
823   bb[H][1] = 1;
824   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, 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, PANGO_WRAP_WORD, &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, PANGO_WRAP_WORD, &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                 PangoWrapMode wrap, int *width, int *height)
858 {
859   struct xr_font *font;
860
861   font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
862           : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
863           : &xr->fonts[XR_FONT_PROPORTIONAL]);
864
865   pango_layout_set_text (font->layout, cell->contents, -1);
866
867   pango_layout_set_alignment (
868     font->layout,
869     ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
870      : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
871      : PANGO_ALIGN_CENTER));
872   pango_layout_set_width (font->layout,
873                           bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]);
874   pango_layout_set_wrap (font->layout, wrap);
875
876   if (clip[H][0] != clip[H][1])
877     {
878       cairo_save (xr->cairo);
879
880       if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
881         {
882           double x0 = xr_to_pt (clip[H][0]);
883           double y0 = xr_to_pt (clip[V][0] + xr->y);
884           double x1 = xr_to_pt (clip[H][1]);
885           double y1 = xr_to_pt (clip[V][1] + xr->y);
886
887           cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
888           cairo_clip (xr->cairo);
889         }
890
891       cairo_translate (xr->cairo,
892                        xr_to_pt (bb[H][0]),
893                        xr_to_pt (bb[V][0] + xr->y));
894       pango_cairo_show_layout (xr->cairo, font->layout);
895       cairo_restore (xr->cairo);
896     }
897
898   if (width != NULL || height != NULL)
899     {
900       int w, h;
901
902       pango_layout_get_size (font->layout, &w, &h);
903       if (width != NULL)
904         *width = w;
905       if (height != NULL)
906         *height = h;
907     }
908 }
909
910 static void
911 xr_draw_title (struct xr_driver *xr, const char *title,
912                int title_width, int title_height)
913 {
914   struct table_cell cell;
915   int bb[TABLE_N_AXES][2];
916
917   xr_init_caption_cell (title, &cell);
918   bb[H][0] = 0;
919   bb[H][1] = title_width;
920   bb[V][0] = 0;
921   bb[V][1] = title_height;
922   xr_draw_cell (xr, &cell, bb, bb);
923 }
924 \f
925 struct output_driver_factory pdf_driver_factory =
926   { "pdf", "pspp.pdf", xr_pdf_create };
927 struct output_driver_factory ps_driver_factory =
928   { "ps", "pspp.ps", xr_ps_create };
929 struct output_driver_factory svg_driver_factory =
930   { "svg", "pspp.svg", xr_svg_create };
931
932 static const struct output_driver_class cairo_driver_class =
933 {
934   "cairo",
935   xr_destroy,
936   xr_submit,
937   xr_flush,
938 };
939 \f
940 /* GUI rendering helpers. */
941
942 struct xr_rendering
943   {
944     struct output_item *item;
945
946     /* Table items. */
947     struct render_page *page;
948     struct xr_driver *xr;
949     int title_width;
950     int title_height;
951   };
952
953 #define CHART_WIDTH 500
954 #define CHART_HEIGHT 375
955
956
957
958 struct xr_driver *
959 xr_driver_create (cairo_t *cairo, struct string_map *options)
960 {
961   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
962   if (!xr_set_cairo (xr, cairo))
963     {
964       output_driver_destroy (&xr->driver);
965       return NULL;
966     }
967   return xr;
968 }
969
970 /* Destroy XR, which should have been created with xr_driver_create().  Any
971    cairo_t added to XR is not destroyed, because it is owned by the client. */
972 void
973 xr_driver_destroy (struct xr_driver *xr)
974 {
975   if (xr != NULL)
976     {
977       xr->cairo = NULL;
978       output_driver_destroy (&xr->driver);
979     }
980 }
981
982 static struct xr_rendering *
983 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
984 {
985   struct table_item *table_item;
986   struct xr_rendering *r;
987
988   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
989   r = xr_rendering_create (xr, &table_item->output_item, cr);
990   table_item_unref (table_item);
991
992   return r;
993 }
994
995 void 
996 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
997 {
998   if (is_table_item (xr->item))
999     apply_options (xr->xr, o);
1000 }
1001
1002 struct xr_rendering *
1003 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1004                      cairo_t *cr)
1005 {
1006   struct xr_rendering *r = NULL;
1007
1008   if (is_text_item (item))
1009     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1010                                   cr);
1011   else if (is_message_item (item))
1012     {
1013       const struct message_item *message_item = to_message_item (item);
1014       const struct msg *msg = message_item_get_msg (message_item);
1015       char *s = msg_to_string (msg, NULL);
1016       r = xr_rendering_create_text (xr, s, cr);
1017       free (s);
1018     }
1019   else if (is_table_item (item))
1020     {
1021       r = xzalloc (sizeof *r);
1022       r->item = output_item_ref (item);
1023       r->xr = xr;
1024       xr_set_cairo (xr, cr);
1025       r->page = xr_render_table_item (xr, to_table_item (item),
1026                                       &r->title_width, &r->title_height);
1027     }
1028   else if (is_chart_item (item))
1029     {
1030       r = xzalloc (sizeof *r);
1031       r->item = output_item_ref (item);
1032     }
1033
1034   return r;
1035 }
1036
1037 void
1038 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1039 {
1040   if (is_table_item (r->item))
1041     {
1042       int w0 = render_page_get_size (r->page, H);
1043       int w1 = r->title_width;
1044       *w = MAX (w0, w1) / 1024;
1045       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
1046     }
1047   else
1048     {
1049       *w = CHART_WIDTH;
1050       *h = CHART_HEIGHT;
1051     }
1052 }
1053
1054 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1055                     double x, double y, double width, double height);
1056
1057 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1058    and possibly some additional parts. */
1059 void
1060 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1061                    int x, int y, int w, int h)
1062 {
1063   if (is_table_item (r->item))
1064     {
1065       struct xr_driver *xr = r->xr;
1066
1067       xr_set_cairo (xr, cr);
1068
1069       if (r->title_height > 0)
1070         {
1071           xr->y = 0;
1072           xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)),
1073                          r->title_width, r->title_height);
1074         }
1075
1076       xr->y = r->title_height;
1077       render_page_draw_region (r->page, x * 1024, (y * 1024) - r->title_height,
1078                                w * 1024, h * 1024);
1079     }
1080   else
1081     xr_draw_chart (to_chart_item (r->item), cr,
1082                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1083 }
1084
1085 static void
1086 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1087                double x, double y, double width, double height)
1088 {
1089   struct xrchart_geometry geom;
1090
1091   cairo_save (cr);
1092   cairo_translate (cr, x, y + height);
1093   cairo_scale (cr, 1.0, -1.0);
1094   xrchart_geometry_init (cr, &geom, width, height);
1095   if (is_boxplot (chart_item))
1096     xrchart_draw_boxplot (chart_item, cr, &geom);
1097   else if (is_histogram_chart (chart_item))
1098     xrchart_draw_histogram (chart_item, cr, &geom);
1099   else if (is_np_plot_chart (chart_item))
1100     xrchart_draw_np_plot (chart_item, cr, &geom);
1101   else if (is_piechart (chart_item))
1102     xrchart_draw_piechart (chart_item, cr, &geom);
1103   else if (is_roc_chart (chart_item))
1104     xrchart_draw_roc (chart_item, cr, &geom);
1105   else if (is_scree (chart_item))
1106     xrchart_draw_scree (chart_item, cr, &geom);
1107   else if (is_spreadlevel_plot_chart (chart_item))
1108     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1109   else
1110     NOT_REACHED ();
1111   xrchart_geometry_free (cr, &geom);
1112
1113   cairo_restore (cr);
1114 }
1115
1116 char *
1117 xr_draw_png_chart (const struct chart_item *item,
1118                    const char *file_name_template, int number)
1119 {
1120   const int width = 640;
1121   const int length = 480;
1122
1123   cairo_surface_t *surface;
1124   cairo_status_t status;
1125   const char *number_pos;
1126   char *file_name;
1127   cairo_t *cr;
1128
1129   number_pos = strchr (file_name_template, '#');
1130   if (number_pos != NULL)
1131     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1132                            file_name_template, number, number_pos + 1);
1133   else
1134     file_name = xstrdup (file_name_template);
1135
1136   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1137   cr = cairo_create (surface);
1138
1139   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1140
1141   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1142
1143   status = cairo_surface_write_to_png (surface, file_name);
1144   if (status != CAIRO_STATUS_SUCCESS)
1145     error (0, 0, _("error writing output file `%s': %s"),
1146            file_name, cairo_status_to_string (status));
1147
1148   cairo_destroy (cr);
1149   cairo_surface_destroy (surface);
1150
1151   return file_name;
1152 }
1153 \f
1154 struct xr_table_state
1155   {
1156     struct xr_render_fsm fsm;
1157     struct table_item *table_item;
1158     struct render_break x_break;
1159     struct render_break y_break;
1160     int caption_height;
1161   };
1162
1163 static bool
1164 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1165 {
1166   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1167
1168   for (;;)
1169     {
1170       struct render_page *y_slice;
1171       int space;
1172
1173       while (!render_break_has_next (&ts->y_break))
1174         {
1175           struct render_page *x_slice;
1176
1177           render_break_destroy (&ts->y_break);
1178           if (!render_break_has_next (&ts->x_break))
1179             return false;
1180
1181           x_slice = render_break_next (&ts->x_break, xr->width);
1182           render_break_init (&ts->y_break, x_slice, V);
1183         }
1184
1185       space = xr->length - xr->y;
1186       if (render_break_next_size (&ts->y_break) > space)
1187         {
1188           assert (xr->y > 0);
1189           return true;
1190         }
1191
1192       y_slice = render_break_next (&ts->y_break, space);
1193       if (ts->caption_height)
1194         {
1195           if (xr->cairo)
1196             xr_draw_title (xr, table_item_get_caption (ts->table_item),
1197                            xr->width, ts->caption_height);
1198
1199           xr->y += ts->caption_height;
1200           ts->caption_height = 0;
1201         }
1202
1203       if (xr->cairo)
1204         render_page_draw (y_slice);
1205       xr->y += render_page_get_size (y_slice, V);
1206       render_page_unref (y_slice);
1207     }
1208 }
1209
1210 static void
1211 xr_table_destroy (struct xr_render_fsm *fsm)
1212 {
1213   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1214
1215   table_item_unref (ts->table_item);
1216   render_break_destroy (&ts->x_break);
1217   render_break_destroy (&ts->y_break);
1218   free (ts);
1219 }
1220
1221 static struct xr_render_fsm *
1222 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1223 {
1224   struct xr_table_state *ts;
1225   struct render_page *page;
1226   int caption_width;
1227
1228   ts = xmalloc (sizeof *ts);
1229   ts->fsm.render = xr_table_render;
1230   ts->fsm.destroy = xr_table_destroy;
1231   ts->table_item = table_item_ref (table_item);
1232
1233   if (xr->y > 0)
1234     xr->y += xr->char_height;
1235
1236   page = xr_render_table_item (xr, table_item,
1237                                &caption_width, &ts->caption_height);
1238   xr->params->size[V] = xr->length - ts->caption_height;
1239
1240   render_break_init (&ts->x_break, page, H);
1241   render_break_init_empty (&ts->y_break);
1242
1243   return &ts->fsm;
1244 }
1245 \f
1246 struct xr_chart_state
1247   {
1248     struct xr_render_fsm fsm;
1249     struct chart_item *chart_item;
1250   };
1251
1252 static bool
1253 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1254 {
1255   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1256
1257   if (xr->y > 0)
1258     return true;
1259
1260   if (xr->cairo != NULL)
1261     xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1262                    xr_to_pt (xr->width), xr_to_pt (xr->length));
1263   xr->y = xr->length;
1264
1265   return false;
1266 }
1267
1268 static void
1269 xr_chart_destroy (struct xr_render_fsm *fsm)
1270 {
1271   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1272
1273   chart_item_unref (cs->chart_item);
1274   free (cs);
1275 }
1276
1277 static struct xr_render_fsm *
1278 xr_render_chart (const struct chart_item *chart_item)
1279 {
1280   struct xr_chart_state *cs;
1281
1282   cs = xmalloc (sizeof *cs);
1283   cs->fsm.render = xr_chart_render;
1284   cs->fsm.destroy = xr_chart_destroy;
1285   cs->chart_item = chart_item_ref (chart_item);
1286
1287   return &cs->fsm;
1288 }
1289 \f
1290 static bool
1291 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1292 {
1293   return xr->y > 0;
1294 }
1295
1296 static void
1297 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1298 {
1299   /* Nothing to do. */
1300 }
1301
1302 static struct xr_render_fsm *
1303 xr_render_eject (void)
1304 {
1305   static struct xr_render_fsm eject_renderer =
1306     {
1307       xr_eject_render,
1308       xr_eject_destroy
1309     };
1310
1311   return &eject_renderer;
1312 }
1313 \f
1314 static struct xr_render_fsm *
1315 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1316 {
1317   struct table_item *table_item;
1318   struct xr_render_fsm *fsm;
1319
1320   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
1321   fsm = xr_render_table (xr, table_item);
1322   table_item_unref (table_item);
1323
1324   return fsm;
1325 }
1326
1327 static struct xr_render_fsm *
1328 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1329 {
1330   enum text_item_type type = text_item_get_type (text_item);
1331   const char *text = text_item_get_text (text_item);
1332
1333   switch (type)
1334     {
1335     case TEXT_ITEM_TITLE:
1336       free (xr->title);
1337       xr->title = xstrdup (text);
1338       break;
1339
1340     case TEXT_ITEM_SUBTITLE:
1341       free (xr->subtitle);
1342       xr->subtitle = xstrdup (text);
1343       break;
1344
1345     case TEXT_ITEM_COMMAND_CLOSE:
1346       break;
1347
1348     case TEXT_ITEM_BLANK_LINE:
1349       if (xr->y > 0)
1350         xr->y += xr->char_height;
1351       break;
1352
1353     case TEXT_ITEM_EJECT_PAGE:
1354       if (xr->y > 0)
1355         return xr_render_eject ();
1356       break;
1357
1358     default:
1359       return xr_create_text_renderer (xr, text);
1360     }
1361
1362   return NULL;
1363 }
1364
1365 static struct xr_render_fsm *
1366 xr_render_message (struct xr_driver *xr,
1367                    const struct message_item *message_item)
1368 {
1369   const struct msg *msg = message_item_get_msg (message_item);
1370   struct xr_render_fsm *fsm;
1371   char *s;
1372
1373   s = msg_to_string (msg, xr->command_name);
1374   fsm = xr_create_text_renderer (xr, s);
1375   free (s);
1376
1377   return fsm;
1378 }
1379
1380 static struct xr_render_fsm *
1381 xr_render_output_item (struct xr_driver *xr,
1382                        const struct output_item *output_item)
1383 {
1384   if (is_table_item (output_item))
1385     return xr_render_table (xr, to_table_item (output_item));
1386   else if (is_chart_item (output_item))
1387     return xr_render_chart (to_chart_item (output_item));
1388   else if (is_text_item (output_item))
1389     return xr_render_text (xr, to_text_item (output_item));
1390   else if (is_message_item (output_item))
1391     return xr_render_message (xr, to_message_item (output_item));
1392   else
1393     return NULL;
1394 }