1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "output/cairo.h"
21 #include "libpspp/assertion.h"
22 #include "libpspp/cast.h"
23 #include "libpspp/message.h"
24 #include "libpspp/start-date.h"
25 #include "libpspp/str.h"
26 #include "libpspp/string-map.h"
27 #include "libpspp/version.h"
28 #include "output/cairo-chart.h"
29 #include "output/chart-item-provider.h"
30 #include "output/charts/boxplot.h"
31 #include "output/charts/np-plot.h"
32 #include "output/charts/piechart.h"
33 #include "output/charts/barchart.h"
34 #include "output/charts/plot-hist.h"
35 #include "output/charts/roc-chart.h"
36 #include "output/charts/spreadlevel-plot.h"
37 #include "output/charts/scree.h"
38 #include "output/charts/scatterplot.h"
39 #include "output/driver-provider.h"
40 #include "output/message-item.h"
41 #include "output/options.h"
42 #include "output/render.h"
43 #include "output/tab.h"
44 #include "output/table-item.h"
45 #include "output/table.h"
46 #include "output/text-item.h"
48 #include <cairo/cairo-pdf.h>
49 #include <cairo/cairo-ps.h>
50 #include <cairo/cairo-svg.h>
51 #include <cairo/cairo.h>
53 #include <pango/pango-font.h>
54 #include <pango/pango-layout.h>
55 #include <pango/pango.h>
56 #include <pango/pangocairo.h>
59 #include "gl/intprops.h"
60 #include "gl/minmax.h"
61 #include "gl/xalloc.h"
64 #define _(msgid) gettext (msgid)
66 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
70 /* The unit used for internal measurements is inch/(72 * XR_POINT). */
71 #define XR_POINT PANGO_SCALE
73 /* Conversions to and from points. */
77 return x / (double) XR_POINT;
98 /* A font for use with Cairo. */
101 PangoFontDescription *desc;
105 /* An output item whose rendering is in progress. */
108 /* Renders as much of itself as it can on the current page. Returns true
109 if rendering is complete, false if the output item needs another
111 bool (*render) (struct xr_render_fsm *, struct xr_driver *);
113 /* Destroys the output item. */
114 void (*destroy) (struct xr_render_fsm *);
117 /* Cairo output driver. */
120 struct output_driver driver;
122 /* User parameters. */
123 struct xr_font fonts[XR_N_FONTS];
125 int width; /* Page width minus margins. */
126 int length; /* Page length minus margins and header. */
128 int left_margin; /* Left margin in inch/(72 * XR_POINT). */
129 int right_margin; /* Right margin in inch/(72 * XR_POINT). */
130 int top_margin; /* Top margin in inch/(72 * XR_POINT). */
131 int bottom_margin; /* Bottom margin in inch/(72 * XR_POINT). */
133 int line_gutter; /* Space around lines. */
134 int line_space; /* Space between lines. */
135 int line_width; /* Width of lines. */
139 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
141 struct xr_color bg; /* Background color */
142 struct xr_color fg; /* Foreground color */
144 /* Internal state. */
145 struct render_params *params;
146 int char_width, char_height;
151 int page_number; /* Current page number. */
153 struct xr_render_fsm *fsm;
157 static const struct output_driver_class cairo_driver_class;
159 static void xr_driver_destroy_fsm (struct xr_driver *);
160 static void xr_driver_run_fsm (struct xr_driver *);
162 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
163 enum render_line_style styles[TABLE_N_AXES][2]);
164 static void xr_measure_cell_width (void *, const struct table_cell *,
165 int footnote_idx, int *min, int *max);
166 static int xr_measure_cell_height (void *, const struct table_cell *,
167 int footnote_idx, int width);
168 static void xr_draw_cell (void *, const struct table_cell *, int footnote_idx,
169 int bb[TABLE_N_AXES][2],
170 int clip[TABLE_N_AXES][2]);
171 static int xr_adjust_break (void *, const struct table_cell *, int footnote_idx,
172 int width, int height);
174 static struct xr_render_fsm *xr_render_output_item (
175 struct xr_driver *, const struct output_item *);
177 /* Output driver basics. */
179 static struct xr_driver *
180 xr_driver_cast (struct output_driver *driver)
182 assert (driver->class == &cairo_driver_class);
183 return UP_CAST (driver, struct xr_driver, driver);
186 static struct driver_option *
187 opt (struct output_driver *d, struct string_map *options, const char *key,
188 const char *default_value)
190 return driver_option_get (d, options, key, default_value);
193 /* Parse color information specified by KEY into {RED,GREEN,BLUE}.
194 Currently, the input string must be of the form "#RRRRGGGGBBBB"
195 Future implementations might allow things like "yellow" and
196 "sky-blue-ultra-brown"
199 parse_color (struct output_driver *d, struct string_map *options,
200 const char *key, const char *default_value,
201 struct xr_color *color)
203 int red, green, blue;
204 char *string = parse_string (opt (d, options, key, default_value));
206 if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
208 /* If the parsed option string fails, then try the default value */
209 if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
211 /* ... and if that fails set everything to zero */
212 red = green = blue = 0;
218 /* Convert 16 bit ints to float */
219 color->red = red / (double) 0xFFFF;
220 color->green = green / (double) 0xFFFF;
221 color->blue = blue / (double) 0xFFFF;
224 static PangoFontDescription *
225 parse_font (struct output_driver *d, struct string_map *options,
226 const char *key, const char *default_value,
229 PangoFontDescription *desc;
232 /* Parse KEY as a font description. */
233 string = parse_string (opt (d, options, key, default_value));
234 desc = pango_font_description_from_string (string);
237 msg (MW, _("`%s': bad font specification"), string);
239 /* Fall back to DEFAULT_VALUE, which had better be a valid font
241 desc = pango_font_description_from_string (default_value);
242 assert (desc != NULL);
246 /* If the font description didn't include an explicit font size, then set it
247 to DEFAULT_SIZE, which is in inch/72000 units. */
248 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
249 pango_font_description_set_size (desc,
250 (default_size / 1000.0) * PANGO_SCALE);
257 apply_options (struct xr_driver *xr, struct string_map *o)
259 struct output_driver *d = &xr->driver;
261 /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
262 int left_margin, right_margin;
263 int top_margin, bottom_margin;
264 int paper_width, paper_length;
266 int min_break[TABLE_N_AXES];
268 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
269 const double scale = XR_POINT / 1000.;
273 for (i = 0; i < XR_N_FONTS; i++)
275 struct xr_font *font = &xr->fonts[i];
277 if (font->desc != NULL)
278 pango_font_description_free (font->desc);
281 font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
282 xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
284 xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
286 xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
287 "serif italic", font_size);
288 xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "serif",
289 font_size * PANGO_SCALE_X_SMALL);
291 xr->line_gutter = XR_POINT / 2;
292 xr->line_space = XR_POINT;
293 xr->line_width = XR_POINT / 2;
296 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
297 parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
299 /* Get dimensions. */
300 parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
301 left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
302 right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
303 top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
304 bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
306 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
307 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
309 /* Convert to inch/(XR_POINT * 72). */
310 xr->left_margin = left_margin * scale;
311 xr->right_margin = right_margin * scale;
312 xr->top_margin = top_margin * scale;
313 xr->bottom_margin = bottom_margin * scale;
314 xr->width = (paper_width - left_margin - right_margin) * scale;
315 xr->length = (paper_length - top_margin - bottom_margin) * scale;
316 xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
317 xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
320 static struct xr_driver *
321 xr_allocate (const char *name, int device_type, struct string_map *o)
323 struct xr_driver *xr = xzalloc (sizeof *xr);
324 struct output_driver *d = &xr->driver;
326 output_driver_init (d, &cairo_driver_class, name, device_type);
328 apply_options (xr, o);
334 pango_to_xr (int pango)
336 return (XR_POINT != PANGO_SCALE
337 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
344 return (XR_POINT != PANGO_SCALE
345 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
350 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
356 cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
360 for (i = 0; i < XR_N_FONTS; i++)
362 struct xr_font *font = &xr->fonts[i];
363 int char_width, char_height;
365 font->layout = pango_cairo_create_layout (cairo);
366 pango_layout_set_font_description (font->layout, font->desc);
368 pango_layout_set_text (font->layout, "0", 1);
369 pango_layout_get_size (font->layout, &char_width, &char_height);
370 xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
371 xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
373 xr->cell_margin = xr->char_width;
375 if (xr->params == NULL)
377 int single_width, double_width;
379 xr->params = xmalloc (sizeof *xr->params);
380 xr->params->draw_line = xr_draw_line;
381 xr->params->measure_cell_width = xr_measure_cell_width;
382 xr->params->measure_cell_height = xr_measure_cell_height;
383 xr->params->adjust_break = xr_adjust_break;
384 xr->params->draw_cell = xr_draw_cell;
385 xr->params->aux = xr;
386 xr->params->size[H] = xr->width;
387 xr->params->size[V] = xr->length;
388 xr->params->font_size[H] = xr->char_width;
389 xr->params->font_size[V] = xr->char_height;
391 single_width = 2 * xr->line_gutter + xr->line_width;
392 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
393 for (i = 0; i < TABLE_N_AXES; i++)
395 xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
396 xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
397 xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
400 for (i = 0; i < TABLE_N_AXES; i++)
401 xr->params->min_break[i] = xr->min_break[i];
404 cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
409 static struct output_driver *
410 xr_create (const char *file_name, enum settings_output_devices device_type,
411 struct string_map *o, enum xr_output_type file_type)
413 enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
414 struct xr_driver *xr;
415 cairo_surface_t *surface;
416 cairo_status_t status;
417 double width_pt, length_pt;
419 xr = xr_allocate (file_name, device_type, o);
421 width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
422 length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
423 if (file_type == XR_PDF)
424 surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
425 else if (file_type == XR_PS)
426 surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
427 else if (file_type == XR_SVG)
428 surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
432 status = cairo_surface_status (surface);
433 if (status != CAIRO_STATUS_SUCCESS)
435 msg (ME, _("error opening output file `%s': %s"),
436 file_name, cairo_status_to_string (status));
437 cairo_surface_destroy (surface);
441 xr->cairo = cairo_create (surface);
442 cairo_surface_destroy (surface);
444 if (!xr_set_cairo (xr, xr->cairo))
447 cairo_save (xr->cairo);
448 xr_driver_next_page (xr, xr->cairo);
450 if (xr->width / xr->char_width < MIN_WIDTH)
452 msg (ME, _("The defined page is not wide enough to hold at least %d "
453 "characters in the default font. In fact, there's only "
454 "room for %d characters."),
456 xr->width / xr->char_width);
460 if (xr->length / xr->char_height < MIN_LENGTH)
462 msg (ME, _("The defined page is not long enough to hold at least %d "
463 "lines in the default font. In fact, there's only "
464 "room for %d lines."),
466 xr->length / xr->char_height);
473 output_driver_destroy (&xr->driver);
477 static struct output_driver *
478 xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
479 struct string_map *o)
481 return xr_create (file_name, device_type, o, XR_PDF);
484 static struct output_driver *
485 xr_ps_create (const char *file_name, enum settings_output_devices device_type,
486 struct string_map *o)
488 return xr_create (file_name, device_type, o, XR_PS);
491 static struct output_driver *
492 xr_svg_create (const char *file_name, enum settings_output_devices device_type,
493 struct string_map *o)
495 return xr_create (file_name, device_type, o, XR_SVG);
499 xr_destroy (struct output_driver *driver)
501 struct xr_driver *xr = xr_driver_cast (driver);
504 xr_driver_destroy_fsm (xr);
506 if (xr->cairo != NULL)
508 cairo_status_t status;
510 cairo_surface_finish (cairo_get_target (xr->cairo));
511 status = cairo_status (xr->cairo);
512 if (status != CAIRO_STATUS_SUCCESS)
513 msg (ME, _("error drawing output for %s driver: %s"),
514 output_driver_get_name (driver),
515 cairo_status_to_string (status));
516 cairo_destroy (xr->cairo);
519 free (xr->command_name);
520 for (i = 0; i < XR_N_FONTS; i++)
522 struct xr_font *font = &xr->fonts[i];
524 if (font->desc != NULL)
525 pango_font_description_free (font->desc);
526 if (font->layout != NULL)
527 g_object_unref (font->layout);
535 xr_flush (struct output_driver *driver)
537 struct xr_driver *xr = xr_driver_cast (driver);
539 cairo_surface_flush (cairo_get_target (xr->cairo));
543 xr_submit (struct output_driver *driver, const struct output_item *output_item)
545 struct xr_driver *xr = xr_driver_cast (driver);
547 output_driver_track_current_command (output_item, &xr->command_name);
549 xr_driver_output_item (xr, output_item);
550 while (xr_driver_need_new_page (xr))
552 cairo_restore (xr->cairo);
553 cairo_show_page (xr->cairo);
554 cairo_save (xr->cairo);
555 xr_driver_next_page (xr, xr->cairo);
559 /* Functions for rendering a series of output items to a series of Cairo
560 contexts, with pagination.
562 Used by PSPPIRE for printing, and by the basic Cairo output driver above as
563 its underlying implementation.
565 See the big comment in cairo.h for intended usage. */
567 /* Gives new page CAIRO to XR for output. */
569 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
572 cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
573 cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
575 cairo_restore (cairo);
577 cairo_translate (cairo,
578 xr_to_pt (xr->left_margin),
579 xr_to_pt (xr->top_margin));
584 xr_driver_run_fsm (xr);
587 /* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of
588 rendering a previous output item, that is, only if xr_driver_need_new_page()
591 xr_driver_output_item (struct xr_driver *xr,
592 const struct output_item *output_item)
594 assert (xr->fsm == NULL);
595 xr->fsm = xr_render_output_item (xr, output_item);
596 xr_driver_run_fsm (xr);
599 /* Returns true if XR is in the middle of rendering an output item and needs a
600 new page to be appended using xr_driver_next_page() to make progress,
603 xr_driver_need_new_page (const struct xr_driver *xr)
605 return xr->fsm != NULL;
608 /* Returns true if the current page doesn't have any content yet. */
610 xr_driver_is_page_blank (const struct xr_driver *xr)
616 xr_driver_destroy_fsm (struct xr_driver *xr)
620 xr->fsm->destroy (xr->fsm);
626 xr_driver_run_fsm (struct xr_driver *xr)
628 if (xr->fsm != NULL && !xr->fsm->render (xr->fsm, xr))
629 xr_driver_destroy_fsm (xr);
633 xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx,
634 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
635 int *width, int *height, int *brk);
638 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
640 cairo_new_path (xr->cairo);
641 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
642 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
643 cairo_stroke (xr->cairo);
647 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
649 cairo_new_path (xr->cairo);
650 cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
651 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
652 cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
653 cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
654 cairo_close_path (xr->cairo);
655 cairo_stroke (xr->cairo);
658 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
659 shortening it to X0...X1 if SHORTEN is true.
660 Draws a horizontal line X1...X3 at Y if RIGHT says so,
661 shortening it to X2...X3 if SHORTEN is true. */
663 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
664 enum render_line_style left, enum render_line_style right,
667 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
668 dump_line (xr, x0, y, x3, y);
671 if (left != RENDER_LINE_NONE)
672 dump_line (xr, x0, y, shorten ? x1 : x2, y);
673 if (right != RENDER_LINE_NONE)
674 dump_line (xr, shorten ? x2 : x1, y, x3, y);
678 /* Draws a vertical line Y0...Y2 at X if TOP says so,
679 shortening it to Y0...Y1 if SHORTEN is true.
680 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
681 shortening it to Y2...Y3 if SHORTEN is true. */
683 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
684 enum render_line_style top, enum render_line_style bottom,
687 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
688 dump_line (xr, x, y0, x, y3);
691 if (top != RENDER_LINE_NONE)
692 dump_line (xr, x, y0, x, shorten ? y1 : y2);
693 if (bottom != RENDER_LINE_NONE)
694 dump_line (xr, x, shorten ? y2 : y1, x, y3);
699 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
700 enum render_line_style styles[TABLE_N_AXES][2])
702 const int x0 = bb[H][0];
703 const int y0 = bb[V][0];
704 const int x3 = bb[H][1];
705 const int y3 = bb[V][1];
706 const int top = styles[H][0];
707 const int left = styles[V][0];
708 const int bottom = styles[H][1];
709 const int right = styles[V][1];
711 /* The algorithm here is somewhat subtle, to allow it to handle
712 all the kinds of intersections that we need.
714 Three additional ordinates are assigned along the x axis. The
715 first is xc, midway between x0 and x3. The others are x1 and
716 x2; for a single vertical line these are equal to xc, and for
717 a double vertical line they are the ordinates of the left and
718 right half of the double line.
720 yc, y1, and y2 are assigned similarly along the y axis.
722 The following diagram shows the coordinate system and output
723 for double top and bottom lines, single left line, and no
727 y0 ________________________
733 y1 = y2 = yc |######### # |
738 y3 |________#_____#_______|
740 struct xr_driver *xr = xr_;
742 /* Offset from center of each line in a pair of double lines. */
743 int double_line_ofs = (xr->line_space + xr->line_width) / 2;
745 /* Are the lines along each axis single or double?
746 (It doesn't make sense to have different kinds of line on the
747 same axis, so we don't try to gracefully handle that case.) */
748 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
749 bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
751 /* When horizontal lines are doubled,
752 the left-side line along y1 normally runs from x0 to x2,
753 and the right-side line along y1 from x3 to x1.
754 If the top-side line is also doubled, we shorten the y1 lines,
755 so that the left-side line runs only to x1,
756 and the right-side line only to x2.
757 Otherwise, the horizontal line at y = y1 below would cut off
758 the intersection, which looks ugly:
760 y0 ________________________
765 y1 |######### ########|
768 y2 |######################|
771 y3 |______________________|
772 It is more of a judgment call when the horizontal line is
773 single. We actually choose to cut off the line anyhow, as
774 shown in the first diagram above.
776 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
777 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
778 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
779 int horz_line_ofs = double_vert ? double_line_ofs : 0;
780 int xc = (x0 + x3) / 2;
781 int x1 = xc - horz_line_ofs;
782 int x2 = xc + horz_line_ofs;
784 bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
785 bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
786 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
787 int vert_line_ofs = double_horz ? double_line_ofs : 0;
788 int yc = (y0 + y3) / 2;
789 int y1 = yc - vert_line_ofs;
790 int y2 = yc + vert_line_ofs;
793 horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
796 horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
797 horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
801 vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
804 vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
805 vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
810 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
811 int footnote_idx, int *min_width, int *max_width)
813 struct xr_driver *xr = xr_;
814 int bb[TABLE_N_AXES][2];
815 int clip[TABLE_N_AXES][2];
822 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
823 xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL);
826 xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL);
829 *min_width += xr->cell_margin * 2;
831 *max_width += xr->cell_margin * 2;
835 xr_measure_cell_height (void *xr_, const struct table_cell *cell,
836 int footnote_idx, int width)
838 struct xr_driver *xr = xr_;
839 int bb[TABLE_N_AXES][2];
840 int clip[TABLE_N_AXES][2];
844 bb[H][1] = width - xr->cell_margin * 2;
849 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
850 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL);
855 xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx,
856 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
858 struct xr_driver *xr = xr_;
861 bb[H][0] += xr->cell_margin;
862 bb[H][1] -= xr->cell_margin;
863 if (bb[H][0] >= bb[H][1])
865 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
869 xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx,
870 int width, int height)
872 struct xr_driver *xr = xr_;
873 int bb[TABLE_N_AXES][2];
874 int clip[TABLE_N_AXES][2];
877 if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height)
881 bb[H][1] = width - 2 * xr->cell_margin;
886 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
887 xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk);
892 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
894 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
896 double x0 = xr_to_pt (clip[H][0] + xr->x);
897 double y0 = xr_to_pt (clip[V][0] + xr->y);
898 double x1 = xr_to_pt (clip[H][1] + xr->x);
899 double y1 = xr_to_pt (clip[V][1] + xr->y);
901 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
902 cairo_clip (xr->cairo);
907 add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
909 attr->start_index = start_index;
910 pango_attr_list_insert (list, attr);
914 xr_layout_cell_text (struct xr_driver *xr,
915 const struct cell_contents *contents, int footnote_idx,
916 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
917 int y, int *widthp, int *brk)
919 unsigned int options = contents->options;
920 struct xr_font *font;
921 bool merge_footnotes;
925 if (contents->n_footnotes == 0)
926 merge_footnotes = false;
927 else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT)
929 PangoAttrList *attrs;
932 font = &xr->fonts[XR_FONT_MARKER];
934 str_format_26adic (footnote_idx + 1, false, marker, sizeof marker);
935 pango_layout_set_text (font->layout, marker, strlen (marker));
937 attrs = pango_attr_list_new ();
938 pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
939 pango_layout_set_attributes (font->layout, attrs);
940 pango_attr_list_unref (attrs);
942 pango_layout_get_size (font->layout, &w, &h);
943 merge_footnotes = w > xr->cell_margin;
944 if (!merge_footnotes && clip[H][0] != clip[H][1])
946 cairo_save (xr->cairo);
948 cairo_translate (xr->cairo,
949 xr_to_pt (bb[H][1] + xr->x),
950 xr_to_pt (y + xr->y));
951 pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT);
952 pango_layout_set_width (font->layout, -1);
953 pango_cairo_show_layout (xr->cairo, font->layout);
954 cairo_restore (xr->cairo);
957 pango_layout_set_attributes (font->layout, NULL);
960 merge_footnotes = true;
962 font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
963 : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
964 : &xr->fonts[XR_FONT_PROPORTIONAL]);
966 length = strlen (contents->text);
969 PangoAttrList *attrs;
973 bb[H][1] += xr->cell_margin;
976 ds_extend (&s, length + contents->n_footnotes * 10);
977 ds_put_cstr (&s, contents->text);
978 for (i = 0; i < contents->n_footnotes; i++)
983 ds_put_byte (&s, ',');
984 str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker);
985 ds_put_cstr (&s, marker);
987 pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
990 attrs = pango_attr_list_new ();
991 add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
992 add_attr_with_start (
993 attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length);
994 pango_layout_set_attributes (font->layout, attrs);
995 pango_attr_list_unref (attrs);
998 pango_layout_set_text (font->layout, contents->text, -1);
1000 pango_layout_set_alignment (
1002 ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
1003 : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
1004 : PANGO_ALIGN_CENTER));
1005 pango_layout_set_width (
1007 bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
1008 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1010 if (clip[H][0] != clip[H][1])
1012 cairo_save (xr->cairo);
1014 cairo_translate (xr->cairo,
1015 xr_to_pt (bb[H][0] + xr->x),
1016 xr_to_pt (y + xr->y));
1017 pango_cairo_show_layout (xr->cairo, font->layout);
1019 /* If enabled, this draws a blue rectangle around the extents of each
1020 line of text, which can be rather useful for debugging layout
1024 PangoLayoutIter *iter;
1025 iter = pango_layout_get_iter (font->layout);
1028 PangoRectangle extents;
1030 pango_layout_iter_get_line_extents (iter, &extents, NULL);
1031 cairo_save (xr->cairo);
1032 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1034 pango_to_xr (extents.x) - xr->x,
1035 pango_to_xr (extents.y) - xr->y,
1036 pango_to_xr (extents.x + extents.width) - xr->x,
1037 pango_to_xr (extents.y + extents.height) - xr->y);
1038 cairo_restore (xr->cairo);
1040 while (pango_layout_iter_next_line (iter));
1041 pango_layout_iter_free (iter);
1044 cairo_restore (xr->cairo);
1047 pango_layout_get_size (font->layout, &w, &h);
1048 w = pango_to_xr (w);
1049 h = pango_to_xr (h);
1052 if (y + h >= bb[V][1])
1054 PangoLayoutIter *iter;
1055 int best UNUSED = 0;
1057 /* Choose a breakpoint between lines instead of in the middle of one. */
1058 iter = pango_layout_get_iter (font->layout);
1061 PangoRectangle extents;
1065 pango_layout_iter_get_line_extents (iter, NULL, &extents);
1066 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1067 extents.x = pango_to_xr (extents.x);
1068 extents.y = pango_to_xr (y0);
1069 extents.width = pango_to_xr (extents.width);
1070 extents.height = pango_to_xr (y1 - y0);
1071 bottom = y + extents.y + extents.height;
1072 if (bottom < bb[V][1])
1074 if (brk && clip[H][0] != clip[H][1])
1081 while (pango_layout_iter_next_line (iter));
1083 /* If enabled, draws a green line across the chosen breakpoint, which can
1084 be useful for debugging issues with breaking. */
1087 if (best && !xr->nest)
1089 cairo_save (xr->cairo);
1090 cairo_set_source_rgb (xr->cairo, 0, 1, 0);
1091 dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
1092 cairo_restore (xr->cairo);
1097 pango_layout_set_attributes (font->layout, NULL);
1102 xr_layout_cell_subtable (struct xr_driver *xr,
1103 const struct cell_contents *contents,
1104 int footnote_idx UNUSED,
1105 int bb[TABLE_N_AXES][2],
1106 int clip[TABLE_N_AXES][2], int *widthp, int *brk)
1108 int single_width, double_width;
1109 struct render_params params;
1110 struct render_pager *p;
1111 int r[TABLE_N_AXES][2];
1115 params.draw_line = xr_draw_line;
1116 params.measure_cell_width = xr_measure_cell_width;
1117 params.measure_cell_height = xr_measure_cell_height;
1118 params.adjust_break = NULL;
1119 params.draw_cell = xr_draw_cell;
1121 params.size[H] = bb[H][1] - bb[H][0];
1122 params.size[V] = bb[V][1] - bb[V][0];
1123 params.font_size[H] = xr->char_width;
1124 params.font_size[V] = xr->char_height;
1126 single_width = 2 * xr->line_gutter + xr->line_width;
1127 double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
1128 for (i = 0; i < TABLE_N_AXES; i++)
1130 params.line_widths[i][RENDER_LINE_NONE] = 0;
1131 params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
1132 params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
1136 p = render_pager_create (¶ms, contents->table);
1137 width = render_pager_get_size (p, H);
1138 height = render_pager_get_size (p, V);
1139 if (bb[V][0] + height >= bb[V][1])
1140 *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
1142 /* r = intersect(bb, clip) - bb. */
1143 for (i = 0; i < TABLE_N_AXES; i++)
1145 r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
1146 r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
1149 if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
1151 unsigned int alignment = contents->options & TAB_ALIGNMENT;
1154 cairo_save (xr->cairo);
1157 if (alignment == TAB_RIGHT)
1158 xr->x += params.size[H] - width;
1159 else if (alignment == TAB_CENTER)
1160 xr->x += (params.size[H] - width) / 2;
1162 render_pager_draw_region (p, r[H][0], r[V][0],
1163 r[H][1] - r[H][0], r[V][1] - r[V][0]);
1166 cairo_restore (xr->cairo);
1168 render_pager_destroy (p);
1171 if (width > *widthp)
1173 return bb[V][0] + height;
1177 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1179 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1180 int *width, int *height, int *brk)
1182 int bb[TABLE_N_AXES][2];
1190 memcpy (bb, bb_, sizeof bb);
1192 /* If enabled, draws a blue rectangle around the cell extents, which can be
1193 useful for debugging layout. */
1196 if (clip[H][0] != clip[H][1])
1198 int offset = (xr->nest) * XR_POINT;
1200 cairo_save (xr->cairo);
1201 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1203 bb[H][0] + offset, bb[V][0] + offset,
1204 bb[H][1] - offset, bb[V][1] - offset);
1205 cairo_restore (xr->cairo);
1209 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1211 const struct cell_contents *contents = &cell->contents[i];
1217 bb[V][0] += xr->char_height / 2;
1218 if (bb[V][0] >= bb[V][1])
1225 bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip,
1226 bb[V][0], width, brk);
1228 bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx,
1229 bb, clip, width, brk);
1230 footnote_idx += contents->n_footnotes;
1232 *height = bb[V][0] - bb_[V][0];
1235 struct output_driver_factory pdf_driver_factory =
1236 { "pdf", "pspp.pdf", xr_pdf_create };
1237 struct output_driver_factory ps_driver_factory =
1238 { "ps", "pspp.ps", xr_ps_create };
1239 struct output_driver_factory svg_driver_factory =
1240 { "svg", "pspp.svg", xr_svg_create };
1242 static const struct output_driver_class cairo_driver_class =
1250 /* GUI rendering helpers. */
1254 struct output_item *item;
1257 struct render_pager *p;
1258 struct xr_driver *xr;
1261 #define CHART_WIDTH 500
1262 #define CHART_HEIGHT 375
1267 xr_driver_create (cairo_t *cairo, struct string_map *options)
1269 struct xr_driver *xr = xr_allocate ("cairo", 0, options);
1270 if (!xr_set_cairo (xr, cairo))
1272 output_driver_destroy (&xr->driver);
1278 /* Destroy XR, which should have been created with xr_driver_create(). Any
1279 cairo_t added to XR is not destroyed, because it is owned by the client. */
1281 xr_driver_destroy (struct xr_driver *xr)
1286 output_driver_destroy (&xr->driver);
1290 static struct xr_rendering *
1291 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
1293 struct table_item *table_item;
1294 struct xr_rendering *r;
1296 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1298 r = xr_rendering_create (xr, &table_item->output_item, cr);
1299 table_item_unref (table_item);
1305 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
1307 if (is_table_item (xr->item))
1308 apply_options (xr->xr, o);
1311 struct xr_rendering *
1312 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
1315 struct xr_rendering *r = NULL;
1317 if (is_text_item (item))
1318 r = xr_rendering_create_text (xr, text_item_get_text (to_text_item (item)),
1320 else if (is_message_item (item))
1322 const struct message_item *message_item = to_message_item (item);
1323 const struct msg *msg = message_item_get_msg (message_item);
1324 char *s = msg_to_string (msg, NULL);
1325 r = xr_rendering_create_text (xr, s, cr);
1328 else if (is_table_item (item))
1330 r = xzalloc (sizeof *r);
1331 r->item = output_item_ref (item);
1333 xr_set_cairo (xr, cr);
1334 r->p = render_pager_create (xr->params, to_table_item (item));
1336 else if (is_chart_item (item))
1338 r = xzalloc (sizeof *r);
1339 r->item = output_item_ref (item);
1346 xr_rendering_destroy (struct xr_rendering *r)
1350 output_item_unref (r->item);
1351 render_pager_destroy (r->p);
1357 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
1359 if (is_table_item (r->item))
1361 *w = render_pager_get_size (r->p, H) / XR_POINT;
1362 *h = render_pager_get_size (r->p, V) / XR_POINT;
1371 static void xr_draw_chart (const struct chart_item *, cairo_t *,
1372 double x, double y, double width, double height);
1374 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
1375 and possibly some additional parts. */
1377 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
1378 int x, int y, int w, int h)
1380 if (is_table_item (r->item))
1382 struct xr_driver *xr = r->xr;
1384 xr_set_cairo (xr, cr);
1387 render_pager_draw_region (r->p,
1388 x * XR_POINT, y * XR_POINT,
1389 w * XR_POINT, h * XR_POINT);
1392 xr_draw_chart (to_chart_item (r->item), cr,
1393 0, 0, CHART_WIDTH, CHART_HEIGHT);
1397 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1398 double x, double y, double width, double height)
1400 struct xrchart_geometry geom;
1403 cairo_translate (cr, x, y + height);
1404 cairo_scale (cr, 1.0, -1.0);
1405 xrchart_geometry_init (cr, &geom, width, height);
1406 if (is_boxplot (chart_item))
1407 xrchart_draw_boxplot (chart_item, cr, &geom);
1408 else if (is_histogram_chart (chart_item))
1409 xrchart_draw_histogram (chart_item, cr, &geom);
1410 else if (is_np_plot_chart (chart_item))
1411 xrchart_draw_np_plot (chart_item, cr, &geom);
1412 else if (is_piechart (chart_item))
1413 xrchart_draw_piechart (chart_item, cr, &geom);
1414 else if (is_barchart (chart_item))
1415 xrchart_draw_barchart (chart_item, cr, &geom);
1416 else if (is_roc_chart (chart_item))
1417 xrchart_draw_roc (chart_item, cr, &geom);
1418 else if (is_scree (chart_item))
1419 xrchart_draw_scree (chart_item, cr, &geom);
1420 else if (is_spreadlevel_plot_chart (chart_item))
1421 xrchart_draw_spreadlevel (chart_item, cr, &geom);
1422 else if (is_scatterplot_chart (chart_item))
1423 xrchart_draw_scatterplot (chart_item, cr, &geom);
1426 xrchart_geometry_free (cr, &geom);
1432 xr_draw_png_chart (const struct chart_item *item,
1433 const char *file_name_template, int number,
1434 const struct xr_color *fg,
1435 const struct xr_color *bg
1438 const int width = 640;
1439 const int length = 480;
1441 cairo_surface_t *surface;
1442 cairo_status_t status;
1443 const char *number_pos;
1447 number_pos = strchr (file_name_template, '#');
1448 if (number_pos != NULL)
1449 file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
1450 file_name_template, number, number_pos + 1);
1452 file_name = xstrdup (file_name_template);
1454 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1455 cr = cairo_create (surface);
1457 cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
1460 cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
1462 xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1464 status = cairo_surface_write_to_png (surface, file_name);
1465 if (status != CAIRO_STATUS_SUCCESS)
1466 msg (ME, _("error writing output file `%s': %s"),
1467 file_name, cairo_status_to_string (status));
1470 cairo_surface_destroy (surface);
1475 struct xr_table_state
1477 struct xr_render_fsm fsm;
1478 struct table_item *table_item;
1479 struct render_pager *p;
1483 xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1485 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1487 while (render_pager_has_next (ts->p))
1491 used = render_pager_draw_next (ts->p, xr->length - xr->y);
1504 xr_table_destroy (struct xr_render_fsm *fsm)
1506 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
1508 table_item_unref (ts->table_item);
1509 render_pager_destroy (ts->p);
1513 static struct xr_render_fsm *
1514 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
1516 struct xr_table_state *ts;
1518 ts = xmalloc (sizeof *ts);
1519 ts->fsm.render = xr_table_render;
1520 ts->fsm.destroy = xr_table_destroy;
1521 ts->table_item = table_item_ref (table_item);
1524 xr->y += xr->char_height;
1526 ts->p = render_pager_create (xr->params, table_item);
1531 struct xr_chart_state
1533 struct xr_render_fsm fsm;
1534 struct chart_item *chart_item;
1538 xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
1540 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1545 if (xr->cairo != NULL)
1546 xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
1547 xr_to_pt (xr->width), xr_to_pt (xr->length));
1554 xr_chart_destroy (struct xr_render_fsm *fsm)
1556 struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
1558 chart_item_unref (cs->chart_item);
1562 static struct xr_render_fsm *
1563 xr_render_chart (const struct chart_item *chart_item)
1565 struct xr_chart_state *cs;
1567 cs = xmalloc (sizeof *cs);
1568 cs->fsm.render = xr_chart_render;
1569 cs->fsm.destroy = xr_chart_destroy;
1570 cs->chart_item = chart_item_ref (chart_item);
1576 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_driver *xr)
1582 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
1584 /* Nothing to do. */
1587 static struct xr_render_fsm *
1588 xr_render_eject (void)
1590 static struct xr_render_fsm eject_renderer =
1596 return &eject_renderer;
1599 static struct xr_render_fsm *
1600 xr_create_text_renderer (struct xr_driver *xr, const char *text)
1602 struct table_item *table_item;
1603 struct xr_render_fsm *fsm;
1605 table_item = table_item_create (table_from_string (TAB_LEFT, text),
1607 fsm = xr_render_table (xr, table_item);
1608 table_item_unref (table_item);
1613 static struct xr_render_fsm *
1614 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
1616 enum text_item_type type = text_item_get_type (text_item);
1617 const char *text = text_item_get_text (text_item);
1621 case TEXT_ITEM_TITLE:
1623 xr->title = xstrdup (text);
1626 case TEXT_ITEM_SUBTITLE:
1627 free (xr->subtitle);
1628 xr->subtitle = xstrdup (text);
1631 case TEXT_ITEM_COMMAND_CLOSE:
1634 case TEXT_ITEM_BLANK_LINE:
1636 xr->y += xr->char_height;
1639 case TEXT_ITEM_EJECT_PAGE:
1641 return xr_render_eject ();
1645 return xr_create_text_renderer (xr, text);
1651 static struct xr_render_fsm *
1652 xr_render_message (struct xr_driver *xr,
1653 const struct message_item *message_item)
1655 const struct msg *msg = message_item_get_msg (message_item);
1656 struct xr_render_fsm *fsm;
1659 s = msg_to_string (msg, xr->command_name);
1660 fsm = xr_create_text_renderer (xr, s);
1666 static struct xr_render_fsm *
1667 xr_render_output_item (struct xr_driver *xr,
1668 const struct output_item *output_item)
1670 if (is_table_item (output_item))
1671 return xr_render_table (xr, to_table_item (output_item));
1672 else if (is_chart_item (output_item))
1673 return xr_render_chart (to_chart_item (output_item));
1674 else if (is_text_item (output_item))
1675 return xr_render_text (xr, to_text_item (output_item));
1676 else if (is_message_item (output_item))
1677 return xr_render_message (xr, to_message_item (output_item));