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