cairo: Correctly render table during scrolling.
[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 (0, 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 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
982    and possibly some additional parts. */
983 void
984 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
985                    int x, int y, int w, int h)
986 {
987   if (is_table_item (r->item))
988     {
989       struct xr_driver *xr = r->xr;
990
991       xr_set_cairo (xr, cr);
992
993       if (r->title_height > 0)
994         {
995           xr->y = 0;
996           xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)),
997                          r->title_width, r->title_height);
998         }
999
1000       xr->y = r->title_height;
1001       render_page_draw_region (r->page, x * 1024, (y * 1024) - r->title_height,
1002                                w * 1024, h * 1024);
1003     }
1004   else
1005     xr_draw_chart (to_chart_item (r->item), cr,
1006                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1007 }
1008
1009 void
1010 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1011                double x, double y, double width, double height)
1012 {
1013   struct xrchart_geometry geom;
1014
1015   cairo_save (cr);
1016   cairo_translate (cr, x, y + height);
1017   cairo_scale (cr, 1.0, -1.0);
1018   xrchart_geometry_init (cr, &geom, width, height);
1019   if (is_boxplot (chart_item))
1020     xrchart_draw_boxplot (chart_item, cr, &geom);
1021   else if (is_histogram_chart (chart_item))
1022     xrchart_draw_histogram (chart_item, cr, &geom);
1023   else if (is_np_plot_chart (chart_item))
1024     xrchart_draw_np_plot (chart_item, cr, &geom);
1025   else if (is_piechart (chart_item))
1026     xrchart_draw_piechart (chart_item, cr, &geom);
1027   else if (is_roc_chart (chart_item))
1028     xrchart_draw_roc (chart_item, cr, &geom);
1029   else if (is_scree (chart_item))
1030     xrchart_draw_scree (chart_item, cr, &geom);
1031   else
1032     NOT_REACHED ();
1033   xrchart_geometry_free (cr, &geom);
1034
1035   cairo_restore (cr);
1036 }
1037
1038 char *
1039 xr_draw_png_chart (const struct chart_item *item,
1040                    const char *file_name_template, int number)
1041 {
1042   const int width = 640;
1043   const int length = 480;
1044
1045   cairo_surface_t *surface;
1046   cairo_status_t status;
1047   const char *number_pos;
1048   char *file_name;
1049   cairo_t *cr;
1050
1051   number_pos = strchr (file_name_template, '#');
1052   if (number_pos != NULL)
1053     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1054                            file_name_template, number, number_pos + 1);
1055   else
1056     file_name = xstrdup (file_name_template);
1057
1058   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1059   cr = cairo_create (surface);
1060
1061   cairo_save (cr);
1062   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1063   cairo_rectangle (cr, 0, 0, width, length);
1064   cairo_fill (cr);
1065   cairo_restore (cr);
1066
1067   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1068
1069   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1070
1071   status = cairo_surface_write_to_png (surface, file_name);
1072   if (status != CAIRO_STATUS_SUCCESS)
1073     error (0, 0, _("error writing output file `%s': %s"),
1074            file_name, cairo_status_to_string (status));
1075
1076   cairo_destroy (cr);
1077   cairo_surface_destroy (surface);
1078
1079   return file_name;
1080 }
1081 \f
1082 struct xr_table_state
1083   {
1084     struct xr_render_fsm fsm;
1085     struct table_item *table_item;
1086     struct render_break x_break;
1087     struct render_break y_break;
1088     int caption_height;
1089   };
1090
1091 static bool
1092 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1093 {
1094   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1095
1096   for (;;)
1097     {
1098       struct render_page *y_slice;
1099       int space;
1100
1101       while (!render_break_has_next (&ts->y_break))
1102         {
1103           struct render_page *x_slice;
1104
1105           render_break_destroy (&ts->y_break);
1106           if (!render_break_has_next (&ts->x_break))
1107             return false;
1108
1109           x_slice = render_break_next (&ts->x_break, xr->width);
1110           render_break_init (&ts->y_break, x_slice, V);
1111         }
1112
1113       space = xr->length - xr->y;
1114       if (render_break_next_size (&ts->y_break) > space)
1115         {
1116           assert (xr->y > 0);
1117           return true;
1118         }
1119
1120       y_slice = render_break_next (&ts->y_break, space);
1121       if (ts->caption_height)
1122         {
1123           if (xr->cairo)
1124             xr_draw_title (xr, table_item_get_caption (ts->table_item),
1125                            xr->width, ts->caption_height);
1126
1127           xr->y += ts->caption_height;
1128           ts->caption_height = 0;
1129         }
1130
1131       if (xr->cairo)
1132         render_page_draw (y_slice);
1133       xr->y += render_page_get_size (y_slice, V);
1134       render_page_unref (y_slice);
1135     }
1136 }
1137
1138 static void
1139 xr_table_destroy (struct xr_render_fsm *fsm)
1140 {
1141   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1142
1143   table_item_unref (ts->table_item);
1144   render_break_destroy (&ts->x_break);
1145   render_break_destroy (&ts->y_break);
1146   free (ts);
1147 }
1148
1149 static struct xr_render_fsm *
1150 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1151 {
1152   struct xr_table_state *ts;
1153   struct render_page *page;
1154   int caption_width;
1155
1156   ts = xmalloc (sizeof *ts);
1157   ts->fsm.render = xr_table_render;
1158   ts->fsm.destroy = xr_table_destroy;
1159   ts->table_item = table_item_ref (table_item);
1160
1161   if (xr->y > 0)
1162     xr->y += xr->char_height;
1163
1164   page = xr_render_table_item (xr, table_item,
1165                                &caption_width, &ts->caption_height);
1166   xr->params->size[V] = xr->length - ts->caption_height;
1167
1168   render_break_init (&ts->x_break, page, H);
1169   render_break_init_empty (&ts->y_break);
1170
1171   return &ts->fsm;
1172 }
1173 \f
1174 struct xr_chart_state
1175   {
1176     struct xr_render_fsm fsm;
1177     struct chart_item *chart_item;
1178   };
1179
1180 static bool
1181 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1182 {
1183   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1184
1185   if (xr->y > 0)
1186     return true;
1187
1188   if (xr->cairo != NULL)
1189     xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1190                    xr_to_pt (xr->width), xr_to_pt (xr->length));
1191   xr->y = xr->length;
1192
1193   return false;
1194 }
1195
1196 static void
1197 xr_chart_destroy (struct xr_render_fsm *fsm)
1198 {
1199   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1200
1201   chart_item_unref (cs->chart_item);
1202   free (cs);
1203 }
1204
1205 static struct xr_render_fsm *
1206 xr_render_chart (const struct chart_item *chart_item)
1207 {
1208   struct xr_chart_state *cs;
1209
1210   cs = xmalloc (sizeof *cs);
1211   cs->fsm.render = xr_chart_render;
1212   cs->fsm.destroy = xr_chart_destroy;
1213   cs->chart_item = chart_item_ref (chart_item);
1214
1215   return &cs->fsm;
1216 }
1217 \f
1218 static bool
1219 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1220 {
1221   return xr->y > 0;
1222 }
1223
1224 static void
1225 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1226 {
1227   /* Nothing to do. */
1228 }
1229
1230 static struct xr_render_fsm *
1231 xr_render_eject (void)
1232 {
1233   static struct xr_render_fsm eject_renderer =
1234     {
1235       xr_eject_render,
1236       xr_eject_destroy
1237     };
1238
1239   return &eject_renderer;
1240 }
1241 \f
1242 static struct xr_render_fsm *
1243 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1244 {
1245   struct table_item *table_item;
1246   struct xr_render_fsm *fsm;
1247
1248   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
1249   fsm = xr_render_table (xr, table_item);
1250   table_item_unref (table_item);
1251
1252   return fsm;
1253 }
1254
1255 static struct xr_render_fsm *
1256 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1257 {
1258   enum text_item_type type = text_item_get_type (text_item);
1259   const char *text = text_item_get_text (text_item);
1260
1261   switch (type)
1262     {
1263     case TEXT_ITEM_TITLE:
1264       free (xr->title);
1265       xr->title = xstrdup (text);
1266       break;
1267
1268     case TEXT_ITEM_SUBTITLE:
1269       free (xr->subtitle);
1270       xr->subtitle = xstrdup (text);
1271       break;
1272
1273     case TEXT_ITEM_COMMAND_CLOSE:
1274       break;
1275
1276     case TEXT_ITEM_BLANK_LINE:
1277       if (xr->y > 0)
1278         xr->y += xr->char_height;
1279       break;
1280
1281     case TEXT_ITEM_EJECT_PAGE:
1282       if (xr->y > 0)
1283         return xr_render_eject ();
1284       break;
1285
1286     default:
1287       return xr_create_text_renderer (xr, text);
1288     }
1289
1290   return NULL;
1291 }
1292
1293 static struct xr_render_fsm *
1294 xr_render_message (struct xr_driver *xr,
1295                    const struct message_item *message_item)
1296 {
1297   const struct msg *msg = message_item_get_msg (message_item);
1298   struct xr_render_fsm *fsm;
1299   char *s;
1300
1301   s = msg_to_string (msg, xr->command_name);
1302   fsm = xr_create_text_renderer (xr, s);
1303   free (s);
1304
1305   return fsm;
1306 }
1307
1308 static struct xr_render_fsm *
1309 xr_render_output_item (struct xr_driver *xr,
1310                        const struct output_item *output_item)
1311 {
1312   if (is_table_item (output_item))
1313     return xr_render_table (xr, to_table_item (output_item));
1314   else if (is_chart_item (output_item))
1315     return xr_render_chart (to_chart_item (output_item));
1316   else if (is_text_item (output_item))
1317     return xr_render_text (xr, to_text_item (output_item));
1318   else if (is_message_item (output_item))
1319     return xr_render_message (xr, to_message_item (output_item));
1320   else
1321     return NULL;
1322 }