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