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/pivot-output.h"
45 #include "output/pivot-table.h"
46 #include "output/render.h"
47 #include "output/table-item.h"
48 #include "output/text-item.h"
50 #include "gl/c-ctype.h"
51 #include "gl/c-strcase.h"
52 #include "gl/xalloc.h"
54 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
59 xr_fsm_style_ref (const struct xr_fsm_style *style_)
61 assert (style_->ref_cnt > 0);
63 struct xr_fsm_style *style = CONST_CAST (struct xr_fsm_style *, style_);
69 xr_fsm_style_unshare (struct xr_fsm_style *old)
71 assert (old->ref_cnt > 0);
72 if (old->ref_cnt == 1)
75 xr_fsm_style_unref (old);
77 struct xr_fsm_style *new = xmemdup (old, sizeof *old);
79 for (int i = 0; i < XR_N_FONTS; i++)
81 new->fonts[i] = pango_font_description_copy (old->fonts[i]);
87 xr_fsm_style_unref (struct xr_fsm_style *style)
91 assert (style->ref_cnt > 0);
92 if (!--style->ref_cnt)
94 for (size_t i = 0; i < XR_N_FONTS; i++)
95 pango_font_description_free (style->fonts[i]);
102 xr_fsm_style_equals (const struct xr_fsm_style *a,
103 const struct xr_fsm_style *b)
105 if (a->size[H] != b->size[H]
106 || a->size[V] != b->size[V]
107 || a->min_break[H] != b->min_break[H]
108 || a->min_break[V] != b->min_break[V]
109 || a->use_system_colors != b->use_system_colors
110 || a->object_spacing != b->object_spacing
111 || a->font_resolution != b->font_resolution)
114 for (size_t i = 0; i < XR_N_FONTS; i++)
115 if (!pango_font_description_equal (a->fonts[i], b->fonts[i]))
121 /* Renders a single output_item to an output device in one of two ways:
123 - 'print == true': Broken across multiple pages if necessary.
125 - 'print == false': In a single region that the user may scroll around if
128 Normally 'output_item' corresponds to a single rendering. There is a
129 special case when 'print == true' and 'output_item' is a table_item with
130 multiple layers and 'item->pt->table_look->print_all_layers == true'. In
131 that case, each layer is rendered separately from the FSM's internal point
132 of view; from the client's point of view, it is all one operation.
136 struct xr_fsm_style *style;
137 struct output_item *item;
140 /* Print mode only. */
143 /* Table items only. */
144 size_t *layer_indexes;
145 struct render_params rp;
146 struct render_pager *p;
147 cairo_t *cairo; /* XXX should this be here?! */
150 /* The unit used for internal measurements is inch/(72 * XR_POINT).
151 (Thus, XR_POINT units represent one point.) */
152 #define XR_POINT PANGO_SCALE
154 /* Conversions to and from points. */
158 return x / (double) XR_POINT;
161 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
165 return x * (PANGO_SCALE * 72 / 96);
169 pango_to_xr (int pango)
171 return (XR_POINT != PANGO_SCALE
172 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
179 return (XR_POINT != PANGO_SCALE
180 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
184 /* Dimensions for drawing lines in tables. */
185 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
186 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
189 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
190 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
191 int *width, int *height, int *brk);
194 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
196 cairo_set_source_rgba (cairo,
197 color->r / 255., color->g / 255., color->b / 255.,
198 color->alpha / 255.);
202 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
203 const struct cell_color *color)
205 cairo_new_path (xr->cairo);
206 cairo_set_line_width (
208 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
209 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
211 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
212 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
214 if (!xr->style->use_system_colors)
215 xr_set_source_rgba (xr->cairo, color);
216 if (style == RENDER_LINE_DASHED)
217 cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
218 cairo_stroke (xr->cairo);
219 if (style == RENDER_LINE_DASHED)
220 cairo_set_dash (xr->cairo, NULL, 0, 0);
224 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
226 cairo_new_path (xr->cairo);
227 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
228 cairo_close_path (xr->cairo);
229 cairo_stroke (xr->cairo);
230 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
231 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
232 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
233 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
237 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
239 cairo_new_path (xr->cairo);
240 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
241 cairo_rectangle (xr->cairo,
242 xr_to_pt (x0), xr_to_pt (y0),
243 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
244 cairo_fill (xr->cairo);
247 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
248 shortening it to X0...X1 if SHORTEN is true.
249 Draws a horizontal line X1...X3 at Y if RIGHT says so,
250 shortening it to X2...X3 if SHORTEN is true. */
252 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
253 enum render_line_style left, enum render_line_style right,
254 const struct cell_color *left_color,
255 const struct cell_color *right_color,
258 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
259 && cell_color_equal (left_color, right_color))
260 xr_draw_line (xr, x0, y, x3, y, left, left_color);
263 if (left != RENDER_LINE_NONE)
264 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
265 if (right != RENDER_LINE_NONE)
266 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
270 /* Draws a vertical line Y0...Y2 at X if TOP says so,
271 shortening it to Y0...Y1 if SHORTEN is true.
272 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
273 shortening it to Y2...Y3 if SHORTEN is true. */
275 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
276 enum render_line_style top, enum render_line_style bottom,
277 const struct cell_color *top_color,
278 const struct cell_color *bottom_color,
281 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
282 && cell_color_equal (top_color, bottom_color))
283 xr_draw_line (xr, x, y0, x, y3, top, top_color);
286 if (top != RENDER_LINE_NONE)
287 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
288 if (bottom != RENDER_LINE_NONE)
289 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
294 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
295 enum render_line_style styles[TABLE_N_AXES][2],
296 struct cell_color colors[TABLE_N_AXES][2])
298 const int x0 = bb[H][0];
299 const int y0 = bb[V][0];
300 const int x3 = bb[H][1];
301 const int y3 = bb[V][1];
302 const int top = styles[H][0];
303 const int bottom = styles[H][1];
305 int start_side = render_direction_rtl();
306 int end_side = !start_side;
307 const int start_of_line = styles[V][start_side];
308 const int end_of_line = styles[V][end_side];
309 const struct cell_color *top_color = &colors[H][0];
310 const struct cell_color *bottom_color = &colors[H][1];
311 const struct cell_color *start_color = &colors[V][start_side];
312 const struct cell_color *end_color = &colors[V][end_side];
314 /* The algorithm here is somewhat subtle, to allow it to handle
315 all the kinds of intersections that we need.
317 Three additional ordinates are assigned along the x axis. The
318 first is xc, midway between x0 and x3. The others are x1 and
319 x2; for a single vertical line these are equal to xc, and for
320 a double vertical line they are the ordinates of the left and
321 right half of the double line.
323 yc, y1, and y2 are assigned similarly along the y axis.
325 The following diagram shows the coordinate system and output
326 for double top and bottom lines, single left line, and no
330 y0 ________________________
336 y1 = y2 = yc |######### # |
341 y3 |________#_____#_______|
343 struct xr_fsm *xr = xr_;
345 /* Offset from center of each line in a pair of double lines. */
346 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
348 /* Are the lines along each axis single or double?
349 (It doesn't make sense to have different kinds of line on the
350 same axis, so we don't try to gracefully handle that case.) */
351 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
352 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
354 /* When horizontal lines are doubled,
355 the left-side line along y1 normally runs from x0 to x2,
356 and the right-side line along y1 from x3 to x1.
357 If the top-side line is also doubled, we shorten the y1 lines,
358 so that the left-side line runs only to x1,
359 and the right-side line only to x2.
360 Otherwise, the horizontal line at y = y1 below would cut off
361 the intersection, which looks ugly:
363 y0 ________________________
368 y1 |######### ########|
371 y2 |######################|
374 y3 |______________________|
375 It is more of a judgment call when the horizontal line is
376 single. We actually choose to cut off the line anyhow, as
377 shown in the first diagram above.
379 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
380 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
381 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
382 int horz_line_ofs = double_vert ? double_line_ofs : 0;
383 int xc = (x0 + x3) / 2;
384 int x1 = xc - horz_line_ofs;
385 int x2 = xc + horz_line_ofs;
387 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
388 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
389 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
390 int vert_line_ofs = double_horz ? double_line_ofs : 0;
391 int yc = (y0 + y3) / 2;
392 int y1 = yc - vert_line_ofs;
393 int y2 = yc + vert_line_ofs;
396 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
397 start_color, end_color, shorten_yc_line);
400 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
401 start_color, end_color, shorten_y1_lines);
402 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
403 start_color, end_color, shorten_y2_lines);
407 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
408 top_color, bottom_color, shorten_xc_line);
411 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
412 top_color, bottom_color, shorten_x1_lines);
413 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
414 top_color, bottom_color, shorten_x2_lines);
419 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
420 int *min_width, int *max_width)
422 struct xr_fsm *xr = xr_;
423 int bb[TABLE_N_AXES][2];
424 int clip[TABLE_N_AXES][2];
431 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
432 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
435 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
438 *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
439 + cell->style->cell_style.margin[H][1]);
441 *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
442 + cell->style->cell_style.margin[H][1]);
446 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
448 struct xr_fsm *xr = xr_;
449 int bb[TABLE_N_AXES][2];
450 int clip[TABLE_N_AXES][2];
454 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
455 + cell->style->cell_style.margin[H][1]);
458 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
459 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
460 h += px_to_xr (cell->style->cell_style.margin[V][0]
461 + cell->style->cell_style.margin[V][1]);
465 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
468 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
469 int bb[TABLE_N_AXES][2], int valign_offset,
470 int spill[TABLE_N_AXES][2],
471 int clip[TABLE_N_AXES][2])
473 struct xr_fsm *xr = xr_;
476 const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
477 if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
479 cairo_save (xr->cairo);
480 int bg_clip[TABLE_N_AXES][2];
481 for (int axis = 0; axis < TABLE_N_AXES; axis++)
483 bg_clip[axis][0] = clip[axis][0];
484 if (bb[axis][0] == clip[axis][0])
485 bg_clip[axis][0] -= spill[axis][0];
487 bg_clip[axis][1] = clip[axis][1];
488 if (bb[axis][1] == clip[axis][1])
489 bg_clip[axis][1] += spill[axis][1];
491 xr_clip (xr, bg_clip);
492 xr_set_source_rgba (xr->cairo, bg);
494 bb[H][0] - spill[H][0],
495 bb[V][0] - spill[V][0],
496 bb[H][1] + spill[H][1],
497 bb[V][1] + spill[V][1]);
498 cairo_restore (xr->cairo);
500 cairo_save (xr->cairo);
501 if (!xr->style->use_system_colors)
502 xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
504 bb[V][0] += valign_offset;
506 for (int axis = 0; axis < TABLE_N_AXES; axis++)
508 bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
509 bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
511 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
512 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
513 cairo_restore (xr->cairo);
517 xrr_adjust_break (void *xr_, const struct table_cell *cell,
518 int width, int height)
520 struct xr_fsm *xr = xr_;
521 int bb[TABLE_N_AXES][2];
522 int clip[TABLE_N_AXES][2];
525 if (xrr_measure_cell_height (xr_, cell, width) < height)
529 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
530 + cell->style->cell_style.margin[H][1]);
534 bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
535 + cell->style->cell_style.margin[V][1]);
536 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
537 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
542 xrr_scale (void *xr_, double scale)
544 struct xr_fsm *xr = xr_;
545 cairo_scale (xr->cairo, scale, scale);
549 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
551 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
553 double x0 = xr_to_pt (clip[H][0]);
554 double y0 = xr_to_pt (clip[V][0]);
555 double x1 = xr_to_pt (clip[H][1]);
556 double y1 = xr_to_pt (clip[V][1]);
558 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
559 cairo_clip (xr->cairo);
564 add_attr (PangoAttrList *list, PangoAttribute *attr,
565 guint start_index, guint end_index)
567 attr->start_index = start_index;
568 attr->end_index = end_index;
569 pango_attr_list_insert (list, attr);
573 markup_escape (struct string *out, unsigned int options,
574 const char *in, size_t len)
576 if (!(options & TAB_MARKUP))
578 ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
590 ds_put_cstr (out, "&");
593 ds_put_cstr (out, "<");
596 ds_put_cstr (out, ">");
599 ds_put_byte (out, c);
606 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
608 int size[TABLE_N_AXES];
609 pango_layout_get_size (layout, &size[H], &size[V]);
613 static PangoFontDescription *
614 parse_font (const char *font, int default_size, bool bold, bool italic)
616 if (!c_strcasecmp (font, "Monospaced"))
619 PangoFontDescription *desc = pango_font_description_from_string (font);
623 /* If the font description didn't include an explicit font size, then set it
624 to DEFAULT_SIZE, which is in inch/72000 units. */
625 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
626 pango_font_description_set_size (desc,
627 (default_size / 1000.0) * PANGO_SCALE);
629 pango_font_description_set_weight (desc, (bold
631 : PANGO_WEIGHT_NORMAL));
632 pango_font_description_set_style (desc, (italic
634 : PANGO_STYLE_NORMAL));
640 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
641 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
642 int *widthp, int *brk)
644 const struct font_style *font_style = &cell->style->font_style;
645 const struct cell_style *cell_style = &cell->style->cell_style;
646 unsigned int options = cell->options;
648 enum table_axis X = options & TAB_ROTATE ? V : H;
649 enum table_axis Y = !X;
650 int R = options & TAB_ROTATE ? 0 : 1;
652 enum xr_font_type font_type = (options & TAB_FIX
654 : XR_FONT_PROPORTIONAL);
655 PangoFontDescription *desc = NULL;
656 if (font_style->typeface)
658 font_style->typeface,
659 font_style->size ? font_style->size * 1000 : 10000,
660 font_style->bold, font_style->italic);
662 desc = xr->style->fonts[font_type];
665 PangoContext *context = pango_cairo_create_context (xr->cairo);
666 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
667 PangoLayout *layout = pango_layout_new (context);
668 g_object_unref (context);
670 pango_layout_set_font_description (layout, desc);
672 const char *text = cell->text;
673 enum table_halign halign = table_halign_interpret (
674 cell_style->halign, cell->options & TAB_NUMERIC);
675 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
677 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
679 const char *decimal = strrchr (text, cell_style->decimal_char);
682 pango_layout_set_text (layout, decimal, strlen (decimal));
683 pango_layout_set_width (layout, -1);
684 margin_adjustment += get_layout_dimension (layout, H);
687 if (margin_adjustment < 0)
688 bb[H][1] += margin_adjustment;
691 struct string tmp = DS_EMPTY_INITIALIZER;
692 PangoAttrList *attrs = NULL;
694 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
695 Pango's implementation of it): it will break after a period or a comma
696 that precedes a digit, e.g. in ".000" it will break after the period.
697 This code looks for such a situation and inserts a U+2060 WORD JOINER
698 to prevent the break.
700 This isn't necessary when the decimal point is between two digits
701 (e.g. "0.000" won't be broken) or when the display width is not limited so
702 that word wrapping won't happen.
704 It isn't necessary to look for more than one period or comma, as would
705 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
706 are present then there will always be a digit on both sides of every
708 if (options & TAB_MARKUP)
710 PangoAttrList *new_attrs;
712 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
715 tmp.ss = ss_cstr (new_text);
716 tmp.capacity = tmp.ss.length;
720 /* XXX should we report the error? */
721 ds_put_cstr (&tmp, text);
724 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
726 const char *decimal = text + strcspn (text, ".,");
728 && c_isdigit (decimal[1])
729 && (decimal == text || !c_isdigit (decimal[-1])))
731 ds_extend (&tmp, strlen (text) + 16);
732 markup_escape (&tmp, options, text, decimal - text + 1);
733 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
734 markup_escape (&tmp, options, decimal + 1, -1);
738 if (font_style->underline)
741 attrs = pango_attr_list_new ();
742 pango_attr_list_insert (attrs, pango_attr_underline_new (
743 PANGO_UNDERLINE_SINGLE));
746 if (cell->n_footnotes || cell->n_subscripts)
748 /* If we haven't already put TEXT into tmp, do it now. */
749 if (ds_is_empty (&tmp))
751 ds_extend (&tmp, strlen (text) + 16);
752 markup_escape (&tmp, options, text, -1);
755 size_t subscript_ofs = ds_length (&tmp);
756 for (size_t i = 0; i < cell->n_subscripts; i++)
759 ds_put_byte (&tmp, ',');
760 ds_put_cstr (&tmp, cell->subscripts[i]);
763 size_t footnote_ofs = ds_length (&tmp);
764 for (size_t i = 0; i < cell->n_footnotes; i++)
767 ds_put_byte (&tmp, ',');
769 pivot_value_format (cell->footnotes[i]->marker,
770 SETTINGS_VALUE_SHOW_DEFAULT,
771 SETTINGS_VALUE_SHOW_DEFAULT, &tmp);
774 /* Allow footnote markers to occupy the right margin. That way, numbers
775 in the column are still aligned. */
776 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
778 /* Measure the width of the footnote marker, so we know how much we
779 need to make room for. */
780 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
781 ds_length (&tmp) - footnote_ofs);
783 PangoAttrList *fn_attrs = pango_attr_list_new ();
784 pango_attr_list_insert (
785 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
786 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
787 pango_layout_set_attributes (layout, fn_attrs);
788 pango_attr_list_unref (fn_attrs);
789 int footnote_width = get_layout_dimension (layout, X);
791 /* Bound the adjustment by the width of the right margin. */
792 int right_margin = px_to_xr (cell_style->margin[X][R]);
793 int footnote_adjustment = MIN (footnote_width, right_margin);
795 /* Adjust the bounding box. */
796 if (options & TAB_ROTATE)
797 footnote_adjustment = -footnote_adjustment;
798 bb[X][R] += footnote_adjustment;
801 pango_layout_set_attributes (layout, NULL);
804 /* Set attributes. */
806 attrs = pango_attr_list_new ();
807 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
808 PANGO_ATTR_INDEX_TO_TEXT_END);
809 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
810 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
811 if (cell->n_subscripts)
812 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
813 footnote_ofs - subscript_ofs);
814 if (cell->n_footnotes)
815 add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
816 PANGO_ATTR_INDEX_TO_TEXT_END);
819 /* Set the attributes, if any. */
822 pango_layout_set_attributes (layout, attrs);
823 pango_attr_list_unref (attrs);
827 if (ds_is_empty (&tmp))
828 pango_layout_set_text (layout, text, -1);
830 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
833 pango_layout_set_alignment (layout,
834 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
835 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
836 : PANGO_ALIGN_CENTER));
837 pango_layout_set_width (
839 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
840 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
842 int size[TABLE_N_AXES];
843 pango_layout_get_size (layout, &size[H], &size[V]);
845 if (clip[H][0] != clip[H][1])
847 cairo_save (xr->cairo);
848 if (!(options & TAB_ROTATE))
850 if (options & TAB_ROTATE)
852 int extra = bb[H][1] - bb[H][0] - size[V];
853 int halign_offset = extra > 0 ? extra / 2 : 0;
854 cairo_translate (xr->cairo,
855 xr_to_pt (bb[H][0] + halign_offset),
856 xr_to_pt (bb[V][1]));
857 cairo_rotate (xr->cairo, -M_PI_2);
860 cairo_translate (xr->cairo,
862 xr_to_pt (bb[V][0]));
863 pango_cairo_show_layout (xr->cairo, layout);
865 /* If enabled, this draws a blue rectangle around the extents of each
866 line of text, which can be rather useful for debugging layout
870 PangoLayoutIter *iter;
871 iter = pango_layout_get_iter (layout);
874 PangoRectangle extents;
876 pango_layout_iter_get_line_extents (iter, &extents, NULL);
877 cairo_save (xr->cairo);
878 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
881 pango_to_xr (extents.x),
882 pango_to_xr (extents.y),
883 pango_to_xr (extents.x + extents.width),
884 pango_to_xr (extents.y + extents.height));
885 cairo_restore (xr->cairo);
887 while (pango_layout_iter_next_line (iter));
888 pango_layout_iter_free (iter);
891 cairo_restore (xr->cairo);
894 int w = pango_to_xr (size[X]);
895 int h = pango_to_xr (size[Y]);
898 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
900 PangoLayoutIter *iter;
903 /* Choose a breakpoint between lines instead of in the middle of one. */
904 iter = pango_layout_get_iter (layout);
907 PangoRectangle extents;
911 pango_layout_iter_get_line_extents (iter, NULL, &extents);
912 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
913 extents.x = pango_to_xr (extents.x);
914 extents.y = pango_to_xr (y0);
915 extents.width = pango_to_xr (extents.width);
916 extents.height = pango_to_xr (y1 - y0);
917 bottom = bb[V][0] + extents.y + extents.height;
918 if (bottom < bb[V][1])
920 if (brk && clip[H][0] != clip[H][1])
928 while (pango_layout_iter_next_line (iter));
929 pango_layout_iter_free (iter);
931 /* If enabled, draws a green line across the chosen breakpoint, which can
932 be useful for debugging issues with breaking. */
936 xr_draw_line (xr, 0, best,
937 xr->style->size[H], best,
939 &(struct cell_color) CELL_COLOR (0, 255, 0));
943 pango_layout_set_attributes (layout, NULL);
945 if (desc != xr->style->fonts[font_type])
946 pango_font_description_free (desc);
947 g_object_unref (G_OBJECT (layout));
953 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
954 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
955 int *width, int *height, int *brk)
960 /* If enabled, draws a blue rectangle around the cell extents, which can be
961 useful for debugging layout. */
964 if (clip[H][0] != clip[H][1])
966 cairo_save (xr->cairo);
967 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
968 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
969 cairo_restore (xr->cairo);
975 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
978 #define CHART_WIDTH 500
979 #define CHART_HEIGHT 375
981 static struct xr_fsm *
982 xr_fsm_create (const struct output_item *item_,
983 const struct xr_fsm_style *style, cairo_t *cr,
986 if (is_page_setup_item (item_)
987 || is_group_open_item (item_)
988 || is_group_close_item (item_))
991 struct output_item *item;
992 if (is_table_item (item_)
993 || is_chart_item (item_)
994 || is_page_eject_item (item_))
995 item = output_item_ref (item_);
996 else if (is_message_item (item_))
997 item = table_item_super (
998 text_item_to_table_item (
999 message_item_to_text_item (
1001 output_item_ref (item_)))));
1002 else if (is_text_item (item_))
1004 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1007 item = table_item_super (
1008 text_item_to_table_item (
1010 output_item_ref (item_))));
1012 else if (is_group_open_item (item_))
1014 item = table_item_super (
1015 text_item_to_table_item (
1016 text_item_create (TEXT_ITEM_TITLE,
1017 to_group_open_item (item_)->command_name,
1022 assert (is_table_item (item)
1023 || is_chart_item (item)
1024 || is_page_eject_item (item));
1026 size_t *layer_indexes = NULL;
1027 if (is_table_item (item))
1029 const struct table_item *table_item = to_table_item (item);
1030 layer_indexes = pivot_output_next_layer (table_item->pt, NULL, print);
1035 static const struct render_ops xrr_render_ops = {
1036 .measure_cell_width = xrr_measure_cell_width,
1037 .measure_cell_height = xrr_measure_cell_height,
1038 .adjust_break = xrr_adjust_break,
1039 .draw_line = xrr_draw_line,
1040 .draw_cell = xrr_draw_cell,
1044 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1045 static const int xr_line_widths[RENDER_N_LINES] =
1047 [RENDER_LINE_NONE] = 0,
1048 [RENDER_LINE_SINGLE] = LW,
1049 [RENDER_LINE_DASHED] = LW,
1050 [RENDER_LINE_THICK] = LW * 2,
1051 [RENDER_LINE_THIN] = LW / 2,
1052 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1055 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1056 *fsm = (struct xr_fsm) {
1057 .style = xr_fsm_style_ref (style),
1060 .layer_indexes = layer_indexes,
1062 .ops = &xrr_render_ops,
1064 .size = { [H] = style->size[H], [V] = style->size[V] },
1065 .line_widths = xr_line_widths,
1066 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1067 .supports_margins = true,
1068 .rtl = render_direction_rtl (),
1073 for (int i = 0; i < XR_N_FONTS; i++)
1075 PangoContext *context = pango_cairo_create_context (cr);
1076 pango_cairo_context_set_resolution (context, style->font_resolution);
1077 PangoLayout *layout = pango_layout_new (context);
1078 g_object_unref (context);
1080 pango_layout_set_font_description (layout, style->fonts[i]);
1082 pango_layout_set_text (layout, "0", 1);
1084 int char_size[TABLE_N_AXES];
1085 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1086 for (int a = 0; a < TABLE_N_AXES; a++)
1088 int csa = pango_to_xr (char_size[a]);
1089 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1092 g_object_unref (G_OBJECT (layout));
1095 if (is_table_item (item))
1097 struct table_item *table_item = to_table_item (item);
1100 fsm->p = render_pager_create (&fsm->rp, table_item, fsm->layer_indexes);
1108 xr_fsm_create_for_printing (const struct output_item *item,
1109 const struct xr_fsm_style *style, cairo_t *cr)
1111 return xr_fsm_create (item, style, cr, true);
1115 xr_fsm_destroy (struct xr_fsm *fsm)
1119 xr_fsm_style_unref (fsm->style);
1120 output_item_unref (fsm->item);
1121 free (fsm->layer_indexes);
1122 render_pager_destroy (fsm->p);
1123 assert (!fsm->cairo);
1128 /* Scrolling API. */
1131 xr_fsm_create_for_scrolling (const struct output_item *item,
1132 const struct xr_fsm_style *style, cairo_t *cr)
1134 return xr_fsm_create (item, style, cr, false);
1138 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1140 assert (!fsm->print);
1144 if (is_table_item (fsm->item))
1147 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1148 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1151 else if (is_chart_item (fsm->item))
1166 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1168 assert (!fsm->print);
1169 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1173 mul_XR_POINT (int x)
1175 return (x >= INT_MAX / XR_POINT ? INT_MAX
1176 : x <= INT_MIN / XR_POINT ? INT_MIN
1181 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1182 int x, int y, int w, int h)
1184 assert (!fsm->print);
1185 if (is_table_item (fsm->item))
1188 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1189 mul_XR_POINT (w), mul_XR_POINT (h));
1192 else if (is_chart_item (fsm->item))
1193 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1194 else if (is_page_eject_item (fsm->item))
1196 /* Nothing to do. */
1205 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1207 struct table_item *table_item = to_table_item (fsm->item);
1208 int used = render_pager_draw_next (fsm->p, space);
1209 if (!render_pager_has_next (fsm->p))
1211 render_pager_destroy (fsm->p);
1213 fsm->layer_indexes = pivot_output_next_layer (table_item->pt,
1214 fsm->layer_indexes, true);
1215 if (fsm->layer_indexes)
1217 fsm->p = render_pager_create (&fsm->rp, table_item,
1218 fsm->layer_indexes);
1219 if (table_item->pt->look->paginate_layers)
1222 used += fsm->style->object_spacing;
1230 return MIN (used, space);
1234 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1236 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1237 if (space < chart_height)
1241 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1242 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1243 return chart_height;
1247 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1249 if (space >= fsm->rp.size[V])
1255 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1257 assert (fsm->print);
1264 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1265 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1266 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1275 xr_fsm_is_empty (const struct xr_fsm *fsm)
1277 assert (fsm->print);