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