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);
78 new->font = pango_font_description_copy (old->font);
84 xr_fsm_style_unref (struct xr_fsm_style *style)
88 assert (style->ref_cnt > 0);
89 if (!--style->ref_cnt)
91 pango_font_description_free (style->font);
98 xr_fsm_style_equals (const struct xr_fsm_style *a,
99 const struct xr_fsm_style *b)
101 if (a->size[H] != b->size[H]
102 || a->size[V] != b->size[V]
103 || a->min_break[H] != b->min_break[H]
104 || a->min_break[V] != b->min_break[V]
105 || !pango_font_description_equal (a->font, b->font)
106 || a->use_system_colors != b->use_system_colors
107 || a->font_resolution != b->font_resolution)
115 struct xr_fsm_style *style;
116 struct output_item *item;
118 /* Table items only. */
119 struct render_params rp;
120 struct render_pager *p;
121 cairo_t *cairo; /* XXX should this be here?! */
123 /* Chart and page-eject items only. */
127 /* The unit used for internal measurements is inch/(72 * XR_POINT).
128 (Thus, XR_POINT units represent one point.) */
129 #define XR_POINT PANGO_SCALE
131 /* Conversions to and from points. */
135 return x / (double) XR_POINT;
138 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
142 return x * (PANGO_SCALE * 72 / 96);
146 pango_to_xr (int pango)
148 return (XR_POINT != PANGO_SCALE
149 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
156 return (XR_POINT != PANGO_SCALE
157 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
161 /* Dimensions for drawing lines in tables. */
162 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
163 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
166 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
167 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
168 int *width, int *height, int *brk);
171 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
173 cairo_set_source_rgba (cairo,
174 color->r / 255., color->g / 255., color->b / 255.,
175 color->alpha / 255.);
179 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
180 const struct cell_color *color)
182 cairo_new_path (xr->cairo);
183 cairo_set_line_width (
185 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
186 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
188 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
189 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
191 if (!xr->style->use_system_colors)
192 xr_set_source_rgba (xr->cairo, color);
193 if (style == RENDER_LINE_DASHED)
194 cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
195 cairo_stroke (xr->cairo);
196 if (style == RENDER_LINE_DASHED)
197 cairo_set_dash (xr->cairo, NULL, 0, 0);
201 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
203 cairo_new_path (xr->cairo);
204 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
205 cairo_close_path (xr->cairo);
206 cairo_stroke (xr->cairo);
207 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
208 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
209 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
210 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
214 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
216 cairo_new_path (xr->cairo);
217 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
218 cairo_rectangle (xr->cairo,
219 xr_to_pt (x0), xr_to_pt (y0),
220 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
221 cairo_fill (xr->cairo);
224 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
225 shortening it to X0...X1 if SHORTEN is true.
226 Draws a horizontal line X1...X3 at Y if RIGHT says so,
227 shortening it to X2...X3 if SHORTEN is true. */
229 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
230 enum render_line_style left, enum render_line_style right,
231 const struct cell_color *left_color,
232 const struct cell_color *right_color,
235 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
236 && cell_color_equal (left_color, right_color))
237 xr_draw_line (xr, x0, y, x3, y, left, left_color);
240 if (left != RENDER_LINE_NONE)
241 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
242 if (right != RENDER_LINE_NONE)
243 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
247 /* Draws a vertical line Y0...Y2 at X if TOP says so,
248 shortening it to Y0...Y1 if SHORTEN is true.
249 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
250 shortening it to Y2...Y3 if SHORTEN is true. */
252 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
253 enum render_line_style top, enum render_line_style bottom,
254 const struct cell_color *top_color,
255 const struct cell_color *bottom_color,
258 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
259 && cell_color_equal (top_color, bottom_color))
260 xr_draw_line (xr, x, y0, x, y3, top, top_color);
263 if (top != RENDER_LINE_NONE)
264 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
265 if (bottom != RENDER_LINE_NONE)
266 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
271 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
272 enum render_line_style styles[TABLE_N_AXES][2],
273 struct cell_color colors[TABLE_N_AXES][2])
275 const int x0 = bb[H][0];
276 const int y0 = bb[V][0];
277 const int x3 = bb[H][1];
278 const int y3 = bb[V][1];
279 const int top = styles[H][0];
280 const int bottom = styles[H][1];
282 int start_side = render_direction_rtl();
283 int end_side = !start_side;
284 const int start_of_line = styles[V][start_side];
285 const int end_of_line = styles[V][end_side];
286 const struct cell_color *top_color = &colors[H][0];
287 const struct cell_color *bottom_color = &colors[H][1];
288 const struct cell_color *start_color = &colors[V][start_side];
289 const struct cell_color *end_color = &colors[V][end_side];
291 /* The algorithm here is somewhat subtle, to allow it to handle
292 all the kinds of intersections that we need.
294 Three additional ordinates are assigned along the x axis. The
295 first is xc, midway between x0 and x3. The others are x1 and
296 x2; for a single vertical line these are equal to xc, and for
297 a double vertical line they are the ordinates of the left and
298 right half of the double line.
300 yc, y1, and y2 are assigned similarly along the y axis.
302 The following diagram shows the coordinate system and output
303 for double top and bottom lines, single left line, and no
307 y0 ________________________
313 y1 = y2 = yc |######### # |
318 y3 |________#_____#_______|
320 struct xr_fsm *xr = xr_;
322 /* Offset from center of each line in a pair of double lines. */
323 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
325 /* Are the lines along each axis single or double?
326 (It doesn't make sense to have different kinds of line on the
327 same axis, so we don't try to gracefully handle that case.) */
328 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
329 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
331 /* When horizontal lines are doubled,
332 the left-side line along y1 normally runs from x0 to x2,
333 and the right-side line along y1 from x3 to x1.
334 If the top-side line is also doubled, we shorten the y1 lines,
335 so that the left-side line runs only to x1,
336 and the right-side line only to x2.
337 Otherwise, the horizontal line at y = y1 below would cut off
338 the intersection, which looks ugly:
340 y0 ________________________
345 y1 |######### ########|
348 y2 |######################|
351 y3 |______________________|
352 It is more of a judgment call when the horizontal line is
353 single. We actually choose to cut off the line anyhow, as
354 shown in the first diagram above.
356 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
357 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
358 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
359 int horz_line_ofs = double_vert ? double_line_ofs : 0;
360 int xc = (x0 + x3) / 2;
361 int x1 = xc - horz_line_ofs;
362 int x2 = xc + horz_line_ofs;
364 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
365 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
366 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
367 int vert_line_ofs = double_horz ? double_line_ofs : 0;
368 int yc = (y0 + y3) / 2;
369 int y1 = yc - vert_line_ofs;
370 int y2 = yc + vert_line_ofs;
373 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
374 start_color, end_color, shorten_yc_line);
377 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
378 start_color, end_color, shorten_y1_lines);
379 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
380 start_color, end_color, shorten_y2_lines);
384 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
385 top_color, bottom_color, shorten_xc_line);
388 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
389 top_color, bottom_color, shorten_x1_lines);
390 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
391 top_color, bottom_color, shorten_x2_lines);
396 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
397 int *min_width, int *max_width)
399 struct xr_fsm *xr = xr_;
400 int bb[TABLE_N_AXES][2];
401 int clip[TABLE_N_AXES][2];
408 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
409 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
412 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
415 *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
416 + cell->style->cell_style.margin[H][1]);
418 *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
419 + cell->style->cell_style.margin[H][1]);
423 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
425 struct xr_fsm *xr = xr_;
426 int bb[TABLE_N_AXES][2];
427 int clip[TABLE_N_AXES][2];
431 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
432 + cell->style->cell_style.margin[H][1]);
435 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
436 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
437 h += px_to_xr (cell->style->cell_style.margin[V][0]
438 + cell->style->cell_style.margin[V][1]);
442 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
445 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
446 int bb[TABLE_N_AXES][2], int valign_offset,
447 int spill[TABLE_N_AXES][2],
448 int clip[TABLE_N_AXES][2])
450 struct xr_fsm *xr = xr_;
453 const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
454 if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
456 cairo_save (xr->cairo);
457 int bg_clip[TABLE_N_AXES][2];
458 for (int axis = 0; axis < TABLE_N_AXES; axis++)
460 bg_clip[axis][0] = clip[axis][0];
461 if (bb[axis][0] == clip[axis][0])
462 bg_clip[axis][0] -= spill[axis][0];
464 bg_clip[axis][1] = clip[axis][1];
465 if (bb[axis][1] == clip[axis][1])
466 bg_clip[axis][1] += spill[axis][1];
468 xr_clip (xr, bg_clip);
469 xr_set_source_rgba (xr->cairo, bg);
471 bb[H][0] - spill[H][0],
472 bb[V][0] - spill[V][0],
473 bb[H][1] + spill[H][1],
474 bb[V][1] + spill[V][1]);
475 cairo_restore (xr->cairo);
477 cairo_save (xr->cairo);
478 if (!xr->style->use_system_colors)
479 xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
481 bb[V][0] += valign_offset;
483 for (int axis = 0; axis < TABLE_N_AXES; axis++)
485 bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
486 bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
488 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
489 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
490 cairo_restore (xr->cairo);
494 xrr_adjust_break (void *xr_, const struct table_cell *cell,
495 int width, int height)
497 struct xr_fsm *xr = xr_;
498 int bb[TABLE_N_AXES][2];
499 int clip[TABLE_N_AXES][2];
502 if (xrr_measure_cell_height (xr_, cell, width) < height)
506 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
507 + cell->style->cell_style.margin[H][1]);
511 bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
512 + cell->style->cell_style.margin[V][1]);
513 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
514 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
519 xrr_scale (void *xr_, double scale)
521 struct xr_fsm *xr = xr_;
522 cairo_scale (xr->cairo, scale, scale);
526 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
528 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
530 double x0 = xr_to_pt (clip[H][0]);
531 double y0 = xr_to_pt (clip[V][0]);
532 double x1 = xr_to_pt (clip[H][1]);
533 double y1 = xr_to_pt (clip[V][1]);
535 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
536 cairo_clip (xr->cairo);
541 add_attr (PangoAttrList *list, PangoAttribute *attr,
542 guint start_index, guint end_index)
544 attr->start_index = start_index;
545 attr->end_index = end_index;
546 pango_attr_list_insert (list, attr);
550 markup_escape (struct string *out, unsigned int options,
551 const char *in, size_t len)
553 if (!(options & TAB_MARKUP))
555 ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
567 ds_put_cstr (out, "&");
570 ds_put_cstr (out, "<");
573 ds_put_cstr (out, ">");
576 ds_put_byte (out, c);
583 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
585 int size[TABLE_N_AXES];
586 pango_layout_get_size (layout, &size[H], &size[V]);
590 static PangoFontDescription *
591 parse_font (const char *font, int default_size, bool bold, bool italic)
593 if (!c_strcasecmp (font, "Monospaced"))
596 PangoFontDescription *desc = pango_font_description_from_string (font);
600 /* If the font description didn't include an explicit font size, then set it
601 to DEFAULT_SIZE, which is in inch/72000 units. */
602 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
603 pango_font_description_set_size (desc,
604 (default_size / 1000.0) * PANGO_SCALE);
606 pango_font_description_set_weight (desc, (bold
608 : PANGO_WEIGHT_NORMAL));
609 pango_font_description_set_style (desc, (italic
611 : PANGO_STYLE_NORMAL));
617 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
618 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
619 int *widthp, int *brk)
621 const struct font_style *font_style = &cell->style->font_style;
622 const struct cell_style *cell_style = &cell->style->cell_style;
623 unsigned int options = cell->options;
625 enum table_axis X = options & TAB_ROTATE ? V : H;
626 enum table_axis Y = !X;
627 int R = options & TAB_ROTATE ? 0 : 1;
629 PangoFontDescription *desc = NULL;
630 if (font_style->typeface)
632 font_style->typeface,
633 font_style->size ? font_style->size * 1000 : 10000,
634 font_style->bold, font_style->italic);
636 desc = xr->style->font;
639 PangoContext *context = pango_cairo_create_context (xr->cairo);
640 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
641 PangoLayout *layout = pango_layout_new (context);
642 g_object_unref (context);
644 pango_layout_set_font_description (layout, desc);
646 const char *text = cell->text;
647 enum table_halign halign = table_halign_interpret (
648 cell_style->halign, cell->options & TAB_NUMERIC);
649 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
651 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
653 const char *decimal = strrchr (text, cell_style->decimal_char);
656 pango_layout_set_text (layout, decimal, strlen (decimal));
657 pango_layout_set_width (layout, -1);
658 margin_adjustment += get_layout_dimension (layout, H);
661 if (margin_adjustment < 0)
662 bb[H][1] += margin_adjustment;
665 struct string tmp = DS_EMPTY_INITIALIZER;
666 PangoAttrList *attrs = NULL;
668 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
669 Pango's implementation of it): it will break after a period or a comma
670 that precedes a digit, e.g. in ".000" it will break after the period.
671 This code looks for such a situation and inserts a U+2060 WORD JOINER
672 to prevent the break.
674 This isn't necessary when the decimal point is between two digits
675 (e.g. "0.000" won't be broken) or when the display width is not limited so
676 that word wrapping won't happen.
678 It isn't necessary to look for more than one period or comma, as would
679 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
680 are present then there will always be a digit on both sides of every
682 if (options & TAB_MARKUP)
684 PangoAttrList *new_attrs;
686 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
689 tmp.ss = ss_cstr (new_text);
690 tmp.capacity = tmp.ss.length;
694 /* XXX should we report the error? */
695 ds_put_cstr (&tmp, text);
698 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
700 const char *decimal = text + strcspn (text, ".,");
702 && c_isdigit (decimal[1])
703 && (decimal == text || !c_isdigit (decimal[-1])))
705 ds_extend (&tmp, strlen (text) + 16);
706 markup_escape (&tmp, options, text, decimal - text + 1);
707 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
708 markup_escape (&tmp, options, decimal + 1, -1);
712 if (font_style->underline)
715 attrs = pango_attr_list_new ();
716 pango_attr_list_insert (attrs, pango_attr_underline_new (
717 PANGO_UNDERLINE_SINGLE));
720 if (cell->n_footnotes || cell->n_subscripts)
722 /* If we haven't already put TEXT into tmp, do it now. */
723 if (ds_is_empty (&tmp))
725 ds_extend (&tmp, strlen (text) + 16);
726 markup_escape (&tmp, options, text, -1);
729 size_t subscript_ofs = ds_length (&tmp);
730 for (size_t i = 0; i < cell->n_subscripts; i++)
733 ds_put_byte (&tmp, ',');
734 ds_put_cstr (&tmp, cell->subscripts[i]);
737 size_t footnote_ofs = ds_length (&tmp);
738 for (size_t i = 0; i < cell->n_footnotes; i++)
741 ds_put_byte (&tmp, ',');
742 ds_put_cstr (&tmp, cell->footnotes[i]->marker);
745 /* Allow footnote markers to occupy the right margin. That way, numbers
746 in the column are still aligned. */
747 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
749 /* Measure the width of the footnote marker, so we know how much we
750 need to make room for. */
751 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
752 ds_length (&tmp) - footnote_ofs);
754 PangoAttrList *fn_attrs = pango_attr_list_new ();
755 pango_attr_list_insert (
756 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
757 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
758 pango_layout_set_attributes (layout, fn_attrs);
759 pango_attr_list_unref (fn_attrs);
760 int footnote_width = get_layout_dimension (layout, X);
762 /* Bound the adjustment by the width of the right margin. */
763 int right_margin = px_to_xr (cell_style->margin[X][R]);
764 int footnote_adjustment = MIN (footnote_width, right_margin);
766 /* Adjust the bounding box. */
767 if (options & TAB_ROTATE)
768 footnote_adjustment = -footnote_adjustment;
769 bb[X][R] += footnote_adjustment;
772 pango_layout_set_attributes (layout, NULL);
775 /* Set attributes. */
777 attrs = pango_attr_list_new ();
778 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
779 PANGO_ATTR_INDEX_TO_TEXT_END);
780 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
781 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
782 if (cell->n_subscripts)
783 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
784 footnote_ofs - subscript_ofs);
785 if (cell->n_footnotes)
786 add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
787 PANGO_ATTR_INDEX_TO_TEXT_END);
790 /* Set the attributes, if any. */
793 pango_layout_set_attributes (layout, attrs);
794 pango_attr_list_unref (attrs);
798 if (ds_is_empty (&tmp))
799 pango_layout_set_text (layout, text, -1);
801 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
804 pango_layout_set_alignment (layout,
805 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
806 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
807 : PANGO_ALIGN_CENTER));
808 pango_layout_set_width (
810 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
811 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
813 int size[TABLE_N_AXES];
814 pango_layout_get_size (layout, &size[H], &size[V]);
816 if (clip[H][0] != clip[H][1])
818 cairo_save (xr->cairo);
819 if (!(options & TAB_ROTATE))
821 if (options & TAB_ROTATE)
823 int extra = bb[H][1] - bb[H][0] - size[V];
824 int halign_offset = extra > 0 ? extra / 2 : 0;
825 cairo_translate (xr->cairo,
826 xr_to_pt (bb[H][0] + halign_offset),
827 xr_to_pt (bb[V][1]));
828 cairo_rotate (xr->cairo, -M_PI_2);
831 cairo_translate (xr->cairo,
833 xr_to_pt (bb[V][0]));
834 pango_cairo_show_layout (xr->cairo, layout);
836 /* If enabled, this draws a blue rectangle around the extents of each
837 line of text, which can be rather useful for debugging layout
841 PangoLayoutIter *iter;
842 iter = pango_layout_get_iter (layout);
845 PangoRectangle extents;
847 pango_layout_iter_get_line_extents (iter, &extents, NULL);
848 cairo_save (xr->cairo);
849 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
852 pango_to_xr (extents.x),
853 pango_to_xr (extents.y),
854 pango_to_xr (extents.x + extents.width),
855 pango_to_xr (extents.y + extents.height));
856 cairo_restore (xr->cairo);
858 while (pango_layout_iter_next_line (iter));
859 pango_layout_iter_free (iter);
862 cairo_restore (xr->cairo);
865 int w = pango_to_xr (size[X]);
866 int h = pango_to_xr (size[Y]);
869 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
871 PangoLayoutIter *iter;
874 /* Choose a breakpoint between lines instead of in the middle of one. */
875 iter = pango_layout_get_iter (layout);
878 PangoRectangle extents;
882 pango_layout_iter_get_line_extents (iter, NULL, &extents);
883 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
884 extents.x = pango_to_xr (extents.x);
885 extents.y = pango_to_xr (y0);
886 extents.width = pango_to_xr (extents.width);
887 extents.height = pango_to_xr (y1 - y0);
888 bottom = bb[V][0] + extents.y + extents.height;
889 if (bottom < bb[V][1])
891 if (brk && clip[H][0] != clip[H][1])
899 while (pango_layout_iter_next_line (iter));
900 pango_layout_iter_free (iter);
902 /* If enabled, draws a green line across the chosen breakpoint, which can
903 be useful for debugging issues with breaking. */
907 xr_draw_line (xr, 0, best,
908 xr->style->size[H], best,
910 &(struct cell_color) CELL_COLOR (0, 255, 0));
914 pango_layout_set_attributes (layout, NULL);
916 if (desc != xr->style->font)
917 pango_font_description_free (desc);
918 g_object_unref (G_OBJECT (layout));
924 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
925 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
926 int *width, int *height, int *brk)
931 /* If enabled, draws a blue rectangle around the cell extents, which can be
932 useful for debugging layout. */
935 if (clip[H][0] != clip[H][1])
937 cairo_save (xr->cairo);
938 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
939 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
940 cairo_restore (xr->cairo);
946 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
949 #define CHART_WIDTH 500
950 #define CHART_HEIGHT 375
953 xr_fsm_create (const struct output_item *item_,
954 const struct xr_fsm_style *style, cairo_t *cr)
956 if (is_page_setup_item (item_)
957 || is_group_open_item (item_)
958 || is_group_close_item (item_))
961 struct output_item *item;
962 if (is_table_item (item_)
963 || is_chart_item (item_)
964 || is_page_eject_item (item_))
965 item = output_item_ref (item_);
966 else if (is_message_item (item_))
967 item = table_item_super (
968 text_item_to_table_item (
969 message_item_to_text_item (
971 output_item_ref (item_)))));
972 else if (is_text_item (item_))
974 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
977 item = table_item_super (
978 text_item_to_table_item (
980 output_item_ref (item_))));
982 else if (is_group_open_item (item_))
984 item = table_item_super (
985 text_item_to_table_item (
986 text_item_create (TEXT_ITEM_TITLE,
987 to_group_open_item (item_)->command_name,
992 assert (is_table_item (item)
993 || is_chart_item (item)
994 || is_page_eject_item (item));
996 static const struct render_ops xrr_render_ops = {
997 .measure_cell_width = xrr_measure_cell_width,
998 .measure_cell_height = xrr_measure_cell_height,
999 .adjust_break = xrr_adjust_break,
1000 .draw_line = xrr_draw_line,
1001 .draw_cell = xrr_draw_cell,
1005 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1006 static const int xr_line_widths[RENDER_N_LINES] =
1008 [RENDER_LINE_NONE] = 0,
1009 [RENDER_LINE_SINGLE] = LW,
1010 [RENDER_LINE_DASHED] = LW,
1011 [RENDER_LINE_THICK] = LW * 2,
1012 [RENDER_LINE_THIN] = LW / 2,
1013 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1016 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1017 *fsm = (struct xr_fsm) {
1018 .style = xr_fsm_style_ref (style),
1021 .ops = &xrr_render_ops,
1023 .size = { [H] = style->size[H], [V] = style->size[V] },
1024 .line_widths = xr_line_widths,
1025 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1026 .supports_margins = true,
1027 .rtl = render_direction_rtl (),
1031 if (is_table_item (item))
1034 fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1038 /* Get font size. */
1039 PangoContext *context = pango_cairo_create_context (cr);
1040 pango_cairo_context_set_resolution (context, style->font_resolution);
1041 PangoLayout *layout = pango_layout_new (context);
1042 g_object_unref (context);
1044 pango_layout_set_font_description (layout, style->font);
1046 pango_layout_set_text (layout, "0", 1);
1048 int char_size[TABLE_N_AXES];
1049 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1050 for (int a = 0; a < TABLE_N_AXES; a++)
1052 int csa = pango_to_xr (char_size[a]);
1053 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1056 g_object_unref (G_OBJECT (layout));
1062 xr_fsm_destroy (struct xr_fsm *fsm)
1066 xr_fsm_style_unref (fsm->style);
1067 output_item_unref (fsm->item);
1068 render_pager_destroy (fsm->p);
1069 assert (!fsm->cairo);
1074 /* This is primarily meant for use with screen rendering since the result is a
1075 fixed value for charts. */
1077 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1081 if (is_table_item (fsm->item))
1084 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1085 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1088 else if (is_chart_item (fsm->item))
1103 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1105 return (render_pager_has_next (fsm->p)
1106 ? render_pager_draw_next (fsm->p, space)
1111 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1113 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1114 if (space < chart_height)
1118 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1119 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1120 return chart_height;
1124 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1126 if (space >= fsm->rp.size[V])
1132 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1134 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1138 mul_XR_POINT (int x)
1140 return (x >= INT_MAX / XR_POINT ? INT_MAX
1141 : x <= INT_MIN / XR_POINT ? INT_MIN
1146 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1147 int x, int y, int w, int h)
1149 if (is_table_item (fsm->item))
1152 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1153 mul_XR_POINT (w), mul_XR_POINT (h));
1156 else if (is_chart_item (fsm->item))
1157 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1158 else if (is_page_eject_item (fsm->item))
1160 /* Nothing to do. */
1167 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1169 if (xr_fsm_is_empty (fsm))
1174 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1175 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1176 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1186 xr_fsm_is_empty (const struct xr_fsm *fsm)
1188 return (is_table_item (fsm->item)
1189 ? !render_pager_has_next (fsm->p)