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