1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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);
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. CAIRO may be null to skip actually
567 rendering the page (which might be useful to find out how many pages an
568 output document has without actually rendering it). */
570 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
575 cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
576 cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
578 cairo_restore (cairo);
580 cairo_translate (cairo,
581 xr_to_pt (xr->left_margin),
582 xr_to_pt (xr->top_margin));
588 xr_driver_run_fsm (xr);
591 /* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of
592 rendering a previous output item, that is, only if xr_driver_need_new_page()
595 xr_driver_output_item (struct xr_driver *xr,
596 const struct output_item *output_item)
598 assert (xr->fsm == NULL);
599 xr->fsm = xr_render_output_item (xr, output_item);
600 xr_driver_run_fsm (xr);
603 /* Returns true if XR is in the middle of rendering an output item and needs a
604 new page to be appended using xr_driver_next_page() to make progress,
607 xr_driver_need_new_page (const struct xr_driver *xr)
609 return xr->fsm != NULL;
612 /* Returns true if the current page doesn't have any content yet. */
614 xr_driver_is_page_blank (const struct xr_driver *xr)
620 xr_driver_destroy_fsm (struct xr_driver *xr)
624 xr->fsm->destroy (xr->fsm);
630 xr_driver_run_fsm (struct xr_driver *xr)
632 if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
633 xr_driver_destroy_fsm (xr);
637 xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx,
638 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
639 int *width, int *height, int *brk);
642 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
644 cairo_new_path (xr->cairo);
645 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
646 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
647 cairo_stroke (xr->cairo);
651 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
653 cairo_new_path (xr->cairo);
654 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
655 cairo_line_to (xr->cairo, xr_to_pt (x1 + 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_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
658 cairo_close_path (xr->cairo);
659 cairo_stroke (xr->cairo);
662 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
663 shortening it to X0...X1 if SHORTEN is true.
664 Draws a horizontal line X1...X3 at Y if RIGHT says so,
665 shortening it to X2...X3 if SHORTEN is true. */
667 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
668 enum render_line_style left, enum render_line_style right,
671 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
672 dump_line (xr, x0, y, x3, y);
675 if (left != RENDER_LINE_NONE)
676 dump_line (xr, x0, y, shorten ? x1 : x2, y);
677 if (right != RENDER_LINE_NONE)
678 dump_line (xr, shorten ? x2 : x1, y, x3, y);
682 /* Draws a vertical line Y0...Y2 at X if TOP says so,
683 shortening it to Y0...Y1 if SHORTEN is true.
684 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
685 shortening it to Y2...Y3 if SHORTEN is true. */
687 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
688 enum render_line_style top, enum render_line_style bottom,
691 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
692 dump_line (xr, x, y0, x, y3);
695 if (top != RENDER_LINE_NONE)
696 dump_line (xr, x, y0, x, shorten ? y1 : y2);
697 if (bottom != RENDER_LINE_NONE)
698 dump_line (xr, x, shorten ? y2 : y1, x, y3);
703 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
704 enum render_line_style styles[TABLE_N_AXES][2])
706 const int x0 = bb[H][0];
707 const int y0 = bb[V][0];
708 const int x3 = bb[H][1];
709 const int y3 = bb[V][1];
710 const int top = styles[H][0];
711 const int left = styles[V][0];
712 const int bottom = styles[H][1];
713 const int right = styles[V][1];
715 /* The algorithm here is somewhat subtle, to allow it to handle
716 all the kinds of intersections that we need.
718 Three additional ordinates are assigned along the x axis. The
719 first is xc, midway between x0 and x3. The others are x1 and
720 x2; for a single vertical line these are equal to xc, and for
721 a double vertical line they are the ordinates of the left and
722 right half of the double line.
724 yc, y1, and y2 are assigned similarly along the y axis.
726 The following diagram shows the coordinate system and output
727 for double top and bottom lines, single left line, and no
731 y0 ________________________
737 y1 = y2 = yc |######### # |
742 y3 |________#_____#_______|
744 struct xr_driver *xr = xr_;
746 /* Offset from center of each line in a pair of double lines. */
747 int double_line_ofs = (xr->line_space + xr->line_width) / 2;
749 /* Are the lines along each axis single or double?
750 (It doesn't make sense to have different kinds of line on the
751 same axis, so we don't try to gracefully handle that case.) */
752 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
753 bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
755 /* When horizontal lines are doubled,
756 the left-side line along y1 normally runs from x0 to x2,
757 and the right-side line along y1 from x3 to x1.
758 If the top-side line is also doubled, we shorten the y1 lines,
759 so that the left-side line runs only to x1,
760 and the right-side line only to x2.
761 Otherwise, the horizontal line at y = y1 below would cut off
762 the intersection, which looks ugly:
764 y0 ________________________
769 y1 |######### ########|
772 y2 |######################|
775 y3 |______________________|
776 It is more of a judgment call when the horizontal line is
777 single. We actually choose to cut off the line anyhow, as
778 shown in the first diagram above.
780 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
781 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
782 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
783 int horz_line_ofs = double_vert ? double_line_ofs : 0;
784 int xc = (x0 + x3) / 2;
785 int x1 = xc - horz_line_ofs;
786 int x2 = xc + horz_line_ofs;
788 bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
789 bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
790 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
791 int vert_line_ofs = double_horz ? double_line_ofs : 0;
792 int yc = (y0 + y3) / 2;
793 int y1 = yc - vert_line_ofs;
794 int y2 = yc + vert_line_ofs;
797 horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
800 horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
801 horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
805 vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
808 vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
809 vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
814 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
815 int footnote_idx, int *min_width, int *max_width)
817 struct xr_driver *xr = xr_;
818 int bb[TABLE_N_AXES][2];
819 int clip[TABLE_N_AXES][2];
826 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
827 xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL);
830 xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL);
833 *min_width += xr->cell_margin * 2;
835 *max_width += xr->cell_margin * 2;
839 xr_measure_cell_height (void *xr_, const struct table_cell *cell,
840 int footnote_idx, int width)
842 struct xr_driver *xr = xr_;
843 int bb[TABLE_N_AXES][2];
844 int clip[TABLE_N_AXES][2];
848 bb[H][1] = width - xr->cell_margin * 2;
853 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
854 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL);
859 xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx,
860 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
862 struct xr_driver *xr = xr_;
865 bb[H][0] += xr->cell_margin;
866 bb[H][1] -= xr->cell_margin;
867 if (bb[H][0] >= bb[H][1])
869 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
873 xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx,
874 int width, int height)
876 struct xr_driver *xr = xr_;
877 int bb[TABLE_N_AXES][2];
878 int clip[TABLE_N_AXES][2];
881 if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height)
885 bb[H][1] = width - 2 * xr->cell_margin;
890 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
891 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
896 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
898 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
900 double x0 = xr_to_pt (clip[H][0] + xr->x);
901 double y0 = xr_to_pt (clip[V][0] + xr->y);
902 double x1 = xr_to_pt (clip[H][1] + xr->x);
903 double y1 = xr_to_pt (clip[V][1] + xr->y);
905 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
906 cairo_clip (xr->cairo);
911 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
913 attr->start_index = start_index;
914 pango_attr_list_insert (list, attr);
918 xr_layout_cell_text (struct xr_driver *xr,
919 const struct cell_contents *contents, int footnote_idx,
920 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
921 int y, int *widthp, int *brk)
923 unsigned int options = contents->options;
924 struct xr_font *font;
925 bool merge_footnotes;
929 if (contents->n_footnotes == 0)
930 merge_footnotes = false;
931 else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT)
933 PangoAttrList *attrs;
936 font = &xr->fonts[XR_FONT_MARKER];
938 str_format_26adic (footnote_idx + 1, false, marker, sizeof marker);
939 pango_layout_set_text (font->layout, marker, strlen (marker));
941 attrs = pango_attr_list_new ();
942 pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
943 pango_layout_set_attributes (font->layout, attrs);
944 pango_attr_list_unref (attrs);
946 pango_layout_get_size (font->layout, &w, &h);
947 merge_footnotes = w > xr->cell_margin;
948 if (!merge_footnotes && clip[H][0] != clip[H][1])
950 cairo_save (xr->cairo);
952 cairo_translate (xr->cairo,
953 xr_to_pt (bb[H][1] + xr->x),
954 xr_to_pt (y + xr->y));
955 pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
956 pango_layout_set_width (font->layout, -1);
957 pango_cairo_show_layout (xr->cairo, font->layout);
958 cairo_restore (xr->cairo);
961 pango_layout_set_attributes (font->layout, NULL);
964 merge_footnotes = true;
966 font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
967 : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
968 : &xr->fonts[XR_FONT_PROPORTIONAL]);
970 length = strlen (contents->text);
973 PangoAttrList *attrs;
977 bb[H][1] += xr->cell_margin;
980 ds_extend (&s, length + contents->n_footnotes * 10);
981 ds_put_cstr (&s, contents->text);
982 for (i = 0; i < contents->n_footnotes; i++)
987 ds_put_byte (&s, ',');
988 str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker);
989 ds_put_cstr (&s, marker);
991 pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
994 attrs = pango_attr_list_new ();
995 add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
996 add_attr_with_start (
997 attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
998 pango_layout_set_attributes (font->layout, attrs);
999 pango_attr_list_unref (attrs);
1002 pango_layout_set_text (font->layout, contents->text, -1);
1004 pango_layout_set_alignment (
1006 ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1007 : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
1008 : PANGO_ALIGN_CENTER));
1009 pango_layout_set_width (
1011 bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1012 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1014 if (clip[H][0] != clip[H][1])
1016 cairo_save (xr->cairo);
1018 cairo_translate (xr->cairo,
1019 xr_to_pt (bb[H][0] + xr->x),
1020 xr_to_pt (y + xr->y));
1021 pango_cairo_show_layout (xr->cairo, font->layout);
1023 /* If enabled, this draws a blue rectangle around the extents of each
1024 line of text, which can be rather useful for debugging layout
1028 PangoLayoutIter *iter;
1029 iter = pango_layout_get_iter (font->layout);
1032 PangoRectangle extents;
1034 pango_layout_iter_get_line_extents (iter, &extents, NULL);
1035 cairo_save (xr->cairo);
1036 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1038 pango_to_xr (extents.x) - xr->x,
1039 pango_to_xr (extents.y) - xr->y,
1040 pango_to_xr (extents.x + extents.width) - xr->x,
1041 pango_to_xr (extents.y + extents.height) - xr->y);
1042 cairo_restore (xr->cairo);
1044 while (pango_layout_iter_next_line (iter));
1045 pango_layout_iter_free (iter);
1048 cairo_restore (xr->cairo);
1051 pango_layout_get_size (font->layout, &w, &h);
1052 w = pango_to_xr (w);
1053 h = pango_to_xr (h);
1056 if (y + h >= bb[V][1])
1058 PangoLayoutIter *iter;
1059 int best UNUSED = 0;
1061 /* Choose a breakpoint between lines instead of in the middle of one. */
1062 iter = pango_layout_get_iter (font->layout);
1065 PangoRectangle extents;
1069 pango_layout_iter_get_line_extents (iter, NULL, &extents);
1070 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1071 extents.x = pango_to_xr (extents.x);
1072 extents.y = pango_to_xr (y0);
1073 extents.width = pango_to_xr (extents.width);
1074 extents.height = pango_to_xr (y1 - y0);
1075 bottom = y + extents.y + extents.height;
1076 if (bottom < bb[V][1])
1078 if (brk && clip[H][0] != clip[H][1])
1085 while (pango_layout_iter_next_line (iter));
1087 /* If enabled, draws a green line across the chosen breakpoint, which can
1088 be useful for debugging issues with breaking. */
1091 if (best && !xr->nest)
1093 cairo_save (xr->cairo);
1094 cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1095 dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
1096 cairo_restore (xr->cairo);
1101 pango_layout_set_attributes (font->layout, NULL);
1106 xr_layout_cell_subtable (struct xr_driver *xr,
1107 const struct cell_contents *contents,
1108 int footnote_idx UNUSED,
1109 int bb[TABLE_N_AXES][2],
1110 int clip[TABLE_N_AXES][2], int *widthp, int *brk)
1112 int single_width, double_width;
1113 struct render_params params;
1114 struct render_pager *p;
1115 int r[TABLE_N_AXES][2];
1119 params.draw_line = xr_draw_line;
1120 params.measure_cell_width = xr_measure_cell_width;
1121 params.measure_cell_height = xr_measure_cell_height;
1122 params.adjust_break = NULL;
1123 params.draw_cell = xr_draw_cell;
1125 params.size[H] = bb[H][1] - bb[H][0];
1126 params.size[V] = bb[V][1] - bb[V][0];
1127 params.font_size[H] = xr->char_width;
1128 params.font_size[V] = xr->char_height;
1130 single_width = 2 * xr->line_gutter + xr->line_width;
1131 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
1132 for (i = 0; i < TABLE_N_AXES; i++)
1134 params.line_widths[i][RENDER_LINE_NONE] = 0;
1135 params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
1136 params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
1140 p = render_pager_create (¶ms, contents->table);
1141 width = render_pager_get_size (p, H);
1142 height = render_pager_get_size (p, V);
1143 if (bb[V][0] + height >= bb[V][1])
1144 *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
1146 /* r = intersect(bb, clip) - bb. */
1147 for (i = 0; i < TABLE_N_AXES; i++)
1149 r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
1150 r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
1153 if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
1155 unsigned int alignment = contents->options & TAB_ALIGNMENT;
1158 cairo_save (xr->cairo);
1161 if (alignment == TAB_RIGHT)
1162 xr->x += params.size[H] - width;
1163 else if (alignment == TAB_CENTER)
1164 xr->x += (params.size[H] - width) / 2;
1166 render_pager_draw_region (p, r[H][0], r[V][0],
1167 r[H][1] - r[H][0], r[V][1] - r[V][0]);
1170 cairo_restore (xr->cairo);
1172 render_pager_destroy (p);
1175 if (width > *widthp)
1177 return bb[V][0] + height;
1181 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1183 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1184 int *width, int *height, int *brk)
1186 int bb[TABLE_N_AXES][2];
1194 memcpy (bb, bb_, sizeof bb);
1196 /* If enabled, draws a blue rectangle around the cell extents, which can be
1197 useful for debugging layout. */
1200 if (clip[H][0] != clip[H][1])
1202 int offset = (xr->nest) * XR_POINT;
1204 cairo_save (xr->cairo);
1205 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1207 bb[H][0] + offset, bb[V][0] + offset,
1208 bb[H][1] - offset, bb[V][1] - offset);
1209 cairo_restore (xr->cairo);
1213 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1215 const struct cell_contents *contents = &cell->contents[i];
1221 bb[V][0] += xr->char_height / 2;
1222 if (bb[V][0] >= bb[V][1])
1229 bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip,
1230 bb[V][0], width, brk);
1232 bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx,
1233 bb, clip, width, brk);
1234 footnote_idx += contents->n_footnotes;
1236 *height = bb[V][0] - bb_[V][0];
1239 struct output_driver_factory pdf_driver_factory =
1240 { "pdf", "pspp.pdf", xr_pdf_create };
1241 struct output_driver_factory ps_driver_factory =
1242 { "ps", "pspp.ps", xr_ps_create };
1243 struct output_driver_factory svg_driver_factory =
1244 { "svg", "pspp.svg", xr_svg_create };
1246 static const struct output_driver_class cairo_driver_class =
1254 /* GUI rendering helpers. */
1258 struct output_item *item;
1261 struct render_pager *p;
1262 struct xr_driver *xr;
1265 #define CHART_WIDTH 500
1266 #define CHART_HEIGHT 375
1271 xr_driver_create (cairo_t *cairo, struct string_map *options)
1273 struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1274 if (!xr_set_cairo (xr, cairo))
1276 output_driver_destroy (&xr->driver);
1282 /* Destroy XR, which should have been created with xr_driver_create(). Any
1283 cairo_t added to XR is not destroyed, because it is owned by the client. */
1285 xr_driver_destroy (struct xr_driver *xr)
1290 output_driver_destroy (&xr->driver);
1294 static struct xr_rendering *
1295 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1297 struct table_item *table_item;
1298 struct xr_rendering *r;
1300 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1302 r = xr_rendering_create (xr, &table_item->output_item, cr);
1303 table_item_unref (table_item);
1309 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1311 if (is_table_item (xr->item))
1312 apply_options (xr->xr, o);
1315 struct xr_rendering *
1316 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1319 struct xr_rendering *r = NULL;
1321 if (is_text_item (item))
1322 r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1324 else if (is_message_item (item))
1326 const struct message_item *message_item = to_message_item (item);
1327 const struct msg *msg = message_item_get_msg (message_item);
1328 char *s = msg_to_string (msg, NULL);
1329 r = xr_rendering_create_text (xr, s, cr);
1332 else if (is_table_item (item))
1334 r = xzalloc (sizeof *r);
1335 r->item = output_item_ref (item);
1337 xr_set_cairo (xr, cr);
1338 r->p = render_pager_create (xr->params, to_table_item (item));
1340 else if (is_chart_item (item))
1342 r = xzalloc (sizeof *r);
1343 r->item = output_item_ref (item);
1350 xr_rendering_destroy (struct xr_rendering *r)
1354 output_item_unref (r->item);
1355 render_pager_destroy (r->p);
1361 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1363 if (is_table_item (r->item))
1365 *w = render_pager_get_size (r->p, H) / XR_POINT;
1366 *h = render_pager_get_size (r->p, V) / XR_POINT;
1375 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1376 double x, double y, double width, double height);
1378 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1379 and possibly some additional parts. */
1381 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1382 int x, int y, int w, int h)
1384 if (is_table_item (r->item))
1386 struct xr_driver *xr = r->xr;
1388 xr_set_cairo (xr, cr);
1391 render_pager_draw_region (r->p,
1392 x * XR_POINT, y * XR_POINT,
1393 w * XR_POINT, h * XR_POINT);
1396 xr_draw_chart (to_chart_item (r->item), cr,
1397 0, 0, CHART_WIDTH, CHART_HEIGHT);
1401 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1402 double x, double y, double width, double height)
1404 struct xrchart_geometry geom;
1407 cairo_translate (cr, x, y + height);
1408 cairo_scale (cr, 1.0, -1.0);
1409 xrchart_geometry_init (cr, &geom, width, height);
1410 if (is_boxplot (chart_item))
1411 xrchart_draw_boxplot (chart_item, cr, &geom);
1412 else if (is_histogram_chart (chart_item))
1413 xrchart_draw_histogram (chart_item, cr, &geom);
1414 else if (is_np_plot_chart (chart_item))
1415 xrchart_draw_np_plot (chart_item, cr, &geom);
1416 else if (is_piechart (chart_item))
1417 xrchart_draw_piechart (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));