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;
81 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
85 return x * (PANGO_SCALE * 72 / 96);
106 /* A font for use with Cairo. */
109 PangoFontDescription *desc;
113 /* An output item whose rendering is in progress. */
116 /* Renders as much of itself as it can on the current page. Returns true
117 if rendering is complete, false if the output item needs another
119 bool (*render) (struct xr_render_fsm *, struct xr_driver *);
121 /* Destroys the output item. */
122 void (*destroy) (struct xr_render_fsm *);
125 /* Cairo output driver. */
128 struct output_driver driver;
130 /* User parameters. */
131 struct xr_font fonts[XR_N_FONTS];
133 int width; /* Page width minus margins. */
134 int length; /* Page length minus margins and header. */
136 int left_margin; /* Left margin in inch/(72 * XR_POINT). */
137 int right_margin; /* Right margin in inch/(72 * XR_POINT). */
138 int top_margin; /* Top margin in inch/(72 * XR_POINT). */
139 int bottom_margin; /* Bottom margin in inch/(72 * XR_POINT). */
141 int line_space; /* Space between lines. */
142 int line_width; /* Width of lines. */
144 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
146 struct xr_color bg; /* Background color */
147 struct xr_color fg; /* Foreground color */
149 /* Internal state. */
150 struct render_params *params;
151 int char_width, char_height;
156 int page_number; /* Current page number. */
158 struct xr_render_fsm *fsm;
162 static const struct output_driver_class cairo_driver_class;
164 static void xr_driver_destroy_fsm (struct xr_driver *);
165 static void xr_driver_run_fsm (struct xr_driver *);
167 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
168 enum render_line_style styles[TABLE_N_AXES][2]);
169 static void xr_measure_cell_width (void *, const struct table_cell *,
171 static int xr_measure_cell_height (void *, const struct table_cell *,
173 static void xr_draw_cell (void *, const struct table_cell *,
174 int bb[TABLE_N_AXES][2],
175 int spill[TABLE_N_AXES][2],
176 int clip[TABLE_N_AXES][2]);
177 static int xr_adjust_break (void *, const struct table_cell *,
178 int width, int height);
180 static struct xr_render_fsm *xr_render_output_item (
181 struct xr_driver *, const struct output_item *);
183 /* Output driver basics. */
185 static struct xr_driver *
186 xr_driver_cast (struct output_driver *driver)
188 assert (driver->class == &cairo_driver_class);
189 return UP_CAST (driver, struct xr_driver, driver);
192 static struct driver_option *
193 opt (struct output_driver *d, struct string_map *options, const char *key,
194 const char *default_value)
196 return driver_option_get (d, options, key, default_value);
199 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
200 Currently, the input string must be of the form "#RRRRGGGGBBBB"
201 Future implementations might allow things like "yellow" and
202 "sky-blue-ultra-brown"
205 parse_color (struct output_driver *d, struct string_map *options,
206 const char *key, const char *default_value,
207 struct xr_color *color)
209 int red, green, blue;
210 char *string = parse_string (opt (d, options, key, default_value));
212 if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
214 /* If the parsed option string fails, then try the default value */
215 if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
217 /* ... and if that fails set everything to zero */
218 red = green = blue = 0;
224 /* Convert 16 bit ints to float */
225 color->red = red / (double) 0xFFFF;
226 color->green = green / (double) 0xFFFF;
227 color->blue = blue / (double) 0xFFFF;
230 static PangoFontDescription *
231 parse_font (struct output_driver *d, struct string_map *options,
232 const char *key, const char *default_value,
235 PangoFontDescription *desc;
238 /* Parse KEY as a font description. */
239 string = parse_string (opt (d, options, key, default_value));
240 desc = pango_font_description_from_string (string);
243 msg (MW, _("`%s': bad font specification"), string);
245 /* Fall back to DEFAULT_VALUE, which had better be a valid font
247 desc = pango_font_description_from_string (default_value);
248 assert (desc != NULL);
252 /* If the font description didn't include an explicit font size, then set it
253 to DEFAULT_SIZE, which is in inch/72000 units. */
254 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
255 pango_font_description_set_size (desc,
256 (default_size / 1000.0) * PANGO_SCALE);
263 apply_options (struct xr_driver *xr, struct string_map *o)
265 struct output_driver *d = &xr->driver;
267 /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
268 int left_margin, right_margin;
269 int top_margin, bottom_margin;
270 int paper_width, paper_length;
272 int min_break[TABLE_N_AXES];
274 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
275 const double scale = XR_POINT / 1000.;
279 for (i = 0; i < XR_N_FONTS; i++)
281 struct xr_font *font = &xr->fonts[i];
283 if (font->desc != NULL)
284 pango_font_description_free (font->desc);
287 font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
288 xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
290 xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
291 "sans serif", font_size);
292 xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
293 "sans serif italic", font_size);
294 xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "sans serif",
295 font_size * PANGO_SCALE_X_SMALL);
297 xr->line_space = XR_POINT;
298 xr->line_width = XR_POINT / 2;
301 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
302 parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
304 /* Get dimensions. */
305 parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
306 left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
307 right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
308 top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
309 bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
311 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
312 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
314 /* Convert to inch/(XR_POINT * 72). */
315 xr->left_margin = left_margin * scale;
316 xr->right_margin = right_margin * scale;
317 xr->top_margin = top_margin * scale;
318 xr->bottom_margin = bottom_margin * scale;
319 xr->width = (paper_width - left_margin - right_margin) * scale;
320 xr->length = (paper_length - top_margin - bottom_margin) * scale;
321 xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
322 xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
325 static struct xr_driver *
326 xr_allocate (const char *name, int device_type, struct string_map *o)
328 struct xr_driver *xr = xzalloc (sizeof *xr);
329 struct output_driver *d = &xr->driver;
331 output_driver_init (d, &cairo_driver_class, name, device_type);
333 apply_options (xr, o);
339 pango_to_xr (int pango)
341 return (XR_POINT != PANGO_SCALE
342 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
349 return (XR_POINT != PANGO_SCALE
350 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
355 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
361 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
365 for (i = 0; i < XR_N_FONTS; i++)
367 struct xr_font *font = &xr->fonts[i];
368 int char_width, char_height;
370 font->layout = pango_cairo_create_layout (cairo);
371 pango_layout_set_font_description (font->layout, font->desc);
373 pango_layout_set_text (font->layout, "0", 1);
374 pango_layout_get_size (font->layout, &char_width, &char_height);
375 xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
376 xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
379 if (xr->params == NULL)
381 xr->params = xmalloc (sizeof *xr->params);
382 xr->params->draw_line = xr_draw_line;
383 xr->params->measure_cell_width = xr_measure_cell_width;
384 xr->params->measure_cell_height = xr_measure_cell_height;
385 xr->params->adjust_break = xr_adjust_break;
386 xr->params->draw_cell = xr_draw_cell;
387 xr->params->aux = xr;
388 xr->params->size[H] = xr->width;
389 xr->params->size[V] = xr->length;
390 xr->params->font_size[H] = xr->char_width;
391 xr->params->font_size[V] = xr->char_height;
393 int lw = xr->line_width;
394 int ls = xr->line_space;
395 for (i = 0; i < TABLE_N_AXES; i++)
397 xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
398 xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw;
399 xr->params->line_widths[i][RENDER_LINE_DASHED] = lw;
400 xr->params->line_widths[i][RENDER_LINE_THICK] = lw * 3;
401 xr->params->line_widths[i][RENDER_LINE_THIN] = lw / 2;
402 xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls;
405 for (i = 0; i < TABLE_N_AXES; i++)
406 xr->params->min_break[i] = xr->min_break[i];
407 xr->params->supports_margins = true;
410 cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
415 static struct output_driver *
416 xr_create (const char *file_name, enum settings_output_devices device_type,
417 struct string_map *o, enum xr_output_type file_type)
419 enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
420 struct xr_driver *xr;
421 cairo_surface_t *surface;
422 cairo_status_t status;
423 double width_pt, length_pt;
425 xr = xr_allocate (file_name, device_type, o);
427 width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
428 length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
429 if (file_type == XR_PDF)
430 surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
431 else if (file_type == XR_PS)
432 surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
433 else if (file_type == XR_SVG)
434 surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
438 status = cairo_surface_status (surface);
439 if (status != CAIRO_STATUS_SUCCESS)
441 msg (ME, _("error opening output file `%s': %s"),
442 file_name, cairo_status_to_string (status));
443 cairo_surface_destroy (surface);
447 xr->cairo = cairo_create (surface);
448 cairo_surface_destroy (surface);
450 if (!xr_set_cairo (xr, xr->cairo))
453 cairo_save (xr->cairo);
454 xr_driver_next_page (xr, xr->cairo);
456 if (xr->width / xr->char_width < MIN_WIDTH)
458 msg (ME, _("The defined page is not wide enough to hold at least %d "
459 "characters in the default font. In fact, there's only "
460 "room for %d characters."),
462 xr->width / xr->char_width);
466 if (xr->length / xr->char_height < MIN_LENGTH)
468 msg (ME, _("The defined page is not long enough to hold at least %d "
469 "lines in the default font. In fact, there's only "
470 "room for %d lines."),
472 xr->length / xr->char_height);
479 output_driver_destroy (&xr->driver);
483 static struct output_driver *
484 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
485 struct string_map *o)
487 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
492 static struct output_driver *
493 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
494 struct string_map *o)
496 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
501 static struct output_driver *
502 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
503 struct string_map *o)
505 struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
511 xr_destroy (struct output_driver *driver)
513 struct xr_driver *xr = xr_driver_cast (driver);
516 xr_driver_destroy_fsm (xr);
518 if (xr->cairo != NULL)
520 cairo_status_t status;
522 cairo_surface_finish (cairo_get_target (xr->cairo));
523 status = cairo_status (xr->cairo);
524 if (status != CAIRO_STATUS_SUCCESS)
525 msg (ME, _("error drawing output for %s driver: %s"),
526 output_driver_get_name (driver),
527 cairo_status_to_string (status));
528 cairo_destroy (xr->cairo);
531 for (i = 0; i < XR_N_FONTS; i++)
533 struct xr_font *font = &xr->fonts[i];
535 if (font->desc != NULL)
536 pango_font_description_free (font->desc);
537 if (font->layout != NULL)
538 g_object_unref (font->layout);
546 xr_flush (struct output_driver *driver)
548 struct xr_driver *xr = xr_driver_cast (driver);
550 cairo_surface_flush (cairo_get_target (xr->cairo));
554 xr_submit (struct output_driver *driver, const struct output_item *output_item)
556 struct xr_driver *xr = xr_driver_cast (driver);
558 xr_driver_output_item (xr, output_item);
559 while (xr_driver_need_new_page (xr))
561 cairo_restore (xr->cairo);
562 cairo_show_page (xr->cairo);
563 cairo_save (xr->cairo);
564 xr_driver_next_page (xr, xr->cairo);
568 /* Functions for rendering a series of output items to a series of Cairo
569 contexts, with pagination.
571 Used by PSPPIRE for printing, and by the basic Cairo output driver above as
572 its underlying implementation.
574 See the big comment in cairo.h for intended usage. */
576 /* Gives new page CAIRO to XR for output. */
578 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
581 cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
582 cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
584 cairo_restore (cairo);
586 cairo_translate (cairo,
587 xr_to_pt (xr->left_margin),
588 xr_to_pt (xr->top_margin));
593 xr_driver_run_fsm (xr);
596 /* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of
597 rendering a previous output item, that is, only if xr_driver_need_new_page()
600 xr_driver_output_item (struct xr_driver *xr,
601 const struct output_item *output_item)
603 assert (xr->fsm == NULL);
604 xr->fsm = xr_render_output_item (xr, output_item);
605 xr_driver_run_fsm (xr);
608 /* Returns true if XR is in the middle of rendering an output item and needs a
609 new page to be appended using xr_driver_next_page() to make progress,
612 xr_driver_need_new_page (const struct xr_driver *xr)
614 return xr->fsm != NULL;
617 /* Returns true if the current page doesn't have any content yet. */
619 xr_driver_is_page_blank (const struct xr_driver *xr)
625 xr_driver_destroy_fsm (struct xr_driver *xr)
629 xr->fsm->destroy (xr->fsm);
635 xr_driver_run_fsm (struct xr_driver *xr)
637 if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
638 xr_driver_destroy_fsm (xr);
642 xr_layout_cell (struct xr_driver *, const struct table_cell *,
643 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
644 int *width, int *height, int *brk);
647 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style)
649 cairo_new_path (xr->cairo);
650 cairo_set_line_width (
652 xr_to_pt (style == RENDER_LINE_THICK ? xr->line_width * 3
653 : style == RENDER_LINE_THIN ? xr->line_width / 2
655 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
656 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
657 cairo_stroke (xr->cairo);
661 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
663 cairo_new_path (xr->cairo);
664 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
665 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
666 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
667 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
668 cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
669 cairo_close_path (xr->cairo);
670 cairo_stroke (xr->cairo);
674 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
676 cairo_new_path (xr->cairo);
677 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
678 cairo_rectangle (xr->cairo,
679 xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
680 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
681 cairo_fill (xr->cairo);
684 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
685 shortening it to X0...X1 if SHORTEN is true.
686 Draws a horizontal line X1...X3 at Y if RIGHT says so,
687 shortening it to X2...X3 if SHORTEN is true. */
689 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
690 enum render_line_style left, enum render_line_style right,
693 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
694 dump_line (xr, x0, y, x3, y, left);
697 if (left != RENDER_LINE_NONE)
698 dump_line (xr, x0, y, shorten ? x1 : x2, y, left);
699 if (right != RENDER_LINE_NONE)
700 dump_line (xr, shorten ? x2 : x1, y, x3, y, right);
704 /* Draws a vertical line Y0...Y2 at X if TOP says so,
705 shortening it to Y0...Y1 if SHORTEN is true.
706 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
707 shortening it to Y2...Y3 if SHORTEN is true. */
709 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
710 enum render_line_style top, enum render_line_style bottom,
713 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
714 dump_line (xr, x, y0, x, y3, top);
717 if (top != RENDER_LINE_NONE)
718 dump_line (xr, x, y0, x, shorten ? y1 : y2, top);
719 if (bottom != RENDER_LINE_NONE)
720 dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom);
725 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
726 enum render_line_style styles[TABLE_N_AXES][2])
728 const int x0 = bb[H][0];
729 const int y0 = bb[V][0];
730 const int x3 = bb[H][1];
731 const int y3 = bb[V][1];
732 const int top = styles[H][0];
733 const int bottom = styles[H][1];
734 const int start_of_line = render_direction_rtl() ? styles[V][1]: styles[V][0];
735 const int end_of_line = render_direction_rtl() ? styles[V][0]: styles[V][1];
737 /* The algorithm here is somewhat subtle, to allow it to handle
738 all the kinds of intersections that we need.
740 Three additional ordinates are assigned along the x axis. The
741 first is xc, midway between x0 and x3. The others are x1 and
742 x2; for a single vertical line these are equal to xc, and for
743 a double vertical line they are the ordinates of the left and
744 right half of the double line.
746 yc, y1, and y2 are assigned similarly along the y axis.
748 The following diagram shows the coordinate system and output
749 for double top and bottom lines, single left line, and no
753 y0 ________________________
759 y1 = y2 = yc |######### # |
764 y3 |________#_____#_______|
766 struct xr_driver *xr = xr_;
768 /* Offset from center of each line in a pair of double lines. */
769 int double_line_ofs = (xr->line_space + xr->line_width) / 2;
771 /* Are the lines along each axis single or double?
772 (It doesn't make sense to have different kinds of line on the
773 same axis, so we don't try to gracefully handle that case.) */
774 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
775 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
777 /* When horizontal lines are doubled,
778 the left-side line along y1 normally runs from x0 to x2,
779 and the right-side line along y1 from x3 to x1.
780 If the top-side line is also doubled, we shorten the y1 lines,
781 so that the left-side line runs only to x1,
782 and the right-side line only to x2.
783 Otherwise, the horizontal line at y = y1 below would cut off
784 the intersection, which looks ugly:
786 y0 ________________________
791 y1 |######### ########|
794 y2 |######################|
797 y3 |______________________|
798 It is more of a judgment call when the horizontal line is
799 single. We actually choose to cut off the line anyhow, as
800 shown in the first diagram above.
802 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
803 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
804 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
805 int horz_line_ofs = double_vert ? double_line_ofs : 0;
806 int xc = (x0 + x3) / 2;
807 int x1 = xc - horz_line_ofs;
808 int x2 = xc + horz_line_ofs;
810 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
811 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
812 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
813 int vert_line_ofs = double_horz ? double_line_ofs : 0;
814 int yc = (y0 + y3) / 2;
815 int y1 = yc - vert_line_ofs;
816 int y2 = yc + vert_line_ofs;
819 horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line, shorten_yc_line);
822 horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line, shorten_y1_lines);
823 horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line, shorten_y2_lines);
827 vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
830 vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
831 vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
836 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
837 int *min_width, int *max_width)
839 struct xr_driver *xr = xr_;
840 int bb[TABLE_N_AXES][2];
841 int clip[TABLE_N_AXES][2];
848 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
849 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
852 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
855 *min_width += px_to_xr (cell->style->margin[H][0]
856 + cell->style->margin[H][1]);
858 *max_width += px_to_xr (cell->style->margin[H][0]
859 + cell->style->margin[H][1]);
863 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
865 struct xr_driver *xr = xr_;
866 int bb[TABLE_N_AXES][2];
867 int clip[TABLE_N_AXES][2];
871 bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
872 + cell->style->margin[H][1]);
875 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
876 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
877 h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]);
881 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
884 xr_draw_cell (void *xr_, const struct table_cell *cell,
885 int bb[TABLE_N_AXES][2],
886 int spill[TABLE_N_AXES][2],
887 int clip[TABLE_N_AXES][2])
889 struct xr_driver *xr = xr_;
892 cairo_save (xr->cairo);
893 int bg_clip[TABLE_N_AXES][2];
894 for (int axis = 0; axis < TABLE_N_AXES; axis++)
896 bg_clip[axis][0] = clip[axis][0];
897 if (bb[axis][0] == clip[axis][0])
898 bg_clip[axis][0] -= spill[axis][0];
900 bg_clip[axis][1] = clip[axis][1];
901 if (bb[axis][1] == clip[axis][1])
902 bg_clip[axis][1] += spill[axis][1];
904 xr_clip (xr, bg_clip);
905 cairo_set_source_rgb (xr->cairo,
906 cell->style->bg.r / 255.,
907 cell->style->bg.g / 255.,
908 cell->style->bg.b / 255.);
910 bb[H][0] - spill[H][0],
911 bb[V][0] - spill[V][0],
912 bb[H][1] + spill[H][1],
913 bb[V][1] + spill[V][1]);
914 cairo_restore (xr->cairo);
916 cairo_save (xr->cairo);
917 cairo_set_source_rgb (xr->cairo,
918 cell->style->fg.r / 255.,
919 cell->style->fg.g / 255.,
920 cell->style->fg.b / 255.);
922 for (int axis = 0; axis < TABLE_N_AXES; axis++)
924 bb[axis][0] += px_to_xr (cell->style->margin[axis][0]);
925 bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]);
927 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
928 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
929 cairo_restore (xr->cairo);
933 xr_adjust_break (void *xr_, const struct table_cell *cell,
934 int width, int height)
936 struct xr_driver *xr = xr_;
937 int bb[TABLE_N_AXES][2];
938 int clip[TABLE_N_AXES][2];
941 if (xr_measure_cell_height (xr_, cell, width) < height)
945 bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
946 + cell->style->margin[H][1]);
950 bb[V][1] = height - px_to_xr (cell->style->margin[V][0]
951 + cell->style->margin[V][1]);
952 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
953 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
958 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
960 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
962 double x0 = xr_to_pt (clip[H][0] + xr->x);
963 double y0 = xr_to_pt (clip[V][0] + xr->y);
964 double x1 = xr_to_pt (clip[H][1] + xr->x);
965 double y1 = xr_to_pt (clip[V][1] + xr->y);
967 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
968 cairo_clip (xr->cairo);
973 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
975 attr->start_index = start_index;
976 pango_attr_list_insert (list, attr);
980 xr_layout_cell_text (struct xr_driver *xr,
981 const struct cell_contents *contents,
982 const struct cell_style *style,
983 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
984 int *widthp, int *brk)
986 unsigned int options = contents->options;
987 struct xr_font *font;
988 bool merge_footnotes;
992 if (contents->n_footnotes == 0)
993 merge_footnotes = false;
994 else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT)
996 PangoAttrList *attrs;
998 font = &xr->fonts[XR_FONT_MARKER];
1000 const char *marker = contents->footnotes[0]->marker;
1001 pango_layout_set_text (font->layout, marker, strlen (marker));
1003 attrs = pango_attr_list_new ();
1004 pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
1005 pango_layout_set_attributes (font->layout, attrs);
1006 pango_attr_list_unref (attrs);
1008 pango_layout_get_size (font->layout, &w, &h);
1009 merge_footnotes = w > px_to_xr (style->margin[H][1]);
1010 if (!merge_footnotes && clip[H][0] != clip[H][1])
1012 cairo_save (xr->cairo);
1014 cairo_translate (xr->cairo,
1015 xr_to_pt (bb[H][1] + xr->x),
1016 xr_to_pt (bb[V][0] + xr->y));
1017 pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
1018 pango_layout_set_width (font->layout, -1);
1019 pango_cairo_show_layout (xr->cairo, font->layout);
1020 cairo_restore (xr->cairo);
1023 pango_layout_set_attributes (font->layout, NULL);
1026 merge_footnotes = true;
1028 font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1029 : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
1030 : &xr->fonts[XR_FONT_PROPORTIONAL]);
1032 length = strlen (contents->text);
1033 if (merge_footnotes)
1035 PangoAttrList *attrs;
1038 bb[H][1] += px_to_xr (style->margin[H][1]);
1041 ds_extend (&s, length + contents->n_footnotes * 10);
1042 ds_put_cstr (&s, contents->text);
1043 cell_contents_format_footnote_markers (contents, &s);
1044 pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
1047 attrs = pango_attr_list_new ();
1048 add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
1049 add_attr_with_start (
1050 attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
1051 pango_layout_set_attributes (font->layout, attrs);
1052 pango_attr_list_unref (attrs);
1055 pango_layout_set_text (font->layout, contents->text, -1);
1057 pango_layout_set_alignment (
1059 ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1060 : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT
1061 : PANGO_ALIGN_CENTER));
1062 pango_layout_set_width (
1064 bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1065 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1067 if (clip[H][0] != clip[H][1])
1069 cairo_save (xr->cairo);
1071 cairo_translate (xr->cairo,
1072 xr_to_pt (bb[H][0] + xr->x),
1073 xr_to_pt (bb[V][0] + xr->y));
1074 pango_cairo_show_layout (xr->cairo, font->layout);
1076 /* If enabled, this draws a blue rectangle around the extents of each
1077 line of text, which can be rather useful for debugging layout
1081 PangoLayoutIter *iter;
1082 iter = pango_layout_get_iter (font->layout);
1085 PangoRectangle extents;
1087 pango_layout_iter_get_line_extents (iter, &extents, NULL);
1088 cairo_save (xr->cairo);
1089 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1091 pango_to_xr (extents.x) - xr->x,
1092 pango_to_xr (extents.y) - xr->y,
1093 pango_to_xr (extents.x + extents.width) - xr->x,
1094 pango_to_xr (extents.y + extents.height) - xr->y);
1095 cairo_restore (xr->cairo);
1097 while (pango_layout_iter_next_line (iter));
1098 pango_layout_iter_free (iter);
1101 cairo_restore (xr->cairo);
1104 pango_layout_get_size (font->layout, &w, &h);
1105 w = pango_to_xr (w);
1106 h = pango_to_xr (h);
1109 if (bb[V][0] + h >= bb[V][1])
1111 PangoLayoutIter *iter;
1112 int best UNUSED = 0;
1114 /* Choose a breakpoint between lines instead of in the middle of one. */
1115 iter = pango_layout_get_iter (font->layout);
1118 PangoRectangle extents;
1122 pango_layout_iter_get_line_extents (iter, NULL, &extents);
1123 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1124 extents.x = pango_to_xr (extents.x);
1125 extents.y = pango_to_xr (y0);
1126 extents.width = pango_to_xr (extents.width);
1127 extents.height = pango_to_xr (y1 - y0);
1128 bottom = bb[V][0] + extents.y + extents.height;
1129 if (bottom < bb[V][1])
1131 if (brk && clip[H][0] != clip[H][1])
1139 while (pango_layout_iter_next_line (iter));
1140 pango_layout_iter_free (iter);
1142 /* If enabled, draws a green line across the chosen breakpoint, which can
1143 be useful for debugging issues with breaking. */
1146 if (best && !xr->nest)
1148 cairo_save (xr->cairo);
1149 cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1150 dump_line (xr, -xr->left_margin, best,
1151 xr->width + xr->right_margin, best,
1152 RENDER_LINE_SINGLE);
1153 cairo_restore (xr->cairo);
1158 pango_layout_set_attributes (font->layout, NULL);
1159 return bb[V][0] + h;
1163 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1164 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1165 int *width, int *height, int *brk)
1167 int bb[TABLE_N_AXES][2];
1175 memcpy (bb, bb_, sizeof bb);
1177 /* If enabled, draws a blue rectangle around the cell extents, which can be
1178 useful for debugging layout. */
1181 if (clip[H][0] != clip[H][1])
1183 int offset = (xr->nest) * XR_POINT;
1185 cairo_save (xr->cairo);
1186 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1188 bb[H][0] + offset, bb[V][0] + offset,
1189 bb[H][1] - offset, bb[V][1] - offset);
1190 cairo_restore (xr->cairo);
1194 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1196 const struct cell_contents *contents = &cell->contents[i];
1202 bb[V][0] += xr->char_height / 2;
1203 if (bb[V][0] >= bb[V][1])
1209 bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip,
1212 *height = bb[V][0] - bb_[V][0];
1215 struct output_driver_factory pdf_driver_factory =
1216 { "pdf", "pspp.pdf", xr_pdf_create };
1217 struct output_driver_factory ps_driver_factory =
1218 { "ps", "pspp.ps", xr_ps_create };
1219 struct output_driver_factory svg_driver_factory =
1220 { "svg", "pspp.svg", xr_svg_create };
1222 static const struct output_driver_class cairo_driver_class =
1230 /* GUI rendering helpers. */
1234 struct output_item *item;
1237 struct render_pager *p;
1238 struct xr_driver *xr;
1241 #define CHART_WIDTH 500
1242 #define CHART_HEIGHT 375
1247 xr_driver_create (cairo_t *cairo, struct string_map *options)
1249 struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1250 if (!xr_set_cairo (xr, cairo))
1252 output_driver_destroy (&xr->driver);
1258 /* Destroy XR, which should have been created with xr_driver_create(). Any
1259 cairo_t added to XR is not destroyed, because it is owned by the client. */
1261 xr_driver_destroy (struct xr_driver *xr)
1266 output_driver_destroy (&xr->driver);
1270 static struct xr_rendering *
1271 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1273 struct table_item *table_item;
1274 struct xr_rendering *r;
1276 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1278 r = xr_rendering_create (xr, &table_item->output_item, cr);
1279 table_item_unref (table_item);
1285 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1287 if (is_table_item (xr->item))
1288 apply_options (xr->xr, o);
1291 struct xr_rendering *
1292 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1295 struct xr_rendering *r = NULL;
1297 if (is_text_item (item))
1298 r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1300 else if (is_message_item (item))
1302 const struct message_item *message_item = to_message_item (item);
1303 const struct msg *msg = message_item_get_msg (message_item);
1304 char *s = msg_to_string (msg, NULL);
1305 r = xr_rendering_create_text (xr, s, cr);
1308 else if (is_table_item (item))
1310 r = xzalloc (sizeof *r);
1311 r->item = output_item_ref (item);
1313 xr_set_cairo (xr, cr);
1314 r->p = render_pager_create (xr->params, to_table_item (item));
1316 else if (is_chart_item (item))
1318 r = xzalloc (sizeof *r);
1319 r->item = output_item_ref (item);
1326 xr_rendering_destroy (struct xr_rendering *r)
1330 output_item_unref (r->item);
1331 render_pager_destroy (r->p);
1337 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1339 if (is_table_item (r->item))
1341 *w = render_pager_get_size (r->p, H) / XR_POINT;
1342 *h = render_pager_get_size (r->p, V) / XR_POINT;
1351 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1352 double x, double y, double width, double height);
1356 xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
1358 if (is_table_item (r->item))
1360 struct xr_driver *xr = r->xr;
1362 xr_set_cairo (xr, cr);
1364 render_pager_draw (r->p);
1368 xr_draw_chart (to_chart_item (r->item), cr,
1369 0, 0, CHART_WIDTH, CHART_HEIGHT);
1373 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1374 double x, double y, double width, double height)
1376 struct xrchart_geometry geom;
1379 cairo_translate (cr, x, y + height);
1380 cairo_scale (cr, 1.0, -1.0);
1381 xrchart_geometry_init (cr, &geom, width, height);
1382 if (is_boxplot (chart_item))
1383 xrchart_draw_boxplot (chart_item, cr, &geom);
1384 else if (is_histogram_chart (chart_item))
1385 xrchart_draw_histogram (chart_item, cr, &geom);
1386 else if (is_np_plot_chart (chart_item))
1387 xrchart_draw_np_plot (chart_item, cr, &geom);
1388 else if (is_piechart (chart_item))
1389 xrchart_draw_piechart (chart_item, cr, &geom);
1390 else if (is_barchart (chart_item))
1391 xrchart_draw_barchart (chart_item, cr, &geom);
1392 else if (is_roc_chart (chart_item))
1393 xrchart_draw_roc (chart_item, cr, &geom);
1394 else if (is_scree (chart_item))
1395 xrchart_draw_scree (chart_item, cr, &geom);
1396 else if (is_spreadlevel_plot_chart (chart_item))
1397 xrchart_draw_spreadlevel (chart_item, cr, &geom);
1398 else if (is_scatterplot_chart (chart_item))
1399 xrchart_draw_scatterplot (chart_item, cr, &geom);
1402 xrchart_geometry_free (cr, &geom);
1408 xr_draw_png_chart (const struct chart_item *item,
1409 const char *file_name_template, int number,
1410 const struct xr_color *fg,
1411 const struct xr_color *bg
1414 const int width = 640;
1415 const int length = 480;
1417 cairo_surface_t *surface;
1418 cairo_status_t status;
1419 const char *number_pos;
1423 number_pos = strchr (file_name_template, '#');
1424 if (number_pos != NULL)
1425 file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1426 file_name_template, number, number_pos + 1);
1428 file_name = xstrdup (file_name_template);
1430 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1431 cr = cairo_create (surface);
1433 cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1436 cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1438 xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1440 status = cairo_surface_write_to_png (surface, file_name);
1441 if (status != CAIRO_STATUS_SUCCESS)
1442 msg (ME, _("error writing output file `%s': %s"),
1443 file_name, cairo_status_to_string (status));
1446 cairo_surface_destroy (surface);
1451 struct xr_table_state
1453 struct xr_render_fsm fsm;
1454 struct table_item *table_item;
1455 struct render_pager *p;
1459 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1461 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1463 while (render_pager_has_next (ts->p))
1467 used = render_pager_draw_next (ts->p, xr->length - xr->y);
1480 xr_table_destroy (struct xr_render_fsm *fsm)
1482 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1484 table_item_unref (ts->table_item);
1485 render_pager_destroy (ts->p);
1489 static struct xr_render_fsm *
1490 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1492 struct xr_table_state *ts;
1494 ts = xmalloc (sizeof *ts);
1495 ts->fsm.render = xr_table_render;
1496 ts->fsm.destroy = xr_table_destroy;
1497 ts->table_item = table_item_ref (table_item);
1500 xr->y += xr->char_height;
1502 ts->p = render_pager_create (xr->params, table_item);
1507 struct xr_chart_state
1509 struct xr_render_fsm fsm;
1510 struct chart_item *chart_item;
1514 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1516 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1518 const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
1520 if (xr->y > xr->length - chart_height)
1523 if (xr->cairo != NULL)
1525 xr_draw_chart (cs->chart_item, xr->cairo,
1528 xr_to_pt (xr->width),
1529 xr_to_pt (chart_height));
1531 xr->y += chart_height;
1537 xr_chart_destroy (struct xr_render_fsm *fsm)
1539 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1541 chart_item_unref (cs->chart_item);
1545 static struct xr_render_fsm *
1546 xr_render_chart (const struct chart_item *chart_item)
1548 struct xr_chart_state *cs;
1550 cs = xmalloc (sizeof *cs);
1551 cs->fsm.render = xr_chart_render;
1552 cs->fsm.destroy = xr_chart_destroy;
1553 cs->chart_item = chart_item_ref (chart_item);
1559 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1565 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1567 /* Nothing to do. */
1570 static struct xr_render_fsm *
1571 xr_render_eject (void)
1573 static struct xr_render_fsm eject_renderer =
1579 return &eject_renderer;
1582 static struct xr_render_fsm *
1583 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1585 struct table_item *table_item;
1586 struct xr_render_fsm *fsm;
1588 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1590 fsm = xr_render_table (xr, table_item);
1591 table_item_unref (table_item);
1596 static struct xr_render_fsm *
1597 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1599 enum text_item_type type = text_item_get_type (text_item);
1600 const char *text = text_item_get_text (text_item);
1604 case TEXT_ITEM_TITLE:
1606 xr->title = xstrdup (text);
1609 case TEXT_ITEM_SUBTITLE:
1610 free (xr->subtitle);
1611 xr->subtitle = xstrdup (text);
1614 case TEXT_ITEM_COMMAND_CLOSE:
1617 case TEXT_ITEM_BLANK_LINE:
1619 xr->y += xr->char_height;
1622 case TEXT_ITEM_EJECT_PAGE:
1624 return xr_render_eject ();
1628 return xr_create_text_renderer (xr, text);
1634 static struct xr_render_fsm *
1635 xr_render_message (struct xr_driver *xr,
1636 const struct message_item *message_item)
1638 const struct msg *msg = message_item_get_msg (message_item);
1639 struct xr_render_fsm *fsm;
1642 s = msg_to_string (msg, message_item->command_name);
1643 fsm = xr_create_text_renderer (xr, s);
1649 static struct xr_render_fsm *
1650 xr_render_output_item (struct xr_driver *xr,
1651 const struct output_item *output_item)
1653 if (is_table_item (output_item))
1654 return xr_render_table (xr, to_table_item (output_item));
1655 else if (is_chart_item (output_item))
1656 return xr_render_chart (to_chart_item (output_item));
1657 else if (is_text_item (output_item))
1658 return xr_render_text (xr, to_text_item (output_item));
1659 else if (is_message_item (output_item))
1660 return xr_render_message (xr, to_message_item (output_item));