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-fsm.h"
22 #include <pango/pango-layout.h>
23 #include <pango/pango.h>
24 #include <pango/pangocairo.h>
26 #include "libpspp/assertion.h"
27 #include "libpspp/str.h"
28 #include "output/cairo-chart.h"
29 #include "output/chart-item-provider.h"
30 #include "output/chart-item.h"
31 #include "output/charts/barchart.h"
32 #include "output/charts/boxplot.h"
33 #include "output/charts/np-plot.h"
34 #include "output/charts/piechart.h"
35 #include "output/charts/plot-hist.h"
36 #include "output/charts/roc-chart.h"
37 #include "output/charts/scatterplot.h"
38 #include "output/charts/scree.h"
39 #include "output/charts/spreadlevel-plot.h"
40 #include "output/group-item.h"
41 #include "output/message-item.h"
42 #include "output/page-eject-item.h"
43 #include "output/page-setup-item.h"
44 #include "output/render.h"
45 #include "output/table-item.h"
46 #include "output/text-item.h"
48 #include "gl/c-ctype.h"
49 #include "gl/c-strcase.h"
50 #include "gl/xalloc.h"
52 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
57 xr_fsm_style_ref (const struct xr_fsm_style *style_)
59 assert (style_->ref_cnt > 0);
61 struct xr_fsm_style *style = CONST_CAST (struct xr_fsm_style *, style_);
67 xr_fsm_style_unshare (struct xr_fsm_style *old)
69 assert (old->ref_cnt > 0);
70 if (old->ref_cnt == 1)
73 xr_fsm_style_unref (old);
75 struct xr_fsm_style *new = xmemdup (old, sizeof *old);
77 for (int i = 0; i < XR_N_FONTS; i++)
79 new->fonts[i] = pango_font_description_copy (old->fonts[i]);
85 xr_fsm_style_unref (struct xr_fsm_style *style)
89 assert (style->ref_cnt > 0);
90 if (!--style->ref_cnt)
92 for (size_t i = 0; i < XR_N_FONTS; i++)
93 pango_font_description_free (style->fonts[i]);
100 xr_fsm_style_equals (const struct xr_fsm_style *a,
101 const struct xr_fsm_style *b)
103 if (a->size[H] != b->size[H]
104 || a->size[V] != b->size[V]
105 || a->min_break[H] != b->min_break[H]
106 || a->min_break[V] != b->min_break[V]
107 || a->use_system_colors != b->use_system_colors
108 || a->font_resolution != b->font_resolution)
111 for (size_t i = 0; i < XR_N_FONTS; i++)
112 if (!pango_font_description_equal (a->fonts[i], b->fonts[i]))
118 /* Renders a single output_item to an output device in one of two ways:
120 - 'print == true': Broken across multiple pages if necessary.
122 - 'print == false': In a single region that the user may scroll around if
125 Normally 'output_item' corresponds to a single rendering. There is a
126 special case when 'print == true' and 'output_item' is a table_item with
127 multiple layers and 'item->pt->table_look->print_all_layers == true'. In
128 that case, each layer is rendered separately from the FSM's internal point
129 of view; from the client's point of view, it is all one operation.
133 struct xr_fsm_style *style;
134 struct output_item *item;
137 /* Table items only. */
138 size_t *layer_indexes;
139 struct render_params rp;
140 struct render_pager *p;
141 cairo_t *cairo; /* XXX should this be here?! */
143 /* Chart and page-eject items only. */
147 /* The unit used for internal measurements is inch/(72 * XR_POINT).
148 (Thus, XR_POINT units represent one point.) */
149 #define XR_POINT PANGO_SCALE
151 /* Conversions to and from points. */
155 return x / (double) XR_POINT;
158 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
162 return x * (PANGO_SCALE * 72 / 96);
166 pango_to_xr (int pango)
168 return (XR_POINT != PANGO_SCALE
169 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
176 return (XR_POINT != PANGO_SCALE
177 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
181 /* Dimensions for drawing lines in tables. */
182 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
183 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
186 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
187 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
188 int *width, int *height, int *brk);
191 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
193 cairo_set_source_rgba (cairo,
194 color->r / 255., color->g / 255., color->b / 255.,
195 color->alpha / 255.);
199 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
200 const struct cell_color *color)
202 cairo_new_path (xr->cairo);
203 cairo_set_line_width (
205 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
206 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
208 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
209 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
211 if (!xr->style->use_system_colors)
212 xr_set_source_rgba (xr->cairo, color);
213 if (style == RENDER_LINE_DASHED)
214 cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
215 cairo_stroke (xr->cairo);
216 if (style == RENDER_LINE_DASHED)
217 cairo_set_dash (xr->cairo, NULL, 0, 0);
221 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
223 cairo_new_path (xr->cairo);
224 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
225 cairo_close_path (xr->cairo);
226 cairo_stroke (xr->cairo);
227 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
228 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
229 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
230 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
234 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
236 cairo_new_path (xr->cairo);
237 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
238 cairo_rectangle (xr->cairo,
239 xr_to_pt (x0), xr_to_pt (y0),
240 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
241 cairo_fill (xr->cairo);
244 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
245 shortening it to X0...X1 if SHORTEN is true.
246 Draws a horizontal line X1...X3 at Y if RIGHT says so,
247 shortening it to X2...X3 if SHORTEN is true. */
249 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
250 enum render_line_style left, enum render_line_style right,
251 const struct cell_color *left_color,
252 const struct cell_color *right_color,
255 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
256 && cell_color_equal (left_color, right_color))
257 xr_draw_line (xr, x0, y, x3, y, left, left_color);
260 if (left != RENDER_LINE_NONE)
261 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
262 if (right != RENDER_LINE_NONE)
263 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
267 /* Draws a vertical line Y0...Y2 at X if TOP says so,
268 shortening it to Y0...Y1 if SHORTEN is true.
269 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
270 shortening it to Y2...Y3 if SHORTEN is true. */
272 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
273 enum render_line_style top, enum render_line_style bottom,
274 const struct cell_color *top_color,
275 const struct cell_color *bottom_color,
278 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
279 && cell_color_equal (top_color, bottom_color))
280 xr_draw_line (xr, x, y0, x, y3, top, top_color);
283 if (top != RENDER_LINE_NONE)
284 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
285 if (bottom != RENDER_LINE_NONE)
286 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
291 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
292 enum render_line_style styles[TABLE_N_AXES][2],
293 struct cell_color colors[TABLE_N_AXES][2])
295 const int x0 = bb[H][0];
296 const int y0 = bb[V][0];
297 const int x3 = bb[H][1];
298 const int y3 = bb[V][1];
299 const int top = styles[H][0];
300 const int bottom = styles[H][1];
302 int start_side = render_direction_rtl();
303 int end_side = !start_side;
304 const int start_of_line = styles[V][start_side];
305 const int end_of_line = styles[V][end_side];
306 const struct cell_color *top_color = &colors[H][0];
307 const struct cell_color *bottom_color = &colors[H][1];
308 const struct cell_color *start_color = &colors[V][start_side];
309 const struct cell_color *end_color = &colors[V][end_side];
311 /* The algorithm here is somewhat subtle, to allow it to handle
312 all the kinds of intersections that we need.
314 Three additional ordinates are assigned along the x axis. The
315 first is xc, midway between x0 and x3. The others are x1 and
316 x2; for a single vertical line these are equal to xc, and for
317 a double vertical line they are the ordinates of the left and
318 right half of the double line.
320 yc, y1, and y2 are assigned similarly along the y axis.
322 The following diagram shows the coordinate system and output
323 for double top and bottom lines, single left line, and no
327 y0 ________________________
333 y1 = y2 = yc |######### # |
338 y3 |________#_____#_______|
340 struct xr_fsm *xr = xr_;
342 /* Offset from center of each line in a pair of double lines. */
343 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
345 /* Are the lines along each axis single or double?
346 (It doesn't make sense to have different kinds of line on the
347 same axis, so we don't try to gracefully handle that case.) */
348 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
349 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
351 /* When horizontal lines are doubled,
352 the left-side line along y1 normally runs from x0 to x2,
353 and the right-side line along y1 from x3 to x1.
354 If the top-side line is also doubled, we shorten the y1 lines,
355 so that the left-side line runs only to x1,
356 and the right-side line only to x2.
357 Otherwise, the horizontal line at y = y1 below would cut off
358 the intersection, which looks ugly:
360 y0 ________________________
365 y1 |######### ########|
368 y2 |######################|
371 y3 |______________________|
372 It is more of a judgment call when the horizontal line is
373 single. We actually choose to cut off the line anyhow, as
374 shown in the first diagram above.
376 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
377 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
378 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
379 int horz_line_ofs = double_vert ? double_line_ofs : 0;
380 int xc = (x0 + x3) / 2;
381 int x1 = xc - horz_line_ofs;
382 int x2 = xc + horz_line_ofs;
384 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
385 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
386 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
387 int vert_line_ofs = double_horz ? double_line_ofs : 0;
388 int yc = (y0 + y3) / 2;
389 int y1 = yc - vert_line_ofs;
390 int y2 = yc + vert_line_ofs;
393 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
394 start_color, end_color, shorten_yc_line);
397 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
398 start_color, end_color, shorten_y1_lines);
399 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
400 start_color, end_color, shorten_y2_lines);
404 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
405 top_color, bottom_color, shorten_xc_line);
408 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
409 top_color, bottom_color, shorten_x1_lines);
410 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
411 top_color, bottom_color, shorten_x2_lines);
416 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
417 int *min_width, int *max_width)
419 struct xr_fsm *xr = xr_;
420 int bb[TABLE_N_AXES][2];
421 int clip[TABLE_N_AXES][2];
428 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
429 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
432 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
435 *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
436 + cell->style->cell_style.margin[H][1]);
438 *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
439 + cell->style->cell_style.margin[H][1]);
443 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
445 struct xr_fsm *xr = xr_;
446 int bb[TABLE_N_AXES][2];
447 int clip[TABLE_N_AXES][2];
451 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
452 + cell->style->cell_style.margin[H][1]);
455 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
456 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
457 h += px_to_xr (cell->style->cell_style.margin[V][0]
458 + cell->style->cell_style.margin[V][1]);
462 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
465 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
466 int bb[TABLE_N_AXES][2], int valign_offset,
467 int spill[TABLE_N_AXES][2],
468 int clip[TABLE_N_AXES][2])
470 struct xr_fsm *xr = xr_;
473 const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
474 if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
476 cairo_save (xr->cairo);
477 int bg_clip[TABLE_N_AXES][2];
478 for (int axis = 0; axis < TABLE_N_AXES; axis++)
480 bg_clip[axis][0] = clip[axis][0];
481 if (bb[axis][0] == clip[axis][0])
482 bg_clip[axis][0] -= spill[axis][0];
484 bg_clip[axis][1] = clip[axis][1];
485 if (bb[axis][1] == clip[axis][1])
486 bg_clip[axis][1] += spill[axis][1];
488 xr_clip (xr, bg_clip);
489 xr_set_source_rgba (xr->cairo, bg);
491 bb[H][0] - spill[H][0],
492 bb[V][0] - spill[V][0],
493 bb[H][1] + spill[H][1],
494 bb[V][1] + spill[V][1]);
495 cairo_restore (xr->cairo);
497 cairo_save (xr->cairo);
498 if (!xr->style->use_system_colors)
499 xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
501 bb[V][0] += valign_offset;
503 for (int axis = 0; axis < TABLE_N_AXES; axis++)
505 bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
506 bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
508 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
509 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
510 cairo_restore (xr->cairo);
514 xrr_adjust_break (void *xr_, const struct table_cell *cell,
515 int width, int height)
517 struct xr_fsm *xr = xr_;
518 int bb[TABLE_N_AXES][2];
519 int clip[TABLE_N_AXES][2];
522 if (xrr_measure_cell_height (xr_, cell, width) < height)
526 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
527 + cell->style->cell_style.margin[H][1]);
531 bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
532 + cell->style->cell_style.margin[V][1]);
533 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
534 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
539 xrr_scale (void *xr_, double scale)
541 struct xr_fsm *xr = xr_;
542 cairo_scale (xr->cairo, scale, scale);
546 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
548 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
550 double x0 = xr_to_pt (clip[H][0]);
551 double y0 = xr_to_pt (clip[V][0]);
552 double x1 = xr_to_pt (clip[H][1]);
553 double y1 = xr_to_pt (clip[V][1]);
555 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
556 cairo_clip (xr->cairo);
561 add_attr (PangoAttrList *list, PangoAttribute *attr,
562 guint start_index, guint end_index)
564 attr->start_index = start_index;
565 attr->end_index = end_index;
566 pango_attr_list_insert (list, attr);
570 markup_escape (struct string *out, unsigned int options,
571 const char *in, size_t len)
573 if (!(options & TAB_MARKUP))
575 ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
587 ds_put_cstr (out, "&");
590 ds_put_cstr (out, "<");
593 ds_put_cstr (out, ">");
596 ds_put_byte (out, c);
603 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
605 int size[TABLE_N_AXES];
606 pango_layout_get_size (layout, &size[H], &size[V]);
610 static PangoFontDescription *
611 parse_font (const char *font, int default_size, bool bold, bool italic)
613 if (!c_strcasecmp (font, "Monospaced"))
616 PangoFontDescription *desc = pango_font_description_from_string (font);
620 /* If the font description didn't include an explicit font size, then set it
621 to DEFAULT_SIZE, which is in inch/72000 units. */
622 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
623 pango_font_description_set_size (desc,
624 (default_size / 1000.0) * PANGO_SCALE);
626 pango_font_description_set_weight (desc, (bold
628 : PANGO_WEIGHT_NORMAL));
629 pango_font_description_set_style (desc, (italic
631 : PANGO_STYLE_NORMAL));
637 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
638 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
639 int *widthp, int *brk)
641 const struct font_style *font_style = &cell->style->font_style;
642 const struct cell_style *cell_style = &cell->style->cell_style;
643 unsigned int options = cell->options;
645 enum table_axis X = options & TAB_ROTATE ? V : H;
646 enum table_axis Y = !X;
647 int R = options & TAB_ROTATE ? 0 : 1;
649 enum xr_font_type font_type = (options & TAB_FIX
651 : XR_FONT_PROPORTIONAL);
652 PangoFontDescription *desc = NULL;
653 if (font_style->typeface)
655 font_style->typeface,
656 font_style->size ? font_style->size * 1000 : 10000,
657 font_style->bold, font_style->italic);
659 desc = xr->style->fonts[font_type];
662 PangoContext *context = pango_cairo_create_context (xr->cairo);
663 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
664 PangoLayout *layout = pango_layout_new (context);
665 g_object_unref (context);
667 pango_layout_set_font_description (layout, desc);
669 const char *text = cell->text;
670 enum table_halign halign = table_halign_interpret (
671 cell_style->halign, cell->options & TAB_NUMERIC);
672 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
674 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
676 const char *decimal = strrchr (text, cell_style->decimal_char);
679 pango_layout_set_text (layout, decimal, strlen (decimal));
680 pango_layout_set_width (layout, -1);
681 margin_adjustment += get_layout_dimension (layout, H);
684 if (margin_adjustment < 0)
685 bb[H][1] += margin_adjustment;
688 struct string tmp = DS_EMPTY_INITIALIZER;
689 PangoAttrList *attrs = NULL;
691 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
692 Pango's implementation of it): it will break after a period or a comma
693 that precedes a digit, e.g. in ".000" it will break after the period.
694 This code looks for such a situation and inserts a U+2060 WORD JOINER
695 to prevent the break.
697 This isn't necessary when the decimal point is between two digits
698 (e.g. "0.000" won't be broken) or when the display width is not limited so
699 that word wrapping won't happen.
701 It isn't necessary to look for more than one period or comma, as would
702 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
703 are present then there will always be a digit on both sides of every
705 if (options & TAB_MARKUP)
707 PangoAttrList *new_attrs;
709 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
712 tmp.ss = ss_cstr (new_text);
713 tmp.capacity = tmp.ss.length;
717 /* XXX should we report the error? */
718 ds_put_cstr (&tmp, text);
721 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
723 const char *decimal = text + strcspn (text, ".,");
725 && c_isdigit (decimal[1])
726 && (decimal == text || !c_isdigit (decimal[-1])))
728 ds_extend (&tmp, strlen (text) + 16);
729 markup_escape (&tmp, options, text, decimal - text + 1);
730 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
731 markup_escape (&tmp, options, decimal + 1, -1);
735 if (font_style->underline)
738 attrs = pango_attr_list_new ();
739 pango_attr_list_insert (attrs, pango_attr_underline_new (
740 PANGO_UNDERLINE_SINGLE));
743 if (cell->n_footnotes || cell->n_subscripts)
745 /* If we haven't already put TEXT into tmp, do it now. */
746 if (ds_is_empty (&tmp))
748 ds_extend (&tmp, strlen (text) + 16);
749 markup_escape (&tmp, options, text, -1);
752 size_t subscript_ofs = ds_length (&tmp);
753 for (size_t i = 0; i < cell->n_subscripts; i++)
756 ds_put_byte (&tmp, ',');
757 ds_put_cstr (&tmp, cell->subscripts[i]);
760 size_t footnote_ofs = ds_length (&tmp);
761 for (size_t i = 0; i < cell->n_footnotes; i++)
764 ds_put_byte (&tmp, ',');
765 ds_put_cstr (&tmp, cell->footnotes[i]->marker);
768 /* Allow footnote markers to occupy the right margin. That way, numbers
769 in the column are still aligned. */
770 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
772 /* Measure the width of the footnote marker, so we know how much we
773 need to make room for. */
774 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
775 ds_length (&tmp) - footnote_ofs);
777 PangoAttrList *fn_attrs = pango_attr_list_new ();
778 pango_attr_list_insert (
779 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
780 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
781 pango_layout_set_attributes (layout, fn_attrs);
782 pango_attr_list_unref (fn_attrs);
783 int footnote_width = get_layout_dimension (layout, X);
785 /* Bound the adjustment by the width of the right margin. */
786 int right_margin = px_to_xr (cell_style->margin[X][R]);
787 int footnote_adjustment = MIN (footnote_width, right_margin);
789 /* Adjust the bounding box. */
790 if (options & TAB_ROTATE)
791 footnote_adjustment = -footnote_adjustment;
792 bb[X][R] += footnote_adjustment;
795 pango_layout_set_attributes (layout, NULL);
798 /* Set attributes. */
800 attrs = pango_attr_list_new ();
801 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
802 PANGO_ATTR_INDEX_TO_TEXT_END);
803 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
804 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
805 if (cell->n_subscripts)
806 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
807 footnote_ofs - subscript_ofs);
808 if (cell->n_footnotes)
809 add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
810 PANGO_ATTR_INDEX_TO_TEXT_END);
813 /* Set the attributes, if any. */
816 pango_layout_set_attributes (layout, attrs);
817 pango_attr_list_unref (attrs);
821 if (ds_is_empty (&tmp))
822 pango_layout_set_text (layout, text, -1);
824 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
827 pango_layout_set_alignment (layout,
828 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
829 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
830 : PANGO_ALIGN_CENTER));
831 pango_layout_set_width (
833 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
834 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
836 int size[TABLE_N_AXES];
837 pango_layout_get_size (layout, &size[H], &size[V]);
839 if (clip[H][0] != clip[H][1])
841 cairo_save (xr->cairo);
842 if (!(options & TAB_ROTATE))
844 if (options & TAB_ROTATE)
846 int extra = bb[H][1] - bb[H][0] - size[V];
847 int halign_offset = extra > 0 ? extra / 2 : 0;
848 cairo_translate (xr->cairo,
849 xr_to_pt (bb[H][0] + halign_offset),
850 xr_to_pt (bb[V][1]));
851 cairo_rotate (xr->cairo, -M_PI_2);
854 cairo_translate (xr->cairo,
856 xr_to_pt (bb[V][0]));
857 pango_cairo_show_layout (xr->cairo, layout);
859 /* If enabled, this draws a blue rectangle around the extents of each
860 line of text, which can be rather useful for debugging layout
864 PangoLayoutIter *iter;
865 iter = pango_layout_get_iter (layout);
868 PangoRectangle extents;
870 pango_layout_iter_get_line_extents (iter, &extents, NULL);
871 cairo_save (xr->cairo);
872 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
875 pango_to_xr (extents.x),
876 pango_to_xr (extents.y),
877 pango_to_xr (extents.x + extents.width),
878 pango_to_xr (extents.y + extents.height));
879 cairo_restore (xr->cairo);
881 while (pango_layout_iter_next_line (iter));
882 pango_layout_iter_free (iter);
885 cairo_restore (xr->cairo);
888 int w = pango_to_xr (size[X]);
889 int h = pango_to_xr (size[Y]);
892 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
894 PangoLayoutIter *iter;
897 /* Choose a breakpoint between lines instead of in the middle of one. */
898 iter = pango_layout_get_iter (layout);
901 PangoRectangle extents;
905 pango_layout_iter_get_line_extents (iter, NULL, &extents);
906 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
907 extents.x = pango_to_xr (extents.x);
908 extents.y = pango_to_xr (y0);
909 extents.width = pango_to_xr (extents.width);
910 extents.height = pango_to_xr (y1 - y0);
911 bottom = bb[V][0] + extents.y + extents.height;
912 if (bottom < bb[V][1])
914 if (brk && clip[H][0] != clip[H][1])
922 while (pango_layout_iter_next_line (iter));
923 pango_layout_iter_free (iter);
925 /* If enabled, draws a green line across the chosen breakpoint, which can
926 be useful for debugging issues with breaking. */
930 xr_draw_line (xr, 0, best,
931 xr->style->size[H], best,
933 &(struct cell_color) CELL_COLOR (0, 255, 0));
937 pango_layout_set_attributes (layout, NULL);
939 if (desc != xr->style->fonts[font_type])
940 pango_font_description_free (desc);
941 g_object_unref (G_OBJECT (layout));
947 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
948 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
949 int *width, int *height, int *brk)
954 /* If enabled, draws a blue rectangle around the cell extents, which can be
955 useful for debugging layout. */
958 if (clip[H][0] != clip[H][1])
960 cairo_save (xr->cairo);
961 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
962 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
963 cairo_restore (xr->cairo);
969 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
972 #define CHART_WIDTH 500
973 #define CHART_HEIGHT 375
975 static struct xr_fsm *
976 xr_fsm_create (const struct output_item *item_,
977 const struct xr_fsm_style *style, cairo_t *cr,
980 if (is_page_setup_item (item_)
981 || is_group_open_item (item_)
982 || is_group_close_item (item_))
985 struct output_item *item;
986 if (is_table_item (item_)
987 || is_chart_item (item_)
988 || is_page_eject_item (item_))
989 item = output_item_ref (item_);
990 else if (is_message_item (item_))
991 item = table_item_super (
992 text_item_to_table_item (
993 message_item_to_text_item (
995 output_item_ref (item_)))));
996 else if (is_text_item (item_))
998 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1001 item = table_item_super (
1002 text_item_to_table_item (
1004 output_item_ref (item_))));
1006 else if (is_group_open_item (item_))
1008 item = table_item_super (
1009 text_item_to_table_item (
1010 text_item_create (TEXT_ITEM_TITLE,
1011 to_group_open_item (item_)->command_name,
1016 assert (is_table_item (item)
1017 || is_chart_item (item)
1018 || is_page_eject_item (item));
1020 size_t *layer_indexes = NULL;
1021 if (is_table_item (item))
1023 layer_indexes = pivot_table_next_layer (table_item->pt, NULL, print);
1028 static const struct render_ops xrr_render_ops = {
1029 .measure_cell_width = xrr_measure_cell_width,
1030 .measure_cell_height = xrr_measure_cell_height,
1031 .adjust_break = xrr_adjust_break,
1032 .draw_line = xrr_draw_line,
1033 .draw_cell = xrr_draw_cell,
1037 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1038 static const int xr_line_widths[RENDER_N_LINES] =
1040 [RENDER_LINE_NONE] = 0,
1041 [RENDER_LINE_SINGLE] = LW,
1042 [RENDER_LINE_DASHED] = LW,
1043 [RENDER_LINE_THICK] = LW * 2,
1044 [RENDER_LINE_THIN] = LW / 2,
1045 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1048 size_t *layer_indexes = NULL;
1049 if (is_table_item (item)
1050 pivot_table_next_display_layer (
1052 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1053 *fsm = (struct xr_fsm) {
1054 .style = xr_fsm_style_ref (style),
1057 .layer_indexes = layer_indexes,
1059 .ops = &xrr_render_ops,
1061 .size = { [H] = style->size[H], [V] = style->size[V] },
1062 .line_widths = xr_line_widths,
1063 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1064 .supports_margins = true,
1065 .rtl = render_direction_rtl (),
1069 for (int i = 0; i < XR_N_FONTS; i++)
1071 PangoContext *context = pango_cairo_create_context (cr);
1072 pango_cairo_context_set_resolution (context, style->font_resolution);
1073 PangoLayout *layout = pango_layout_new (context);
1074 g_object_unref (context);
1076 pango_layout_set_font_description (layout, style->fonts[i]);
1078 pango_layout_set_text (layout, "0", 1);
1080 int char_size[TABLE_N_AXES];
1081 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1082 for (int a = 0; a < TABLE_N_AXES; a++)
1084 int csa = pango_to_xr (char_size[a]);
1085 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1088 g_object_unref (G_OBJECT (layout));
1091 if (is_table_item (item))
1093 struct table_item *table_item = to_table_item (item);
1096 fsm->p = render_pager_create (&fsm->rp, table_item);
1104 xr_fsm_destroy (struct xr_fsm *fsm)
1108 xr_fsm_style_unref (fsm->style);
1109 output_item_unref (fsm->item);
1110 render_pager_destroy (fsm->p);
1111 assert (!fsm->cairo);
1116 /* This is primarily meant for use with screen rendering since the result is a
1117 fixed value for charts. */
1119 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1123 if (is_table_item (fsm->item))
1126 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1127 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1130 else if (is_chart_item (fsm->item))
1145 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1147 return (render_pager_has_next (fsm->p)
1148 ? render_pager_draw_next (fsm->p, space)
1153 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1155 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1156 if (space < chart_height)
1160 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1161 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1162 return chart_height;
1166 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1168 if (space >= fsm->rp.size[V])
1174 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1176 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1180 mul_XR_POINT (int x)
1182 return (x >= INT_MAX / XR_POINT ? INT_MAX
1183 : x <= INT_MIN / XR_POINT ? INT_MIN
1188 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1189 int x, int y, int w, int h)
1191 if (is_table_item (fsm->item))
1194 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1195 mul_XR_POINT (w), mul_XR_POINT (h));
1198 else if (is_chart_item (fsm->item))
1199 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1200 else if (is_page_eject_item (fsm->item))
1202 /* Nothing to do. */
1209 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1211 if (xr_fsm_is_empty (fsm))
1216 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1217 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1218 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1228 xr_fsm_is_empty (const struct xr_fsm *fsm)
1230 return (is_table_item (fsm->item)
1231 ? !render_pager_has_next (fsm->p)