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