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 if (!xr->style->use_system_colors)
189 xr_set_source_rgba (xr->cairo, color);
190 cairo_set_line_width (
192 xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
193 : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
195 cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
196 cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
197 cairo_stroke (xr->cairo);
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 enum xr_font_type font_type = (options & TAB_FIX
631 : XR_FONT_PROPORTIONAL);
632 PangoFontDescription *desc = NULL;
633 if (font_style->typeface)
635 font_style->typeface,
636 font_style->size ? font_style->size * 1000 : 10000,
637 font_style->bold, font_style->italic);
639 desc = xr->style->fonts[font_type];
642 PangoContext *context = pango_cairo_create_context (xr->cairo);
643 pango_cairo_context_set_resolution (context, xr->style->font_resolution);
644 PangoLayout *layout = pango_layout_new (context);
645 g_object_unref (context);
647 pango_layout_set_font_description (layout, desc);
649 const char *text = cell->text;
650 enum table_halign halign = table_halign_interpret (
651 cell_style->halign, cell->options & TAB_NUMERIC);
652 if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
654 int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
656 const char *decimal = strrchr (text, cell_style->decimal_char);
659 pango_layout_set_text (layout, decimal, strlen (decimal));
660 pango_layout_set_width (layout, -1);
661 margin_adjustment += get_layout_dimension (layout, H);
664 if (margin_adjustment < 0)
665 bb[H][1] += margin_adjustment;
668 struct string tmp = DS_EMPTY_INITIALIZER;
669 PangoAttrList *attrs = NULL;
671 /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
672 Pango's implementation of it): it will break after a period or a comma
673 that precedes a digit, e.g. in ".000" it will break after the period.
674 This code looks for such a situation and inserts a U+2060 WORD JOINER
675 to prevent the break.
677 This isn't necessary when the decimal point is between two digits
678 (e.g. "0.000" won't be broken) or when the display width is not limited so
679 that word wrapping won't happen.
681 It isn't necessary to look for more than one period or comma, as would
682 happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
683 are present then there will always be a digit on both sides of every
685 if (options & TAB_MARKUP)
687 PangoAttrList *new_attrs;
689 if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
692 tmp.ss = ss_cstr (new_text);
693 tmp.capacity = tmp.ss.length;
697 /* XXX should we report the error? */
698 ds_put_cstr (&tmp, text);
701 else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
703 const char *decimal = text + strcspn (text, ".,");
705 && c_isdigit (decimal[1])
706 && (decimal == text || !c_isdigit (decimal[-1])))
708 ds_extend (&tmp, strlen (text) + 16);
709 markup_escape (&tmp, options, text, decimal - text + 1);
710 ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
711 markup_escape (&tmp, options, decimal + 1, -1);
715 if (font_style->underline)
718 attrs = pango_attr_list_new ();
719 pango_attr_list_insert (attrs, pango_attr_underline_new (
720 PANGO_UNDERLINE_SINGLE));
723 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
725 /* If we haven't already put TEXT into tmp, do it now. */
726 if (ds_is_empty (&tmp))
728 ds_extend (&tmp, strlen (text) + 16);
729 markup_escape (&tmp, options, text, -1);
732 size_t subscript_ofs = ds_length (&tmp);
733 for (size_t i = 0; i < cell->n_subscripts; i++)
736 ds_put_byte (&tmp, ',');
737 ds_put_cstr (&tmp, cell->subscripts[i]);
740 size_t superscript_ofs = ds_length (&tmp);
741 if (cell->superscript)
742 ds_put_cstr (&tmp, cell->superscript);
744 size_t footnote_ofs = ds_length (&tmp);
745 for (size_t i = 0; i < cell->n_footnotes; i++)
748 ds_put_byte (&tmp, ',');
749 ds_put_cstr (&tmp, cell->footnotes[i]->marker);
752 /* Allow footnote markers to occupy the right margin. That way, numbers
753 in the column are still aligned. */
754 if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
756 /* Measure the width of the footnote marker, so we know how much we
757 need to make room for. */
758 pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
759 ds_length (&tmp) - footnote_ofs);
761 PangoAttrList *fn_attrs = pango_attr_list_new ();
762 pango_attr_list_insert (
763 fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
764 pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
765 pango_layout_set_attributes (layout, fn_attrs);
766 pango_attr_list_unref (fn_attrs);
767 int footnote_width = get_layout_dimension (layout, X);
769 /* Bound the adjustment by the width of the right margin. */
770 int right_margin = px_to_xr (cell_style->margin[X][R]);
771 int footnote_adjustment = MIN (footnote_width, right_margin);
773 /* Adjust the bounding box. */
774 if (options & TAB_ROTATE)
775 footnote_adjustment = -footnote_adjustment;
776 bb[X][R] += footnote_adjustment;
779 pango_layout_set_attributes (layout, NULL);
782 /* Set attributes. */
784 attrs = pango_attr_list_new ();
785 add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
786 PANGO_ATTR_INDEX_TO_TEXT_END);
787 add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
788 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
789 if (cell->n_subscripts)
790 add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
791 superscript_ofs - subscript_ofs);
792 if (cell->superscript || cell->n_footnotes)
793 add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
794 PANGO_ATTR_INDEX_TO_TEXT_END);
797 /* Set the attributes, if any. */
800 pango_layout_set_attributes (layout, attrs);
801 pango_attr_list_unref (attrs);
805 if (ds_is_empty (&tmp))
806 pango_layout_set_text (layout, text, -1);
808 pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
811 pango_layout_set_alignment (layout,
812 (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
813 : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
814 : PANGO_ALIGN_CENTER));
815 pango_layout_set_width (
817 bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
818 pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
820 int size[TABLE_N_AXES];
821 pango_layout_get_size (layout, &size[H], &size[V]);
823 if (clip[H][0] != clip[H][1])
825 cairo_save (xr->cairo);
826 if (!(options & TAB_ROTATE))
828 if (options & TAB_ROTATE)
830 int extra = bb[H][1] - bb[H][0] - size[V];
831 int halign_offset = extra > 0 ? extra / 2 : 0;
832 cairo_translate (xr->cairo,
833 xr_to_pt (bb[H][0] + halign_offset),
834 xr_to_pt (bb[V][1]));
835 cairo_rotate (xr->cairo, -M_PI_2);
838 cairo_translate (xr->cairo,
840 xr_to_pt (bb[V][0]));
841 pango_cairo_show_layout (xr->cairo, layout);
843 /* If enabled, this draws a blue rectangle around the extents of each
844 line of text, which can be rather useful for debugging layout
848 PangoLayoutIter *iter;
849 iter = pango_layout_get_iter (layout);
852 PangoRectangle extents;
854 pango_layout_iter_get_line_extents (iter, &extents, NULL);
855 cairo_save (xr->cairo);
856 cairo_set_source_rgb (xr->cairo, 1, 0, 0);
859 pango_to_xr (extents.x),
860 pango_to_xr (extents.y),
861 pango_to_xr (extents.x + extents.width),
862 pango_to_xr (extents.y + extents.height));
863 cairo_restore (xr->cairo);
865 while (pango_layout_iter_next_line (iter));
866 pango_layout_iter_free (iter);
869 cairo_restore (xr->cairo);
872 int w = pango_to_xr (size[X]);
873 int h = pango_to_xr (size[Y]);
876 if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
878 PangoLayoutIter *iter;
881 /* Choose a breakpoint between lines instead of in the middle of one. */
882 iter = pango_layout_get_iter (layout);
885 PangoRectangle extents;
889 pango_layout_iter_get_line_extents (iter, NULL, &extents);
890 pango_layout_iter_get_line_yrange (iter, &y0, &y1);
891 extents.x = pango_to_xr (extents.x);
892 extents.y = pango_to_xr (y0);
893 extents.width = pango_to_xr (extents.width);
894 extents.height = pango_to_xr (y1 - y0);
895 bottom = bb[V][0] + extents.y + extents.height;
896 if (bottom < bb[V][1])
898 if (brk && clip[H][0] != clip[H][1])
906 while (pango_layout_iter_next_line (iter));
907 pango_layout_iter_free (iter);
909 /* If enabled, draws a green line across the chosen breakpoint, which can
910 be useful for debugging issues with breaking. */
914 xr_draw_line (xr, 0, best,
915 xr->style->size[H], best,
917 &(struct cell_color) CELL_COLOR (0, 255, 0));
921 pango_layout_set_attributes (layout, NULL);
923 if (desc != xr->style->fonts[font_type])
924 pango_font_description_free (desc);
925 g_object_unref (G_OBJECT (layout));
931 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
932 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
933 int *width, int *height, int *brk)
938 /* If enabled, draws a blue rectangle around the cell extents, which can be
939 useful for debugging layout. */
942 if (clip[H][0] != clip[H][1])
944 cairo_save (xr->cairo);
945 cairo_set_source_rgb (xr->cairo, 0, 0, 1);
946 xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
947 cairo_restore (xr->cairo);
953 *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
956 #define CHART_WIDTH 500
957 #define CHART_HEIGHT 375
960 xr_fsm_create (const struct output_item *item_,
961 const struct xr_fsm_style *style, cairo_t *cr)
963 if (is_page_setup_item (item_)
964 || is_group_open_item (item_)
965 || is_group_close_item (item_))
968 struct output_item *item;
969 if (is_table_item (item_)
970 || is_chart_item (item_)
971 || is_page_eject_item (item_))
972 item = output_item_ref (item_);
973 else if (is_message_item (item_))
974 item = table_item_super (
975 text_item_to_table_item (
976 message_item_to_text_item (
978 output_item_ref (item_)))));
979 else if (is_text_item (item_))
981 if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
984 item = table_item_super (
985 text_item_to_table_item (
987 output_item_ref (item_))));
989 else if (is_group_open_item (item_))
991 item = table_item_super (
992 text_item_to_table_item (
993 text_item_create (TEXT_ITEM_TITLE,
994 to_group_open_item (item_)->command_name)));
998 assert (is_table_item (item)
999 || is_chart_item (item)
1000 || is_page_eject_item (item));
1002 static const struct render_ops xrr_render_ops = {
1003 .measure_cell_width = xrr_measure_cell_width,
1004 .measure_cell_height = xrr_measure_cell_height,
1005 .adjust_break = xrr_adjust_break,
1006 .draw_line = xrr_draw_line,
1007 .draw_cell = xrr_draw_cell,
1011 enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1012 static const int xr_line_widths[RENDER_N_LINES] =
1014 [RENDER_LINE_NONE] = 0,
1015 [RENDER_LINE_SINGLE] = LW,
1016 [RENDER_LINE_DASHED] = LW,
1017 [RENDER_LINE_THICK] = LW * 2,
1018 [RENDER_LINE_THIN] = LW / 2,
1019 [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1022 struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1023 *fsm = (struct xr_fsm) {
1024 .style = xr_fsm_style_ref (style),
1027 .ops = &xrr_render_ops,
1029 .size = { [H] = style->size[H], [V] = style->size[V] },
1030 .line_widths = xr_line_widths,
1031 .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1032 .supports_margins = true,
1033 .rtl = render_direction_rtl (),
1037 if (is_table_item (item))
1040 fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1044 for (int i = 0; i < XR_N_FONTS; i++)
1046 PangoContext *context = pango_cairo_create_context (cr);
1047 pango_cairo_context_set_resolution (context, style->font_resolution);
1048 PangoLayout *layout = pango_layout_new (context);
1049 g_object_unref (context);
1051 pango_layout_set_font_description (layout, style->fonts[i]);
1053 pango_layout_set_text (layout, "0", 1);
1055 int char_size[TABLE_N_AXES];
1056 pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1057 for (int a = 0; a < TABLE_N_AXES; a++)
1059 int csa = pango_to_xr (char_size[a]);
1060 fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1063 g_object_unref (G_OBJECT (layout));
1070 xr_fsm_destroy (struct xr_fsm *fsm)
1074 xr_fsm_style_unref (fsm->style);
1075 output_item_unref (fsm->item);
1076 render_pager_destroy (fsm->p);
1077 assert (!fsm->cairo);
1082 /* This is primarily meant for use with screen rendering since the result is a
1083 fixed value for charts. */
1085 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1089 if (is_table_item (fsm->item))
1092 w = render_pager_get_size (fsm->p, H) / XR_POINT;
1093 h = render_pager_get_size (fsm->p, V) / XR_POINT;
1096 else if (is_chart_item (fsm->item))
1111 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1113 return (render_pager_has_next (fsm->p)
1114 ? render_pager_draw_next (fsm->p, space)
1119 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1121 const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1122 if (space < chart_height)
1126 xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1127 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1128 return chart_height;
1132 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1134 if (space >= fsm->rp.size[V])
1140 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1142 xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1146 mul_XR_POINT (int x)
1148 return (x >= INT_MAX / XR_POINT ? INT_MAX
1149 : x <= INT_MIN / XR_POINT ? INT_MIN
1154 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1155 int x, int y, int w, int h)
1157 if (is_table_item (fsm->item))
1160 render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1161 mul_XR_POINT (w), mul_XR_POINT (h));
1164 else if (is_chart_item (fsm->item))
1165 xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1166 else if (is_page_eject_item (fsm->item))
1168 /* Nothing to do. */
1175 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1177 if (xr_fsm_is_empty (fsm))
1182 int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1183 : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1184 : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1194 xr_fsm_is_empty (const struct xr_fsm *fsm)
1196 return (is_table_item (fsm->item)
1197 ? !render_pager_has_next (fsm->p)