1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "output/cairo.h"
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/charts/scatterplot.h"
38 #include "output/driver-provider.h"
39 #include "output/message-item.h"
40 #include "output/options.h"
41 #include "output/render.h"
42 #include "output/tab.h"
43 #include "output/table-item.h"
44 #include "output/table.h"
45 #include "output/text-item.h"
47 #include <cairo/cairo-pdf.h>
48 #include <cairo/cairo-ps.h>
49 #include <cairo/cairo-svg.h>
50 #include <cairo/cairo.h>
52 #include <pango/pango-font.h>
53 #include <pango/pango-layout.h>
54 #include <pango/pango.h>
55 #include <pango/pangocairo.h>
58 #include "gl/intprops.h"
59 #include "gl/minmax.h"
60 #include "gl/xalloc.h"
63 #define _(msgid) gettext (msgid)
65 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
69 /* The unit used for internal measurements is inch/(72 * XR_POINT). */
70 #define XR_POINT PANGO_SCALE
72 /* Conversions to and from points. */
76 return x / (double) XR_POINT;
97 /* A font for use with Cairo. */
100 PangoFontDescription *desc;
104 /* An output item whose rendering is in progress. */
107 /* Renders as much of itself as it can on the current page. Returns true
108 if rendering is complete, false if the output item needs another
110 bool (*render) (struct xr_render_fsm *, struct xr_driver *);
112 /* Destroys the output item. */
113 void (*destroy) (struct xr_render_fsm *);
116 /* Cairo output driver. */
119 struct output_driver driver;
121 /* User parameters. */
122 struct xr_font fonts[XR_N_FONTS];
124 int width; /* Page width minus margins. */
125 int length; /* Page length minus margins and header. */
127 int left_margin; /* Left margin in inch/(72 * XR_POINT). */
128 int right_margin; /* Right margin in inch/(72 * XR_POINT). */
129 int top_margin; /* Top margin in inch/(72 * XR_POINT). */
130 int bottom_margin; /* Bottom margin in inch/(72 * XR_POINT). */
132 int line_gutter; /* Space around lines. */
133 int line_space; /* Space between lines. */
134 int line_width; /* Width of lines. */
138 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
140 struct xr_color bg; /* Background color */
141 struct xr_color fg; /* Foreground color */
143 /* Internal state. */
144 struct render_params *params;
145 int char_width, char_height;
150 int page_number; /* Current page number. */
152 struct xr_render_fsm *fsm;
156 static const struct output_driver_class cairo_driver_class;
158 static void xr_driver_destroy_fsm (struct xr_driver *);
159 static void xr_driver_run_fsm (struct xr_driver *);
161 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
162 enum render_line_style styles[TABLE_N_AXES][2]);
163 static void xr_measure_cell_width (void *, const struct table_cell *,
164 int footnote_idx, int *min, int *max);
165 static int xr_measure_cell_height (void *, const struct table_cell *,
166 int footnote_idx, int width);
167 static void xr_draw_cell (void *, const struct table_cell *, int footnote_idx,
168 int bb[TABLE_N_AXES][2],
169 int clip[TABLE_N_AXES][2]);
170 static int xr_adjust_break (void *, const struct table_cell *, int footnote_idx,
171 int width, int height);
173 static struct xr_render_fsm *xr_render_output_item (
174 struct xr_driver *, const struct output_item *);
176 /* Output driver basics. */
178 static struct xr_driver *
179 xr_driver_cast (struct output_driver *driver)
181 assert (driver->class == &cairo_driver_class);
182 return UP_CAST (driver, struct xr_driver, driver);
185 static struct driver_option *
186 opt (struct output_driver *d, struct string_map *options, const char *key,
187 const char *default_value)
189 return driver_option_get (d, options, key, default_value);
192 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
193 Currently, the input string must be of the form "#RRRRGGGGBBBB"
194 Future implementations might allow things like "yellow" and
195 "sky-blue-ultra-brown"
198 parse_color (struct output_driver *d, struct string_map *options,
199 const char *key, const char *default_value,
200 struct xr_color *color)
202 int red, green, blue;
203 char *string = parse_string (opt (d, options, key, default_value));
205 if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
207 /* If the parsed option string fails, then try the default value */
208 if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
210 /* ... and if that fails set everything to zero */
211 red = green = blue = 0;
217 /* Convert 16 bit ints to float */
218 color->red = red / (double) 0xFFFF;
219 color->green = green / (double) 0xFFFF;
220 color->blue = blue / (double) 0xFFFF;
223 static PangoFontDescription *
224 parse_font (struct output_driver *d, struct string_map *options,
225 const char *key, const char *default_value,
228 PangoFontDescription *desc;
231 /* Parse KEY as a font description. */
232 string = parse_string (opt (d, options, key, default_value));
233 desc = pango_font_description_from_string (string);
236 msg (MW, _("`%s': bad font specification"), string);
238 /* Fall back to DEFAULT_VALUE, which had better be a valid font
240 desc = pango_font_description_from_string (default_value);
241 assert (desc != NULL);
245 /* If the font description didn't include an explicit font size, then set it
246 to DEFAULT_SIZE, which is in inch/72000 units. */
247 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
248 pango_font_description_set_size (desc,
249 (default_size / 1000.0) * PANGO_SCALE);
256 apply_options (struct xr_driver *xr, struct string_map *o)
258 struct output_driver *d = &xr->driver;
260 /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
261 int left_margin, right_margin;
262 int top_margin, bottom_margin;
263 int paper_width, paper_length;
265 int min_break[TABLE_N_AXES];
267 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
268 const double scale = XR_POINT / 1000.;
272 for (i = 0; i < XR_N_FONTS; i++)
274 struct xr_font *font = &xr->fonts[i];
276 if (font->desc != NULL)
277 pango_font_description_free (font->desc);
280 font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
281 xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
283 xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
285 xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
286 "serif italic", font_size);
287 xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "serif",
288 font_size * PANGO_SCALE_X_SMALL);
290 xr->line_gutter = XR_POINT / 2;
291 xr->line_space = XR_POINT;
292 xr->line_width = XR_POINT / 2;
295 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
296 parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
298 /* Get dimensions. */
299 parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
300 left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
301 right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
302 top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
303 bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
305 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
306 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
308 /* Convert to inch/(XR_POINT * 72). */
309 xr->left_margin = left_margin * scale;
310 xr->right_margin = right_margin * scale;
311 xr->top_margin = top_margin * scale;
312 xr->bottom_margin = bottom_margin * scale;
313 xr->width = (paper_width - left_margin - right_margin) * scale;
314 xr->length = (paper_length - top_margin - bottom_margin) * scale;
315 xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
316 xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
319 static struct xr_driver *
320 xr_allocate (const char *name, int device_type, struct string_map *o)
322 struct xr_driver *xr = xzalloc (sizeof *xr);
323 struct output_driver *d = &xr->driver;
325 output_driver_init (d, &cairo_driver_class, name, device_type);
327 apply_options (xr, o);
333 pango_to_xr (int pango)
335 return (XR_POINT != PANGO_SCALE
336 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
343 return (XR_POINT != PANGO_SCALE
344 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
349 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
355 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
359 for (i = 0; i < XR_N_FONTS; i++)
361 struct xr_font *font = &xr->fonts[i];
362 int char_width, char_height;
364 font->layout = pango_cairo_create_layout (cairo);
365 pango_layout_set_font_description (font->layout, font->desc);
367 pango_layout_set_text (font->layout, "0", 1);
368 pango_layout_get_size (font->layout, &char_width, &char_height);
369 xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
370 xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
372 xr->cell_margin = xr->char_width;
374 if (xr->params == NULL)
376 int single_width, double_width;
378 xr->params = xmalloc (sizeof *xr->params);
379 xr->params->draw_line = xr_draw_line;
380 xr->params->measure_cell_width = xr_measure_cell_width;
381 xr->params->measure_cell_height = xr_measure_cell_height;
382 xr->params->adjust_break = xr_adjust_break;
383 xr->params->draw_cell = xr_draw_cell;
384 xr->params->aux = xr;
385 xr->params->size[H] = xr->width;
386 xr->params->size[V] = xr->length;
387 xr->params->font_size[H] = xr->char_width;
388 xr->params->font_size[V] = xr->char_height;
390 single_width = 2 * xr->line_gutter + xr->line_width;
391 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
392 for (i = 0; i < TABLE_N_AXES; i++)
394 xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
395 xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
396 xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
399 for (i = 0; i < TABLE_N_AXES; i++)
400 xr->params->min_break[i] = xr->min_break[i];
403 cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
408 static struct output_driver *
409 xr_create (const char *file_name, enum settings_output_devices device_type,
410 struct string_map *o, enum xr_output_type file_type)
412 enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
413 struct xr_driver *xr;
414 cairo_surface_t *surface;
415 cairo_status_t status;
416 double width_pt, length_pt;
418 xr = xr_allocate (file_name, device_type, o);
420 width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
421 length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
422 if (file_type == XR_PDF)
423 surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
424 else if (file_type == XR_PS)
425 surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
426 else if (file_type == XR_SVG)
427 surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
431 status = cairo_surface_status (surface);
432 if (status != CAIRO_STATUS_SUCCESS)
434 msg (ME, _("error opening output file `%s': %s"),
435 file_name, cairo_status_to_string (status));
436 cairo_surface_destroy (surface);
440 xr->cairo = cairo_create (surface);
441 cairo_surface_destroy (surface);
443 if (!xr_set_cairo (xr, xr->cairo))
446 cairo_save (xr->cairo);
447 xr_driver_next_page (xr, xr->cairo);
449 if (xr->width / xr->char_width < MIN_WIDTH)
451 msg (ME, _("The defined page is not wide enough to hold at least %d "
452 "characters in the default font. In fact, there's only "
453 "room for %d characters."),
455 xr->width / xr->char_width);
459 if (xr->length / xr->char_height < MIN_LENGTH)
461 msg (ME, _("The defined page is not long enough to hold at least %d "
462 "lines in the default font. In fact, there's only "
463 "room for %d lines."),
465 xr->length / xr->char_height);
472 output_driver_destroy (&xr->driver);
476 static struct output_driver *
477 xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
478 struct string_map *o)
480 return xr_create (file_name, device_type, o, XR_PDF);
483 static struct output_driver *
484 xr_ps_create (const char *file_name, enum settings_output_devices device_type,
485 struct string_map *o)
487 return xr_create (file_name, device_type, o, XR_PS);
490 static struct output_driver *
491 xr_svg_create (const char *file_name, enum settings_output_devices device_type,
492 struct string_map *o)
494 return xr_create (file_name, device_type, o, XR_SVG);
498 xr_destroy (struct output_driver *driver)
500 struct xr_driver *xr = xr_driver_cast (driver);
503 xr_driver_destroy_fsm (xr);
505 if (xr->cairo != NULL)
507 cairo_status_t status;
509 cairo_surface_finish (cairo_get_target (xr->cairo));
510 status = cairo_status (xr->cairo);
511 if (status != CAIRO_STATUS_SUCCESS)
512 msg (ME, _("error drawing output for %s driver: %s"),
513 output_driver_get_name (driver),
514 cairo_status_to_string (status));
515 cairo_destroy (xr->cairo);
518 free (xr->command_name);
519 for (i = 0; i < XR_N_FONTS; i++)
521 struct xr_font *font = &xr->fonts[i];
523 if (font->desc != NULL)
524 pango_font_description_free (font->desc);
525 if (font->layout != NULL)
526 g_object_unref (font->layout);
534 xr_flush (struct output_driver *driver)
536 struct xr_driver *xr = xr_driver_cast (driver);
538 cairo_surface_flush (cairo_get_target (xr->cairo));
542 xr_submit (struct output_driver *driver, const struct output_item *output_item)
544 struct xr_driver *xr = xr_driver_cast (driver);
546 output_driver_track_current_command (output_item, &xr->command_name);
548 xr_driver_output_item (xr, output_item);
549 while (xr_driver_need_new_page (xr))
551 cairo_restore (xr->cairo);
552 cairo_show_page (xr->cairo);
553 cairo_save (xr->cairo);
554 xr_driver_next_page (xr, xr->cairo);
558 /* Functions for rendering a series of output items to a series of Cairo
559 contexts, with pagination.
561 Used by PSPPIRE for printing, and by the basic Cairo output driver above as
562 its underlying implementation.
564 See the big comment in cairo.h for intended usage. */
566 /* Gives new page CAIRO to XR for output. */
568 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
571 cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
572 cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
574 cairo_restore (cairo);
576 cairo_translate (cairo,
577 xr_to_pt (xr->left_margin),
578 xr_to_pt (xr->top_margin));
583 xr_driver_run_fsm (xr);
586 /* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of
587 rendering a previous output item, that is, only if xr_driver_need_new_page()
590 xr_driver_output_item (struct xr_driver *xr,
591 const struct output_item *output_item)
593 assert (xr->fsm == NULL);
594 xr->fsm = xr_render_output_item (xr, output_item);
595 xr_driver_run_fsm (xr);
598 /* Returns true if XR is in the middle of rendering an output item and needs a
599 new page to be appended using xr_driver_next_page() to make progress,
602 xr_driver_need_new_page (const struct xr_driver *xr)
604 return xr->fsm != NULL;
607 /* Returns true if the current page doesn't have any content yet. */
609 xr_driver_is_page_blank (const struct xr_driver *xr)
615 xr_driver_destroy_fsm (struct xr_driver *xr)
619 xr->fsm->destroy (xr->fsm);
625 xr_driver_run_fsm (struct xr_driver *xr)
627 if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
628 xr_driver_destroy_fsm (xr);
632 xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx,
633 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
634 int *width, int *height, int *brk);
637 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
639 cairo_new_path (xr->cairo);
640 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
641 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
642 cairo_stroke (xr->cairo);
646 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
648 cairo_new_path (xr->cairo);
649 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
650 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
651 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
652 cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
653 cairo_close_path (xr->cairo);
654 cairo_stroke (xr->cairo);
657 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
658 shortening it to X0...X1 if SHORTEN is true.
659 Draws a horizontal line X1...X3 at Y if RIGHT says so,
660 shortening it to X2...X3 if SHORTEN is true. */
662 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
663 enum render_line_style left, enum render_line_style right,
666 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
667 dump_line (xr, x0, y, x3, y);
670 if (left != RENDER_LINE_NONE)
671 dump_line (xr, x0, y, shorten ? x1 : x2, y);
672 if (right != RENDER_LINE_NONE)
673 dump_line (xr, shorten ? x2 : x1, y, x3, y);
677 /* Draws a vertical line Y0...Y2 at X if TOP says so,
678 shortening it to Y0...Y1 if SHORTEN is true.
679 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
680 shortening it to Y2...Y3 if SHORTEN is true. */
682 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
683 enum render_line_style top, enum render_line_style bottom,
686 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
687 dump_line (xr, x, y0, x, y3);
690 if (top != RENDER_LINE_NONE)
691 dump_line (xr, x, y0, x, shorten ? y1 : y2);
692 if (bottom != RENDER_LINE_NONE)
693 dump_line (xr, x, shorten ? y2 : y1, x, y3);
698 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
699 enum render_line_style styles[TABLE_N_AXES][2])
701 const int x0 = bb[H][0];
702 const int y0 = bb[V][0];
703 const int x3 = bb[H][1];
704 const int y3 = bb[V][1];
705 const int top = styles[H][0];
706 const int left = styles[V][0];
707 const int bottom = styles[H][1];
708 const int right = styles[V][1];
710 /* The algorithm here is somewhat subtle, to allow it to handle
711 all the kinds of intersections that we need.
713 Three additional ordinates are assigned along the x axis. The
714 first is xc, midway between x0 and x3. The others are x1 and
715 x2; for a single vertical line these are equal to xc, and for
716 a double vertical line they are the ordinates of the left and
717 right half of the double line.
719 yc, y1, and y2 are assigned similarly along the y axis.
721 The following diagram shows the coordinate system and output
722 for double top and bottom lines, single left line, and no
726 y0 ________________________
732 y1 = y2 = yc |######### # |
737 y3 |________#_____#_______|
739 struct xr_driver *xr = xr_;
741 /* Offset from center of each line in a pair of double lines. */
742 int double_line_ofs = (xr->line_space + xr->line_width) / 2;
744 /* Are the lines along each axis single or double?
745 (It doesn't make sense to have different kinds of line on the
746 same axis, so we don't try to gracefully handle that case.) */
747 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
748 bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
750 /* When horizontal lines are doubled,
751 the left-side line along y1 normally runs from x0 to x2,
752 and the right-side line along y1 from x3 to x1.
753 If the top-side line is also doubled, we shorten the y1 lines,
754 so that the left-side line runs only to x1,
755 and the right-side line only to x2.
756 Otherwise, the horizontal line at y = y1 below would cut off
757 the intersection, which looks ugly:
759 y0 ________________________
764 y1 |######### ########|
767 y2 |######################|
770 y3 |______________________|
771 It is more of a judgment call when the horizontal line is
772 single. We actually choose to cut off the line anyhow, as
773 shown in the first diagram above.
775 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
776 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
777 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
778 int horz_line_ofs = double_vert ? double_line_ofs : 0;
779 int xc = (x0 + x3) / 2;
780 int x1 = xc - horz_line_ofs;
781 int x2 = xc + horz_line_ofs;
783 bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
784 bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
785 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
786 int vert_line_ofs = double_horz ? double_line_ofs : 0;
787 int yc = (y0 + y3) / 2;
788 int y1 = yc - vert_line_ofs;
789 int y2 = yc + vert_line_ofs;
792 horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
795 horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
796 horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
800 vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
803 vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
804 vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
809 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
810 int footnote_idx, int *min_width, int *max_width)
812 struct xr_driver *xr = xr_;
813 int bb[TABLE_N_AXES][2];
814 int clip[TABLE_N_AXES][2];
821 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
822 xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL);
825 xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL);
828 *min_width += xr->cell_margin * 2;
830 *max_width += xr->cell_margin * 2;
834 xr_measure_cell_height (void *xr_, const struct table_cell *cell,
835 int footnote_idx, int width)
837 struct xr_driver *xr = xr_;
838 int bb[TABLE_N_AXES][2];
839 int clip[TABLE_N_AXES][2];
843 bb[H][1] = width - xr->cell_margin * 2;
848 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
849 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL);
854 xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx,
855 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
857 struct xr_driver *xr = xr_;
860 bb[H][0] += xr->cell_margin;
861 bb[H][1] -= xr->cell_margin;
862 if (bb[H][0] >= bb[H][1])
864 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
868 xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx,
869 int width, int height)
871 struct xr_driver *xr = xr_;
872 int bb[TABLE_N_AXES][2];
873 int clip[TABLE_N_AXES][2];
876 if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height)
880 bb[H][1] = width - 2 * xr->cell_margin;
885 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
886 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
891 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
893 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
895 double x0 = xr_to_pt (clip[H][0] + xr->x);
896 double y0 = xr_to_pt (clip[V][0] + xr->y);
897 double x1 = xr_to_pt (clip[H][1] + xr->x);
898 double y1 = xr_to_pt (clip[V][1] + xr->y);
900 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
901 cairo_clip (xr->cairo);
906 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
908 attr->start_index = start_index;
909 pango_attr_list_insert (list, attr);
913 xr_layout_cell_text (struct xr_driver *xr,
914 const struct cell_contents *contents, int footnote_idx,
915 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
916 int y, int *widthp, int *brk)
918 unsigned int options = contents->options;
919 struct xr_font *font;
920 bool merge_footnotes;
924 if (contents->n_footnotes == 0)
925 merge_footnotes = false;
926 else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT)
928 PangoAttrList *attrs;
931 font = &xr->fonts[XR_FONT_MARKER];
933 str_format_26adic (footnote_idx + 1, false, marker, sizeof marker);
934 pango_layout_set_text (font->layout, marker, strlen (marker));
936 attrs = pango_attr_list_new ();
937 pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
938 pango_layout_set_attributes (font->layout, attrs);
939 pango_attr_list_unref (attrs);
941 pango_layout_get_size (font->layout, &w, &h);
942 merge_footnotes = w > xr->cell_margin;
943 if (!merge_footnotes && clip[H][0] != clip[H][1])
945 cairo_save (xr->cairo);
947 cairo_translate (xr->cairo,
948 xr_to_pt (bb[H][1] + xr->x),
949 xr_to_pt (y + xr->y));
950 pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
951 pango_layout_set_width (font->layout, -1);
952 pango_cairo_show_layout (xr->cairo, font->layout);
953 cairo_restore (xr->cairo);
956 pango_layout_set_attributes (font->layout, NULL);
959 merge_footnotes = true;
961 font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
962 : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
963 : &xr->fonts[XR_FONT_PROPORTIONAL]);
965 length = strlen (contents->text);
968 PangoAttrList *attrs;
972 bb[H][1] += xr->cell_margin;
975 ds_extend (&s, length + contents->n_footnotes * 10);
976 ds_put_cstr (&s, contents->text);
977 for (i = 0; i < contents->n_footnotes; i++)
982 ds_put_byte (&s, ',');
983 str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker);
984 ds_put_cstr (&s, marker);
986 pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
989 attrs = pango_attr_list_new ();
990 add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
991 add_attr_with_start (
992 attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
993 pango_layout_set_attributes (font->layout, attrs);
994 pango_attr_list_unref (attrs);
997 pango_layout_set_text (font->layout, contents->text, -1);
999 pango_layout_set_alignment (
1001 ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1002 : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
1003 : PANGO_ALIGN_CENTER));
1004 pango_layout_set_width (
1006 bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1007 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1009 if (clip[H][0] != clip[H][1])
1011 cairo_save (xr->cairo);
1013 cairo_translate (xr->cairo,
1014 xr_to_pt (bb[H][0] + xr->x),
1015 xr_to_pt (y + xr->y));
1016 pango_cairo_show_layout (xr->cairo, font->layout);
1018 /* If enabled, this draws a blue rectangle around the extents of each
1019 line of text, which can be rather useful for debugging layout
1023 PangoLayoutIter *iter;
1024 iter = pango_layout_get_iter (font->layout);
1027 PangoRectangle extents;
1029 pango_layout_iter_get_line_extents (iter, &extents, NULL);
1030 cairo_save (xr->cairo);
1031 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1033 pango_to_xr (extents.x) - xr->x,
1034 pango_to_xr (extents.y) - xr->y,
1035 pango_to_xr (extents.x + extents.width) - xr->x,
1036 pango_to_xr (extents.y + extents.height) - xr->y);
1037 cairo_restore (xr->cairo);
1039 while (pango_layout_iter_next_line (iter));
1040 pango_layout_iter_free (iter);
1043 cairo_restore (xr->cairo);
1046 pango_layout_get_size (font->layout, &w, &h);
1047 w = pango_to_xr (w);
1048 h = pango_to_xr (h);
1051 if (y + h >= bb[V][1])
1053 PangoLayoutIter *iter;
1054 int best UNUSED = 0;
1056 /* Choose a breakpoint between lines instead of in the middle of one. */
1057 iter = pango_layout_get_iter (font->layout);
1060 PangoRectangle extents;
1064 pango_layout_iter_get_line_extents (iter, NULL, &extents);
1065 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1066 extents.x = pango_to_xr (extents.x);
1067 extents.y = pango_to_xr (y0);
1068 extents.width = pango_to_xr (extents.width);
1069 extents.height = pango_to_xr (y1 - y0);
1070 bottom = y + extents.y + extents.height;
1071 if (bottom < bb[V][1])
1073 if (brk && clip[H][0] != clip[H][1])
1080 while (pango_layout_iter_next_line (iter));
1082 /* If enabled, draws a green line across the chosen breakpoint, which can
1083 be useful for debugging issues with breaking. */
1086 if (best && !xr->nest)
1088 cairo_save (xr->cairo);
1089 cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1090 dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
1091 cairo_restore (xr->cairo);
1096 pango_layout_set_attributes (font->layout, NULL);
1101 xr_layout_cell_subtable (struct xr_driver *xr,
1102 const struct cell_contents *contents,
1103 int footnote_idx UNUSED,
1104 int bb[TABLE_N_AXES][2],
1105 int clip[TABLE_N_AXES][2], int *widthp, int *brk)
1107 int single_width, double_width;
1108 struct render_params params;
1109 struct render_pager *p;
1110 int r[TABLE_N_AXES][2];
1114 params.draw_line = xr_draw_line;
1115 params.measure_cell_width = xr_measure_cell_width;
1116 params.measure_cell_height = xr_measure_cell_height;
1117 params.adjust_break = NULL;
1118 params.draw_cell = xr_draw_cell;
1120 params.size[H] = bb[H][1] - bb[H][0];
1121 params.size[V] = bb[V][1] - bb[V][0];
1122 params.font_size[H] = xr->char_width;
1123 params.font_size[V] = xr->char_height;
1125 single_width = 2 * xr->line_gutter + xr->line_width;
1126 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
1127 for (i = 0; i < TABLE_N_AXES; i++)
1129 params.line_widths[i][RENDER_LINE_NONE] = 0;
1130 params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
1131 params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
1135 p = render_pager_create (¶ms, contents->table);
1136 width = render_pager_get_size (p, H);
1137 height = render_pager_get_size (p, V);
1138 if (bb[V][0] + height >= bb[V][1])
1139 *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
1141 /* r = intersect(bb, clip) - bb. */
1142 for (i = 0; i < TABLE_N_AXES; i++)
1144 r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
1145 r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
1148 if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
1150 unsigned int alignment = contents->options & TAB_ALIGNMENT;
1153 cairo_save (xr->cairo);
1156 if (alignment == TAB_RIGHT)
1157 xr->x += params.size[H] - width;
1158 else if (alignment == TAB_CENTER)
1159 xr->x += (params.size[H] - width) / 2;
1161 render_pager_draw_region (p, r[H][0], r[V][0],
1162 r[H][1] - r[H][0], r[V][1] - r[V][0]);
1165 cairo_restore (xr->cairo);
1167 render_pager_destroy (p);
1170 if (width > *widthp)
1172 return bb[V][0] + height;
1176 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1178 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1179 int *width, int *height, int *brk)
1181 int bb[TABLE_N_AXES][2];
1189 memcpy (bb, bb_, sizeof bb);
1191 /* If enabled, draws a blue rectangle around the cell extents, which can be
1192 useful for debugging layout. */
1195 if (clip[H][0] != clip[H][1])
1197 int offset = (xr->nest) * XR_POINT;
1199 cairo_save (xr->cairo);
1200 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1202 bb[H][0] + offset, bb[V][0] + offset,
1203 bb[H][1] - offset, bb[V][1] - offset);
1204 cairo_restore (xr->cairo);
1208 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1210 const struct cell_contents *contents = &cell->contents[i];
1216 bb[V][0] += xr->char_height / 2;
1217 if (bb[V][0] >= bb[V][1])
1224 bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip,
1225 bb[V][0], width, brk);
1227 bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx,
1228 bb, clip, width, brk);
1229 footnote_idx += contents->n_footnotes;
1231 *height = bb[V][0] - bb_[V][0];
1234 struct output_driver_factory pdf_driver_factory =
1235 { "pdf", "pspp.pdf", xr_pdf_create };
1236 struct output_driver_factory ps_driver_factory =
1237 { "ps", "pspp.ps", xr_ps_create };
1238 struct output_driver_factory svg_driver_factory =
1239 { "svg", "pspp.svg", xr_svg_create };
1241 static const struct output_driver_class cairo_driver_class =
1249 /* GUI rendering helpers. */
1253 struct output_item *item;
1256 struct render_pager *p;
1257 struct xr_driver *xr;
1260 #define CHART_WIDTH 500
1261 #define CHART_HEIGHT 375
1266 xr_driver_create (cairo_t *cairo, struct string_map *options)
1268 struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1269 if (!xr_set_cairo (xr, cairo))
1271 output_driver_destroy (&xr->driver);
1277 /* Destroy XR, which should have been created with xr_driver_create(). Any
1278 cairo_t added to XR is not destroyed, because it is owned by the client. */
1280 xr_driver_destroy (struct xr_driver *xr)
1285 output_driver_destroy (&xr->driver);
1289 static struct xr_rendering *
1290 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1292 struct table_item *table_item;
1293 struct xr_rendering *r;
1295 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1297 r = xr_rendering_create (xr, &table_item->output_item, cr);
1298 table_item_unref (table_item);
1304 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1306 if (is_table_item (xr->item))
1307 apply_options (xr->xr, o);
1310 struct xr_rendering *
1311 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1314 struct xr_rendering *r = NULL;
1316 if (is_text_item (item))
1317 r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1319 else if (is_message_item (item))
1321 const struct message_item *message_item = to_message_item (item);
1322 const struct msg *msg = message_item_get_msg (message_item);
1323 char *s = msg_to_string (msg, NULL);
1324 r = xr_rendering_create_text (xr, s, cr);
1327 else if (is_table_item (item))
1329 r = xzalloc (sizeof *r);
1330 r->item = output_item_ref (item);
1332 xr_set_cairo (xr, cr);
1333 r->p = render_pager_create (xr->params, to_table_item (item));
1335 else if (is_chart_item (item))
1337 r = xzalloc (sizeof *r);
1338 r->item = output_item_ref (item);
1345 xr_rendering_destroy (struct xr_rendering *r)
1349 output_item_unref (r->item);
1350 render_pager_destroy (r->p);
1356 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1358 if (is_table_item (r->item))
1360 *w = render_pager_get_size (r->p, H) / XR_POINT;
1361 *h = render_pager_get_size (r->p, V) / XR_POINT;
1370 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1371 double x, double y, double width, double height);
1373 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1374 and possibly some additional parts. */
1376 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1377 int x, int y, int w, int h)
1379 if (is_table_item (r->item))
1381 struct xr_driver *xr = r->xr;
1383 xr_set_cairo (xr, cr);
1386 render_pager_draw_region (r->p,
1387 x * XR_POINT, y * XR_POINT,
1388 w * XR_POINT, h * XR_POINT);
1391 xr_draw_chart (to_chart_item (r->item), cr,
1392 0, 0, CHART_WIDTH, CHART_HEIGHT);
1396 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1397 double x, double y, double width, double height)
1399 struct xrchart_geometry geom;
1402 cairo_translate (cr, x, y + height);
1403 cairo_scale (cr, 1.0, -1.0);
1404 xrchart_geometry_init (cr, &geom, width, height);
1405 if (is_boxplot (chart_item))
1406 xrchart_draw_boxplot (chart_item, cr, &geom);
1407 else if (is_histogram_chart (chart_item))
1408 xrchart_draw_histogram (chart_item, cr, &geom);
1409 else if (is_np_plot_chart (chart_item))
1410 xrchart_draw_np_plot (chart_item, cr, &geom);
1411 else if (is_piechart (chart_item))
1412 xrchart_draw_piechart (chart_item, cr, &geom);
1413 else if (is_roc_chart (chart_item))
1414 xrchart_draw_roc (chart_item, cr, &geom);
1415 else if (is_scree (chart_item))
1416 xrchart_draw_scree (chart_item, cr, &geom);
1417 else if (is_spreadlevel_plot_chart (chart_item))
1418 xrchart_draw_spreadlevel (chart_item, cr, &geom);
1419 else if (is_scatterplot_chart (chart_item))
1420 xrchart_draw_scatterplot (chart_item, cr, &geom);
1423 xrchart_geometry_free (cr, &geom);
1429 xr_draw_png_chart (const struct chart_item *item,
1430 const char *file_name_template, int number,
1431 const struct xr_color *fg,
1432 const struct xr_color *bg
1435 const int width = 640;
1436 const int length = 480;
1438 cairo_surface_t *surface;
1439 cairo_status_t status;
1440 const char *number_pos;
1444 number_pos = strchr (file_name_template, '#');
1445 if (number_pos != NULL)
1446 file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1447 file_name_template, number, number_pos + 1);
1449 file_name = xstrdup (file_name_template);
1451 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1452 cr = cairo_create (surface);
1454 cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1457 cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1459 xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1461 status = cairo_surface_write_to_png (surface, file_name);
1462 if (status != CAIRO_STATUS_SUCCESS)
1463 msg (ME, _("error writing output file `%s': %s"),
1464 file_name, cairo_status_to_string (status));
1467 cairo_surface_destroy (surface);
1472 struct xr_table_state
1474 struct xr_render_fsm fsm;
1475 struct table_item *table_item;
1476 struct render_pager *p;
1480 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1482 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1484 while (render_pager_has_next (ts->p))
1488 used = render_pager_draw_next (ts->p, xr->length - xr->y);
1501 xr_table_destroy (struct xr_render_fsm *fsm)
1503 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1505 table_item_unref (ts->table_item);
1506 render_pager_destroy (ts->p);
1510 static struct xr_render_fsm *
1511 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1513 struct xr_table_state *ts;
1515 ts = xmalloc (sizeof *ts);
1516 ts->fsm.render = xr_table_render;
1517 ts->fsm.destroy = xr_table_destroy;
1518 ts->table_item = table_item_ref (table_item);
1521 xr->y += xr->char_height;
1523 ts->p = render_pager_create (xr->params, table_item);
1528 struct xr_chart_state
1530 struct xr_render_fsm fsm;
1531 struct chart_item *chart_item;
1535 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1537 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1542 if (xr->cairo != NULL)
1543 xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1544 xr_to_pt (xr->width), xr_to_pt (xr->length));
1551 xr_chart_destroy (struct xr_render_fsm *fsm)
1553 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1555 chart_item_unref (cs->chart_item);
1559 static struct xr_render_fsm *
1560 xr_render_chart (const struct chart_item *chart_item)
1562 struct xr_chart_state *cs;
1564 cs = xmalloc (sizeof *cs);
1565 cs->fsm.render = xr_chart_render;
1566 cs->fsm.destroy = xr_chart_destroy;
1567 cs->chart_item = chart_item_ref (chart_item);
1573 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1579 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1581 /* Nothing to do. */
1584 static struct xr_render_fsm *
1585 xr_render_eject (void)
1587 static struct xr_render_fsm eject_renderer =
1593 return &eject_renderer;
1596 static struct xr_render_fsm *
1597 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1599 struct table_item *table_item;
1600 struct xr_render_fsm *fsm;
1602 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1604 fsm = xr_render_table (xr, table_item);
1605 table_item_unref (table_item);
1610 static struct xr_render_fsm *
1611 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1613 enum text_item_type type = text_item_get_type (text_item);
1614 const char *text = text_item_get_text (text_item);
1618 case TEXT_ITEM_TITLE:
1620 xr->title = xstrdup (text);
1623 case TEXT_ITEM_SUBTITLE:
1624 free (xr->subtitle);
1625 xr->subtitle = xstrdup (text);
1628 case TEXT_ITEM_COMMAND_CLOSE:
1631 case TEXT_ITEM_BLANK_LINE:
1633 xr->y += xr->char_height;
1636 case TEXT_ITEM_EJECT_PAGE:
1638 return xr_render_eject ();
1642 return xr_create_text_renderer (xr, text);
1648 static struct xr_render_fsm *
1649 xr_render_message (struct xr_driver *xr,
1650 const struct message_item *message_item)
1652 const struct msg *msg = message_item_get_msg (message_item);
1653 struct xr_render_fsm *fsm;
1656 s = msg_to_string (msg, xr->command_name);
1657 fsm = xr_create_text_renderer (xr, s);
1663 static struct xr_render_fsm *
1664 xr_render_output_item (struct xr_driver *xr,
1665 const struct output_item *output_item)
1667 if (is_table_item (output_item))
1668 return xr_render_table (xr, to_table_item (output_item));
1669 else if (is_chart_item (output_item))
1670 return xr_render_chart (to_chart_item (output_item));
1671 else if (is_text_item (output_item))
1672 return xr_render_text (xr, to_text_item (output_item));
1673 else if (is_message_item (output_item))
1674 return xr_render_message (xr, to_message_item (output_item));