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_unref (struct xr_fsm_style *style)
71 assert (style->ref_cnt > 0);
72 if (!--style->ref_cnt)
74 for (size_t i = 0; i < XR_N_FONTS; i++)
75 pango_font_description_free (style->fonts[i]);
82 xr_fsm_style_equals (const struct xr_fsm_style *a,
83 const struct xr_fsm_style *b)
85 if (a->size[H] != b->size[H]
86 || a->size[V] != b->size[V]
87 || a->min_break[H] != b->min_break[H]
88 || a->min_break[V] != b->min_break[V]
89 || a->use_system_colors != b->use_system_colors
90 || a->transparent != b->transparent
91 || a->font_resolution != b->font_resolution)
94 for (size_t i = 0; i < XR_N_FONTS; i++)
95 if (!pango_font_description_equal (a->fonts[i], b->fonts[i]))
103 struct xr_fsm_style *style;
104 struct output_item *item;
106 /* Table items only. */
107 struct render_params rp;
108 struct render_pager *p;
109 cairo_t *cairo; /* XXX should this be here?! */
111 /* Chart and page-eject items only. */
115 /* The unit used for internal measurements is inch/(72 * XR_POINT).
116 (Thus, XR_POINT units represent one point.) */
117 #define XR_POINT PANGO_SCALE
119 /* Conversions to and from points. */
123 return x / (double) XR_POINT;
126 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
130 return x * (PANGO_SCALE * 72 / 96);
134 pango_to_xr (int pango)
136 return (XR_POINT != PANGO_SCALE
137 ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
144 return (XR_POINT != PANGO_SCALE
145 ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
149 /* Dimensions for drawing lines in tables. */
150 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
151 #define XR_LINE_SPACE XR_POINT /* Space between double lines. */
154 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
155 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
156 int *width, int *height, int *brk);
159 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
161 cairo_set_source_rgba (cairo,
162 color->r / 255., color->g / 255., color->b / 255.,
163 color->alpha / 255.);
167 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
168 const struct cell_color *color)
170 cairo_new_path (xr->cairo);
171 if (!xr->style->use_system_colors)
172 xr_set_source_rgba (xr->cairo, color);
173 cairo_set_line_width (
175 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
176 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
178 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
179 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
180 cairo_stroke (xr->cairo);
184 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
186 cairo_new_path (xr->cairo);
187 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
188 cairo_close_path (xr->cairo);
189 cairo_stroke (xr->cairo);
190 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
191 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
192 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
193 cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
197 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
199 cairo_new_path (xr->cairo);
200 cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
201 cairo_rectangle (xr->cairo,
202 xr_to_pt (x0), xr_to_pt (y0),
203 xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
204 cairo_fill (xr->cairo);
207 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
208 shortening it to X0...X1 if SHORTEN is true.
209 Draws a horizontal line X1...X3 at Y if RIGHT says so,
210 shortening it to X2...X3 if SHORTEN is true. */
212 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
213 enum render_line_style left, enum render_line_style right,
214 const struct cell_color *left_color,
215 const struct cell_color *right_color,
218 if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
219 && cell_color_equal (left_color, right_color))
220 xr_draw_line (xr, x0, y, x3, y, left, left_color);
223 if (left != RENDER_LINE_NONE)
224 xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
225 if (right != RENDER_LINE_NONE)
226 xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
230 /* Draws a vertical line Y0...Y2 at X if TOP says so,
231 shortening it to Y0...Y1 if SHORTEN is true.
232 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
233 shortening it to Y2...Y3 if SHORTEN is true. */
235 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
236 enum render_line_style top, enum render_line_style bottom,
237 const struct cell_color *top_color,
238 const struct cell_color *bottom_color,
241 if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
242 && cell_color_equal (top_color, bottom_color))
243 xr_draw_line (xr, x, y0, x, y3, top, top_color);
246 if (top != RENDER_LINE_NONE)
247 xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
248 if (bottom != RENDER_LINE_NONE)
249 xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
254 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
255 enum render_line_style styles[TABLE_N_AXES][2],
256 struct cell_color colors[TABLE_N_AXES][2])
258 const int x0 = bb[H][0];
259 const int y0 = bb[V][0];
260 const int x3 = bb[H][1];
261 const int y3 = bb[V][1];
262 const int top = styles[H][0];
263 const int bottom = styles[H][1];
265 int start_side = render_direction_rtl();
266 int end_side = !start_side;
267 const int start_of_line = styles[V][start_side];
268 const int end_of_line = styles[V][end_side];
269 const struct cell_color *top_color = &colors[H][0];
270 const struct cell_color *bottom_color = &colors[H][1];
271 const struct cell_color *start_color = &colors[V][start_side];
272 const struct cell_color *end_color = &colors[V][end_side];
274 /* The algorithm here is somewhat subtle, to allow it to handle
275 all the kinds of intersections that we need.
277 Three additional ordinates are assigned along the x axis. The
278 first is xc, midway between x0 and x3. The others are x1 and
279 x2; for a single vertical line these are equal to xc, and for
280 a double vertical line they are the ordinates of the left and
281 right half of the double line.
283 yc, y1, and y2 are assigned similarly along the y axis.
285 The following diagram shows the coordinate system and output
286 for double top and bottom lines, single left line, and no
290 y0 ________________________
296 y1 = y2 = yc |######### # |
301 y3 |________#_____#_______|
303 struct xr_fsm *xr = xr_;
305 /* Offset from center of each line in a pair of double lines. */
306 int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
308 /* Are the lines along each axis single or double?
309 (It doesn't make sense to have different kinds of line on the
310 same axis, so we don't try to gracefully handle that case.) */
311 bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
312 bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
314 /* When horizontal lines are doubled,
315 the left-side line along y1 normally runs from x0 to x2,
316 and the right-side line along y1 from x3 to x1.
317 If the top-side line is also doubled, we shorten the y1 lines,
318 so that the left-side line runs only to x1,
319 and the right-side line only to x2.
320 Otherwise, the horizontal line at y = y1 below would cut off
321 the intersection, which looks ugly:
323 y0 ________________________
328 y1 |######### ########|
331 y2 |######################|
334 y3 |______________________|
335 It is more of a judgment call when the horizontal line is
336 single. We actually choose to cut off the line anyhow, as
337 shown in the first diagram above.
339 bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
340 bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
341 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
342 int horz_line_ofs = double_vert ? double_line_ofs : 0;
343 int xc = (x0 + x3) / 2;
344 int x1 = xc - horz_line_ofs;
345 int x2 = xc + horz_line_ofs;
347 bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
348 bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
349 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
350 int vert_line_ofs = double_horz ? double_line_ofs : 0;
351 int yc = (y0 + y3) / 2;
352 int y1 = yc - vert_line_ofs;
353 int y2 = yc + vert_line_ofs;
356 xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
357 start_color, end_color, shorten_yc_line);
360 xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
361 start_color, end_color, shorten_y1_lines);
362 xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
363 start_color, end_color, shorten_y2_lines);
367 xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
368 top_color, bottom_color, shorten_xc_line);
371 xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
372 top_color, bottom_color, shorten_x1_lines);
373 xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
374 top_color, bottom_color, shorten_x2_lines);
379 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
380 int *min_width, int *max_width)
382 struct xr_fsm *xr = xr_;
383 int bb[TABLE_N_AXES][2];
384 int clip[TABLE_N_AXES][2];
391 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
392 xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
395 xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
398 *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
399 + cell->style->cell_style.margin[H][1]);
401 *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
402 + cell->style->cell_style.margin[H][1]);
406 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
408 struct xr_fsm *xr = xr_;
409 int bb[TABLE_N_AXES][2];
410 int clip[TABLE_N_AXES][2];
414 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
415 + cell->style->cell_style.margin[H][1]);
418 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
419 xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
420 h += px_to_xr (cell->style->cell_style.margin[V][0]
421 + cell->style->cell_style.margin[V][1]);
425 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
428 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
429 int bb[TABLE_N_AXES][2], int valign_offset,
430 int spill[TABLE_N_AXES][2],
431 int clip[TABLE_N_AXES][2])
433 struct xr_fsm *xr = xr_;
436 if (!xr->style->transparent)
438 cairo_save (xr->cairo);
439 int bg_clip[TABLE_N_AXES][2];
440 for (int axis = 0; axis < TABLE_N_AXES; axis++)
442 bg_clip[axis][0] = clip[axis][0];
443 if (bb[axis][0] == clip[axis][0])
444 bg_clip[axis][0] -= spill[axis][0];
446 bg_clip[axis][1] = clip[axis][1];
447 if (bb[axis][1] == clip[axis][1])
448 bg_clip[axis][1] += spill[axis][1];
450 xr_clip (xr, bg_clip);
451 xr_set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]);
453 bb[H][0] - spill[H][0],
454 bb[V][0] - spill[V][0],
455 bb[H][1] + spill[H][1],
456 bb[V][1] + spill[V][1]);
457 cairo_restore (xr->cairo);
459 cairo_save (xr->cairo);
460 if (!xr->style->use_system_colors)
461 xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
463 bb[V][0] += valign_offset;
465 for (int axis = 0; axis < TABLE_N_AXES; axis++)
467 bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
468 bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
470 if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
471 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
472 cairo_restore (xr->cairo);
476 xrr_adjust_break (void *xr_, const struct table_cell *cell,
477 int width, int height)
479 struct xr_fsm *xr = xr_;
480 int bb[TABLE_N_AXES][2];
481 int clip[TABLE_N_AXES][2];
484 if (xrr_measure_cell_height (xr_, cell, width) < height)
488 bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
489 + cell->style->cell_style.margin[H][1]);
493 bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
494 + cell->style->cell_style.margin[V][1]);
495 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
496 xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
501 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
503 if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
505 double x0 = xr_to_pt (clip[H][0]);
506 double y0 = xr_to_pt (clip[V][0]);
507 double x1 = xr_to_pt (clip[H][1]);
508 double y1 = xr_to_pt (clip[V][1]);
510 cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
511 cairo_clip (xr->cairo);
516 add_attr (PangoAttrList *list, PangoAttribute *attr,
517 guint start_index, guint end_index)
519 attr->start_index = start_index;
520 attr->end_index = end_index;
521 pango_attr_list_insert (list, attr);
525 markup_escape (struct string *out, unsigned int options,
526 const char *in, size_t len)
528 if (!(options & TAB_MARKUP))
530 ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
542 ds_put_cstr (out, "&");
545 ds_put_cstr (out, "<");
548 ds_put_cstr (out, ">");
551 ds_put_byte (out, c);
558 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
560 int size[TABLE_N_AXES];
561 pango_layout_get_size (layout, &size[H], &size[V]);
565 static PangoFontDescription *
566 parse_font (const char *font, int default_size, bool bold, bool italic)
568 if (!c_strcasecmp (font, "Monospaced"))
571 PangoFontDescription *desc = pango_font_description_from_string (font);
575 /* If the font description didn't include an explicit font size, then set it
576 to DEFAULT_SIZE, which is in inch/72000 units. */
577 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
578 pango_font_description_set_size (desc,
579 (default_size / 1000.0) * PANGO_SCALE);
581 pango_font_description_set_weight (desc, (bold
583 : PANGO_WEIGHT_NORMAL));
584 pango_font_description_set_style (desc, (italic
586 : PANGO_STYLE_NORMAL));
592 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
593 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
594 int *widthp, int *brk)
596 const struct font_style *font_style = &cell->style->font_style;
597 const struct cell_style *cell_style = &cell->style->cell_style;
598 unsigned int options = cell->options;
600 enum table_axis X = options & TAB_ROTATE ? V : H;
601 enum table_axis Y = !X;
602 int R = options & TAB_ROTATE ? 0 : 1;
604 enum xr_font_type font_type = (options & TAB_FIX
606 : XR_FONT_PROPORTIONAL);
607 PangoFontDescription *desc = NULL;
608 if (font_style->typeface)
610 font_style->typeface,
611 font_style->size ? font_style->size * 1000 : 10000,
612 font_style->bold, font_style->italic);
614 desc = xr->style->fonts[font_type];
617 PangoContext *context = pango_cairo_create_context (xr->cairo);
618 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
619 PangoLayout *layout = pango_layout_new (context);
620 g_object_unref (context);
621 pango_layout_set_font_description (layout, desc);
623 const char *text = cell->text;
624 enum table_halign halign = table_halign_interpret (
625 cell_style->halign, cell->options & TAB_NUMERIC);
626 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
628 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
630 const char *decimal = strrchr (text, cell_style->decimal_char);
633 pango_layout_set_text (layout, decimal, strlen (decimal));
634 pango_layout_set_width (layout, -1);
635 margin_adjustment += get_layout_dimension (layout, H);
638 if (margin_adjustment < 0)
639 bb[H][1] += margin_adjustment;
642 struct string tmp = DS_EMPTY_INITIALIZER;
643 PangoAttrList *attrs = NULL;
645 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
646 Pango's implementation of it): it will break after a period or a comma
647 that precedes a digit, e.g. in ".000" it will break after the period.
648 This code looks for such a situation and inserts a U+2060 WORD JOINER
649 to prevent the break.
651 This isn't necessary when the decimal point is between two digits
652 (e.g. "0.000" won't be broken) or when the display width is not limited so
653 that word wrapping won't happen.
655 It isn't necessary to look for more than one period or comma, as would
656 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
657 are present then there will always be a digit on both sides of every
659 if (options & TAB_MARKUP)
661 PangoAttrList *new_attrs;
663 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
666 tmp.ss = ss_cstr (new_text);
667 tmp.capacity = tmp.ss.length;
671 /* XXX should we report the error? */
672 ds_put_cstr (&tmp, text);
675 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
677 const char *decimal = text + strcspn (text, ".,");
679 && c_isdigit (decimal[1])
680 && (decimal == text || !c_isdigit (decimal[-1])))
682 ds_extend (&tmp, strlen (text) + 16);
683 markup_escape (&tmp, options, text, decimal - text + 1);
684 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
685 markup_escape (&tmp, options, decimal + 1, -1);
689 if (font_style->underline)
692 attrs = pango_attr_list_new ();
693 pango_attr_list_insert (attrs, pango_attr_underline_new (
694 PANGO_UNDERLINE_SINGLE));
697 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
699 /* If we haven't already put TEXT into tmp, do it now. */
700 if (ds_is_empty (&tmp))
702 ds_extend (&tmp, strlen (text) + 16);
703 markup_escape (&tmp, options, text, -1);
706 size_t subscript_ofs = ds_length (&tmp);
707 for (size_t i = 0; i < cell->n_subscripts; i++)
710 ds_put_byte (&tmp, ',');
711 ds_put_cstr (&tmp, cell->subscripts[i]);
714 size_t superscript_ofs = ds_length (&tmp);
715 if (cell->superscript)
716 ds_put_cstr (&tmp, cell->superscript);
718 size_t footnote_ofs = ds_length (&tmp);
719 for (size_t i = 0; i < cell->n_footnotes; i++)
722 ds_put_byte (&tmp, ',');
723 ds_put_cstr (&tmp, cell->footnotes[i]->marker);
726 /* Allow footnote markers to occupy the right margin. That way, numbers
727 in the column are still aligned. */
728 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
730 /* Measure the width of the footnote marker, so we know how much we
731 need to make room for. */
732 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
733 ds_length (&tmp) - footnote_ofs);
735 PangoAttrList *fn_attrs = pango_attr_list_new ();
736 pango_attr_list_insert (
737 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
738 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
739 pango_layout_set_attributes (layout, fn_attrs);
740 pango_attr_list_unref (fn_attrs);
741 int footnote_width = get_layout_dimension (layout, X);
743 /* Bound the adjustment by the width of the right margin. */
744 int right_margin = px_to_xr (cell_style->margin[X][R]);
745 int footnote_adjustment = MIN (footnote_width, right_margin);
747 /* Adjust the bounding box. */
748 if (options & TAB_ROTATE)
749 footnote_adjustment = -footnote_adjustment;
750 bb[X][R] += footnote_adjustment;
753 pango_layout_set_attributes (layout, NULL);
756 /* Set attributes. */
758 attrs = pango_attr_list_new ();
759 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
760 PANGO_ATTR_INDEX_TO_TEXT_END);
761 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
762 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
763 if (cell->n_subscripts)
764 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
765 superscript_ofs - subscript_ofs);
766 if (cell->superscript || cell->n_footnotes)
767 add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
768 PANGO_ATTR_INDEX_TO_TEXT_END);
771 /* Set the attributes, if any. */
774 pango_layout_set_attributes (layout, attrs);
775 pango_attr_list_unref (attrs);
779 if (ds_is_empty (&tmp))
780 pango_layout_set_text (layout, text, -1);
782 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
785 pango_layout_set_alignment (layout,
786 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
787 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
788 : PANGO_ALIGN_CENTER));
789 pango_layout_set_width (
791 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
792 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
794 if (clip[H][0] != clip[H][1])
796 cairo_save (xr->cairo);
797 if (!(options & TAB_ROTATE))
799 if (options & TAB_ROTATE)
801 cairo_translate (xr->cairo,
803 xr_to_pt (bb[V][1]));
804 cairo_rotate (xr->cairo, -M_PI_2);
807 cairo_translate (xr->cairo,
809 xr_to_pt (bb[V][0]));
810 pango_cairo_show_layout (xr->cairo, layout);
812 /* If enabled, this draws a blue rectangle around the extents of each
813 line of text, which can be rather useful for debugging layout
817 PangoLayoutIter *iter;
818 iter = pango_layout_get_iter (layout);
821 PangoRectangle extents;
823 pango_layout_iter_get_line_extents (iter, &extents, NULL);
824 cairo_save (xr->cairo);
825 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
828 pango_to_xr (extents.x),
829 pango_to_xr (extents.y),
830 pango_to_xr (extents.x + extents.width),
831 pango_to_xr (extents.y + extents.height));
832 cairo_restore (xr->cairo);
834 while (pango_layout_iter_next_line (iter));
835 pango_layout_iter_free (iter);
838 cairo_restore (xr->cairo);
841 int size[TABLE_N_AXES];
842 pango_layout_get_size (layout, &size[H], &size[V]);
843 int w = pango_to_xr (size[X]);
844 int h = pango_to_xr (size[Y]);
847 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
849 PangoLayoutIter *iter;
852 /* Choose a breakpoint between lines instead of in the middle of one. */
853 iter = pango_layout_get_iter (layout);
856 PangoRectangle extents;
860 pango_layout_iter_get_line_extents (iter, NULL, &extents);
861 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
862 extents.x = pango_to_xr (extents.x);
863 extents.y = pango_to_xr (y0);
864 extents.width = pango_to_xr (extents.width);
865 extents.height = pango_to_xr (y1 - y0);
866 bottom = bb[V][0] + extents.y + extents.height;
867 if (bottom < bb[V][1])
869 if (brk && clip[H][0] != clip[H][1])
877 while (pango_layout_iter_next_line (iter));
878 pango_layout_iter_free (iter);
880 /* If enabled, draws a green line across the chosen breakpoint, which can
881 be useful for debugging issues with breaking. */
885 xr_draw_line (xr, 0, best,
886 xr->style->size[H], best,
888 &(struct cell_color) CELL_COLOR (0, 255, 0));
892 pango_layout_set_attributes (layout, NULL);
894 if (desc != xr->style->fonts[font_type])
895 pango_font_description_free (desc);
896 g_object_unref (G_OBJECT (layout));
902 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
903 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
904 int *width, int *height, int *brk)
909 /* If enabled, draws a blue rectangle around the cell extents, which can be
910 useful for debugging layout. */
913 if (clip[H][0] != clip[H][1])
915 cairo_save (xr->cairo);
916 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
917 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
918 cairo_restore (xr->cairo);
924 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
929 xr_table_render (struct xr_render_fsm *fsm, struct xr_fsm *xr)
931 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
933 while (render_pager_has_next (ts->p))
937 used = render_pager_draw_next (ts->p, xr->length);
950 xr_table_destroy (struct xr_render_fsm *fsm)
952 struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
954 render_pager_destroy (ts->p);
958 static struct xr_render_fsm *
959 xr_render_table (struct xr_fsm *xr, struct table_item *table_item)
961 struct xr_table_state *ts;
963 ts = xmalloc (sizeof *ts);
964 ts->fsm.render = xr_table_render;
965 ts->fsm.destroy = xr_table_destroy;
968 xr->y += xr->char_height;
970 ts->p = render_pager_create (xr->params, table_item);
971 table_item_unref (table_item);
977 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_fsm *xr)
983 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
988 static struct xr_render_fsm *
989 xr_render_eject (void)
991 static struct xr_render_fsm eject_renderer =
997 return &eject_renderer;
1000 #define CHART_WIDTH 500
1001 #define CHART_HEIGHT 375
1003 static struct xr_render_fsm *
1004 xr_render_text (struct xr_fsm *xr, const struct text_item *text_item)
1006 enum text_item_type type = text_item_get_type (text_item);
1010 case TEXT_ITEM_PAGE_TITLE:
1013 case TEXT_ITEM_EJECT_PAGE:
1015 return xr_render_eject ();
1019 return xr_render_table (
1020 xr, text_item_to_table_item (text_item_ref (text_item)));
1027 #define CHART_WIDTH 500
1028 #define CHART_HEIGHT 375
1031 xr_fsm_create (const struct output_item *item_,
1032 const struct xr_fsm_style *style, cairo_t *cr)
1034 if (is_page_setup_item (item_)
1035 || is_group_open_item (item_)
1036 || is_group_close_item (item_))
1039 struct output_item *item;
1040 if (is_table_item (item_)
1041 || is_chart_item (item_)
1042 || is_page_eject_item (item_))
1043 item = output_item_ref (item_);
1044 else if (is_message_item (item_))
1045 item = table_item_super (
1046 text_item_to_table_item (
1047 message_item_to_text_item (
1049 output_item_ref (item_)))));
1050 else if (is_text_item (item_))
1052 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1055 item = table_item_super (
1056 text_item_to_table_item (
1058 output_item_ref (item_))));
1060 else if (is_group_open_item (item_))
1062 item = table_item_super (
1063 text_item_to_table_item (
1064 text_item_create (TEXT_ITEM_TITLE,
1065 to_group_open_item (item_)->command_name)));
1069 assert (is_table_item (item)
1070 || is_chart_item (item)
1071 || is_page_eject_item (item));
1073 static const struct render_ops xrr_render_ops = {
1074 .measure_cell_width = xrr_measure_cell_width,
1075 .measure_cell_height = xrr_measure_cell_height,
1076 .adjust_break = xrr_adjust_break,
1077 .draw_line = xrr_draw_line,
1078 .draw_cell = xrr_draw_cell,
1081 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1082 static const int xr_line_widths[RENDER_N_LINES] =
1084 [RENDER_LINE_NONE] = 0,
1085 [RENDER_LINE_SINGLE] = LW,
1086 [RENDER_LINE_DASHED] = LW,
1087 [RENDER_LINE_THICK] = LW * 2,
1088 [RENDER_LINE_THIN] = LW / 2,
1089 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1092 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1093 *fsm = (struct xr_fsm) {
1094 .style = xr_fsm_style_ref (style),
1097 .ops = &xrr_render_ops,
1099 .size = { [H] = style->size[H], [V] = style->size[V] },
1101 .line_widths = xr_line_widths,
1102 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1103 .supports_margins = true,
1104 .rtl = render_direction_rtl (),
1108 if (is_table_item (item))
1111 fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1115 for (int i = 0; i < XR_N_FONTS; i++)
1117 PangoContext *context = pango_cairo_create_context (cr);
1118 pango_cairo_context_set_resolution (context, style->font_resolution);
1119 PangoLayout *layout = pango_layout_new (context);
1120 g_object_unref (context);
1121 pango_layout_set_font_description (layout, style->fonts[i]);
1123 pango_layout_set_text (layout, "0", 1);
1125 int char_size[TABLE_N_AXES];
1126 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1127 for (int j = 0; j < TABLE_N_AXES; j++)
1129 int csj = pango_to_xr (char_size[j]);
1130 fsm->rp.font_size[j] = MAX (fsm->rp.font_size[j], csj);
1133 g_object_unref (G_OBJECT (layout));
1140 xr_fsm_destroy (struct xr_fsm *fsm)
1144 xr_fsm_style_unref (fsm->style);
1145 output_item_unref (fsm->item);
1146 render_pager_destroy (fsm->p);
1147 assert (!fsm->cairo);
1153 /* This is primarily meant for use with screen rendering since the result is a
1154 fixed value for charts. */
1156 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1160 if (is_table_item (fsm->item))
1163 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1164 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1167 else if (is_chart_item (fsm->item))
1182 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1185 while (render_pager_has_next (fsm->p))
1187 int chunk = render_pager_draw_next (fsm->p, space - used);
1192 cairo_translate (fsm->cairo, 0, chunk);
1198 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1200 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1201 if (space < chart_height)
1205 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1206 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1207 return chart_height;
1211 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1213 if (space >= fsm->rp.size[V])
1219 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1221 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1225 mul_XR_POINT (int x)
1227 return (x >= INT_MAX / XR_POINT ? INT_MAX
1228 : x <= INT_MIN / XR_POINT ? INT_MIN
1233 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1234 int x, int y, int w, int h)
1236 if (is_table_item (fsm->item))
1239 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1240 mul_XR_POINT (w), mul_XR_POINT (h));
1243 else if (is_chart_item (fsm->item))
1244 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1245 else if (is_page_eject_item (fsm->item))
1247 /* Nothing to do. */
1254 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1256 if (xr_fsm_is_empty (fsm))
1261 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1262 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1263 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1273 xr_fsm_is_empty (const struct xr_fsm *fsm)
1275 return (is_table_item (fsm->item)
1276 ? !render_pager_has_next (fsm->p)