94dafd886b7914356b856701c09e6564269426b1
[pspp-builds.git] / 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/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_WIDTH = 3, 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->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 "
316                      "enough to hold margins and headers, plus least %d "
317                      "lines of the default fonts.  In fact, there's only "
318                      "room for %d lines."),
319              MIN_LENGTH,
320              xr->length / xr->font_height);
321       goto error;
322     }
323
324   free (file_name);
325   return &xr->driver;
326
327  error:
328   output_driver_destroy (&xr->driver);
329   return NULL;
330 }
331
332 static void
333 xr_destroy (struct output_driver *driver)
334 {
335   struct xr_driver *xr = xr_driver_cast (driver);
336   size_t i;
337
338   if (xr->cairo != NULL)
339     {
340       cairo_status_t status;
341
342       if (xr->y > 0)
343         xr_show_page (xr);
344
345       cairo_surface_finish (cairo_get_target (xr->cairo));
346       status = cairo_status (xr->cairo);
347       if (status != CAIRO_STATUS_SUCCESS)
348         error (0, 0, _("error drawing output for %s driver: %s"),
349                output_driver_get_name (driver),
350                cairo_status_to_string (status));
351       cairo_destroy (xr->cairo);
352     }
353
354   for (i = 0; i < XR_N_FONTS; i++)
355     free_font (&xr->fonts[i]);
356   free (xr->params);
357   free (xr);
358 }
359
360 static void
361 xr_flush (struct output_driver *driver)
362 {
363   struct xr_driver *xr = xr_driver_cast (driver);
364
365   cairo_surface_flush (cairo_get_target (xr->cairo));
366 }
367
368 static void
369 xr_init_caption_cell (const char *caption, struct table_cell *cell)
370 {
371   cell->contents = caption;
372   cell->options = TAB_LEFT;
373   cell->destructor = NULL;
374 }
375
376 static struct render_page *
377 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
378                       int *caption_heightp)
379 {
380   const char *caption = table_item_get_caption (item);
381
382   if (caption != NULL)
383     {
384       /* XXX doesn't do well with very large captions */
385       struct table_cell cell;
386       xr_init_caption_cell (caption, &cell);
387       *caption_heightp = xr_measure_cell_height (xr, &cell, xr->width);
388     }
389   else
390     *caption_heightp = 0;
391
392   return render_page_create (xr->params, table_item_get_table (item));
393 }
394
395 static void
396 xr_submit (struct output_driver *driver, const struct output_item *output_item)
397 {
398   struct xr_driver *xr = xr_driver_cast (driver);
399   if (is_table_item (output_item))
400     {
401       struct table_item *table_item = to_table_item (output_item);
402       struct render_break x_break;
403       struct render_page *page;
404       int caption_height;
405
406       if (xr->y > 0)
407         xr->y += xr->font_height;
408
409       page = xr_render_table_item (xr, table_item, &caption_height);
410       xr->params->size[V] = xr->length - caption_height;
411       for (render_break_init (&x_break, page, H);
412            render_break_has_next (&x_break); )
413         {
414           struct render_page *x_slice;
415           struct render_break y_break;
416
417           x_slice = render_break_next (&x_break, xr->width);
418           for (render_break_init (&y_break, x_slice, V);
419                render_break_has_next (&y_break); )
420             {
421               int space = xr->length - xr->y;
422               struct render_page *y_slice;
423
424               /* XXX doesn't allow for caption or space between segments */
425               if (render_break_next_size (&y_break) > space)
426                 {
427                   assert (xr->y > 0);
428                   xr_show_page (xr);
429                   continue;
430                 }
431
432               y_slice = render_break_next (&y_break, space);
433               if (caption_height)
434                 {
435                   struct table_cell cell;
436                   int bb[TABLE_N_AXES][2];
437
438                   xr_init_caption_cell (table_item_get_caption (table_item),
439                                         &cell);
440                   bb[H][0] = 0;
441                   bb[H][1] = xr->width;
442                   bb[V][0] = 0;
443                   bb[V][1] = caption_height;
444                   xr_draw_cell (xr, &cell, bb, bb);
445                   xr->y += caption_height;
446                   caption_height = 0;
447                 }
448
449               render_page_draw (y_slice);
450               xr->y += render_page_get_size (y_slice, V);
451               render_page_unref (y_slice);
452             }
453           render_break_destroy (&y_break);
454         }
455       render_break_destroy (&x_break);
456     }
457   else if (is_chart_item (output_item))
458     {
459       if (xr->y > 0)
460         xr_show_page (xr);
461       xr_draw_chart (to_chart_item (output_item), xr->cairo, 0.0, 0.0,
462                      xr_to_pt (xr->width), xr_to_pt (xr->length));
463       xr_show_page (xr);
464     }
465   else if (is_text_item (output_item))
466     {
467       const struct text_item *text_item = to_text_item (output_item);
468       enum text_item_type type = text_item_get_type (text_item);
469       const char *text = text_item_get_text (text_item);
470
471       switch (type)
472         {
473         case TEXT_ITEM_TITLE:
474           free (xr->title);
475           xr->title = xstrdup (text);
476           break;
477
478         case TEXT_ITEM_SUBTITLE:
479           free (xr->subtitle);
480           xr->subtitle = xstrdup (text);
481           break;
482
483         case TEXT_ITEM_COMMAND_CLOSE:
484           break;
485
486         case TEXT_ITEM_BLANK_LINE:
487           if (xr->y > 0)
488             xr->y += xr->font_height;
489           break;
490
491         case TEXT_ITEM_EJECT_PAGE:
492           if (xr->y > 0)
493             xr_show_page (xr);
494           break;
495
496         default:
497           {
498             struct table_item *item;
499
500             item = table_item_create (table_from_string (TAB_LEFT, text),
501                                       NULL);
502             xr_submit (&xr->driver, &item->output_item);
503             table_item_unref (item);
504           }
505           break;
506         }
507
508     }
509 }
510
511 static void
512 xr_show_page (struct xr_driver *xr)
513 {
514   if (xr->headers)
515     {
516       xr->y = 0;
517       draw_headers (xr);
518     }
519   cairo_show_page (xr->cairo);
520
521   xr->page_number++;
522   xr->y = 0;
523 }
524 \f
525 static void
526 xr_layout_cell (struct xr_driver *, const struct table_cell *,
527                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
528                 PangoWrapMode, int *width, int *height);
529
530 static void
531 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
532 {
533   cairo_new_path (xr->cairo);
534   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
535   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
536   cairo_stroke (xr->cairo);
537 }
538
539 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
540    shortening it to X0...X1 if SHORTEN is true.
541    Draws a horizontal line X1...X3 at Y if RIGHT says so,
542    shortening it to X2...X3 if SHORTEN is true. */
543 static void
544 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
545            enum render_line_style left, enum render_line_style right,
546            bool shorten)
547 {
548   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
549     dump_line (xr, x0, y, x3, y);
550   else
551     {
552       if (left != RENDER_LINE_NONE)
553         dump_line (xr, x0, y, shorten ? x1 : x2, y);
554       if (right != RENDER_LINE_NONE)
555         dump_line (xr, shorten ? x2 : x1, y, x3, y);
556     }
557 }
558
559 /* Draws a vertical line Y0...Y2 at X if TOP says so,
560    shortening it to Y0...Y1 if SHORTEN is true.
561    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
562    shortening it to Y2...Y3 if SHORTEN is true. */
563 static void
564 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
565            enum render_line_style top, enum render_line_style bottom,
566            bool shorten)
567 {
568   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
569     dump_line (xr, x, y0, x, y3);
570   else
571     {
572       if (top != RENDER_LINE_NONE)
573         dump_line (xr, x, y0, x, shorten ? y1 : y2);
574       if (bottom != RENDER_LINE_NONE)
575         dump_line (xr, x, shorten ? y2 : y1, x, y3);
576     }
577 }
578
579 static void
580 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
581               enum render_line_style styles[TABLE_N_AXES][2])
582 {
583   const int x0 = bb[H][0];
584   const int y0 = bb[V][0];
585   const int x3 = bb[H][1];
586   const int y3 = bb[V][1];
587   const int top = styles[H][0];
588   const int left = styles[V][0];
589   const int bottom = styles[H][1];
590   const int right = styles[V][1];
591
592   /* The algorithm here is somewhat subtle, to allow it to handle
593      all the kinds of intersections that we need.
594
595      Three additional ordinates are assigned along the x axis.  The
596      first is xc, midway between x0 and x3.  The others are x1 and
597      x2; for a single vertical line these are equal to xc, and for
598      a double vertical line they are the ordinates of the left and
599      right half of the double line.
600
601      yc, y1, and y2 are assigned similarly along the y axis.
602
603      The following diagram shows the coordinate system and output
604      for double top and bottom lines, single left line, and no
605      right line:
606
607                  x0       x1 xc  x2      x3
608                y0 ________________________
609                   |        #     #       |
610                   |        #     #       |
611                   |        #     #       |
612                   |        #     #       |
613                   |        #     #       |
614      y1 = y2 = yc |#########     #       |
615                   |        #     #       |
616                   |        #     #       |
617                   |        #     #       |
618                   |        #     #       |
619                y3 |________#_____#_______|
620   */
621   struct xr_driver *xr = xr_;
622
623   /* Offset from center of each line in a pair of double lines. */
624   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
625
626   /* Are the lines along each axis single or double?
627      (It doesn't make sense to have different kinds of line on the
628      same axis, so we don't try to gracefully handle that case.) */
629   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
630   bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
631
632   /* When horizontal lines are doubled,
633      the left-side line along y1 normally runs from x0 to x2,
634      and the right-side line along y1 from x3 to x1.
635      If the top-side line is also doubled, we shorten the y1 lines,
636      so that the left-side line runs only to x1,
637      and the right-side line only to x2.
638      Otherwise, the horizontal line at y = y1 below would cut off
639      the intersection, which looks ugly:
640                x0       x1     x2      x3
641              y0 ________________________
642                 |        #     #       |
643                 |        #     #       |
644                 |        #     #       |
645                 |        #     #       |
646              y1 |#########     ########|
647                 |                      |
648                 |                      |
649              y2 |######################|
650                 |                      |
651                 |                      |
652              y3 |______________________|
653      It is more of a judgment call when the horizontal line is
654      single.  We actually choose to cut off the line anyhow, as
655      shown in the first diagram above.
656   */
657   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
658   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
659   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
660   int horz_line_ofs = double_vert ? double_line_ofs : 0;
661   int xc = (x0 + x3) / 2;
662   int x1 = xc - horz_line_ofs;
663   int x2 = xc + horz_line_ofs;
664
665   bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
666   bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
667   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
668   int vert_line_ofs = double_horz ? double_line_ofs : 0;
669   int yc = (y0 + y3) / 2;
670   int y1 = yc - vert_line_ofs;
671   int y2 = yc + vert_line_ofs;
672
673   if (!double_horz)
674     horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
675   else
676     {
677       horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
678       horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
679     }
680
681   if (!double_vert)
682     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
683   else
684     {
685       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
686       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
687     }
688 }
689
690 static void
691 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
692                        int *min_width, int *max_width)
693 {
694   struct xr_driver *xr = xr_;
695   int bb[TABLE_N_AXES][2];
696   int clip[TABLE_N_AXES][2];
697   int h;
698
699   bb[H][0] = 0;
700   bb[H][1] = INT_MAX;
701   bb[V][0] = 0;
702   bb[V][1] = INT_MAX;
703   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
704   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, max_width, &h);
705
706   bb[H][1] = 1;
707   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, min_width, &h);
708 }
709
710 static int
711 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
712 {
713   struct xr_driver *xr = xr_;
714   int bb[TABLE_N_AXES][2];
715   int clip[TABLE_N_AXES][2];
716   int w, h;
717
718   bb[H][0] = 0;
719   bb[H][1] = width;
720   bb[V][0] = 0;
721   bb[V][1] = INT_MAX;
722   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
723   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
724   return h;
725 }
726
727 static void
728 xr_draw_cell (void *xr_, const struct table_cell *cell,
729               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
730 {
731   struct xr_driver *xr = xr_;
732   int w, h;
733
734   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
735 }
736 \f
737 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
738    and with the given cell OPTIONS for XR. */
739 static int
740 draw_text (struct xr_driver *xr, const char *string, int x, int y,
741            int max_width, unsigned int options)
742 {
743   struct table_cell cell;
744   int bb[TABLE_N_AXES][2];
745   int w, h;
746
747   cell.contents = string;
748   cell.options = options;
749   bb[H][0] = x;
750   bb[V][0] = y;
751   bb[H][1] = x + max_width;
752   bb[V][1] = xr->font_height;
753   xr_layout_cell (xr, &cell, bb, bb, PANGO_WRAP_WORD_CHAR, &w, &h);
754   return w;
755 }
756
757 /* Writes LEFT left-justified and RIGHT right-justified within
758    (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
759 static void
760 draw_header_line (struct xr_driver *xr, const char *left, const char *right,
761                   int x0, int x1, int y)
762 {
763   int right_width = 0;
764   if (right != NULL)
765     right_width = (draw_text (xr, right, x0, y, x1 - x0, TAB_RIGHT)
766                    + xr->font_height / 2);
767   if (left != NULL)
768     draw_text (xr, left, x0, y, x1 - x0 - right_width, TAB_LEFT);
769 }
770
771 /* Draw top of page headers for XR. */
772 static void
773 draw_headers (struct xr_driver *xr)
774 {
775   char *r1, *r2;
776   int x0, x1;
777   int y;
778
779   y = -3 * xr->font_height;
780   x0 = xr->font_height / 2;
781   x1 = xr->width - xr->font_height / 2;
782
783   /* Draw box. */
784   cairo_rectangle (xr->cairo, 0, xr_to_pt (y), xr_to_pt (xr->width),
785                    xr_to_pt (2 * (xr->font_height
786                                   + xr->line_width + xr->line_gutter)));
787   cairo_save (xr->cairo);
788   cairo_set_source_rgb (xr->cairo, 0.9, 0.9, 0.9);
789   cairo_fill_preserve (xr->cairo);
790   cairo_restore (xr->cairo);
791   cairo_stroke (xr->cairo);
792
793   y += xr->line_width + xr->line_gutter;
794
795   r1 = xasprintf (_("%s - Page %d"), get_start_date (), xr->page_number);
796   r2 = xasprintf ("%s - %s", version, host_system);
797
798   draw_header_line (xr, xr->title, r1, x0, x1, y);
799   y += xr->font_height;
800
801   draw_header_line (xr, xr->subtitle, r2, x0, x1, y);
802
803   free (r1);
804   free (r2);
805 }
806 \f
807 static void
808 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
809                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
810                 PangoWrapMode wrap, int *width, int *height)
811 {
812   struct xr_font *font;
813
814   font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
815           : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
816           : &xr->fonts[XR_FONT_PROPORTIONAL]);
817
818   pango_layout_set_text (font->layout, cell->contents, -1);
819
820   pango_layout_set_alignment (
821     font->layout,
822     ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
823      : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
824      : PANGO_ALIGN_CENTER));
825   pango_layout_set_width (font->layout,
826                           bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]);
827   pango_layout_set_wrap (font->layout, wrap);
828
829   if (clip[H][0] != clip[H][1])
830     {
831       cairo_save (xr->cairo);
832
833       if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
834         {
835           double x0 = xr_to_pt (clip[H][0]);
836           double y0 = xr_to_pt (clip[V][0] + xr->y);
837           double x1 = xr_to_pt (clip[H][1]);
838           double y1 = xr_to_pt (clip[V][1] + xr->y);
839
840           cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
841           cairo_clip (xr->cairo);
842         }
843
844       cairo_translate (xr->cairo,
845                        xr_to_pt (bb[H][0]),
846                        xr_to_pt (bb[V][0] + xr->y));
847       pango_cairo_show_layout (xr->cairo, font->layout);
848       cairo_restore (xr->cairo);
849     }
850
851   if (width != NULL || height != NULL)
852     {
853       int w, h;
854
855       pango_layout_get_size (font->layout, &w, &h);
856       if (width != NULL)
857         *width = w;
858       if (height != NULL)
859         *height = h;
860     }
861 }
862 \f
863 /* Attempts to load FONT, initializing its other members based on
864    its 'string' member and the information in DRIVER.  Returns true
865    if successful, otherwise false. */
866 static bool
867 load_font (struct xr_driver *xr, struct xr_font *font)
868 {
869   PangoContext *context;
870   PangoLanguage *language;
871
872   font->desc = pango_font_description_from_string (font->string);
873   if (font->desc == NULL)
874     {
875       error (0, 0, _("\"%s\": bad font specification"), font->string);
876       return false;
877     }
878   pango_font_description_set_absolute_size (font->desc, xr->font_height);
879
880   font->layout = pango_cairo_create_layout (xr->cairo);
881   pango_layout_set_font_description (font->layout, font->desc);
882
883   language = pango_language_get_default ();
884   context = pango_layout_get_context (font->layout);
885   font->metrics = pango_context_get_metrics (context, font->desc, language);
886
887   return true;
888 }
889
890 /* Frees FONT. */
891 static void
892 free_font (struct xr_font *font)
893 {
894   free (font->string);
895   if (font->desc != NULL)
896     pango_font_description_free (font->desc);
897   pango_font_metrics_unref (font->metrics);
898   g_object_unref (font->layout);
899 }
900
901 /* Cairo driver class. */
902 const struct output_driver_class cairo_class =
903 {
904   "cairo",
905   xr_create,
906   xr_destroy,
907   xr_submit,
908   xr_flush,
909 };
910 \f
911 /* GUI rendering helpers. */
912
913 struct xr_rendering
914   {
915     /* Table items. */
916     struct render_page *page;
917     struct xr_driver *xr;
918     int title_height;
919
920     /* Chart items. */
921     struct chart_item *chart;
922   };
923
924 #define CHART_WIDTH 500
925 #define CHART_HEIGHT 375
926
927 struct xr_driver *
928 xr_create_driver (cairo_t *cairo)
929 {
930   struct xr_driver *xr;
931   struct string_map map;
932
933   string_map_init (&map);
934   xr = xr_allocate ("cairo", 0, &map);
935   string_map_destroy (&map);
936
937   xr->width = INT_MAX / 8;
938   xr->length = INT_MAX / 8;
939   if (!xr_set_cairo (xr, cairo))
940     {
941       output_driver_destroy (&xr->driver);
942       return NULL;
943     }
944   return xr;
945 }
946
947 struct xr_rendering *
948 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
949                      cairo_t *cr)
950 {
951   struct xr_rendering *r = NULL;
952
953   if (is_text_item (item))
954     {
955       const struct text_item *text_item = to_text_item (item);
956       const char *text = text_item_get_text (text_item);
957       struct table_item *table_item;
958
959       table_item = table_item_create (table_from_string (TAB_LEFT, text),
960                                       NULL);
961       r = xr_rendering_create (xr, &table_item->output_item, cr);
962       table_item_unref (table_item);
963     }
964   else if (is_table_item (item))
965     {
966       r = xzalloc (sizeof *r);
967       r->xr = xr;
968       xr_set_cairo (xr, cr);
969       r->page = xr_render_table_item (xr, to_table_item (item),
970                                       &r->title_height);
971     }
972   else if (is_chart_item (item))
973     {
974       r = xzalloc (sizeof *r);
975       r->chart = to_chart_item (output_item_ref (item));
976     }
977
978   return r;
979 }
980
981 void
982 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
983 {
984   if (r->chart == NULL)
985     {
986       *w = render_page_get_size (r->page, H) / 1024;
987       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
988     }
989   else
990     {
991       *w = CHART_WIDTH;
992       *h = CHART_HEIGHT;
993     }
994 }
995
996 void
997 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr)
998 {
999   if (r->chart == NULL)
1000     {
1001       struct xr_driver *xr = r->xr;
1002
1003       xr_set_cairo (xr, cr);
1004       xr->y = 0;
1005       render_page_draw (r->page);
1006     }
1007   else
1008     xr_draw_chart (r->chart, cr, 0, 0, CHART_WIDTH, CHART_HEIGHT);
1009 }
1010
1011 void
1012 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1013                double x, double y, double width, double height)
1014 {
1015   struct xrchart_geometry geom;
1016
1017   cairo_save (cr);
1018   cairo_translate (cr, x, y + height);
1019   cairo_scale (cr, 1.0, -1.0);
1020   xrchart_geometry_init (cr, &geom, width, height);
1021   if (is_boxplot (chart_item))
1022     xrchart_draw_boxplot (chart_item, cr, &geom);
1023   else if (is_histogram_chart (chart_item))
1024     xrchart_draw_histogram (chart_item, cr, &geom);
1025   else if (is_np_plot_chart (chart_item))
1026     xrchart_draw_np_plot (chart_item, cr, &geom);
1027   else if (is_piechart (chart_item))
1028     xrchart_draw_piechart (chart_item, cr, &geom);
1029   else if (is_roc_chart (chart_item))
1030     xrchart_draw_roc (chart_item, cr, &geom);
1031   else if (is_scree (chart_item))
1032     xrchart_draw_scree (chart_item, cr, &geom);
1033   else
1034     NOT_REACHED ();
1035   xrchart_geometry_free (cr, &geom);
1036
1037   cairo_restore (cr);
1038 }
1039
1040 char *
1041 xr_draw_png_chart (const struct chart_item *item,
1042                    const char *file_name_template, int number)
1043 {
1044   const int width = 640;
1045   const int length = 480;
1046
1047   cairo_surface_t *surface;
1048   cairo_status_t status;
1049   const char *number_pos;
1050   char *file_name;
1051   cairo_t *cr;
1052
1053   number_pos = strchr (file_name_template, '#');
1054   if (number_pos != NULL)
1055     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1056                            file_name_template, number, number_pos + 1);
1057   else
1058     file_name = xstrdup (file_name_template);
1059
1060   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1061   cr = cairo_create (surface);
1062
1063   cairo_save (cr);
1064   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1065   cairo_rectangle (cr, 0, 0, width, length);
1066   cairo_fill (cr);
1067   cairo_restore (cr);
1068
1069   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1070
1071   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1072
1073   status = cairo_surface_write_to_png (surface, file_name);
1074   if (status != CAIRO_STATUS_SUCCESS)
1075     error (0, 0, _("writing output file \"%s\": %s"),
1076            file_name, cairo_status_to_string (status));
1077
1078   cairo_destroy (cr);
1079   cairo_surface_destroy (surface);
1080
1081   return file_name;
1082 }