Rewrite PSPP output engine.
[pspp-builds.git] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009 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/start-date.h>
24 #include <libpspp/str.h>
25 #include <libpspp/string-map.h>
26 #include <libpspp/version.h>
27 #include <output/cairo-chart.h>
28 #include <output/chart-item-provider.h>
29 #include <output/charts/boxplot.h>
30 #include <output/charts/np-plot.h>
31 #include <output/charts/piechart.h>
32 #include <output/charts/plot-hist.h>
33 #include <output/charts/roc-chart.h>
34 #include <output/charts/scree.h>
35 #include <output/driver-provider.h>
36 #include <output/options.h>
37 #include <output/render.h>
38 #include <output/tab.h>
39 #include <output/table-item.h>
40 #include <output/table.h>
41 #include <output/text-item.h>
42
43 #include <cairo/cairo-pdf.h>
44 #include <cairo/cairo-ps.h>
45 #include <cairo/cairo-svg.h>
46 #include <cairo/cairo.h>
47 #include <pango/pango-font.h>
48 #include <pango/pango-layout.h>
49 #include <pango/pango.h>
50 #include <pango/pangocairo.h>
51 #include <stdlib.h>
52
53 #include "error.h"
54 #include "intprops.h"
55 #include "minmax.h"
56 #include "xalloc.h"
57
58 #include "gettext.h"
59 #define _(msgid) gettext (msgid)
60
61 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
62 #define H TABLE_HORZ
63 #define V TABLE_VERT
64
65 /* Measurements as we present to the rest of PSPP. */
66 #define XR_POINT PANGO_SCALE
67 #define XR_INCH (XR_POINT * 72)
68
69 /* Conversions to and from points. */
70 static double
71 xr_to_pt (int x)
72 {
73   return x / (double) XR_POINT;
74 }
75
76 /* Output types. */
77 enum xr_output_type
78   {
79     XR_PDF,
80     XR_PS,
81     XR_SVG
82   };
83
84 /* Cairo fonts. */
85 enum xr_font_type
86   {
87     XR_FONT_PROPORTIONAL,
88     XR_FONT_EMPHASIS,
89     XR_FONT_FIXED,
90     XR_N_FONTS
91   };
92
93 /* A font for use with Cairo. */
94 struct xr_font
95   {
96     char *string;
97     PangoFontDescription *desc;
98     PangoLayout *layout;
99     PangoFontMetrics *metrics;
100   };
101
102 /* Cairo output driver. */
103 struct xr_driver
104   {
105     struct output_driver driver;
106
107     /* User parameters. */
108     bool headers;               /* Draw headers at top of page? */
109
110     struct xr_font fonts[XR_N_FONTS];
111     int font_height;            /* In XR units. */
112
113     int width;                  /* Page width minus margins. */
114     int length;                 /* Page length minus margins and header. */
115
116     int left_margin;            /* Left margin in XR units. */
117     int right_margin;           /* Right margin in XR units. */
118     int top_margin;             /* Top margin in XR units. */
119     int bottom_margin;          /* Bottom margin in XR units. */
120
121     int line_gutter;            /* Space around lines. */
122     int line_space;             /* Space between lines. */
123     int line_width;             /* Width of lines. */
124
125     enum xr_output_type file_type; /* Type of output file. */
126
127     /* Internal state. */
128     struct render_params *params;
129     char *title;
130     char *subtitle;
131     cairo_t *cairo;
132     int page_number;            /* Current page number. */
133     int y;
134   };
135
136 static void xr_show_page (struct xr_driver *);
137 static void draw_headers (struct xr_driver *);
138
139 static bool load_font (struct xr_driver *, struct xr_font *);
140 static void free_font (struct xr_font *);
141
142 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
143                           enum render_line_style styles[TABLE_N_AXES][2]);
144 static void xr_measure_cell_width (void *, const struct table_cell *,
145                                    int *min, int *max);
146 static int xr_measure_cell_height (void *, const struct table_cell *,
147                                    int width);
148 static void xr_draw_cell (void *, const struct table_cell *,
149                           int bb[TABLE_N_AXES][2],
150                           int clip[TABLE_N_AXES][2]);
151 \f
152 /* Driver initialization. */
153
154 static struct xr_driver *
155 xr_driver_cast (struct output_driver *driver)
156 {
157   assert (driver->class == &cairo_class);
158   return UP_CAST (driver, struct xr_driver, driver);
159 }
160
161 static struct driver_option *
162 opt (struct output_driver *d, struct string_map *options, const char *key,
163      const char *default_value)
164 {
165   return driver_option_get (d, options, key, default_value);
166 }
167
168 static struct xr_driver *
169 xr_allocate (const char *name, int device_type, struct string_map *o)
170 {
171   struct output_driver *d;
172   struct xr_driver *xr;
173
174   xr = xzalloc (sizeof *xr);
175   d = &xr->driver;
176   output_driver_init (d, &cairo_class, name, device_type);
177   xr->headers = true;
178   xr->font_height = XR_POINT * 10;
179   xr->fonts[XR_FONT_FIXED].string
180     = parse_string (opt (d, o, "fixed-font", "monospace"));
181   xr->fonts[XR_FONT_PROPORTIONAL].string
182     = parse_string (opt (d, o, "prop-font", "serif"));
183   xr->fonts[XR_FONT_EMPHASIS].string
184     = parse_string (opt (d, o, "emph-font", "serif italic"));
185   xr->line_gutter = XR_POINT;
186   xr->line_space = XR_POINT;
187   xr->line_width = XR_POINT / 2;
188   xr->page_number = 1;
189
190   return xr;
191 }
192
193 static bool
194 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
195 {
196   int i;
197
198   xr->cairo = cairo;
199
200   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
201
202   for (i = 0; i < XR_N_FONTS; i++)
203     if (!load_font (xr, &xr->fonts[i]))
204       return false;
205
206   if (xr->params == NULL)
207     {
208       int single_width, double_width;
209
210       xr->params = xmalloc (sizeof *xr->params);
211       xr->params->draw_line = xr_draw_line;
212       xr->params->measure_cell_width = xr_measure_cell_width;
213       xr->params->measure_cell_height = xr_measure_cell_height;
214       xr->params->draw_cell = xr_draw_cell;
215       xr->params->aux = xr;
216       xr->params->size[H] = xr->width;
217       xr->params->size[V] = xr->length;
218       xr->params->font_size[H] = xr->font_height / 2; /* XXX */
219       xr->params->font_size[V] = xr->font_height;
220
221       single_width = 2 * xr->line_gutter + xr->line_width;
222       double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
223       for (i = 0; i < TABLE_N_AXES; i++)
224         {
225           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
226           xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
227           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
228         }
229     }
230
231   return true;
232 }
233
234 static struct output_driver *
235 xr_create (const char *name, enum output_device_type device_type,
236            struct string_map *o)
237 {
238   enum { MIN_LENGTH = 3 };
239   struct output_driver *d;
240   struct xr_driver *xr;
241   cairo_surface_t *surface;
242   cairo_status_t status;
243   double width_pt, length_pt;
244   int paper_width, paper_length;
245   char *file_name;
246
247   xr = xr_allocate (name, device_type, o);
248   d = &xr->driver;
249
250   xr->headers = parse_boolean (opt (d, o, "headers", "true"));
251
252   xr->file_type = parse_enum (opt (d, o, "output-type", "pdf"),
253                               "pdf", XR_PDF,
254                               "ps", XR_PS,
255                               "svg", XR_SVG,
256                               (char *) NULL);
257   file_name = parse_string (opt (d, o, "output-file",
258                                  (xr->file_type == XR_PDF ? "pspp.pdf"
259                                   : xr->file_type == XR_PS ? "pspp.ps"
260                                   : "pspp.svg")));
261
262   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
263   xr->left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
264   xr->right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
265   xr->top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
266   xr->bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
267
268   if (xr->headers)
269     xr->top_margin += 3 * xr->font_height;
270   xr->width = paper_width - xr->left_margin - xr->right_margin;
271   xr->length = paper_length - xr->top_margin - xr->bottom_margin;
272
273   width_pt = paper_width / 1000.0;
274   length_pt = paper_length / 1000.0;
275   if (xr->file_type == XR_PDF)
276     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
277   else if (xr->file_type == XR_PS)
278     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
279   else if (xr->file_type == XR_SVG)
280     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
281   else
282     NOT_REACHED ();
283
284   status = cairo_surface_status (surface);
285   if (status != CAIRO_STATUS_SUCCESS)
286     {
287       error (0, 0, _("opening output file \"%s\": %s"),
288              file_name, cairo_status_to_string (status));
289       cairo_surface_destroy (surface);
290       goto error;
291     }
292
293   xr->cairo = cairo_create (surface);
294   cairo_surface_destroy (surface);
295
296   cairo_translate (xr->cairo,
297                    xr_to_pt (xr->left_margin),
298                    xr_to_pt (xr->top_margin));
299
300   if (!xr_set_cairo (xr, xr->cairo))
301     goto error;
302
303   if (xr->length / xr->font_height < MIN_LENGTH)
304     {
305       error (0, 0, _("The defined page is not long "
306                      "enough to hold margins and headers, plus least %d "
307                      "lines of the default fonts.  In fact, there's only "
308                      "room for %d lines."),
309              MIN_LENGTH,
310              xr->length / xr->font_height);
311       goto error;
312     }
313
314   free (file_name);
315   return &xr->driver;
316
317  error:
318   output_driver_destroy (&xr->driver);
319   return NULL;
320 }
321
322 static void
323 xr_destroy (struct output_driver *driver)
324 {
325   struct xr_driver *xr = xr_driver_cast (driver);
326   size_t i;
327
328   if (xr->cairo != NULL)
329     {
330       cairo_status_t status;
331
332       if (xr->y > 0)
333         xr_show_page (xr);
334
335       cairo_surface_finish (cairo_get_target (xr->cairo));
336       status = cairo_status (xr->cairo);
337       if (status != CAIRO_STATUS_SUCCESS)
338         error (0, 0, _("error drawing output for %s driver: %s"),
339                output_driver_get_name (driver),
340                cairo_status_to_string (status));
341       cairo_destroy (xr->cairo);
342     }
343
344   for (i = 0; i < XR_N_FONTS; i++)
345     free_font (&xr->fonts[i]);
346   free (xr->params);
347   free (xr);
348 }
349
350 static void
351 xr_flush (struct output_driver *driver)
352 {
353   struct xr_driver *xr = xr_driver_cast (driver);
354
355   cairo_surface_flush (cairo_get_target (xr->cairo));
356 }
357
358 static void
359 xr_init_caption_cell (const char *caption, struct table_cell *cell)
360 {
361   cell->contents = caption;
362   cell->options = TAB_LEFT;
363   cell->destructor = NULL;
364 }
365
366 static struct render_page *
367 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
368                       int *caption_heightp)
369 {
370   const char *caption = table_item_get_caption (item);
371
372   if (caption != NULL)
373     {
374       /* XXX doesn't do well with very large captions */
375       struct table_cell cell;
376       xr_init_caption_cell (caption, &cell);
377       *caption_heightp = xr_measure_cell_height (xr, &cell, xr->width);
378     }
379   else
380     *caption_heightp = 0;
381
382   return render_page_create (xr->params, table_item_get_table (item));
383 }
384
385 static void
386 xr_submit (struct output_driver *driver, const struct output_item *output_item)
387 {
388   struct xr_driver *xr = xr_driver_cast (driver);
389   if (is_table_item (output_item))
390     {
391       struct table_item *table_item = to_table_item (output_item);
392       struct render_break x_break;
393       struct render_page *page;
394       int caption_height;
395
396       if (xr->y > 0)
397         xr->y += xr->font_height;
398
399       page = xr_render_table_item (xr, table_item, &caption_height);
400       xr->params->size[V] = xr->length - caption_height;
401       for (render_break_init (&x_break, page, H);
402            render_break_has_next (&x_break); )
403         {
404           struct render_page *x_slice;
405           struct render_break y_break;
406
407           x_slice = render_break_next (&x_break, xr->width);
408           for (render_break_init (&y_break, x_slice, V);
409                render_break_has_next (&y_break); )
410             {
411               int space = xr->length - xr->y;
412               struct render_page *y_slice;
413
414               /* XXX doesn't allow for caption or space between segments */
415               if (render_break_next_size (&y_break) > space)
416                 {
417                   assert (xr->y > 0);
418                   xr_show_page (xr);
419                   continue;
420                 }
421
422               y_slice = render_break_next (&y_break, space);
423               if (caption_height)
424                 {
425                   struct table_cell cell;
426                   int bb[TABLE_N_AXES][2];
427
428                   xr_init_caption_cell (table_item_get_caption (table_item),
429                                         &cell);
430                   bb[H][0] = 0;
431                   bb[H][1] = xr->width;
432                   bb[V][0] = 0;
433                   bb[V][1] = caption_height;
434                   xr_draw_cell (xr, &cell, bb, bb);
435                   xr->y += caption_height;
436                   caption_height = 0;
437                 }
438
439               render_page_draw (y_slice);
440               xr->y += render_page_get_size (y_slice, V);
441               render_page_unref (y_slice);
442             }
443           render_break_destroy (&y_break);
444         }
445       render_break_destroy (&x_break);
446     }
447   else if (is_chart_item (output_item))
448     {
449       if (xr->y > 0)
450         xr_show_page (xr);
451       xr_draw_chart (to_chart_item (output_item), xr->cairo, 0.0, 0.0,
452                      xr_to_pt (xr->width), xr_to_pt (xr->length));
453       xr_show_page (xr);
454     }
455   else if (is_text_item (output_item))
456     {
457       const struct text_item *text_item = to_text_item (output_item);
458       enum text_item_type type = text_item_get_type (text_item);
459       const char *text = text_item_get_text (text_item);
460
461       switch (type)
462         {
463         case TEXT_ITEM_TITLE:
464           free (xr->title);
465           xr->title = xstrdup (text);
466           break;
467
468         case TEXT_ITEM_SUBTITLE:
469           free (xr->subtitle);
470           xr->subtitle = xstrdup (text);
471           break;
472
473         case TEXT_ITEM_COMMAND_CLOSE:
474           break;
475
476         case TEXT_ITEM_BLANK_LINE:
477           if (xr->y > 0)
478             xr->y += xr->font_height;
479           break;
480
481         case TEXT_ITEM_EJECT_PAGE:
482           if (xr->y > 0)
483             xr_show_page (xr);
484           break;
485
486         default:
487           {
488             struct table_item *item;
489
490             item = table_item_create (table_from_string (0, text), NULL);
491             xr_submit (&xr->driver, &item->output_item);
492             table_item_unref (item);
493           }
494           break;
495         }
496
497     }
498 }
499
500 static void
501 xr_show_page (struct xr_driver *xr)
502 {
503   if (xr->headers)
504     {
505       xr->y = 0;
506       draw_headers (xr);
507     }
508   cairo_show_page (xr->cairo);
509
510   xr->page_number++;
511   xr->y = 0;
512 }
513 \f
514 static void
515 xr_layout_cell (struct xr_driver *, const struct table_cell *,
516                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
517                 PangoWrapMode, int *width, int *height);
518
519 static void
520 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
521 {
522   cairo_new_path (xr->cairo);
523   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
524   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
525   cairo_stroke (xr->cairo);
526 }
527
528 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
529    shortening it to X0...X1 if SHORTEN is true.
530    Draws a horizontal line X1...X3 at Y if RIGHT says so,
531    shortening it to X2...X3 if SHORTEN is true. */
532 static void
533 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
534            enum render_line_style left, enum render_line_style right,
535            bool shorten)
536 {
537   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
538     dump_line (xr, x0, y, x3, y);
539   else
540     {
541       if (left != RENDER_LINE_NONE)
542         dump_line (xr, x0, y, shorten ? x1 : x2, y);
543       if (right != RENDER_LINE_NONE)
544         dump_line (xr, shorten ? x2 : x1, y, x3, y);
545     }
546 }
547
548 /* Draws a vertical line Y0...Y2 at X if TOP says so,
549    shortening it to Y0...Y1 if SHORTEN is true.
550    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
551    shortening it to Y2...Y3 if SHORTEN is true. */
552 static void
553 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
554            enum render_line_style top, enum render_line_style bottom,
555            bool shorten)
556 {
557   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
558     dump_line (xr, x, y0, x, y3);
559   else
560     {
561       if (top != RENDER_LINE_NONE)
562         dump_line (xr, x, y0, x, shorten ? y1 : y2);
563       if (bottom != RENDER_LINE_NONE)
564         dump_line (xr, x, shorten ? y2 : y1, x, y3);
565     }
566 }
567
568 static void
569 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
570               enum render_line_style styles[TABLE_N_AXES][2])
571 {
572   const int x0 = bb[H][0];
573   const int y0 = bb[V][0];
574   const int x3 = bb[H][1];
575   const int y3 = bb[V][1];
576   const int top = styles[H][0];
577   const int left = styles[V][0];
578   const int bottom = styles[H][1];
579   const int right = styles[V][1];
580
581   /* The algorithm here is somewhat subtle, to allow it to handle
582      all the kinds of intersections that we need.
583
584      Three additional ordinates are assigned along the x axis.  The
585      first is xc, midway between x0 and x3.  The others are x1 and
586      x2; for a single vertical line these are equal to xc, and for
587      a double vertical line they are the ordinates of the left and
588      right half of the double line.
589
590      yc, y1, and y2 are assigned similarly along the y axis.
591
592      The following diagram shows the coordinate system and output
593      for double top and bottom lines, single left line, and no
594      right line:
595
596                  x0       x1 xc  x2      x3
597                y0 ________________________
598                   |        #     #       |
599                   |        #     #       |
600                   |        #     #       |
601                   |        #     #       |
602                   |        #     #       |
603      y1 = y2 = yc |#########     #       |
604                   |        #     #       |
605                   |        #     #       |
606                   |        #     #       |
607                   |        #     #       |
608                y3 |________#_____#_______|
609   */
610   struct xr_driver *xr = xr_;
611
612   /* Offset from center of each line in a pair of double lines. */
613   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
614
615   /* Are the lines along each axis single or double?
616      (It doesn't make sense to have different kinds of line on the
617      same axis, so we don't try to gracefully handle that case.) */
618   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
619   bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
620
621   /* When horizontal lines are doubled,
622      the left-side line along y1 normally runs from x0 to x2,
623      and the right-side line along y1 from x3 to x1.
624      If the top-side line is also doubled, we shorten the y1 lines,
625      so that the left-side line runs only to x1,
626      and the right-side line only to x2.
627      Otherwise, the horizontal line at y = y1 below would cut off
628      the intersection, which looks ugly:
629                x0       x1     x2      x3
630              y0 ________________________
631                 |        #     #       |
632                 |        #     #       |
633                 |        #     #       |
634                 |        #     #       |
635              y1 |#########     ########|
636                 |                      |
637                 |                      |
638              y2 |######################|
639                 |                      |
640                 |                      |
641              y3 |______________________|
642      It is more of a judgment call when the horizontal line is
643      single.  We actually choose to cut off the line anyhow, as
644      shown in the first diagram above.
645   */
646   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
647   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
648   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
649   int horz_line_ofs = double_vert ? double_line_ofs : 0;
650   int xc = (x0 + x3) / 2;
651   int x1 = xc - horz_line_ofs;
652   int x2 = xc + horz_line_ofs;
653
654   bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
655   bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
656   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
657   int vert_line_ofs = double_horz ? double_line_ofs : 0;
658   int yc = (y0 + y3) / 2;
659   int y1 = yc - vert_line_ofs;
660   int y2 = yc + vert_line_ofs;
661
662   if (!double_horz)
663     horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
664   else
665     {
666       horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
667       horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
668     }
669
670   if (!double_vert)
671     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
672   else
673     {
674       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
675       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
676     }
677 }
678
679 static void
680 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
681                        int *min_width, int *max_width)
682 {
683   struct xr_driver *xr = xr_;
684   int bb[TABLE_N_AXES][2];
685   int clip[TABLE_N_AXES][2];
686   int h;
687
688   bb[H][0] = 0;
689   bb[H][1] = INT_MAX;
690   bb[V][0] = 0;
691   bb[V][1] = INT_MAX;
692   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
693   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, max_width, &h);
694
695   bb[H][1] = 1;
696   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, min_width, &h);
697 }
698
699 static int
700 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
701 {
702   struct xr_driver *xr = xr_;
703   int bb[TABLE_N_AXES][2];
704   int clip[TABLE_N_AXES][2];
705   int w, h;
706
707   bb[H][0] = 0;
708   bb[H][1] = width;
709   bb[V][0] = 0;
710   bb[V][1] = INT_MAX;
711   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
712   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
713   return h;
714 }
715
716 static void
717 xr_draw_cell (void *xr_, const struct table_cell *cell,
718               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
719 {
720   struct xr_driver *xr = xr_;
721   int w, h;
722
723   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
724 }
725 \f
726 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
727    and with the given cell OPTIONS for XR. */
728 static int
729 draw_text (struct xr_driver *xr, const char *string, int x, int y,
730            int max_width, unsigned int options)
731 {
732   struct table_cell cell;
733   int bb[TABLE_N_AXES][2];
734   int w, h;
735
736   cell.contents = string;
737   cell.options = options;
738   bb[H][0] = x;
739   bb[V][0] = y;
740   bb[H][1] = x + max_width;
741   bb[V][1] = xr->font_height;
742   xr_layout_cell (xr, &cell, bb, bb, PANGO_WRAP_WORD_CHAR, &w, &h);
743   return w;
744 }
745
746 /* Writes LEFT left-justified and RIGHT right-justified within
747    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
748 static void
749 draw_header_line (struct xr_driver *xr, const char *left, const char *right,
750                   int x0, int x1, int y)
751 {
752   int right_width = 0;
753   if (right != NULL)
754     right_width = (draw_text (xr, right, x0, y, x1 - x0, TAB_RIGHT)
755                    + xr->font_height / 2);
756   if (left != NULL)
757     draw_text (xr, left, x0, y, x1 - x0 - right_width, TAB_LEFT);
758 }
759
760 /* Draw top of page headers for XR. */
761 static void
762 draw_headers (struct xr_driver *xr)
763 {
764   char *r1, *r2;
765   int x0, x1;
766   int y;
767
768   y = -3 * xr->font_height;
769   x0 = xr->font_height / 2;
770   x1 = xr->width - xr->font_height / 2;
771
772   /* Draw box. */
773   cairo_rectangle (xr->cairo, 0, xr_to_pt (y), xr_to_pt (xr->width),
774                    xr_to_pt (2 * (xr->font_height
775                                   + xr->line_width + xr->line_gutter)));
776   cairo_save (xr->cairo);
777   cairo_set_source_rgb (xr->cairo, 0.9, 0.9, 0.9);
778   cairo_fill_preserve (xr->cairo);
779   cairo_restore (xr->cairo);
780   cairo_stroke (xr->cairo);
781
782   y += xr->line_width + xr->line_gutter;
783
784   r1 = xasprintf (_("%s - Page %d"), get_start_date (), xr->page_number);
785   r2 = xasprintf ("%s - %s", version, host_system);
786
787   draw_header_line (xr, xr->title, r1, x0, x1, y);
788   y += xr->font_height;
789
790   draw_header_line (xr, xr->subtitle, r2, x0, x1, y);
791
792   free (r1);
793   free (r2);
794 }
795 \f
796 static void
797 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
798                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
799                 PangoWrapMode wrap, int *width, int *height)
800 {
801   struct xr_font *font;
802
803   font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
804           : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
805           : &xr->fonts[XR_FONT_PROPORTIONAL]);
806
807   pango_layout_set_text (font->layout, cell->contents, -1);
808
809   pango_layout_set_alignment (
810     font->layout,
811     ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
812      : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
813      : PANGO_ALIGN_CENTER));
814   pango_layout_set_width (font->layout,
815                           bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]);
816   pango_layout_set_wrap (font->layout, wrap);
817
818   if (clip[H][0] != clip[H][1])
819     {
820       cairo_save (xr->cairo);
821
822       if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
823         {
824           double x0 = xr_to_pt (clip[H][0]);
825           double y0 = xr_to_pt (clip[V][0] + xr->y);
826           double x1 = xr_to_pt (clip[H][1]);
827           double y1 = xr_to_pt (clip[V][1] + xr->y);
828
829           cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
830           cairo_clip (xr->cairo);
831         }
832
833       cairo_translate (xr->cairo,
834                        xr_to_pt (bb[H][0]),
835                        xr_to_pt (bb[V][0] + xr->y));
836       pango_cairo_show_layout (xr->cairo, font->layout);
837       cairo_restore (xr->cairo);
838     }
839
840   if (width != NULL || height != NULL)
841     {
842       int w, h;
843
844       pango_layout_get_size (font->layout, &w, &h);
845       if (width != NULL)
846         *width = w;
847       if (height != NULL)
848         *height = h;
849     }
850 }
851 \f
852 /* Attempts to load FONT, initializing its other members based on
853    its 'string' member and the information in DRIVER.  Returns true
854    if successful, otherwise false. */
855 static bool
856 load_font (struct xr_driver *xr, struct xr_font *font)
857 {
858   PangoContext *context;
859   PangoLanguage *language;
860
861   font->desc = pango_font_description_from_string (font->string);
862   if (font->desc == NULL)
863     {
864       error (0, 0, _("\"%s\": bad font specification"), font->string);
865       return false;
866     }
867   pango_font_description_set_absolute_size (font->desc, xr->font_height);
868
869   font->layout = pango_cairo_create_layout (xr->cairo);
870   pango_layout_set_font_description (font->layout, font->desc);
871
872   language = pango_language_get_default ();
873   context = pango_layout_get_context (font->layout);
874   font->metrics = pango_context_get_metrics (context, font->desc, language);
875
876   return true;
877 }
878
879 /* Frees FONT. */
880 static void
881 free_font (struct xr_font *font)
882 {
883   free (font->string);
884   if (font->desc != NULL)
885     pango_font_description_free (font->desc);
886   pango_font_metrics_unref (font->metrics);
887   g_object_unref (font->layout);
888 }
889
890 /* Cairo driver class. */
891 const struct output_driver_class cairo_class =
892 {
893   "cairo",
894   xr_create,
895   xr_destroy,
896   xr_submit,
897   xr_flush,
898 };
899 \f
900 /* GUI rendering helpers. */
901
902 struct xr_rendering
903   {
904     /* Table items. */
905     struct render_page *page;
906     struct xr_driver *xr;
907     int title_height;
908
909     /* Chart items. */
910     struct chart_item *chart;
911   };
912
913 #define CHART_WIDTH 500
914 #define CHART_HEIGHT 375
915
916 struct xr_driver *
917 xr_create_driver (cairo_t *cairo)
918 {
919   struct xr_driver *xr;
920   struct string_map map;
921
922   string_map_init (&map);
923   xr = xr_allocate ("cairo", 0, &map);
924   string_map_destroy (&map);
925
926   xr->width = INT_MAX / 8;
927   xr->length = INT_MAX / 8;
928   if (!xr_set_cairo (xr, cairo))
929     {
930       output_driver_destroy (&xr->driver);
931       return NULL;
932     }
933   return xr;
934 }
935
936 struct xr_rendering *
937 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
938                      cairo_t *cr)
939 {
940   struct xr_rendering *r = NULL;
941
942   if (is_text_item (item))
943     {
944       const struct text_item *text_item = to_text_item (item);
945       const char *text = text_item_get_text (text_item);
946       struct table_item *table_item;
947
948       table_item = table_item_create (table_from_string (0, text), NULL);
949       r = xr_rendering_create (xr, &table_item->output_item, cr);
950       table_item_unref (table_item);
951     }
952   else if (is_table_item (item))
953     {
954       r = xzalloc (sizeof *r);
955       r->xr = xr;
956       xr_set_cairo (xr, cr);
957       r->page = xr_render_table_item (xr, to_table_item (item),
958                                       &r->title_height);
959     }
960   else if (is_chart_item (item))
961     {
962       r = xzalloc (sizeof *r);
963       r->chart = to_chart_item (output_item_ref (item));
964     }
965
966   return r;
967 }
968
969 void
970 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
971 {
972   if (r->chart == NULL)
973     {
974       *w = render_page_get_size (r->page, H) / 1024;
975       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
976     }
977   else
978     {
979       *w = CHART_WIDTH;
980       *h = CHART_HEIGHT;
981     }
982 }
983
984 void
985 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr)
986 {
987   if (r->chart == NULL)
988     {
989       struct xr_driver *xr = r->xr;
990
991       xr_set_cairo (xr, cr);
992       xr->y = 0;
993       render_page_draw (r->page);
994     }
995   else
996     xr_draw_chart (r->chart, cr, 0, 0, CHART_WIDTH, CHART_HEIGHT);
997 }
998
999 void
1000 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1001                double x, double y, double width, double height)
1002 {
1003   struct xrchart_geometry geom;
1004
1005   cairo_save (cr);
1006   cairo_translate (cr, x, y + height);
1007   cairo_scale (cr, 1.0, -1.0);
1008   xrchart_geometry_init (cr, &geom, width, height);
1009   if (is_boxplot (chart_item))
1010     xrchart_draw_boxplot (chart_item, cr, &geom);
1011   else if (is_histogram_chart (chart_item))
1012     xrchart_draw_histogram (chart_item, cr, &geom);
1013   else if (is_np_plot_chart (chart_item))
1014     xrchart_draw_np_plot (chart_item, cr, &geom);
1015   else if (is_piechart (chart_item))
1016     xrchart_draw_piechart (chart_item, cr, &geom);
1017   else if (is_roc_chart (chart_item))
1018     xrchart_draw_roc (chart_item, cr, &geom);
1019   else if (is_scree (chart_item))
1020     xrchart_draw_scree (chart_item, cr, &geom);
1021   else
1022     NOT_REACHED ();
1023   xrchart_geometry_free (cr, &geom);
1024
1025   cairo_restore (cr);
1026 }
1027
1028 char *
1029 xr_draw_png_chart (const struct chart_item *item,
1030                    const char *file_name_template, int number)
1031 {
1032   const int width = 640;
1033   const int length = 480;
1034
1035   cairo_surface_t *surface;
1036   cairo_status_t status;
1037   const char *number_pos;
1038   char *file_name;
1039   cairo_t *cr;
1040
1041   number_pos = strchr (file_name_template, '#');
1042   if (number_pos != NULL)
1043     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1044                            file_name_template, number, number_pos + 1);
1045   else
1046     file_name = xstrdup (file_name_template);
1047
1048   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1049   cr = cairo_create (surface);
1050
1051   cairo_save (cr);
1052   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1053   cairo_rectangle (cr, 0, 0, width, length);
1054   cairo_fill (cr);
1055   cairo_restore (cr);
1056
1057   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1058
1059   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1060
1061   status = cairo_surface_write_to_png (surface, file_name);
1062   if (status != CAIRO_STATUS_SUCCESS)
1063     error (0, 0, _("writing output file \"%s\": %s"),
1064            file_name, cairo_status_to_string (status));
1065
1066   cairo_destroy (cr);
1067   cairo_surface_destroy (surface);
1068
1069   return file_name;
1070 }