1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 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 "data/file-handle-def.h"
29 #include "output/cairo-chart.h"
30 #include "output/chart-item-provider.h"
31 #include "output/charts/boxplot.h"
32 #include "output/charts/np-plot.h"
33 #include "output/charts/piechart.h"
34 #include "output/charts/barchart.h"
35 #include "output/charts/plot-hist.h"
36 #include "output/charts/roc-chart.h"
37 #include "output/charts/spreadlevel-plot.h"
38 #include "output/charts/scree.h"
39 #include "output/charts/scatterplot.h"
40 #include "output/driver-provider.h"
41 #include "output/message-item.h"
42 #include "output/options.h"
43 #include "output/render.h"
44 #include "output/tab.h"
45 #include "output/table-item.h"
46 #include "output/table.h"
47 #include "output/text-item.h"
49 #include <cairo/cairo-pdf.h>
50 #include <cairo/cairo-ps.h>
51 #include <cairo/cairo-svg.h>
52 #include <cairo/cairo.h>
54 #include <pango/pango-font.h>
55 #include <pango/pango-layout.h>
56 #include <pango/pango.h>
57 #include <pango/pangocairo.h>
60 #include "gl/intprops.h"
61 #include "gl/minmax.h"
62 #include "gl/xalloc.h"
65 #define _(msgid) gettext (msgid)
67 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
71 /* The unit used for internal measurements is inch/(72 * XR_POINT). */
72 #define XR_POINT PANGO_SCALE
74 /* Conversions to and from points. */
78 return x / (double) XR_POINT;
99 /* A font for use with Cairo. */
102 PangoFontDescription *desc;
106 /* An output item whose rendering is in progress. */
109 /* Renders as much of itself as it can on the current page. Returns true
110 if rendering is complete, false if the output item needs another
112 bool (*render) (struct xr_render_fsm *, struct xr_driver *);
114 /* Destroys the output item. */
115 void (*destroy) (struct xr_render_fsm *);
118 /* Cairo output driver. */
121 struct output_driver driver;
123 /* User parameters. */
124 struct xr_font fonts[XR_N_FONTS];
126 int width; /* Page width minus margins. */
127 int length; /* Page length minus margins and header. */
129 int left_margin; /* Left margin in inch/(72 * XR_POINT). */
130 int right_margin; /* Right margin in inch/(72 * XR_POINT). */
131 int top_margin; /* Top margin in inch/(72 * XR_POINT). */
132 int bottom_margin; /* Bottom margin in inch/(72 * XR_POINT). */
134 int line_gutter; /* Space around lines. */
135 int line_space; /* Space between lines. */
136 int line_width; /* Width of lines. */
140 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
142 struct xr_color bg; /* Background color */
143 struct xr_color fg; /* Foreground color */
145 /* Internal state. */
146 struct render_params *params;
147 int char_width, char_height;
152 int page_number; /* Current page number. */
154 struct xr_render_fsm *fsm;
158 static const struct output_driver_class cairo_driver_class;
160 static void xr_driver_destroy_fsm (struct xr_driver *);
161 static void xr_driver_run_fsm (struct xr_driver *);
163 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
164 enum render_line_style styles[TABLE_N_AXES][2]);
165 static void xr_measure_cell_width (void *, const struct table_cell *,
166 int footnote_idx, int *min, int *max);
167 static int xr_measure_cell_height (void *, const struct table_cell *,
168 int footnote_idx, int width);
169 static void xr_draw_cell (void *, const struct table_cell *, int footnote_idx,
170 int bb[TABLE_N_AXES][2],
171 int clip[TABLE_N_AXES][2]);
172 static int xr_adjust_break (void *, const struct table_cell *, int footnote_idx,
173 int width, int height);
175 static struct xr_render_fsm *xr_render_output_item (
176 struct xr_driver *, const struct output_item *);
178 /* Output driver basics. */
180 static struct xr_driver *
181 xr_driver_cast (struct output_driver *driver)
183 assert (driver->class == &cairo_driver_class);
184 return UP_CAST (driver, struct xr_driver, driver);
187 static struct driver_option *
188 opt (struct output_driver *d, struct string_map *options, const char *key,
189 const char *default_value)
191 return driver_option_get (d, options, key, default_value);
194 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
195 Currently, the input string must be of the form "#RRRRGGGGBBBB"
196 Future implementations might allow things like "yellow" and
197 "sky-blue-ultra-brown"
200 parse_color (struct output_driver *d, struct string_map *options,
201 const char *key, const char *default_value,
202 struct xr_color *color)
204 int red, green, blue;
205 char *string = parse_string (opt (d, options, key, default_value));
207 if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
209 /* If the parsed option string fails, then try the default value */
210 if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
212 /* ... and if that fails set everything to zero */
213 red = green = blue = 0;
219 /* Convert 16 bit ints to float */
220 color->red = red / (double) 0xFFFF;
221 color->green = green / (double) 0xFFFF;
222 color->blue = blue / (double) 0xFFFF;
225 static PangoFontDescription *
226 parse_font (struct output_driver *d, struct string_map *options,
227 const char *key, const char *default_value,
230 PangoFontDescription *desc;
233 /* Parse KEY as a font description. */
234 string = parse_string (opt (d, options, key, default_value));
235 desc = pango_font_description_from_string (string);
238 msg (MW, _("`%s': bad font specification"), string);
240 /* Fall back to DEFAULT_VALUE, which had better be a valid font
242 desc = pango_font_description_from_string (default_value);
243 assert (desc != NULL);
247 /* If the font description didn't include an explicit font size, then set it
248 to DEFAULT_SIZE, which is in inch/72000 units. */
249 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
250 pango_font_description_set_size (desc,
251 (default_size / 1000.0) * PANGO_SCALE);
258 apply_options (struct xr_driver *xr, struct string_map *o)
260 struct output_driver *d = &xr->driver;
262 /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
263 int left_margin, right_margin;
264 int top_margin, bottom_margin;
265 int paper_width, paper_length;
267 int min_break[TABLE_N_AXES];
269 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
270 const double scale = XR_POINT / 1000.;
274 for (i = 0; i < XR_N_FONTS; i++)
276 struct xr_font *font = &xr->fonts[i];
278 if (font->desc != NULL)
279 pango_font_description_free (font->desc);
282 font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
283 xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
285 xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
287 xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
288 "serif italic", font_size);
289 xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "serif",
290 font_size * PANGO_SCALE_X_SMALL);
292 xr->line_gutter = XR_POINT / 2;
293 xr->line_space = XR_POINT;
294 xr->line_width = XR_POINT / 2;
297 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
298 parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
300 /* Get dimensions. */
301 parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
302 left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
303 right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
304 top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
305 bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
307 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
308 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
310 /* Convert to inch/(XR_POINT * 72). */
311 xr->left_margin = left_margin * scale;
312 xr->right_margin = right_margin * scale;
313 xr->top_margin = top_margin * scale;
314 xr->bottom_margin = bottom_margin * scale;
315 xr->width = (paper_width - left_margin - right_margin) * scale;
316 xr->length = (paper_length - top_margin - bottom_margin) * scale;
317 xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
318 xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
321 static struct xr_driver *
322 xr_allocate (const char *name, int device_type, struct string_map *o)
324 struct xr_driver *xr = xzalloc (sizeof *xr);
325 struct output_driver *d = &xr->driver;
327 output_driver_init (d, &cairo_driver_class, name, device_type);
329 apply_options (xr, o);
335 pango_to_xr (int pango)
337 return (XR_POINT != PANGO_SCALE
338 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
345 return (XR_POINT != PANGO_SCALE
346 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
351 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
357 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
361 for (i = 0; i < XR_N_FONTS; i++)
363 struct xr_font *font = &xr->fonts[i];
364 int char_width, char_height;
366 font->layout = pango_cairo_create_layout (cairo);
367 pango_layout_set_font_description (font->layout, font->desc);
369 pango_layout_set_text (font->layout, "0", 1);
370 pango_layout_get_size (font->layout, &char_width, &char_height);
371 xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
372 xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
374 xr->cell_margin = xr->char_width;
376 if (xr->params == NULL)
378 int single_width, double_width;
380 xr->params = xmalloc (sizeof *xr->params);
381 xr->params->draw_line = xr_draw_line;
382 xr->params->measure_cell_width = xr_measure_cell_width;
383 xr->params->measure_cell_height = xr_measure_cell_height;
384 xr->params->adjust_break = xr_adjust_break;
385 xr->params->draw_cell = xr_draw_cell;
386 xr->params->aux = xr;
387 xr->params->size[H] = xr->width;
388 xr->params->size[V] = xr->length;
389 xr->params->font_size[H] = xr->char_width;
390 xr->params->font_size[V] = xr->char_height;
392 single_width = 2 * xr->line_gutter + xr->line_width;
393 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
394 for (i = 0; i < TABLE_N_AXES; i++)
396 xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
397 xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
398 xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
401 for (i = 0; i < TABLE_N_AXES; i++)
402 xr->params->min_break[i] = xr->min_break[i];
405 cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
410 static struct output_driver *
411 xr_create (const char *file_name, enum settings_output_devices device_type,
412 struct string_map *o, enum xr_output_type file_type)
414 enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
415 struct xr_driver *xr;
416 cairo_surface_t *surface;
417 cairo_status_t status;
418 double width_pt, length_pt;
420 xr = xr_allocate (file_name, device_type, o);
422 width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
423 length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
424 if (file_type == XR_PDF)
425 surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
426 else if (file_type == XR_PS)
427 surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
428 else if (file_type == XR_SVG)
429 surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
433 status = cairo_surface_status (surface);
434 if (status != CAIRO_STATUS_SUCCESS)
436 msg (ME, _("error opening output file `%s': %s"),
437 file_name, cairo_status_to_string (status));
438 cairo_surface_destroy (surface);
442 xr->cairo = cairo_create (surface);
443 cairo_surface_destroy (surface);
445 if (!xr_set_cairo (xr, xr->cairo))
448 cairo_save (xr->cairo);
449 xr_driver_next_page (xr, xr->cairo);
451 if (xr->width / xr->char_width < MIN_WIDTH)
453 msg (ME, _("The defined page is not wide enough to hold at least %d "
454 "characters in the default font. In fact, there's only "
455 "room for %d characters."),
457 xr->width / xr->char_width);
461 if (xr->length / xr->char_height < MIN_LENGTH)
463 msg (ME, _("The defined page is not long enough to hold at least %d "
464 "lines in the default font. In fact, there's only "
465 "room for %d lines."),
467 xr->length / xr->char_height);
474 output_driver_destroy (&xr->driver);
478 static struct output_driver *
479 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
480 struct string_map *o)
482 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
487 static struct output_driver *
488 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
489 struct string_map *o)
491 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
496 static struct output_driver *
497 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
498 struct string_map *o)
500 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
506 xr_destroy (struct output_driver *driver)
508 struct xr_driver *xr = xr_driver_cast (driver);
511 xr_driver_destroy_fsm (xr);
513 if (xr->cairo != NULL)
515 cairo_status_t status;
517 cairo_surface_finish (cairo_get_target (xr->cairo));
518 status = cairo_status (xr->cairo);
519 if (status != CAIRO_STATUS_SUCCESS)
520 msg (ME, _("error drawing output for %s driver: %s"),
521 output_driver_get_name (driver),
522 cairo_status_to_string (status));
523 cairo_destroy (xr->cairo);
526 free (xr->command_name);
527 for (i = 0; i < XR_N_FONTS; i++)
529 struct xr_font *font = &xr->fonts[i];
531 if (font->desc != NULL)
532 pango_font_description_free (font->desc);
533 if (font->layout != NULL)
534 g_object_unref (font->layout);
542 xr_flush (struct output_driver *driver)
544 struct xr_driver *xr = xr_driver_cast (driver);
546 cairo_surface_flush (cairo_get_target (xr->cairo));
550 xr_submit (struct output_driver *driver, const struct output_item *output_item)
552 struct xr_driver *xr = xr_driver_cast (driver);
554 output_driver_track_current_command (output_item, &xr->command_name);
556 xr_driver_output_item (xr, output_item);
557 while (xr_driver_need_new_page (xr))
559 cairo_restore (xr->cairo);
560 cairo_show_page (xr->cairo);
561 cairo_save (xr->cairo);
562 xr_driver_next_page (xr, xr->cairo);
566 /* Functions for rendering a series of output items to a series of Cairo
567 contexts, with pagination.
569 Used by PSPPIRE for printing, and by the basic Cairo output driver above as
570 its underlying implementation.
572 See the big comment in cairo.h for intended usage. */
574 /* Gives new page CAIRO to XR for output. */
576 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
579 cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
580 cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
582 cairo_restore (cairo);
584 cairo_translate (cairo,
585 xr_to_pt (xr->left_margin),
586 xr_to_pt (xr->top_margin));
591 xr_driver_run_fsm (xr);
594 /* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of
595 rendering a previous output item, that is, only if xr_driver_need_new_page()
598 xr_driver_output_item (struct xr_driver *xr,
599 const struct output_item *output_item)
601 assert (xr->fsm == NULL);
602 xr->fsm = xr_render_output_item (xr, output_item);
603 xr_driver_run_fsm (xr);
606 /* Returns true if XR is in the middle of rendering an output item and needs a
607 new page to be appended using xr_driver_next_page() to make progress,
610 xr_driver_need_new_page (const struct xr_driver *xr)
612 return xr->fsm != NULL;
615 /* Returns true if the current page doesn't have any content yet. */
617 xr_driver_is_page_blank (const struct xr_driver *xr)
623 xr_driver_destroy_fsm (struct xr_driver *xr)
627 xr->fsm->destroy (xr->fsm);
633 xr_driver_run_fsm (struct xr_driver *xr)
635 if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
636 xr_driver_destroy_fsm (xr);
640 xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx,
641 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
642 int *width, int *height, int *brk);
645 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
647 cairo_new_path (xr->cairo);
648 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
649 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
650 cairo_stroke (xr->cairo);
654 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
656 cairo_new_path (xr->cairo);
657 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
658 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
659 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
660 cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
661 cairo_close_path (xr->cairo);
662 cairo_stroke (xr->cairo);
665 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
666 shortening it to X0...X1 if SHORTEN is true.
667 Draws a horizontal line X1...X3 at Y if RIGHT says so,
668 shortening it to X2...X3 if SHORTEN is true. */
670 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
671 enum render_line_style left, enum render_line_style right,
674 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
675 dump_line (xr, x0, y, x3, y);
678 if (left != RENDER_LINE_NONE)
679 dump_line (xr, x0, y, shorten ? x1 : x2, y);
680 if (right != RENDER_LINE_NONE)
681 dump_line (xr, shorten ? x2 : x1, y, x3, y);
685 /* Draws a vertical line Y0...Y2 at X if TOP says so,
686 shortening it to Y0...Y1 if SHORTEN is true.
687 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
688 shortening it to Y2...Y3 if SHORTEN is true. */
690 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
691 enum render_line_style top, enum render_line_style bottom,
694 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
695 dump_line (xr, x, y0, x, y3);
698 if (top != RENDER_LINE_NONE)
699 dump_line (xr, x, y0, x, shorten ? y1 : y2);
700 if (bottom != RENDER_LINE_NONE)
701 dump_line (xr, x, shorten ? y2 : y1, x, y3);
706 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
707 enum render_line_style styles[TABLE_N_AXES][2])
709 const int x0 = bb[H][0];
710 const int y0 = bb[V][0];
711 const int x3 = bb[H][1];
712 const int y3 = bb[V][1];
713 const int top = styles[H][0];
714 const int bottom = styles[H][1];
715 const int start_of_line = render_direction_rtl() ? styles[V][1]: styles[V][0];
716 const int end_of_line = render_direction_rtl() ? styles[V][0]: styles[V][1];
718 /* The algorithm here is somewhat subtle, to allow it to handle
719 all the kinds of intersections that we need.
721 Three additional ordinates are assigned along the x axis. The
722 first is xc, midway between x0 and x3. The others are x1 and
723 x2; for a single vertical line these are equal to xc, and for
724 a double vertical line they are the ordinates of the left and
725 right half of the double line.
727 yc, y1, and y2 are assigned similarly along the y axis.
729 The following diagram shows the coordinate system and output
730 for double top and bottom lines, single left line, and no
734 y0 ________________________
740 y1 = y2 = yc |######### # |
745 y3 |________#_____#_______|
747 struct xr_driver *xr = xr_;
749 /* Offset from center of each line in a pair of double lines. */
750 int double_line_ofs = (xr->line_space + xr->line_width) / 2;
752 /* Are the lines along each axis single or double?
753 (It doesn't make sense to have different kinds of line on the
754 same axis, so we don't try to gracefully handle that case.) */
755 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
756 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
758 /* When horizontal lines are doubled,
759 the left-side line along y1 normally runs from x0 to x2,
760 and the right-side line along y1 from x3 to x1.
761 If the top-side line is also doubled, we shorten the y1 lines,
762 so that the left-side line runs only to x1,
763 and the right-side line only to x2.
764 Otherwise, the horizontal line at y = y1 below would cut off
765 the intersection, which looks ugly:
767 y0 ________________________
772 y1 |######### ########|
775 y2 |######################|
778 y3 |______________________|
779 It is more of a judgment call when the horizontal line is
780 single. We actually choose to cut off the line anyhow, as
781 shown in the first diagram above.
783 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
784 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
785 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
786 int horz_line_ofs = double_vert ? double_line_ofs : 0;
787 int xc = (x0 + x3) / 2;
788 int x1 = xc - horz_line_ofs;
789 int x2 = xc + horz_line_ofs;
791 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
792 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
793 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
794 int vert_line_ofs = double_horz ? double_line_ofs : 0;
795 int yc = (y0 + y3) / 2;
796 int y1 = yc - vert_line_ofs;
797 int y2 = yc + vert_line_ofs;
800 horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line, shorten_yc_line);
803 horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line, shorten_y1_lines);
804 horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line, shorten_y2_lines);
808 vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
811 vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
812 vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
817 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
818 int footnote_idx, int *min_width, int *max_width)
820 struct xr_driver *xr = xr_;
821 int bb[TABLE_N_AXES][2];
822 int clip[TABLE_N_AXES][2];
829 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
830 xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL);
833 xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL);
836 *min_width += xr->cell_margin * 2;
838 *max_width += xr->cell_margin * 2;
842 xr_measure_cell_height (void *xr_, const struct table_cell *cell,
843 int footnote_idx, int width)
845 struct xr_driver *xr = xr_;
846 int bb[TABLE_N_AXES][2];
847 int clip[TABLE_N_AXES][2];
851 bb[H][1] = width - xr->cell_margin * 2;
854 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
855 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL);
860 xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx,
861 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
863 struct xr_driver *xr = xr_;
866 bb[H][0] += xr->cell_margin;
867 bb[H][1] -= xr->cell_margin;
868 if (bb[H][0] >= bb[H][1])
870 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
874 xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx,
875 int width, int height)
877 struct xr_driver *xr = xr_;
878 int bb[TABLE_N_AXES][2];
879 int clip[TABLE_N_AXES][2];
882 if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height)
886 bb[H][1] = width - 2 * xr->cell_margin;
891 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
892 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
897 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
899 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
901 double x0 = xr_to_pt (clip[H][0] + xr->x);
902 double y0 = xr_to_pt (clip[V][0] + xr->y);
903 double x1 = xr_to_pt (clip[H][1] + xr->x);
904 double y1 = xr_to_pt (clip[V][1] + xr->y);
906 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
907 cairo_clip (xr->cairo);
912 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
914 attr->start_index = start_index;
915 pango_attr_list_insert (list, attr);
919 xr_layout_cell_text (struct xr_driver *xr,
920 const struct cell_contents *contents, int footnote_idx,
921 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
922 int y, int *widthp, int *brk)
924 unsigned int options = contents->options;
925 struct xr_font *font;
926 bool merge_footnotes;
930 if (contents->n_footnotes == 0)
931 merge_footnotes = false;
932 else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT)
934 PangoAttrList *attrs;
937 font = &xr->fonts[XR_FONT_MARKER];
939 str_format_26adic (footnote_idx + 1, false, marker, sizeof marker);
940 pango_layout_set_text (font->layout, marker, strlen (marker));
942 attrs = pango_attr_list_new ();
943 pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
944 pango_layout_set_attributes (font->layout, attrs);
945 pango_attr_list_unref (attrs);
947 pango_layout_get_size (font->layout, &w, &h);
948 merge_footnotes = w > xr->cell_margin;
949 if (!merge_footnotes && clip[H][0] != clip[H][1])
951 cairo_save (xr->cairo);
953 cairo_translate (xr->cairo,
954 xr_to_pt (bb[H][1] + xr->x),
955 xr_to_pt (y + xr->y));
956 pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
957 pango_layout_set_width (font->layout, -1);
958 pango_cairo_show_layout (xr->cairo, font->layout);
959 cairo_restore (xr->cairo);
962 pango_layout_set_attributes (font->layout, NULL);
965 merge_footnotes = true;
967 font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
968 : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
969 : &xr->fonts[XR_FONT_PROPORTIONAL]);
971 length = strlen (contents->text);
974 PangoAttrList *attrs;
978 bb[H][1] += xr->cell_margin;
981 ds_extend (&s, length + contents->n_footnotes * 10);
982 ds_put_cstr (&s, contents->text);
983 for (i = 0; i < contents->n_footnotes; i++)
988 ds_put_byte (&s, ',');
989 str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker);
990 ds_put_cstr (&s, marker);
992 pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
995 attrs = pango_attr_list_new ();
996 add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
997 add_attr_with_start (
998 attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
999 pango_layout_set_attributes (font->layout, attrs);
1000 pango_attr_list_unref (attrs);
1003 pango_layout_set_text (font->layout, contents->text, -1);
1005 pango_layout_set_alignment (
1007 ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1008 : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
1009 : PANGO_ALIGN_CENTER));
1010 pango_layout_set_width (
1012 bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1013 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1015 if (clip[H][0] != clip[H][1])
1017 cairo_save (xr->cairo);
1019 cairo_translate (xr->cairo,
1020 xr_to_pt (bb[H][0] + xr->x),
1021 xr_to_pt (y + xr->y));
1022 pango_cairo_show_layout (xr->cairo, font->layout);
1024 /* If enabled, this draws a blue rectangle around the extents of each
1025 line of text, which can be rather useful for debugging layout
1029 PangoLayoutIter *iter;
1030 iter = pango_layout_get_iter (font->layout);
1033 PangoRectangle extents;
1035 pango_layout_iter_get_line_extents (iter, &extents, NULL);
1036 cairo_save (xr->cairo);
1037 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1039 pango_to_xr (extents.x) - xr->x,
1040 pango_to_xr (extents.y) - xr->y,
1041 pango_to_xr (extents.x + extents.width) - xr->x,
1042 pango_to_xr (extents.y + extents.height) - xr->y);
1043 cairo_restore (xr->cairo);
1045 while (pango_layout_iter_next_line (iter));
1046 pango_layout_iter_free (iter);
1049 cairo_restore (xr->cairo);
1052 pango_layout_get_size (font->layout, &w, &h);
1053 w = pango_to_xr (w);
1054 h = pango_to_xr (h);
1057 if (y + h >= bb[V][1])
1059 PangoLayoutIter *iter;
1060 int best UNUSED = 0;
1062 /* Choose a breakpoint between lines instead of in the middle of one. */
1063 iter = pango_layout_get_iter (font->layout);
1066 PangoRectangle extents;
1070 pango_layout_iter_get_line_extents (iter, NULL, &extents);
1071 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1072 extents.x = pango_to_xr (extents.x);
1073 extents.y = pango_to_xr (y0);
1074 extents.width = pango_to_xr (extents.width);
1075 extents.height = pango_to_xr (y1 - y0);
1076 bottom = y + extents.y + extents.height;
1077 if (bottom < bb[V][1])
1079 if (brk && clip[H][0] != clip[H][1])
1086 while (pango_layout_iter_next_line (iter));
1087 pango_layout_iter_free (iter);
1089 /* If enabled, draws a green line across the chosen breakpoint, which can
1090 be useful for debugging issues with breaking. */
1093 if (best && !xr->nest)
1095 cairo_save (xr->cairo);
1096 cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1097 dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
1098 cairo_restore (xr->cairo);
1103 pango_layout_set_attributes (font->layout, NULL);
1108 xr_layout_cell_subtable (struct xr_driver *xr,
1109 const struct cell_contents *contents,
1110 int footnote_idx UNUSED,
1111 int bb[TABLE_N_AXES][2],
1112 int clip[TABLE_N_AXES][2], int *widthp, int *brk)
1114 int single_width, double_width;
1115 struct render_params params;
1116 struct render_pager *p;
1117 int r[TABLE_N_AXES][2];
1121 params.draw_line = xr_draw_line;
1122 params.measure_cell_width = xr_measure_cell_width;
1123 params.measure_cell_height = xr_measure_cell_height;
1124 params.adjust_break = NULL;
1125 params.draw_cell = xr_draw_cell;
1127 params.size[H] = bb[H][1] - bb[H][0];
1128 params.size[V] = bb[V][1] - bb[V][0];
1129 params.font_size[H] = xr->char_width;
1130 params.font_size[V] = xr->char_height;
1132 single_width = 2 * xr->line_gutter + xr->line_width;
1133 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
1134 for (i = 0; i < TABLE_N_AXES; i++)
1136 params.line_widths[i][RENDER_LINE_NONE] = 0;
1137 params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
1138 params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
1142 p = render_pager_create (¶ms, contents->table);
1143 width = render_pager_get_size (p, H);
1144 height = render_pager_get_size (p, V);
1145 if (bb[V][0] + height >= bb[V][1])
1146 *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
1148 /* r = intersect(bb, clip) - bb. */
1149 for (i = 0; i < TABLE_N_AXES; i++)
1151 r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
1152 r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
1155 if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
1157 unsigned int alignment = contents->options & TAB_ALIGNMENT;
1160 cairo_save (xr->cairo);
1163 if (alignment == TAB_RIGHT)
1164 xr->x += params.size[H] - width;
1165 else if (alignment == TAB_CENTER)
1166 xr->x += (params.size[H] - width) / 2;
1168 render_pager_draw_region (p, r[H][0], r[V][0],
1169 r[H][1] - r[H][0], r[V][1] - r[V][0]);
1172 cairo_restore (xr->cairo);
1174 render_pager_destroy (p);
1177 if (width > *widthp)
1179 return bb[V][0] + height;
1183 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1185 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1186 int *width, int *height, int *brk)
1188 int bb[TABLE_N_AXES][2];
1196 memcpy (bb, bb_, sizeof bb);
1198 /* If enabled, draws a blue rectangle around the cell extents, which can be
1199 useful for debugging layout. */
1202 if (clip[H][0] != clip[H][1])
1204 int offset = (xr->nest) * XR_POINT;
1206 cairo_save (xr->cairo);
1207 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1209 bb[H][0] + offset, bb[V][0] + offset,
1210 bb[H][1] - offset, bb[V][1] - offset);
1211 cairo_restore (xr->cairo);
1215 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1217 const struct cell_contents *contents = &cell->contents[i];
1223 bb[V][0] += xr->char_height / 2;
1224 if (bb[V][0] >= bb[V][1])
1231 bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip,
1232 bb[V][0], width, brk);
1234 bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx,
1235 bb, clip, width, brk);
1236 footnote_idx += contents->n_footnotes;
1238 *height = bb[V][0] - bb_[V][0];
1241 struct output_driver_factory pdf_driver_factory =
1242 { "pdf", "pspp.pdf", xr_pdf_create };
1243 struct output_driver_factory ps_driver_factory =
1244 { "ps", "pspp.ps", xr_ps_create };
1245 struct output_driver_factory svg_driver_factory =
1246 { "svg", "pspp.svg", xr_svg_create };
1248 static const struct output_driver_class cairo_driver_class =
1256 /* GUI rendering helpers. */
1260 struct output_item *item;
1263 struct render_pager *p;
1264 struct xr_driver *xr;
1267 #define CHART_WIDTH 500
1268 #define CHART_HEIGHT 375
1273 xr_driver_create (cairo_t *cairo, struct string_map *options)
1275 struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1276 if (!xr_set_cairo (xr, cairo))
1278 output_driver_destroy (&xr->driver);
1284 /* Destroy XR, which should have been created with xr_driver_create(). Any
1285 cairo_t added to XR is not destroyed, because it is owned by the client. */
1287 xr_driver_destroy (struct xr_driver *xr)
1292 output_driver_destroy (&xr->driver);
1296 static struct xr_rendering *
1297 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1299 struct table_item *table_item;
1300 struct xr_rendering *r;
1302 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1304 r = xr_rendering_create (xr, &table_item->output_item, cr);
1305 table_item_unref (table_item);
1311 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1313 if (is_table_item (xr->item))
1314 apply_options (xr->xr, o);
1317 struct xr_rendering *
1318 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1321 struct xr_rendering *r = NULL;
1323 if (is_text_item (item))
1324 r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1326 else if (is_message_item (item))
1328 const struct message_item *message_item = to_message_item (item);
1329 const struct msg *msg = message_item_get_msg (message_item);
1330 char *s = msg_to_string (msg, NULL);
1331 r = xr_rendering_create_text (xr, s, cr);
1334 else if (is_table_item (item))
1336 r = xzalloc (sizeof *r);
1337 r->item = output_item_ref (item);
1339 xr_set_cairo (xr, cr);
1340 r->p = render_pager_create (xr->params, to_table_item (item));
1342 else if (is_chart_item (item))
1344 r = xzalloc (sizeof *r);
1345 r->item = output_item_ref (item);
1352 xr_rendering_destroy (struct xr_rendering *r)
1356 output_item_unref (r->item);
1357 render_pager_destroy (r->p);
1363 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1365 if (is_table_item (r->item))
1367 *w = render_pager_get_size (r->p, H) / XR_POINT;
1368 *h = render_pager_get_size (r->p, V) / XR_POINT;
1377 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1378 double x, double y, double width, double height);
1382 xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
1384 if (is_table_item (r->item))
1386 struct xr_driver *xr = r->xr;
1388 xr_set_cairo (xr, cr);
1390 render_pager_draw (r->p);
1394 xr_draw_chart (to_chart_item (r->item), cr,
1395 0, 0, CHART_WIDTH, CHART_HEIGHT);
1399 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1400 double x, double y, double width, double height)
1402 struct xrchart_geometry geom;
1405 cairo_translate (cr, x, y + height);
1406 cairo_scale (cr, 1.0, -1.0);
1407 xrchart_geometry_init (cr, &geom, width, height);
1408 if (is_boxplot (chart_item))
1409 xrchart_draw_boxplot (chart_item, cr, &geom);
1410 else if (is_histogram_chart (chart_item))
1411 xrchart_draw_histogram (chart_item, cr, &geom);
1412 else if (is_np_plot_chart (chart_item))
1413 xrchart_draw_np_plot (chart_item, cr, &geom);
1414 else if (is_piechart (chart_item))
1415 xrchart_draw_piechart (chart_item, cr, &geom);
1416 else if (is_barchart (chart_item))
1417 xrchart_draw_barchart (chart_item, cr, &geom);
1418 else if (is_roc_chart (chart_item))
1419 xrchart_draw_roc (chart_item, cr, &geom);
1420 else if (is_scree (chart_item))
1421 xrchart_draw_scree (chart_item, cr, &geom);
1422 else if (is_spreadlevel_plot_chart (chart_item))
1423 xrchart_draw_spreadlevel (chart_item, cr, &geom);
1424 else if (is_scatterplot_chart (chart_item))
1425 xrchart_draw_scatterplot (chart_item, cr, &geom);
1428 xrchart_geometry_free (cr, &geom);
1434 xr_draw_png_chart (const struct chart_item *item,
1435 const char *file_name_template, int number,
1436 const struct xr_color *fg,
1437 const struct xr_color *bg
1440 const int width = 640;
1441 const int length = 480;
1443 cairo_surface_t *surface;
1444 cairo_status_t status;
1445 const char *number_pos;
1449 number_pos = strchr (file_name_template, '#');
1450 if (number_pos != NULL)
1451 file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1452 file_name_template, number, number_pos + 1);
1454 file_name = xstrdup (file_name_template);
1456 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1457 cr = cairo_create (surface);
1459 cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1462 cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1464 xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1466 status = cairo_surface_write_to_png (surface, file_name);
1467 if (status != CAIRO_STATUS_SUCCESS)
1468 msg (ME, _("error writing output file `%s': %s"),
1469 file_name, cairo_status_to_string (status));
1472 cairo_surface_destroy (surface);
1477 struct xr_table_state
1479 struct xr_render_fsm fsm;
1480 struct table_item *table_item;
1481 struct render_pager *p;
1485 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1487 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1489 while (render_pager_has_next (ts->p))
1493 used = render_pager_draw_next (ts->p, xr->length - xr->y);
1506 xr_table_destroy (struct xr_render_fsm *fsm)
1508 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1510 table_item_unref (ts->table_item);
1511 render_pager_destroy (ts->p);
1515 static struct xr_render_fsm *
1516 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1518 struct xr_table_state *ts;
1520 ts = xmalloc (sizeof *ts);
1521 ts->fsm.render = xr_table_render;
1522 ts->fsm.destroy = xr_table_destroy;
1523 ts->table_item = table_item_ref (table_item);
1526 xr->y += xr->char_height;
1528 ts->p = render_pager_create (xr->params, table_item);
1533 struct xr_chart_state
1535 struct xr_render_fsm fsm;
1536 struct chart_item *chart_item;
1540 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1542 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1547 if (xr->cairo != NULL)
1548 xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1549 xr_to_pt (xr->width), xr_to_pt (xr->length));
1556 xr_chart_destroy (struct xr_render_fsm *fsm)
1558 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1560 chart_item_unref (cs->chart_item);
1564 static struct xr_render_fsm *
1565 xr_render_chart (const struct chart_item *chart_item)
1567 struct xr_chart_state *cs;
1569 cs = xmalloc (sizeof *cs);
1570 cs->fsm.render = xr_chart_render;
1571 cs->fsm.destroy = xr_chart_destroy;
1572 cs->chart_item = chart_item_ref (chart_item);
1578 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1584 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1586 /* Nothing to do. */
1589 static struct xr_render_fsm *
1590 xr_render_eject (void)
1592 static struct xr_render_fsm eject_renderer =
1598 return &eject_renderer;
1601 static struct xr_render_fsm *
1602 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1604 struct table_item *table_item;
1605 struct xr_render_fsm *fsm;
1607 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1609 fsm = xr_render_table (xr, table_item);
1610 table_item_unref (table_item);
1615 static struct xr_render_fsm *
1616 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1618 enum text_item_type type = text_item_get_type (text_item);
1619 const char *text = text_item_get_text (text_item);
1623 case TEXT_ITEM_TITLE:
1625 xr->title = xstrdup (text);
1628 case TEXT_ITEM_SUBTITLE:
1629 free (xr->subtitle);
1630 xr->subtitle = xstrdup (text);
1633 case TEXT_ITEM_COMMAND_CLOSE:
1636 case TEXT_ITEM_BLANK_LINE:
1638 xr->y += xr->char_height;
1641 case TEXT_ITEM_EJECT_PAGE:
1643 return xr_render_eject ();
1647 return xr_create_text_renderer (xr, text);
1653 static struct xr_render_fsm *
1654 xr_render_message (struct xr_driver *xr,
1655 const struct message_item *message_item)
1657 const struct msg *msg = message_item_get_msg (message_item);
1658 struct xr_render_fsm *fsm;
1661 s = msg_to_string (msg, xr->command_name);
1662 fsm = xr_create_text_renderer (xr, s);
1668 static struct xr_render_fsm *
1669 xr_render_output_item (struct xr_driver *xr,
1670 const struct output_item *output_item)
1672 if (is_table_item (output_item))
1673 return xr_render_table (xr, to_table_item (output_item));
1674 else if (is_chart_item (output_item))
1675 return xr_render_chart (to_chart_item (output_item));
1676 else if (is_text_item (output_item))
1677 return xr_render_text (xr, to_text_item (output_item));
1678 else if (is_message_item (output_item))
1679 return xr_render_message (xr, to_message_item (output_item));