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