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