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]))
120 struct xr_fsm_style *style;
121 struct output_item *item;
123 /* Table items only. */
124 struct render_params rp;
125 struct render_pager *p;
126 cairo_t *cairo; /* XXX should this be here?! */
128 /* Chart and page-eject items only. */
132 /* The unit used for internal measurements is inch/(72 * XR_POINT).
133 (Thus, XR_POINT units represent one point.) */
134 #define XR_POINT PANGO_SCALE
136 /* Conversions to and from points. */
140 return x / (double) XR_POINT;
143 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
147 return x * (PANGO_SCALE * 72 / 96);
151 pango_to_xr (int pango)
153 return (XR_POINT != PANGO_SCALE
154 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
161 return (XR_POINT != PANGO_SCALE
162 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
166 /* Dimensions for drawing lines in tables. */
167 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
168 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
171 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
172 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
173 int *width, int *height, int *brk);
176 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
178 cairo_set_source_rgba (cairo,
179 color->r / 255., color->g / 255., color->b / 255.,
180 color->alpha / 255.);
184 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
185 const struct cell_color *color)
187 cairo_new_path (xr->cairo);
188 cairo_set_line_width (
190 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
191 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
193 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
194 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
196 if (!xr->style->use_system_colors)
197 xr_set_source_rgba (xr->cairo, color);
198 if (style == RENDER_LINE_DASHED)
199 cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
200 cairo_stroke (xr->cairo);
201 if (style == RENDER_LINE_DASHED)
202 cairo_set_dash (xr->cairo, NULL, 0, 0);
206 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
208 cairo_new_path (xr->cairo);
209 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
210 cairo_close_path (xr->cairo);
211 cairo_stroke (xr->cairo);
212 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
213 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
214 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
215 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
219 fill_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_rectangle (xr->cairo,
224 xr_to_pt (x0), xr_to_pt (y0),
225 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
226 cairo_fill (xr->cairo);
229 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
230 shortening it to X0...X1 if SHORTEN is true.
231 Draws a horizontal line X1...X3 at Y if RIGHT says so,
232 shortening it to X2...X3 if SHORTEN is true. */
234 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
235 enum render_line_style left, enum render_line_style right,
236 const struct cell_color *left_color,
237 const struct cell_color *right_color,
240 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
241 && cell_color_equal (left_color, right_color))
242 xr_draw_line (xr, x0, y, x3, y, left, left_color);
245 if (left != RENDER_LINE_NONE)
246 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
247 if (right != RENDER_LINE_NONE)
248 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
252 /* Draws a vertical line Y0...Y2 at X if TOP says so,
253 shortening it to Y0...Y1 if SHORTEN is true.
254 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
255 shortening it to Y2...Y3 if SHORTEN is true. */
257 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
258 enum render_line_style top, enum render_line_style bottom,
259 const struct cell_color *top_color,
260 const struct cell_color *bottom_color,
263 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
264 && cell_color_equal (top_color, bottom_color))
265 xr_draw_line (xr, x, y0, x, y3, top, top_color);
268 if (top != RENDER_LINE_NONE)
269 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
270 if (bottom != RENDER_LINE_NONE)
271 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
276 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
277 enum render_line_style styles[TABLE_N_AXES][2],
278 struct cell_color colors[TABLE_N_AXES][2])
280 const int x0 = bb[H][0];
281 const int y0 = bb[V][0];
282 const int x3 = bb[H][1];
283 const int y3 = bb[V][1];
284 const int top = styles[H][0];
285 const int bottom = styles[H][1];
287 int start_side = render_direction_rtl();
288 int end_side = !start_side;
289 const int start_of_line = styles[V][start_side];
290 const int end_of_line = styles[V][end_side];
291 const struct cell_color *top_color = &colors[H][0];
292 const struct cell_color *bottom_color = &colors[H][1];
293 const struct cell_color *start_color = &colors[V][start_side];
294 const struct cell_color *end_color = &colors[V][end_side];
296 /* The algorithm here is somewhat subtle, to allow it to handle
297 all the kinds of intersections that we need.
299 Three additional ordinates are assigned along the x axis. The
300 first is xc, midway between x0 and x3. The others are x1 and
301 x2; for a single vertical line these are equal to xc, and for
302 a double vertical line they are the ordinates of the left and
303 right half of the double line.
305 yc, y1, and y2 are assigned similarly along the y axis.
307 The following diagram shows the coordinate system and output
308 for double top and bottom lines, single left line, and no
312 y0 ________________________
318 y1 = y2 = yc |######### # |
323 y3 |________#_____#_______|
325 struct xr_fsm *xr = xr_;
327 /* Offset from center of each line in a pair of double lines. */
328 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
330 /* Are the lines along each axis single or double?
331 (It doesn't make sense to have different kinds of line on the
332 same axis, so we don't try to gracefully handle that case.) */
333 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
334 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
336 /* When horizontal lines are doubled,
337 the left-side line along y1 normally runs from x0 to x2,
338 and the right-side line along y1 from x3 to x1.
339 If the top-side line is also doubled, we shorten the y1 lines,
340 so that the left-side line runs only to x1,
341 and the right-side line only to x2.
342 Otherwise, the horizontal line at y = y1 below would cut off
343 the intersection, which looks ugly:
345 y0 ________________________
350 y1 |######### ########|
353 y2 |######################|
356 y3 |______________________|
357 It is more of a judgment call when the horizontal line is
358 single. We actually choose to cut off the line anyhow, as
359 shown in the first diagram above.
361 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
362 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
363 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
364 int horz_line_ofs = double_vert ? double_line_ofs : 0;
365 int xc = (x0 + x3) / 2;
366 int x1 = xc - horz_line_ofs;
367 int x2 = xc + horz_line_ofs;
369 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
370 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
371 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
372 int vert_line_ofs = double_horz ? double_line_ofs : 0;
373 int yc = (y0 + y3) / 2;
374 int y1 = yc - vert_line_ofs;
375 int y2 = yc + vert_line_ofs;
378 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
379 start_color, end_color, shorten_yc_line);
382 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
383 start_color, end_color, shorten_y1_lines);
384 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
385 start_color, end_color, shorten_y2_lines);
389 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
390 top_color, bottom_color, shorten_xc_line);
393 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
394 top_color, bottom_color, shorten_x1_lines);
395 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
396 top_color, bottom_color, shorten_x2_lines);
401 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
402 int *min_width, int *max_width)
404 struct xr_fsm *xr = xr_;
405 int bb[TABLE_N_AXES][2];
406 int clip[TABLE_N_AXES][2];
413 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
414 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
417 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
420 *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
421 + cell->style->cell_style.margin[H][1]);
423 *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
424 + cell->style->cell_style.margin[H][1]);
428 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
430 struct xr_fsm *xr = xr_;
431 int bb[TABLE_N_AXES][2];
432 int clip[TABLE_N_AXES][2];
436 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
437 + cell->style->cell_style.margin[H][1]);
440 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
441 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
442 h += px_to_xr (cell->style->cell_style.margin[V][0]
443 + cell->style->cell_style.margin[V][1]);
447 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
450 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
451 int bb[TABLE_N_AXES][2], int valign_offset,
452 int spill[TABLE_N_AXES][2],
453 int clip[TABLE_N_AXES][2])
455 struct xr_fsm *xr = xr_;
458 const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
459 if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
461 cairo_save (xr->cairo);
462 int bg_clip[TABLE_N_AXES][2];
463 for (int axis = 0; axis < TABLE_N_AXES; axis++)
465 bg_clip[axis][0] = clip[axis][0];
466 if (bb[axis][0] == clip[axis][0])
467 bg_clip[axis][0] -= spill[axis][0];
469 bg_clip[axis][1] = clip[axis][1];
470 if (bb[axis][1] == clip[axis][1])
471 bg_clip[axis][1] += spill[axis][1];
473 xr_clip (xr, bg_clip);
474 xr_set_source_rgba (xr->cairo, bg);
476 bb[H][0] - spill[H][0],
477 bb[V][0] - spill[V][0],
478 bb[H][1] + spill[H][1],
479 bb[V][1] + spill[V][1]);
480 cairo_restore (xr->cairo);
482 cairo_save (xr->cairo);
483 if (!xr->style->use_system_colors)
484 xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
486 bb[V][0] += valign_offset;
488 for (int axis = 0; axis < TABLE_N_AXES; axis++)
490 bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
491 bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
493 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
494 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
495 cairo_restore (xr->cairo);
499 xrr_adjust_break (void *xr_, const struct table_cell *cell,
500 int width, int height)
502 struct xr_fsm *xr = xr_;
503 int bb[TABLE_N_AXES][2];
504 int clip[TABLE_N_AXES][2];
507 if (xrr_measure_cell_height (xr_, cell, width) < height)
511 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
512 + cell->style->cell_style.margin[H][1]);
516 bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
517 + cell->style->cell_style.margin[V][1]);
518 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
519 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
524 xrr_scale (void *xr_, double scale)
526 struct xr_fsm *xr = xr_;
527 cairo_scale (xr->cairo, scale, scale);
531 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
533 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
535 double x0 = xr_to_pt (clip[H][0]);
536 double y0 = xr_to_pt (clip[V][0]);
537 double x1 = xr_to_pt (clip[H][1]);
538 double y1 = xr_to_pt (clip[V][1]);
540 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
541 cairo_clip (xr->cairo);
546 add_attr (PangoAttrList *list, PangoAttribute *attr,
547 guint start_index, guint end_index)
549 attr->start_index = start_index;
550 attr->end_index = end_index;
551 pango_attr_list_insert (list, attr);
555 markup_escape (struct string *out, unsigned int options,
556 const char *in, size_t len)
558 if (!(options & TAB_MARKUP))
560 ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
572 ds_put_cstr (out, "&");
575 ds_put_cstr (out, "<");
578 ds_put_cstr (out, ">");
581 ds_put_byte (out, c);
588 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
590 int size[TABLE_N_AXES];
591 pango_layout_get_size (layout, &size[H], &size[V]);
595 static PangoFontDescription *
596 parse_font (const char *font, int default_size, bool bold, bool italic)
598 if (!c_strcasecmp (font, "Monospaced"))
601 PangoFontDescription *desc = pango_font_description_from_string (font);
605 /* If the font description didn't include an explicit font size, then set it
606 to DEFAULT_SIZE, which is in inch/72000 units. */
607 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
608 pango_font_description_set_size (desc,
609 (default_size / 1000.0) * PANGO_SCALE);
611 pango_font_description_set_weight (desc, (bold
613 : PANGO_WEIGHT_NORMAL));
614 pango_font_description_set_style (desc, (italic
616 : PANGO_STYLE_NORMAL));
622 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
623 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
624 int *widthp, int *brk)
626 const struct font_style *font_style = &cell->style->font_style;
627 const struct cell_style *cell_style = &cell->style->cell_style;
628 unsigned int options = cell->options;
630 enum table_axis X = options & TAB_ROTATE ? V : H;
631 enum table_axis Y = !X;
632 int R = options & TAB_ROTATE ? 0 : 1;
634 enum xr_font_type font_type = (options & TAB_FIX
636 : XR_FONT_PROPORTIONAL);
637 PangoFontDescription *desc = NULL;
638 if (font_style->typeface)
640 font_style->typeface,
641 font_style->size ? font_style->size * 1000 : 10000,
642 font_style->bold, font_style->italic);
644 desc = xr->style->fonts[font_type];
647 PangoContext *context = pango_cairo_create_context (xr->cairo);
648 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
649 PangoLayout *layout = pango_layout_new (context);
650 g_object_unref (context);
652 pango_layout_set_font_description (layout, desc);
654 const char *text = cell->text;
655 enum table_halign halign = table_halign_interpret (
656 cell_style->halign, cell->options & TAB_NUMERIC);
657 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
659 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
661 const char *decimal = strrchr (text, cell_style->decimal_char);
664 pango_layout_set_text (layout, decimal, strlen (decimal));
665 pango_layout_set_width (layout, -1);
666 margin_adjustment += get_layout_dimension (layout, H);
669 if (margin_adjustment < 0)
670 bb[H][1] += margin_adjustment;
673 struct string tmp = DS_EMPTY_INITIALIZER;
674 PangoAttrList *attrs = NULL;
676 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
677 Pango's implementation of it): it will break after a period or a comma
678 that precedes a digit, e.g. in ".000" it will break after the period.
679 This code looks for such a situation and inserts a U+2060 WORD JOINER
680 to prevent the break.
682 This isn't necessary when the decimal point is between two digits
683 (e.g. "0.000" won't be broken) or when the display width is not limited so
684 that word wrapping won't happen.
686 It isn't necessary to look for more than one period or comma, as would
687 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
688 are present then there will always be a digit on both sides of every
690 if (options & TAB_MARKUP)
692 PangoAttrList *new_attrs;
694 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
697 tmp.ss = ss_cstr (new_text);
698 tmp.capacity = tmp.ss.length;
702 /* XXX should we report the error? */
703 ds_put_cstr (&tmp, text);
706 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
708 const char *decimal = text + strcspn (text, ".,");
710 && c_isdigit (decimal[1])
711 && (decimal == text || !c_isdigit (decimal[-1])))
713 ds_extend (&tmp, strlen (text) + 16);
714 markup_escape (&tmp, options, text, decimal - text + 1);
715 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
716 markup_escape (&tmp, options, decimal + 1, -1);
720 if (font_style->underline)
723 attrs = pango_attr_list_new ();
724 pango_attr_list_insert (attrs, pango_attr_underline_new (
725 PANGO_UNDERLINE_SINGLE));
728 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
730 /* If we haven't already put TEXT into tmp, do it now. */
731 if (ds_is_empty (&tmp))
733 ds_extend (&tmp, strlen (text) + 16);
734 markup_escape (&tmp, options, text, -1);
737 size_t subscript_ofs = ds_length (&tmp);
738 for (size_t i = 0; i < cell->n_subscripts; i++)
741 ds_put_byte (&tmp, ',');
742 ds_put_cstr (&tmp, cell->subscripts[i]);
745 size_t superscript_ofs = ds_length (&tmp);
746 if (cell->superscript)
747 ds_put_cstr (&tmp, cell->superscript);
749 size_t footnote_ofs = ds_length (&tmp);
750 for (size_t i = 0; i < cell->n_footnotes; i++)
753 ds_put_byte (&tmp, ',');
754 ds_put_cstr (&tmp, cell->footnotes[i]->marker);
757 /* Allow footnote markers to occupy the right margin. That way, numbers
758 in the column are still aligned. */
759 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
761 /* Measure the width of the footnote marker, so we know how much we
762 need to make room for. */
763 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
764 ds_length (&tmp) - footnote_ofs);
766 PangoAttrList *fn_attrs = pango_attr_list_new ();
767 pango_attr_list_insert (
768 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
769 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
770 pango_layout_set_attributes (layout, fn_attrs);
771 pango_attr_list_unref (fn_attrs);
772 int footnote_width = get_layout_dimension (layout, X);
774 /* Bound the adjustment by the width of the right margin. */
775 int right_margin = px_to_xr (cell_style->margin[X][R]);
776 int footnote_adjustment = MIN (footnote_width, right_margin);
778 /* Adjust the bounding box. */
779 if (options & TAB_ROTATE)
780 footnote_adjustment = -footnote_adjustment;
781 bb[X][R] += footnote_adjustment;
784 pango_layout_set_attributes (layout, NULL);
787 /* Set attributes. */
789 attrs = pango_attr_list_new ();
790 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
791 PANGO_ATTR_INDEX_TO_TEXT_END);
792 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
793 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
794 if (cell->n_subscripts)
795 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
796 superscript_ofs - subscript_ofs);
797 if (cell->superscript || cell->n_footnotes)
798 add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
799 PANGO_ATTR_INDEX_TO_TEXT_END);
802 /* Set the attributes, if any. */
805 pango_layout_set_attributes (layout, attrs);
806 pango_attr_list_unref (attrs);
810 if (ds_is_empty (&tmp))
811 pango_layout_set_text (layout, text, -1);
813 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
816 pango_layout_set_alignment (layout,
817 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
818 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
819 : PANGO_ALIGN_CENTER));
820 pango_layout_set_width (
822 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
823 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
825 int size[TABLE_N_AXES];
826 pango_layout_get_size (layout, &size[H], &size[V]);
828 if (clip[H][0] != clip[H][1])
830 cairo_save (xr->cairo);
831 if (!(options & TAB_ROTATE))
833 if (options & TAB_ROTATE)
835 int extra = bb[H][1] - bb[H][0] - size[V];
836 int halign_offset = extra > 0 ? extra / 2 : 0;
837 cairo_translate (xr->cairo,
838 xr_to_pt (bb[H][0] + halign_offset),
839 xr_to_pt (bb[V][1]));
840 cairo_rotate (xr->cairo, -M_PI_2);
843 cairo_translate (xr->cairo,
845 xr_to_pt (bb[V][0]));
846 pango_cairo_show_layout (xr->cairo, layout);
848 /* If enabled, this draws a blue rectangle around the extents of each
849 line of text, which can be rather useful for debugging layout
853 PangoLayoutIter *iter;
854 iter = pango_layout_get_iter (layout);
857 PangoRectangle extents;
859 pango_layout_iter_get_line_extents (iter, &extents, NULL);
860 cairo_save (xr->cairo);
861 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
864 pango_to_xr (extents.x),
865 pango_to_xr (extents.y),
866 pango_to_xr (extents.x + extents.width),
867 pango_to_xr (extents.y + extents.height));
868 cairo_restore (xr->cairo);
870 while (pango_layout_iter_next_line (iter));
871 pango_layout_iter_free (iter);
874 cairo_restore (xr->cairo);
877 int w = pango_to_xr (size[X]);
878 int h = pango_to_xr (size[Y]);
881 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
883 PangoLayoutIter *iter;
886 /* Choose a breakpoint between lines instead of in the middle of one. */
887 iter = pango_layout_get_iter (layout);
890 PangoRectangle extents;
894 pango_layout_iter_get_line_extents (iter, NULL, &extents);
895 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
896 extents.x = pango_to_xr (extents.x);
897 extents.y = pango_to_xr (y0);
898 extents.width = pango_to_xr (extents.width);
899 extents.height = pango_to_xr (y1 - y0);
900 bottom = bb[V][0] + extents.y + extents.height;
901 if (bottom < bb[V][1])
903 if (brk && clip[H][0] != clip[H][1])
911 while (pango_layout_iter_next_line (iter));
912 pango_layout_iter_free (iter);
914 /* If enabled, draws a green line across the chosen breakpoint, which can
915 be useful for debugging issues with breaking. */
919 xr_draw_line (xr, 0, best,
920 xr->style->size[H], best,
922 &(struct cell_color) CELL_COLOR (0, 255, 0));
926 pango_layout_set_attributes (layout, NULL);
928 if (desc != xr->style->fonts[font_type])
929 pango_font_description_free (desc);
930 g_object_unref (G_OBJECT (layout));
936 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
937 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
938 int *width, int *height, int *brk)
943 /* If enabled, draws a blue rectangle around the cell extents, which can be
944 useful for debugging layout. */
947 if (clip[H][0] != clip[H][1])
949 cairo_save (xr->cairo);
950 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
951 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
952 cairo_restore (xr->cairo);
958 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
961 #define CHART_WIDTH 500
962 #define CHART_HEIGHT 375
965 xr_fsm_create (const struct output_item *item_,
966 const struct xr_fsm_style *style, cairo_t *cr)
968 if (is_page_setup_item (item_)
969 || is_group_open_item (item_)
970 || is_group_close_item (item_))
973 struct output_item *item;
974 if (is_table_item (item_)
975 || is_chart_item (item_)
976 || is_page_eject_item (item_))
977 item = output_item_ref (item_);
978 else if (is_message_item (item_))
979 item = table_item_super (
980 text_item_to_table_item (
981 message_item_to_text_item (
983 output_item_ref (item_)))));
984 else if (is_text_item (item_))
986 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
989 item = table_item_super (
990 text_item_to_table_item (
992 output_item_ref (item_))));
994 else if (is_group_open_item (item_))
996 item = table_item_super (
997 text_item_to_table_item (
998 text_item_create (TEXT_ITEM_TITLE,
999 to_group_open_item (item_)->command_name)));
1003 assert (is_table_item (item)
1004 || is_chart_item (item)
1005 || is_page_eject_item (item));
1007 static const struct render_ops xrr_render_ops = {
1008 .measure_cell_width = xrr_measure_cell_width,
1009 .measure_cell_height = xrr_measure_cell_height,
1010 .adjust_break = xrr_adjust_break,
1011 .draw_line = xrr_draw_line,
1012 .draw_cell = xrr_draw_cell,
1016 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1017 static const int xr_line_widths[RENDER_N_LINES] =
1019 [RENDER_LINE_NONE] = 0,
1020 [RENDER_LINE_SINGLE] = LW,
1021 [RENDER_LINE_DASHED] = LW,
1022 [RENDER_LINE_THICK] = LW * 2,
1023 [RENDER_LINE_THIN] = LW / 2,
1024 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1027 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1028 *fsm = (struct xr_fsm) {
1029 .style = xr_fsm_style_ref (style),
1032 .ops = &xrr_render_ops,
1034 .size = { [H] = style->size[H], [V] = style->size[V] },
1035 .line_widths = xr_line_widths,
1036 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1037 .supports_margins = true,
1038 .rtl = render_direction_rtl (),
1042 if (is_table_item (item))
1045 fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1049 for (int i = 0; i < XR_N_FONTS; i++)
1051 PangoContext *context = pango_cairo_create_context (cr);
1052 pango_cairo_context_set_resolution (context, style->font_resolution);
1053 PangoLayout *layout = pango_layout_new (context);
1054 g_object_unref (context);
1056 pango_layout_set_font_description (layout, style->fonts[i]);
1058 pango_layout_set_text (layout, "0", 1);
1060 int char_size[TABLE_N_AXES];
1061 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1062 for (int a = 0; a < TABLE_N_AXES; a++)
1064 int csa = pango_to_xr (char_size[a]);
1065 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1068 g_object_unref (G_OBJECT (layout));
1075 xr_fsm_destroy (struct xr_fsm *fsm)
1079 xr_fsm_style_unref (fsm->style);
1080 output_item_unref (fsm->item);
1081 render_pager_destroy (fsm->p);
1082 assert (!fsm->cairo);
1087 /* This is primarily meant for use with screen rendering since the result is a
1088 fixed value for charts. */
1090 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1094 if (is_table_item (fsm->item))
1097 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1098 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1101 else if (is_chart_item (fsm->item))
1116 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1118 return (render_pager_has_next (fsm->p)
1119 ? render_pager_draw_next (fsm->p, space)
1124 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1126 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1127 if (space < chart_height)
1131 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1132 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1133 return chart_height;
1137 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1139 if (space >= fsm->rp.size[V])
1145 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1147 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1151 mul_XR_POINT (int x)
1153 return (x >= INT_MAX / XR_POINT ? INT_MAX
1154 : x <= INT_MIN / XR_POINT ? INT_MIN
1159 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1160 int x, int y, int w, int h)
1162 if (is_table_item (fsm->item))
1165 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1166 mul_XR_POINT (w), mul_XR_POINT (h));
1169 else if (is_chart_item (fsm->item))
1170 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1171 else if (is_page_eject_item (fsm->item))
1173 /* Nothing to do. */
1180 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1182 if (xr_fsm_is_empty (fsm))
1187 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1188 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1189 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1199 xr_fsm_is_empty (const struct xr_fsm *fsm)
1201 return (is_table_item (fsm->item)
1202 ? !render_pager_has_next (fsm->p)