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);
80 new->font = pango_font_description_copy (old->font);
86 xr_fsm_style_unref (struct xr_fsm_style *style)
90 assert (style->ref_cnt > 0);
91 if (!--style->ref_cnt)
93 pango_font_description_free (style->font);
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 || !pango_font_description_equal (a->font, b->font)
108 || a->use_system_colors != b->use_system_colors
109 || a->object_spacing != b->object_spacing
110 || a->font_resolution != b->font_resolution)
116 /* Renders a single output_item to an output device in one of two ways:
118 - 'print == true': Broken across multiple pages if necessary.
120 - 'print == false': In a single region that the user may scroll around if
123 Normally 'output_item' corresponds to a single rendering. There is a
124 special case when 'print == true' and 'output_item' is a table_item with
125 multiple layers and 'item->pt->table_look->print_all_layers == true'. In
126 that case, each layer is rendered separately from the FSM's internal point
127 of view; from the client's point of view, it is all one operation.
131 struct xr_fsm_style *style;
132 struct output_item *item;
135 /* Print mode only. */
138 /* Table items only. */
139 size_t *layer_indexes;
140 struct render_params rp;
141 struct render_pager *p;
142 cairo_t *cairo; /* XXX should this be here?! */
145 /* The unit used for internal measurements is inch/(72 * XR_POINT).
146 (Thus, XR_POINT units represent one point.) */
147 #define XR_POINT PANGO_SCALE
149 /* Conversions to and from points. */
153 return x / (double) XR_POINT;
156 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
160 return x * (PANGO_SCALE * 72 / 96);
164 pango_to_xr (int pango)
166 return (XR_POINT != PANGO_SCALE
167 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
174 return (XR_POINT != PANGO_SCALE
175 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
179 /* Dimensions for drawing lines in tables. */
180 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
181 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
184 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
185 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
186 int *width, int *height, int *brk);
189 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
191 cairo_set_source_rgba (cairo,
192 color->r / 255., color->g / 255., color->b / 255.,
193 color->alpha / 255.);
197 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
198 const struct cell_color *color)
200 cairo_new_path (xr->cairo);
201 cairo_set_line_width (
203 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
204 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
206 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
207 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
209 if (!xr->style->use_system_colors)
210 xr_set_source_rgba (xr->cairo, color);
211 if (style == RENDER_LINE_DASHED)
212 cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
213 cairo_stroke (xr->cairo);
214 if (style == RENDER_LINE_DASHED)
215 cairo_set_dash (xr->cairo, NULL, 0, 0);
219 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
221 cairo_new_path (xr->cairo);
222 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
223 cairo_close_path (xr->cairo);
224 cairo_stroke (xr->cairo);
225 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
226 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
227 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
228 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
232 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
234 cairo_new_path (xr->cairo);
235 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
236 cairo_rectangle (xr->cairo,
237 xr_to_pt (x0), xr_to_pt (y0),
238 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
239 cairo_fill (xr->cairo);
242 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
243 shortening it to X0...X1 if SHORTEN is true.
244 Draws a horizontal line X1...X3 at Y if RIGHT says so,
245 shortening it to X2...X3 if SHORTEN is true. */
247 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
248 enum render_line_style left, enum render_line_style right,
249 const struct cell_color *left_color,
250 const struct cell_color *right_color,
253 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
254 && cell_color_equal (left_color, right_color))
255 xr_draw_line (xr, x0, y, x3, y, left, left_color);
258 if (left != RENDER_LINE_NONE)
259 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
260 if (right != RENDER_LINE_NONE)
261 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
265 /* Draws a vertical line Y0...Y2 at X if TOP says so,
266 shortening it to Y0...Y1 if SHORTEN is true.
267 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
268 shortening it to Y2...Y3 if SHORTEN is true. */
270 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
271 enum render_line_style top, enum render_line_style bottom,
272 const struct cell_color *top_color,
273 const struct cell_color *bottom_color,
276 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
277 && cell_color_equal (top_color, bottom_color))
278 xr_draw_line (xr, x, y0, x, y3, top, top_color);
281 if (top != RENDER_LINE_NONE)
282 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
283 if (bottom != RENDER_LINE_NONE)
284 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
289 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
290 enum render_line_style styles[TABLE_N_AXES][2],
291 struct cell_color colors[TABLE_N_AXES][2])
293 const int x0 = bb[H][0];
294 const int y0 = bb[V][0];
295 const int x3 = bb[H][1];
296 const int y3 = bb[V][1];
297 const int top = styles[H][0];
298 const int bottom = styles[H][1];
300 int start_side = render_direction_rtl();
301 int end_side = !start_side;
302 const int start_of_line = styles[V][start_side];
303 const int end_of_line = styles[V][end_side];
304 const struct cell_color *top_color = &colors[H][0];
305 const struct cell_color *bottom_color = &colors[H][1];
306 const struct cell_color *start_color = &colors[V][start_side];
307 const struct cell_color *end_color = &colors[V][end_side];
309 /* The algorithm here is somewhat subtle, to allow it to handle
310 all the kinds of intersections that we need.
312 Three additional ordinates are assigned along the x axis. The
313 first is xc, midway between x0 and x3. The others are x1 and
314 x2; for a single vertical line these are equal to xc, and for
315 a double vertical line they are the ordinates of the left and
316 right half of the double line.
318 yc, y1, and y2 are assigned similarly along the y axis.
320 The following diagram shows the coordinate system and output
321 for double top and bottom lines, single left line, and no
325 y0 ________________________
331 y1 = y2 = yc |######### # |
336 y3 |________#_____#_______|
338 struct xr_fsm *xr = xr_;
340 /* Offset from center of each line in a pair of double lines. */
341 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
343 /* Are the lines along each axis single or double?
344 (It doesn't make sense to have different kinds of line on the
345 same axis, so we don't try to gracefully handle that case.) */
346 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
347 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
349 /* When horizontal lines are doubled,
350 the left-side line along y1 normally runs from x0 to x2,
351 and the right-side line along y1 from x3 to x1.
352 If the top-side line is also doubled, we shorten the y1 lines,
353 so that the left-side line runs only to x1,
354 and the right-side line only to x2.
355 Otherwise, the horizontal line at y = y1 below would cut off
356 the intersection, which looks ugly:
358 y0 ________________________
363 y1 |######### ########|
366 y2 |######################|
369 y3 |______________________|
370 It is more of a judgment call when the horizontal line is
371 single. We actually choose to cut off the line anyhow, as
372 shown in the first diagram above.
374 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
375 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
376 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
377 int horz_line_ofs = double_vert ? double_line_ofs : 0;
378 int xc = (x0 + x3) / 2;
379 int x1 = xc - horz_line_ofs;
380 int x2 = xc + horz_line_ofs;
382 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
383 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
384 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
385 int vert_line_ofs = double_horz ? double_line_ofs : 0;
386 int yc = (y0 + y3) / 2;
387 int y1 = yc - vert_line_ofs;
388 int y2 = yc + vert_line_ofs;
391 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
392 start_color, end_color, shorten_yc_line);
395 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
396 start_color, end_color, shorten_y1_lines);
397 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
398 start_color, end_color, shorten_y2_lines);
402 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
403 top_color, bottom_color, shorten_xc_line);
406 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
407 top_color, bottom_color, shorten_x1_lines);
408 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
409 top_color, bottom_color, shorten_x2_lines);
414 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
415 int *min_width, int *max_width)
417 struct xr_fsm *xr = xr_;
418 int bb[TABLE_N_AXES][2];
419 int clip[TABLE_N_AXES][2];
426 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
427 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
430 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
432 const int (*margin)[2] = cell->cell_style->margin;
434 *min_width += px_to_xr (margin[H][0] + margin[H][1]);
436 *max_width += px_to_xr (margin[H][0] + margin[H][1]);
440 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
442 struct xr_fsm *xr = xr_;
444 const int (*margin)[2] = cell->cell_style->margin;
446 int bb[TABLE_N_AXES][2] = {
448 [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
453 int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
456 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
457 h += px_to_xr (margin[V][0] + margin[V][1]);
461 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
464 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
465 int bb[TABLE_N_AXES][2], int valign_offset,
466 int spill[TABLE_N_AXES][2],
467 int clip[TABLE_N_AXES][2])
469 struct xr_fsm *xr = xr_;
472 const struct cell_color *bg = &cell->font_style->bg[color_idx];
473 if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
475 cairo_save (xr->cairo);
476 int bg_clip[TABLE_N_AXES][2];
477 for (int axis = 0; axis < TABLE_N_AXES; axis++)
479 bg_clip[axis][0] = clip[axis][0];
480 if (bb[axis][0] == clip[axis][0])
481 bg_clip[axis][0] -= spill[axis][0];
483 bg_clip[axis][1] = clip[axis][1];
484 if (bb[axis][1] == clip[axis][1])
485 bg_clip[axis][1] += spill[axis][1];
487 xr_clip (xr, bg_clip);
488 xr_set_source_rgba (xr->cairo, bg);
490 bb[H][0] - spill[H][0],
491 bb[V][0] - spill[V][0],
492 bb[H][1] + spill[H][1],
493 bb[V][1] + spill[V][1]);
494 cairo_restore (xr->cairo);
496 cairo_save (xr->cairo);
497 if (!xr->style->use_system_colors)
498 xr_set_source_rgba (xr->cairo, &cell->font_style->fg[color_idx]);
500 bb[V][0] += valign_offset;
502 for (int axis = 0; axis < TABLE_N_AXES; axis++)
504 bb[axis][0] += px_to_xr (cell->cell_style->margin[axis][0]);
505 bb[axis][1] -= px_to_xr (cell->cell_style->margin[axis][1]);
507 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
508 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
509 cairo_restore (xr->cairo);
513 xrr_adjust_break (void *xr_, const struct table_cell *cell,
514 int width, int height)
516 struct xr_fsm *xr = xr_;
518 if (xrr_measure_cell_height (xr_, cell, width) < height)
521 const int (*margin)[2] = cell->cell_style->margin;
523 int bb[TABLE_N_AXES][2] = {
526 [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
527 [V][1] = height - px_to_xr (margin[V][0] + margin[V][1]),
532 int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
535 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
540 xrr_scale (void *xr_, double scale)
542 struct xr_fsm *xr = xr_;
543 cairo_scale (xr->cairo, scale, scale);
547 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
549 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
551 double x0 = xr_to_pt (clip[H][0]);
552 double y0 = xr_to_pt (clip[V][0]);
553 double x1 = xr_to_pt (clip[H][1]);
554 double y1 = xr_to_pt (clip[V][1]);
556 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
557 cairo_clip (xr->cairo);
562 add_attr (PangoAttrList *list, PangoAttribute *attr,
563 guint start_index, guint end_index)
565 attr->start_index = start_index;
566 attr->end_index = end_index;
567 pango_attr_list_insert (list, attr);
571 markup_escape (struct string *out, bool markup, const char *in, size_t len)
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 pivot_table *pt = to_table_item (xr->item)->pt;
642 const struct font_style *font_style = cell->font_style;
643 const struct cell_style *cell_style = cell->cell_style;
644 unsigned int options = cell->options;
646 enum table_axis X = options & TAB_ROTATE ? V : H;
647 enum table_axis Y = !X;
648 int R = options & TAB_ROTATE ? 0 : 1;
650 PangoFontDescription *desc = NULL;
651 if (font_style->typeface)
653 font_style->typeface,
654 font_style->size ? font_style->size * 1000 : 10000,
655 font_style->bold, font_style->italic);
657 desc = xr->style->font;
660 PangoContext *context = pango_cairo_create_context (xr->cairo);
661 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
662 PangoLayout *layout = pango_layout_new (context);
663 g_object_unref (context);
665 pango_layout_set_font_description (layout, desc);
667 struct string body = DS_EMPTY_INITIALIZER;
668 bool numeric = pivot_value_format_body (cell->value, pt, &body);
670 enum table_halign halign = table_halign_interpret (
671 cell->cell_style->halign, numeric);
673 if (cell_style->halign == TABLE_HALIGN_DECIMAL
674 && !(cell->options & TAB_ROTATE))
676 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
678 const char *decimal = strrchr (ds_cstr (&body),
679 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 PangoAttrList *attrs = NULL;
693 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
694 Pango's implementation of it): it will break after a period or a comma
695 that precedes a digit, e.g. in ".000" it will break after the period.
696 This code looks for such a situation and inserts a U+2060 WORD JOINER
697 to prevent the break.
699 This isn't necessary when the decimal point is between two digits
700 (e.g. "0.000" won't be broken) or when the display width is not limited so
701 that word wrapping won't happen.
703 It isn't necessary to look for more than one period or comma, as would
704 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
705 are present then there will always be a digit on both sides of every
707 bool markup = cell->font_style->markup;
710 PangoAttrList *new_attrs;
712 if (pango_parse_markup (ds_cstr (&body), -1, 0,
713 &new_attrs, &new_text, NULL, NULL))
717 body.ss = ss_cstr (new_text);
718 body.capacity = body.ss.length;
722 /* XXX should we report the error? */
725 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
727 const char *text = ds_cstr (&body);
728 const char *decimal = text + strcspn (text, ".,");
730 && c_isdigit (decimal[1])
731 && (decimal == text || !c_isdigit (decimal[-1])))
733 struct string tmp = DS_EMPTY_INITIALIZER;
734 ds_extend (&tmp, ds_length (&body) + 16);
735 markup_escape (&tmp, markup, text, decimal - text + 1);
736 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
737 markup_escape (&tmp, markup, decimal + 1, -1);
738 ds_swap (&tmp, &body);
743 if (font_style->underline)
746 attrs = pango_attr_list_new ();
747 pango_attr_list_insert (attrs, pango_attr_underline_new (
748 PANGO_UNDERLINE_SINGLE));
751 const struct pivot_value *value = cell->value;
752 if (value->n_footnotes || value->n_subscripts)
754 size_t subscript_ofs = ds_length (&body);
755 for (size_t i = 0; i < value->n_subscripts; i++)
758 ds_put_byte (&body, ',');
759 ds_put_cstr (&body, value->subscripts[i]);
762 size_t footnote_ofs = ds_length (&body);
763 for (size_t i = 0; i < value->n_footnotes; i++)
766 ds_put_byte (&body, ',');
768 size_t idx = value->footnote_indexes[i];
769 const struct pivot_footnote *f = pt->footnotes[idx];
770 pivot_value_format (f->marker, pt, &body);
773 /* Allow footnote markers to occupy the right margin. That way, numbers
774 in the column are still aligned. */
775 if (value->n_footnotes && halign == TABLE_HALIGN_RIGHT)
777 /* Measure the width of the footnote marker, so we know how much we
778 need to make room for. */
779 pango_layout_set_text (layout, ds_cstr (&body) + footnote_ofs,
780 ds_length (&body) - footnote_ofs);
782 PangoAttrList *fn_attrs = pango_attr_list_new ();
783 pango_attr_list_insert (
784 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
785 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
786 pango_layout_set_attributes (layout, fn_attrs);
787 pango_attr_list_unref (fn_attrs);
788 int footnote_width = get_layout_dimension (layout, X);
790 /* Bound the adjustment by the width of the right margin. */
791 int right_margin = px_to_xr (cell_style->margin[X][R]);
792 int footnote_adjustment = MIN (footnote_width, right_margin);
794 /* Adjust the bounding box. */
795 if (options & TAB_ROTATE)
796 footnote_adjustment = -footnote_adjustment;
797 bb[X][R] += footnote_adjustment;
800 pango_layout_set_attributes (layout, NULL);
803 /* Set attributes. */
805 attrs = pango_attr_list_new ();
806 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
807 PANGO_ATTR_INDEX_TO_TEXT_END);
808 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
809 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
810 if (value->n_subscripts)
811 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
812 footnote_ofs - subscript_ofs);
813 if (value->n_footnotes)
814 add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
815 PANGO_ATTR_INDEX_TO_TEXT_END);
818 /* Set the attributes, if any. */
821 pango_layout_set_attributes (layout, attrs);
822 pango_attr_list_unref (attrs);
826 pango_layout_set_text (layout, ds_cstr (&body), ds_length (&body));
828 pango_layout_set_alignment (layout,
829 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
830 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
831 : PANGO_ALIGN_CENTER));
832 pango_layout_set_width (
834 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
835 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
837 int size[TABLE_N_AXES];
838 pango_layout_get_size (layout, &size[H], &size[V]);
840 if (clip[H][0] != clip[H][1])
842 cairo_save (xr->cairo);
843 if (!(options & TAB_ROTATE))
845 if (options & TAB_ROTATE)
847 int extra = bb[H][1] - bb[H][0] - size[V];
848 int halign_offset = extra > 0 ? extra / 2 : 0;
849 cairo_translate (xr->cairo,
850 xr_to_pt (bb[H][0] + halign_offset),
851 xr_to_pt (bb[V][1]));
852 cairo_rotate (xr->cairo, -M_PI_2);
855 cairo_translate (xr->cairo,
857 xr_to_pt (bb[V][0]));
858 pango_cairo_show_layout (xr->cairo, layout);
860 /* If enabled, this draws a blue rectangle around the extents of each
861 line of text, which can be rather useful for debugging layout
865 PangoLayoutIter *iter;
866 iter = pango_layout_get_iter (layout);
869 PangoRectangle extents;
871 pango_layout_iter_get_line_extents (iter, &extents, NULL);
872 cairo_save (xr->cairo);
873 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
876 pango_to_xr (extents.x),
877 pango_to_xr (extents.y),
878 pango_to_xr (extents.x + extents.width),
879 pango_to_xr (extents.y + extents.height));
880 cairo_restore (xr->cairo);
882 while (pango_layout_iter_next_line (iter));
883 pango_layout_iter_free (iter);
886 cairo_restore (xr->cairo);
889 int w = pango_to_xr (size[X]);
890 int h = pango_to_xr (size[Y]);
893 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
895 PangoLayoutIter *iter;
898 /* Choose a breakpoint between lines instead of in the middle of one. */
899 iter = pango_layout_get_iter (layout);
902 PangoRectangle extents;
906 pango_layout_iter_get_line_extents (iter, NULL, &extents);
907 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
908 extents.x = pango_to_xr (extents.x);
909 extents.y = pango_to_xr (y0);
910 extents.width = pango_to_xr (extents.width);
911 extents.height = pango_to_xr (y1 - y0);
912 bottom = bb[V][0] + extents.y + extents.height;
913 if (bottom < bb[V][1])
915 if (brk && clip[H][0] != clip[H][1])
923 while (pango_layout_iter_next_line (iter));
924 pango_layout_iter_free (iter);
926 /* If enabled, draws a green line across the chosen breakpoint, which can
927 be useful for debugging issues with breaking. */
931 xr_draw_line (xr, 0, best,
932 xr->style->size[H], best,
934 &(struct cell_color) CELL_COLOR (0, 255, 0));
938 pango_layout_set_attributes (layout, NULL);
940 if (desc != xr->style->font)
941 pango_font_description_free (desc);
942 g_object_unref (G_OBJECT (layout));
949 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
950 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
951 int *width, int *height, int *brk)
956 /* If enabled, draws a blue rectangle around the cell extents, which can be
957 useful for debugging layout. */
960 if (clip[H][0] != clip[H][1])
962 cairo_save (xr->cairo);
963 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
964 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
965 cairo_restore (xr->cairo);
971 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
974 #define CHART_WIDTH 500
975 #define CHART_HEIGHT 375
977 static struct xr_fsm *
978 xr_fsm_create (const struct output_item *item_,
979 const struct xr_fsm_style *style, cairo_t *cr,
982 if (is_page_setup_item (item_)
983 || is_group_open_item (item_)
984 || is_group_close_item (item_))
987 struct output_item *item;
988 if (is_table_item (item_)
989 || is_chart_item (item_)
990 || is_page_eject_item (item_))
991 item = output_item_ref (item_);
992 else if (is_message_item (item_))
993 item = table_item_super (
994 text_item_to_table_item (
995 message_item_to_text_item (
997 output_item_ref (item_)))));
998 else if (is_text_item (item_))
1000 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1003 item = table_item_super (
1004 text_item_to_table_item (
1006 output_item_ref (item_))));
1008 else if (is_group_open_item (item_))
1010 item = table_item_super (
1011 text_item_to_table_item (
1012 text_item_create (TEXT_ITEM_TITLE,
1013 to_group_open_item (item_)->command_name,
1018 assert (is_table_item (item)
1019 || is_chart_item (item)
1020 || is_page_eject_item (item));
1022 size_t *layer_indexes = NULL;
1023 if (is_table_item (item))
1025 const struct table_item *table_item = to_table_item (item);
1026 layer_indexes = pivot_output_next_layer (table_item->pt, NULL, print);
1031 static const struct render_ops xrr_render_ops = {
1032 .measure_cell_width = xrr_measure_cell_width,
1033 .measure_cell_height = xrr_measure_cell_height,
1034 .adjust_break = xrr_adjust_break,
1035 .draw_line = xrr_draw_line,
1036 .draw_cell = xrr_draw_cell,
1040 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1041 static const int xr_line_widths[RENDER_N_LINES] =
1043 [RENDER_LINE_NONE] = 0,
1044 [RENDER_LINE_SINGLE] = LW,
1045 [RENDER_LINE_DASHED] = LW,
1046 [RENDER_LINE_THICK] = LW * 2,
1047 [RENDER_LINE_THIN] = LW / 2,
1048 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1051 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1052 *fsm = (struct xr_fsm) {
1053 .style = xr_fsm_style_ref (style),
1056 .layer_indexes = layer_indexes,
1058 .ops = &xrr_render_ops,
1060 .size = { [H] = style->size[H], [V] = style->size[V] },
1061 .line_widths = xr_line_widths,
1062 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1063 .supports_margins = true,
1064 .rtl = render_direction_rtl (),
1069 /* Get font size. */
1070 PangoContext *context = pango_cairo_create_context (cr);
1071 pango_cairo_context_set_resolution (context, style->font_resolution);
1072 PangoLayout *layout = pango_layout_new (context);
1073 g_object_unref (context);
1075 pango_layout_set_font_description (layout, style->font);
1077 pango_layout_set_text (layout, "0", 1);
1079 int char_size[TABLE_N_AXES];
1080 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1081 for (int a = 0; a < TABLE_N_AXES; a++)
1083 int csa = pango_to_xr (char_size[a]);
1084 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1087 g_object_unref (G_OBJECT (layout));
1089 if (is_table_item (item))
1091 struct table_item *table_item = to_table_item (item);
1094 fsm->p = render_pager_create (&fsm->rp, table_item, fsm->layer_indexes);
1102 xr_fsm_create_for_printing (const struct output_item *item,
1103 const struct xr_fsm_style *style, cairo_t *cr)
1105 return xr_fsm_create (item, style, cr, true);
1109 xr_fsm_destroy (struct xr_fsm *fsm)
1113 xr_fsm_style_unref (fsm->style);
1114 output_item_unref (fsm->item);
1115 free (fsm->layer_indexes);
1116 render_pager_destroy (fsm->p);
1117 assert (!fsm->cairo);
1122 /* Scrolling API. */
1125 xr_fsm_create_for_scrolling (const struct output_item *item,
1126 const struct xr_fsm_style *style, cairo_t *cr)
1128 return xr_fsm_create (item, style, cr, false);
1132 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1134 assert (!fsm->print);
1138 if (is_table_item (fsm->item))
1141 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1142 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1145 else if (is_chart_item (fsm->item))
1160 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1162 assert (!fsm->print);
1163 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1167 mul_XR_POINT (int x)
1169 return (x >= INT_MAX / XR_POINT ? INT_MAX
1170 : x <= INT_MIN / XR_POINT ? INT_MIN
1175 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1176 int x, int y, int w, int h)
1178 assert (!fsm->print);
1179 if (is_table_item (fsm->item))
1182 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1183 mul_XR_POINT (w), mul_XR_POINT (h));
1186 else if (is_chart_item (fsm->item))
1187 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1188 else if (is_page_eject_item (fsm->item))
1190 /* Nothing to do. */
1199 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1201 struct table_item *table_item = to_table_item (fsm->item);
1202 int used = render_pager_draw_next (fsm->p, space);
1203 if (!render_pager_has_next (fsm->p))
1205 render_pager_destroy (fsm->p);
1207 fsm->layer_indexes = pivot_output_next_layer (table_item->pt,
1208 fsm->layer_indexes, true);
1209 if (fsm->layer_indexes)
1211 fsm->p = render_pager_create (&fsm->rp, table_item,
1212 fsm->layer_indexes);
1213 if (table_item->pt->look->paginate_layers)
1216 used += fsm->style->object_spacing;
1224 return MIN (used, space);
1228 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1230 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1231 if (space < chart_height)
1235 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1236 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1237 return chart_height;
1241 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1243 if (space >= fsm->rp.size[V])
1249 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1251 assert (fsm->print);
1258 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1259 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1260 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1269 xr_fsm_is_empty (const struct xr_fsm *fsm)
1271 assert (fsm->print);