Simplify creation of pango layout in xr driver.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012 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/spreadlevel-plot.h"
36 #include "output/charts/scree.h"
37 #include "output/driver-provider.h"
38 #include "output/message-item.h"
39 #include "output/options.h"
40 #include "output/render.h"
41 #include "output/tab.h"
42 #include "output/table-item.h"
43 #include "output/table.h"
44 #include "output/text-item.h"
45
46 #include <cairo/cairo-pdf.h>
47 #include <cairo/cairo-ps.h>
48 #include <cairo/cairo-svg.h>
49 #include <cairo/cairo.h>
50 #include <pango/pango-font.h>
51 #include <pango/pango-layout.h>
52 #include <pango/pango.h>
53 #include <pango/pangocairo.h>
54 #include <stdlib.h>
55
56 #include "gl/error.h"
57 #include "gl/intprops.h"
58 #include "gl/minmax.h"
59 #include "gl/xalloc.h"
60
61 #include "gettext.h"
62 #define _(msgid) gettext (msgid)
63
64 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
65 #define H TABLE_HORZ
66 #define V TABLE_VERT
67
68 /* Measurements as we present to the rest of PSPP. */
69 #define XR_POINT PANGO_SCALE
70 #define XR_INCH (XR_POINT * 72)
71
72 /* Conversions to and from points. */
73 static double
74 xr_to_pt (int x)
75 {
76   return x / (double) XR_POINT;
77 }
78
79 /* Output types. */
80 enum xr_output_type
81   {
82     XR_PDF,
83     XR_PS,
84     XR_SVG
85   };
86
87 /* Cairo fonts. */
88 enum xr_font_type
89   {
90     XR_FONT_PROPORTIONAL,
91     XR_FONT_EMPHASIS,
92     XR_FONT_FIXED,
93     XR_N_FONTS
94   };
95
96 /* A font for use with Cairo. */
97 struct xr_font
98   {
99     PangoFontDescription *desc;
100     PangoLayout *layout;
101   };
102
103 /* An output item whose rendering is in progress. */
104 struct xr_render_fsm
105   {
106     /* Renders as much of itself as it can on the current page.  Returns true
107        if rendering is complete, false if the output item needs another
108        page. */
109     bool (*render) (struct xr_render_fsm *, struct xr_driver *);
110
111     /* Destroys the output item. */
112     void (*destroy) (struct xr_render_fsm *);
113   };
114
115 /* Cairo output driver. */
116 struct xr_driver
117   {
118     struct output_driver driver;
119
120     /* User parameters. */
121     struct xr_font fonts[XR_N_FONTS];
122
123     int width;                  /* Page width minus margins. */
124     int length;                 /* Page length minus margins and header. */
125
126     int left_margin;            /* Left margin in XR units. */
127     int right_margin;           /* Right margin in XR units. */
128     int top_margin;             /* Top margin in XR units. */
129     int bottom_margin;          /* Bottom margin in XR units. */
130
131     int line_gutter;            /* Space around lines. */
132     int line_space;             /* Space between lines. */
133     int line_width;             /* Width of lines. */
134
135     double bg_red, bg_green, bg_blue; /* Background color */
136     double fg_red, fg_green, fg_blue; /* Foreground color */
137
138     /* Internal state. */
139     struct render_params *params;
140     int char_width, char_height;
141     char *command_name;
142     char *title;
143     char *subtitle;
144     cairo_t *cairo;
145     int page_number;            /* Current page number. */
146     int y;
147     struct xr_render_fsm *fsm;
148   };
149
150 static const struct output_driver_class cairo_driver_class;
151
152 static void xr_driver_destroy_fsm (struct xr_driver *);
153 static void xr_driver_run_fsm (struct xr_driver *);
154
155 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
156                           enum render_line_style styles[TABLE_N_AXES][2]);
157 static void xr_measure_cell_width (void *, const struct table_cell *,
158                                    int *min, int *max);
159 static int xr_measure_cell_height (void *, const struct table_cell *,
160                                    int width);
161 static void xr_draw_cell (void *, const struct table_cell *,
162                           int bb[TABLE_N_AXES][2],
163                           int clip[TABLE_N_AXES][2]);
164
165 static struct xr_render_fsm *xr_render_output_item (
166   struct xr_driver *, const struct output_item *);
167 \f
168 /* Output driver basics. */
169
170 static struct xr_driver *
171 xr_driver_cast (struct output_driver *driver)
172 {
173   assert (driver->class == &cairo_driver_class);
174   return UP_CAST (driver, struct xr_driver, driver);
175 }
176
177 static struct driver_option *
178 opt (struct output_driver *d, struct string_map *options, const char *key,
179      const char *default_value)
180 {
181   return driver_option_get (d, options, key, default_value);
182 }
183
184 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
185    Currently, the input string must be of the form "#RRRRGGGGBBBB"
186    Future implementations might allow things like "yellow" and
187    "sky-blue-ultra-brown"
188 */
189 static void
190 parse_color (struct output_driver *d, struct string_map *options,
191               const char *key, const char *default_value,
192               double *dred, double *dgreen, double *dblue)
193 {
194   int red, green, blue;
195   char *string = parse_string (opt (d, options, key, default_value));
196
197   if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
198     {
199       /* If the parsed option string fails, then try the default value */
200       if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
201         {
202           /* ... and if that fails set everything to zero */
203           red = green = blue = 0;
204         }
205     }
206
207   free (string);
208
209   /* Convert 16 bit ints to float */
210   *dred = red / (double) 0xFFFF;
211   *dgreen = green / (double) 0xFFFF;
212   *dblue = blue / (double) 0xFFFF;
213 }
214
215 static PangoFontDescription *
216 parse_font (struct output_driver *d, struct string_map *options,
217             const char *key, const char *default_value,
218             int default_points)
219 {
220   PangoFontDescription *desc;
221   char *string;
222
223   /* Parse KEY as a font description. */
224   string = parse_string (opt (d, options, key, default_value));
225   desc = pango_font_description_from_string (string);
226   if (desc == NULL)
227     {
228       error (0, 0, _("`%s': bad font specification"), string);
229
230       /* Fall back to DEFAULT_VALUE, which had better be a valid font
231          description. */
232       desc = pango_font_description_from_string (default_value);
233       assert (desc != NULL);
234     }
235   free (string);
236
237   /* If the font description didn't include an explicit font size, then set it
238      to DEFAULT_POINTS. */
239   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
240     pango_font_description_set_size (desc,
241                                      default_points / 1000.0 * PANGO_SCALE);
242
243   return desc;
244 }
245
246
247 static void
248 apply_options (struct xr_driver *xr, struct string_map *o)
249 {
250   struct output_driver *d = &xr->driver;
251
252   int paper_width, paper_length, i;
253
254   int font_points = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
255
256   for (i = 0; i < XR_N_FONTS; i++)
257     {
258       struct xr_font *font = &xr->fonts[i];
259
260       if (font->desc != NULL)
261         pango_font_description_free (font->desc);
262     }
263
264   xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
265                                               font_points);
266   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
267                                                      "serif", font_points);
268   xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
269                                                  "serif italic", font_points);
270
271   xr->line_gutter = XR_POINT;
272   xr->line_space = XR_POINT;
273   xr->line_width = XR_POINT / 2;
274   xr->page_number = 0;
275
276   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg_red, &xr->bg_green, &xr->bg_blue);
277   parse_color (d, o, "foreground-color", "#000000000000", &xr->fg_red, &xr->fg_green, &xr->fg_blue);
278
279   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
280   xr->left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
281   xr->right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
282   xr->top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
283   xr->bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
284
285   xr->width = paper_width - xr->left_margin - xr->right_margin;
286   xr->length = paper_length - xr->top_margin - xr->bottom_margin;
287 }
288
289 static struct xr_driver *
290 xr_allocate (const char *name, int device_type, struct string_map *o)
291 {
292   struct xr_driver *xr = xzalloc (sizeof *xr);
293   struct output_driver *d = &xr->driver;
294
295   output_driver_init (d, &cairo_driver_class, name, device_type);
296
297   apply_options (xr, o);
298
299   return xr;
300 }
301
302 static bool
303 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
304 {
305   int i;
306
307   xr->cairo = cairo;
308
309   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
310
311   xr->char_width = 0;
312   xr->char_height = 0;
313   for (i = 0; i < XR_N_FONTS; i++)
314     {
315       struct xr_font *font = &xr->fonts[i];
316       int char_width, char_height;
317
318       font->layout = pango_cairo_create_layout (cairo);
319       pango_layout_set_font_description (font->layout, font->desc);
320
321       pango_layout_set_text (font->layout, "0", 1);
322       pango_layout_get_size (font->layout, &char_width, &char_height);
323       xr->char_width = MAX (xr->char_width, char_width);
324       xr->char_height = MAX (xr->char_height, char_height);
325     }
326
327   if (xr->params == NULL)
328     {
329       int single_width, double_width;
330
331       xr->params = xmalloc (sizeof *xr->params);
332       xr->params->draw_line = xr_draw_line;
333       xr->params->measure_cell_width = xr_measure_cell_width;
334       xr->params->measure_cell_height = xr_measure_cell_height;
335       xr->params->draw_cell = xr_draw_cell;
336       xr->params->aux = xr;
337       xr->params->size[H] = xr->width;
338       xr->params->size[V] = xr->length;
339       xr->params->font_size[H] = xr->char_width;
340       xr->params->font_size[V] = xr->char_height;
341
342       single_width = 2 * xr->line_gutter + xr->line_width;
343       double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
344       for (i = 0; i < TABLE_N_AXES; i++)
345         {
346           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
347           xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
348           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
349         }
350     }
351
352   cairo_set_source_rgb (xr->cairo, xr->fg_red, xr->fg_green, xr->fg_blue);
353
354   return true;
355 }
356
357 static struct output_driver *
358 xr_create (const char *file_name, enum settings_output_devices device_type,
359            struct string_map *o, enum xr_output_type file_type)
360 {
361   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
362   struct xr_driver *xr;
363   cairo_surface_t *surface;
364   cairo_status_t status;
365   double width_pt, length_pt;
366
367   xr = xr_allocate (file_name, device_type, o);
368
369   width_pt = (xr->width + xr->left_margin + xr->right_margin) / 1000.0;
370   length_pt = (xr->length + xr->top_margin + xr->bottom_margin) / 1000.0;
371   if (file_type == XR_PDF)
372     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
373   else if (file_type == XR_PS)
374     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
375   else if (file_type == XR_SVG)
376     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
377   else
378     NOT_REACHED ();
379
380   status = cairo_surface_status (surface);
381   if (status != CAIRO_STATUS_SUCCESS)
382     {
383       error (0, 0, _("error opening output file `%s': %s"),
384              file_name, cairo_status_to_string (status));
385       cairo_surface_destroy (surface);
386       goto error;
387     }
388
389   xr->cairo = cairo_create (surface);
390   cairo_surface_destroy (surface);
391
392   if (!xr_set_cairo (xr, xr->cairo))
393     goto error;
394
395   cairo_save (xr->cairo);
396   xr_driver_next_page (xr, xr->cairo);
397
398   if (xr->width / xr->char_width < MIN_WIDTH)
399     {
400       error (0, 0, _("The defined page is not wide enough to hold at least %d "
401                      "characters in the default font.  In fact, there's only "
402                      "room for %d characters."),
403              MIN_WIDTH,
404              xr->width / xr->char_width);
405       goto error;
406     }
407
408   if (xr->length / xr->char_height < MIN_LENGTH)
409     {
410       error (0, 0, _("The defined page is not long enough to hold at least %d "
411                      "lines in the default font.  In fact, there's only "
412                      "room for %d lines."),
413              MIN_LENGTH,
414              xr->length / xr->char_height);
415       goto error;
416     }
417
418   return &xr->driver;
419
420  error:
421   output_driver_destroy (&xr->driver);
422   return NULL;
423 }
424
425 static struct output_driver *
426 xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
427                struct string_map *o)
428 {
429   return xr_create (file_name, device_type, o, XR_PDF);
430 }
431
432 static struct output_driver *
433 xr_ps_create (const char *file_name, enum settings_output_devices device_type,
434                struct string_map *o)
435 {
436   return xr_create (file_name, device_type, o, XR_PS);
437 }
438
439 static struct output_driver *
440 xr_svg_create (const char *file_name, enum settings_output_devices device_type,
441                struct string_map *o)
442 {
443   return xr_create (file_name, device_type, o, XR_SVG);
444 }
445
446 static void
447 xr_destroy (struct output_driver *driver)
448 {
449   struct xr_driver *xr = xr_driver_cast (driver);
450   size_t i;
451
452   xr_driver_destroy_fsm (xr);
453
454   if (xr->cairo != NULL)
455     {
456       cairo_status_t status;
457
458       cairo_surface_finish (cairo_get_target (xr->cairo));
459       status = cairo_status (xr->cairo);
460       if (status != CAIRO_STATUS_SUCCESS)
461         error (0, 0, _("error drawing output for %s driver: %s"),
462                output_driver_get_name (driver),
463                cairo_status_to_string (status));
464       cairo_destroy (xr->cairo);
465     }
466
467   free (xr->command_name);
468   for (i = 0; i < XR_N_FONTS; i++)
469     {
470       struct xr_font *font = &xr->fonts[i];
471
472       if (font->desc != NULL)
473         pango_font_description_free (font->desc);
474       if (font->layout != NULL)
475         g_object_unref (font->layout);
476     }
477
478   free (xr->params);
479   free (xr);
480 }
481
482 static void
483 xr_flush (struct output_driver *driver)
484 {
485   struct xr_driver *xr = xr_driver_cast (driver);
486
487   cairo_surface_flush (cairo_get_target (xr->cairo));
488 }
489
490 static void
491 xr_init_caption_cell (const char *caption, struct table_cell *cell)
492 {
493   cell->contents = caption;
494   cell->options = TAB_LEFT;
495   cell->destructor = NULL;
496 }
497
498 static struct render_page *
499 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
500                       int *caption_widthp, int *caption_heightp)
501 {
502   const char *caption = table_item_get_caption (item);
503
504   if (caption != NULL)
505     {
506       /* XXX doesn't do well with very large captions */
507       int min_width, max_width;
508       struct table_cell cell;
509
510       xr_init_caption_cell (caption, &cell);
511
512       xr_measure_cell_width (xr, &cell, &min_width, &max_width);
513       *caption_widthp = MIN (max_width, xr->width);
514       *caption_heightp = xr_measure_cell_height (xr, &cell, *caption_widthp);
515     }
516   else
517     *caption_heightp = 0;
518
519   return render_page_create (xr->params, table_item_get_table (item));
520 }
521
522 static void
523 xr_submit (struct output_driver *driver, const struct output_item *output_item)
524 {
525   struct xr_driver *xr = xr_driver_cast (driver);
526
527   xr_driver_output_item (xr, output_item);
528   while (xr_driver_need_new_page (xr))
529     {
530       cairo_restore (xr->cairo);
531       cairo_show_page (xr->cairo);
532       cairo_save (xr->cairo);
533       xr_driver_next_page (xr, xr->cairo);
534     }
535 }
536 \f
537 /* Functions for rendering a series of output items to a series of Cairo
538    contexts, with pagination.
539
540    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
541    its underlying implementation.
542
543    See the big comment in cairo.h for intended usage. */
544
545 /* Gives new page CAIRO to XR for output.  CAIRO may be null to skip actually
546    rendering the page (which might be useful to find out how many pages an
547    output document has without actually rendering it). */
548 void
549 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
550 {
551   if (cairo != NULL)
552     {
553       cairo_save (cairo);
554       cairo_set_source_rgb (cairo, xr->bg_red, xr->bg_green, xr->bg_blue);
555       cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
556       cairo_fill (cairo);
557       cairo_restore (cairo);
558
559       cairo_translate (cairo,
560                        xr_to_pt (xr->left_margin),
561                        xr_to_pt (xr->top_margin));
562     }
563
564   xr->page_number++;
565   xr->cairo = cairo;
566   xr->y = 0;
567   xr_driver_run_fsm (xr);
568 }
569
570 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
571    rendering a previous output item, that is, only if xr_driver_need_new_page()
572    returns false. */
573 void
574 xr_driver_output_item (struct xr_driver *xr,
575                        const struct output_item *output_item)
576 {
577   assert (xr->fsm == NULL);
578   xr->fsm = xr_render_output_item (xr, output_item);
579   xr_driver_run_fsm (xr);
580 }
581
582 /* Returns true if XR is in the middle of rendering an output item and needs a
583    new page to be appended using xr_driver_next_page() to make progress,
584    otherwise false. */
585 bool
586 xr_driver_need_new_page (const struct xr_driver *xr)
587 {
588   return xr->fsm != NULL;
589 }
590
591 /* Returns true if the current page doesn't have any content yet. */
592 bool
593 xr_driver_is_page_blank (const struct xr_driver *xr)
594 {
595   return xr->y == 0;
596 }
597
598 static void
599 xr_driver_destroy_fsm (struct xr_driver *xr)
600 {
601   if (xr->fsm != NULL)
602     {
603       xr->fsm->destroy (xr->fsm);
604       xr->fsm = NULL;
605     }
606 }
607
608 static void
609 xr_driver_run_fsm (struct xr_driver *xr)
610 {
611   if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
612     xr_driver_destroy_fsm (xr);
613 }
614 \f
615 static void
616 xr_layout_cell (struct xr_driver *, const struct table_cell *,
617                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
618                 PangoWrapMode, int *width, int *height);
619
620 static void
621 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
622 {
623   cairo_new_path (xr->cairo);
624   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
625   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
626   cairo_stroke (xr->cairo);
627 }
628
629 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
630    shortening it to X0...X1 if SHORTEN is true.
631    Draws a horizontal line X1...X3 at Y if RIGHT says so,
632    shortening it to X2...X3 if SHORTEN is true. */
633 static void
634 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
635            enum render_line_style left, enum render_line_style right,
636            bool shorten)
637 {
638   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
639     dump_line (xr, x0, y, x3, y);
640   else
641     {
642       if (left != RENDER_LINE_NONE)
643         dump_line (xr, x0, y, shorten ? x1 : x2, y);
644       if (right != RENDER_LINE_NONE)
645         dump_line (xr, shorten ? x2 : x1, y, x3, y);
646     }
647 }
648
649 /* Draws a vertical line Y0...Y2 at X if TOP says so,
650    shortening it to Y0...Y1 if SHORTEN is true.
651    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
652    shortening it to Y2...Y3 if SHORTEN is true. */
653 static void
654 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
655            enum render_line_style top, enum render_line_style bottom,
656            bool shorten)
657 {
658   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
659     dump_line (xr, x, y0, x, y3);
660   else
661     {
662       if (top != RENDER_LINE_NONE)
663         dump_line (xr, x, y0, x, shorten ? y1 : y2);
664       if (bottom != RENDER_LINE_NONE)
665         dump_line (xr, x, shorten ? y2 : y1, x, y3);
666     }
667 }
668
669 static void
670 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
671               enum render_line_style styles[TABLE_N_AXES][2])
672 {
673   const int x0 = bb[H][0];
674   const int y0 = bb[V][0];
675   const int x3 = bb[H][1];
676   const int y3 = bb[V][1];
677   const int top = styles[H][0];
678   const int left = styles[V][0];
679   const int bottom = styles[H][1];
680   const int right = styles[V][1];
681
682   /* The algorithm here is somewhat subtle, to allow it to handle
683      all the kinds of intersections that we need.
684
685      Three additional ordinates are assigned along the x axis.  The
686      first is xc, midway between x0 and x3.  The others are x1 and
687      x2; for a single vertical line these are equal to xc, and for
688      a double vertical line they are the ordinates of the left and
689      right half of the double line.
690
691      yc, y1, and y2 are assigned similarly along the y axis.
692
693      The following diagram shows the coordinate system and output
694      for double top and bottom lines, single left line, and no
695      right line:
696
697                  x0       x1 xc  x2      x3
698                y0 ________________________
699                   |        #     #       |
700                   |        #     #       |
701                   |        #     #       |
702                   |        #     #       |
703                   |        #     #       |
704      y1 = y2 = yc |#########     #       |
705                   |        #     #       |
706                   |        #     #       |
707                   |        #     #       |
708                   |        #     #       |
709                y3 |________#_____#_______|
710   */
711   struct xr_driver *xr = xr_;
712
713   /* Offset from center of each line in a pair of double lines. */
714   int double_line_ofs = (xr->line_space + xr->line_width) / 2;
715
716   /* Are the lines along each axis single or double?
717      (It doesn't make sense to have different kinds of line on the
718      same axis, so we don't try to gracefully handle that case.) */
719   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
720   bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
721
722   /* When horizontal lines are doubled,
723      the left-side line along y1 normally runs from x0 to x2,
724      and the right-side line along y1 from x3 to x1.
725      If the top-side line is also doubled, we shorten the y1 lines,
726      so that the left-side line runs only to x1,
727      and the right-side line only to x2.
728      Otherwise, the horizontal line at y = y1 below would cut off
729      the intersection, which looks ugly:
730                x0       x1     x2      x3
731              y0 ________________________
732                 |        #     #       |
733                 |        #     #       |
734                 |        #     #       |
735                 |        #     #       |
736              y1 |#########     ########|
737                 |                      |
738                 |                      |
739              y2 |######################|
740                 |                      |
741                 |                      |
742              y3 |______________________|
743      It is more of a judgment call when the horizontal line is
744      single.  We actually choose to cut off the line anyhow, as
745      shown in the first diagram above.
746   */
747   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
748   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
749   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
750   int horz_line_ofs = double_vert ? double_line_ofs : 0;
751   int xc = (x0 + x3) / 2;
752   int x1 = xc - horz_line_ofs;
753   int x2 = xc + horz_line_ofs;
754
755   bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
756   bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
757   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
758   int vert_line_ofs = double_horz ? double_line_ofs : 0;
759   int yc = (y0 + y3) / 2;
760   int y1 = yc - vert_line_ofs;
761   int y2 = yc + vert_line_ofs;
762
763   if (!double_horz)
764     horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
765   else
766     {
767       horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
768       horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
769     }
770
771   if (!double_vert)
772     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
773   else
774     {
775       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
776       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
777     }
778 }
779
780 static void
781 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
782                        int *min_width, int *max_width)
783 {
784   struct xr_driver *xr = xr_;
785   int bb[TABLE_N_AXES][2];
786   int clip[TABLE_N_AXES][2];
787   int h;
788
789   bb[H][0] = 0;
790   bb[H][1] = INT_MAX;
791   bb[V][0] = 0;
792   bb[V][1] = INT_MAX;
793   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
794   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, max_width, &h);
795
796   bb[H][1] = 1;
797   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, min_width, &h);
798 }
799
800 static int
801 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
802 {
803   struct xr_driver *xr = xr_;
804   int bb[TABLE_N_AXES][2];
805   int clip[TABLE_N_AXES][2];
806   int w, h;
807
808   bb[H][0] = 0;
809   bb[H][1] = width;
810   bb[V][0] = 0;
811   bb[V][1] = INT_MAX;
812   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
813   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
814   return h;
815 }
816
817 static void
818 xr_draw_cell (void *xr_, const struct table_cell *cell,
819               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
820 {
821   struct xr_driver *xr = xr_;
822   int w, h;
823
824   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
825 }
826 \f
827 static void
828 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
829                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
830                 PangoWrapMode wrap, int *width, int *height)
831 {
832   struct xr_font *font;
833
834   font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
835           : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
836           : &xr->fonts[XR_FONT_PROPORTIONAL]);
837
838   pango_layout_set_text (font->layout, cell->contents, -1);
839
840   pango_layout_set_alignment (
841     font->layout,
842     ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
843      : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
844      : PANGO_ALIGN_CENTER));
845   pango_layout_set_width (font->layout,
846                           bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]);
847   pango_layout_set_wrap (font->layout, wrap);
848
849   if (clip[H][0] != clip[H][1])
850     {
851       cairo_save (xr->cairo);
852
853       if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
854         {
855           double x0 = xr_to_pt (clip[H][0]);
856           double y0 = xr_to_pt (clip[V][0] + xr->y);
857           double x1 = xr_to_pt (clip[H][1]);
858           double y1 = xr_to_pt (clip[V][1] + xr->y);
859
860           cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
861           cairo_clip (xr->cairo);
862         }
863
864       cairo_translate (xr->cairo,
865                        xr_to_pt (bb[H][0]),
866                        xr_to_pt (bb[V][0] + xr->y));
867       pango_cairo_show_layout (xr->cairo, font->layout);
868       cairo_restore (xr->cairo);
869     }
870
871   if (width != NULL || height != NULL)
872     {
873       int w, h;
874
875       pango_layout_get_size (font->layout, &w, &h);
876       if (width != NULL)
877         *width = w;
878       if (height != NULL)
879         *height = h;
880     }
881 }
882
883 static void
884 xr_draw_title (struct xr_driver *xr, const char *title,
885                int title_width, int title_height)
886 {
887   struct table_cell cell;
888   int bb[TABLE_N_AXES][2];
889
890   xr_init_caption_cell (title, &cell);
891   bb[H][0] = 0;
892   bb[H][1] = title_width;
893   bb[V][0] = 0;
894   bb[V][1] = title_height;
895   xr_draw_cell (xr, &cell, bb, bb);
896 }
897 \f
898 struct output_driver_factory pdf_driver_factory =
899   { "pdf", "pspp.pdf", xr_pdf_create };
900 struct output_driver_factory ps_driver_factory =
901   { "ps", "pspp.ps", xr_ps_create };
902 struct output_driver_factory svg_driver_factory =
903   { "svg", "pspp.svg", xr_svg_create };
904
905 static const struct output_driver_class cairo_driver_class =
906 {
907   "cairo",
908   xr_destroy,
909   xr_submit,
910   xr_flush,
911 };
912 \f
913 /* GUI rendering helpers. */
914
915 struct xr_rendering
916   {
917     struct output_item *item;
918
919     /* Table items. */
920     struct render_page *page;
921     struct xr_driver *xr;
922     int title_width;
923     int title_height;
924   };
925
926 #define CHART_WIDTH 500
927 #define CHART_HEIGHT 375
928
929
930
931 struct xr_driver *
932 xr_driver_create (cairo_t *cairo, struct string_map *options)
933 {
934   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
935   if (!xr_set_cairo (xr, cairo))
936     {
937       output_driver_destroy (&xr->driver);
938       return NULL;
939     }
940   return xr;
941 }
942
943 /* Destroy XR, which should have been created with xr_driver_create().  Any
944    cairo_t added to XR is not destroyed, because it is owned by the client. */
945 void
946 xr_driver_destroy (struct xr_driver *xr)
947 {
948   if (xr != NULL)
949     {
950       xr->cairo = NULL;
951       output_driver_destroy (&xr->driver);
952     }
953 }
954
955 static struct xr_rendering *
956 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
957 {
958   struct table_item *table_item;
959   struct xr_rendering *r;
960
961   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
962   r = xr_rendering_create (xr, &table_item->output_item, cr);
963   table_item_unref (table_item);
964
965   return r;
966 }
967
968 void 
969 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
970 {
971   if (is_table_item (xr->item))
972     apply_options (xr->xr, o);
973 }
974
975 struct xr_rendering *
976 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
977                      cairo_t *cr)
978 {
979   struct xr_rendering *r = NULL;
980
981   if (is_text_item (item))
982     r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
983                                   cr);
984   else if (is_message_item (item))
985     {
986       const struct message_item *message_item = to_message_item (item);
987       const struct msg *msg = message_item_get_msg (message_item);
988       char *s = msg_to_string (msg, NULL);
989       r = xr_rendering_create_text (xr, s, cr);
990       free (s);
991     }
992   else if (is_table_item (item))
993     {
994       r = xzalloc (sizeof *r);
995       r->item = output_item_ref (item);
996       r->xr = xr;
997       xr_set_cairo (xr, cr);
998       r->page = xr_render_table_item (xr, to_table_item (item),
999                                       &r->title_width, &r->title_height);
1000     }
1001   else if (is_chart_item (item))
1002     {
1003       r = xzalloc (sizeof *r);
1004       r->item = output_item_ref (item);
1005     }
1006
1007   return r;
1008 }
1009
1010 void
1011 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1012 {
1013   if (is_table_item (r->item))
1014     {
1015       int w0 = render_page_get_size (r->page, H);
1016       int w1 = r->title_width;
1017       *w = MAX (w0, w1) / 1024;
1018       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
1019     }
1020   else
1021     {
1022       *w = CHART_WIDTH;
1023       *h = CHART_HEIGHT;
1024     }
1025 }
1026
1027 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1028                     double x, double y, double width, double height);
1029
1030 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1031    and possibly some additional parts. */
1032 void
1033 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1034                    int x, int y, int w, int h)
1035 {
1036   if (is_table_item (r->item))
1037     {
1038       struct xr_driver *xr = r->xr;
1039
1040       xr_set_cairo (xr, cr);
1041
1042       if (r->title_height > 0)
1043         {
1044           xr->y = 0;
1045           xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)),
1046                          r->title_width, r->title_height);
1047         }
1048
1049       xr->y = r->title_height;
1050       render_page_draw_region (r->page, x * 1024, (y * 1024) - r->title_height,
1051                                w * 1024, h * 1024);
1052     }
1053   else
1054     xr_draw_chart (to_chart_item (r->item), cr,
1055                    0, 0, CHART_WIDTH, CHART_HEIGHT);
1056 }
1057
1058 static 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 if (is_spreadlevel_plot_chart (chart_item))
1081     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1082   else
1083     NOT_REACHED ();
1084   xrchart_geometry_free (cr, &geom);
1085
1086   cairo_restore (cr);
1087 }
1088
1089 char *
1090 xr_draw_png_chart (const struct chart_item *item,
1091                    const char *file_name_template, int number)
1092 {
1093   const int width = 640;
1094   const int length = 480;
1095
1096   cairo_surface_t *surface;
1097   cairo_status_t status;
1098   const char *number_pos;
1099   char *file_name;
1100   cairo_t *cr;
1101
1102   number_pos = strchr (file_name_template, '#');
1103   if (number_pos != NULL)
1104     file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1105                            file_name_template, number, number_pos + 1);
1106   else
1107     file_name = xstrdup (file_name_template);
1108
1109   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1110   cr = cairo_create (surface);
1111
1112   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1113
1114   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1115
1116   status = cairo_surface_write_to_png (surface, file_name);
1117   if (status != CAIRO_STATUS_SUCCESS)
1118     error (0, 0, _("error writing output file `%s': %s"),
1119            file_name, cairo_status_to_string (status));
1120
1121   cairo_destroy (cr);
1122   cairo_surface_destroy (surface);
1123
1124   return file_name;
1125 }
1126 \f
1127 struct xr_table_state
1128   {
1129     struct xr_render_fsm fsm;
1130     struct table_item *table_item;
1131     struct render_break x_break;
1132     struct render_break y_break;
1133     int caption_height;
1134   };
1135
1136 static bool
1137 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1138 {
1139   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1140
1141   for (;;)
1142     {
1143       struct render_page *y_slice;
1144       int space;
1145
1146       while (!render_break_has_next (&ts->y_break))
1147         {
1148           struct render_page *x_slice;
1149
1150           render_break_destroy (&ts->y_break);
1151           if (!render_break_has_next (&ts->x_break))
1152             return false;
1153
1154           x_slice = render_break_next (&ts->x_break, xr->width);
1155           render_break_init (&ts->y_break, x_slice, V);
1156         }
1157
1158       space = xr->length - xr->y;
1159       if (render_break_next_size (&ts->y_break) > space)
1160         {
1161           assert (xr->y > 0);
1162           return true;
1163         }
1164
1165       y_slice = render_break_next (&ts->y_break, space);
1166       if (ts->caption_height)
1167         {
1168           if (xr->cairo)
1169             xr_draw_title (xr, table_item_get_caption (ts->table_item),
1170                            xr->width, ts->caption_height);
1171
1172           xr->y += ts->caption_height;
1173           ts->caption_height = 0;
1174         }
1175
1176       if (xr->cairo)
1177         render_page_draw (y_slice);
1178       xr->y += render_page_get_size (y_slice, V);
1179       render_page_unref (y_slice);
1180     }
1181 }
1182
1183 static void
1184 xr_table_destroy (struct xr_render_fsm *fsm)
1185 {
1186   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1187
1188   table_item_unref (ts->table_item);
1189   render_break_destroy (&ts->x_break);
1190   render_break_destroy (&ts->y_break);
1191   free (ts);
1192 }
1193
1194 static struct xr_render_fsm *
1195 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1196 {
1197   struct xr_table_state *ts;
1198   struct render_page *page;
1199   int caption_width;
1200
1201   ts = xmalloc (sizeof *ts);
1202   ts->fsm.render = xr_table_render;
1203   ts->fsm.destroy = xr_table_destroy;
1204   ts->table_item = table_item_ref (table_item);
1205
1206   if (xr->y > 0)
1207     xr->y += xr->char_height;
1208
1209   page = xr_render_table_item (xr, table_item,
1210                                &caption_width, &ts->caption_height);
1211   xr->params->size[V] = xr->length - ts->caption_height;
1212
1213   render_break_init (&ts->x_break, page, H);
1214   render_break_init_empty (&ts->y_break);
1215
1216   return &ts->fsm;
1217 }
1218 \f
1219 struct xr_chart_state
1220   {
1221     struct xr_render_fsm fsm;
1222     struct chart_item *chart_item;
1223   };
1224
1225 static bool
1226 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1227 {
1228   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1229
1230   if (xr->y > 0)
1231     return true;
1232
1233   if (xr->cairo != NULL)
1234     xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1235                    xr_to_pt (xr->width), xr_to_pt (xr->length));
1236   xr->y = xr->length;
1237
1238   return false;
1239 }
1240
1241 static void
1242 xr_chart_destroy (struct xr_render_fsm *fsm)
1243 {
1244   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1245
1246   chart_item_unref (cs->chart_item);
1247   free (cs);
1248 }
1249
1250 static struct xr_render_fsm *
1251 xr_render_chart (const struct chart_item *chart_item)
1252 {
1253   struct xr_chart_state *cs;
1254
1255   cs = xmalloc (sizeof *cs);
1256   cs->fsm.render = xr_chart_render;
1257   cs->fsm.destroy = xr_chart_destroy;
1258   cs->chart_item = chart_item_ref (chart_item);
1259
1260   return &cs->fsm;
1261 }
1262 \f
1263 static bool
1264 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1265 {
1266   return xr->y > 0;
1267 }
1268
1269 static void
1270 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1271 {
1272   /* Nothing to do. */
1273 }
1274
1275 static struct xr_render_fsm *
1276 xr_render_eject (void)
1277 {
1278   static struct xr_render_fsm eject_renderer =
1279     {
1280       xr_eject_render,
1281       xr_eject_destroy
1282     };
1283
1284   return &eject_renderer;
1285 }
1286 \f
1287 static struct xr_render_fsm *
1288 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1289 {
1290   struct table_item *table_item;
1291   struct xr_render_fsm *fsm;
1292
1293   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
1294   fsm = xr_render_table (xr, table_item);
1295   table_item_unref (table_item);
1296
1297   return fsm;
1298 }
1299
1300 static struct xr_render_fsm *
1301 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1302 {
1303   enum text_item_type type = text_item_get_type (text_item);
1304   const char *text = text_item_get_text (text_item);
1305
1306   switch (type)
1307     {
1308     case TEXT_ITEM_TITLE:
1309       free (xr->title);
1310       xr->title = xstrdup (text);
1311       break;
1312
1313     case TEXT_ITEM_SUBTITLE:
1314       free (xr->subtitle);
1315       xr->subtitle = xstrdup (text);
1316       break;
1317
1318     case TEXT_ITEM_COMMAND_CLOSE:
1319       break;
1320
1321     case TEXT_ITEM_BLANK_LINE:
1322       if (xr->y > 0)
1323         xr->y += xr->char_height;
1324       break;
1325
1326     case TEXT_ITEM_EJECT_PAGE:
1327       if (xr->y > 0)
1328         return xr_render_eject ();
1329       break;
1330
1331     default:
1332       return xr_create_text_renderer (xr, text);
1333     }
1334
1335   return NULL;
1336 }
1337
1338 static struct xr_render_fsm *
1339 xr_render_message (struct xr_driver *xr,
1340                    const struct message_item *message_item)
1341 {
1342   const struct msg *msg = message_item_get_msg (message_item);
1343   struct xr_render_fsm *fsm;
1344   char *s;
1345
1346   s = msg_to_string (msg, xr->command_name);
1347   fsm = xr_create_text_renderer (xr, s);
1348   free (s);
1349
1350   return fsm;
1351 }
1352
1353 static struct xr_render_fsm *
1354 xr_render_output_item (struct xr_driver *xr,
1355                        const struct output_item *output_item)
1356 {
1357   if (is_table_item (output_item))
1358     return xr_render_table (xr, to_table_item (output_item));
1359   else if (is_chart_item (output_item))
1360     return xr_render_chart (to_chart_item (output_item));
1361   else if (is_text_item (output_item))
1362     return xr_render_text (xr, to_text_item (output_item));
1363   else if (is_message_item (output_item))
1364     return xr_render_message (xr, to_message_item (output_item));
1365   else
1366     return NULL;
1367 }