pivot-table: Allow footnote marker style to change after creation.
[pspp] / src / output / cairo-fsm.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include "output/cairo-fsm.h"
20
21 #include <math.h>
22 #include <pango/pango-layout.h>
23 #include <pango/pango.h>
24 #include <pango/pangocairo.h>
25
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/image-item.h"
42 #include "output/message-item.h"
43 #include "output/page-eject-item.h"
44 #include "output/page-setup-item.h"
45 #include "output/pivot-output.h"
46 #include "output/pivot-table.h"
47 #include "output/render.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
50
51 #include "gl/c-ctype.h"
52 #include "gl/c-strcase.h"
53 #include "gl/xalloc.h"
54
55 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
56 #define H TABLE_HORZ
57 #define V TABLE_VERT
58 \f
59 struct xr_fsm_style *
60 xr_fsm_style_ref (const struct xr_fsm_style *style_)
61 {
62   assert (style_->ref_cnt > 0);
63
64   struct xr_fsm_style *style = CONST_CAST (struct xr_fsm_style *, style_);
65   style->ref_cnt++;
66   return style;
67 }
68
69 struct xr_fsm_style *
70 xr_fsm_style_unshare (struct xr_fsm_style *old)
71 {
72   assert (old->ref_cnt > 0);
73   if (old->ref_cnt == 1)
74     return old;
75
76   xr_fsm_style_unref (old);
77
78   struct xr_fsm_style *new = xmemdup (old, sizeof *old);
79   new->ref_cnt = 1;
80   if (old->font)
81     new->font = pango_font_description_copy (old->font);
82
83   return new;
84 }
85
86 void
87 xr_fsm_style_unref (struct xr_fsm_style *style)
88 {
89   if (style)
90     {
91       assert (style->ref_cnt > 0);
92       if (!--style->ref_cnt)
93         {
94           pango_font_description_free (style->font);
95           free (style);
96         }
97     }
98 }
99
100 bool
101 xr_fsm_style_equals (const struct xr_fsm_style *a,
102                      const struct xr_fsm_style *b)
103 {
104   if (a->size[H] != b->size[H]
105       || a->size[V] != b->size[V]
106       || a->min_break[H] != b->min_break[H]
107       || a->min_break[V] != b->min_break[V]
108       || !pango_font_description_equal (a->font, b->font)
109       || a->use_system_colors != b->use_system_colors
110       || a->object_spacing != b->object_spacing
111       || a->font_resolution != b->font_resolution)
112     return false;
113
114   return true;
115 }
116 \f
117 /* Renders a single output_item to an output device in one of two ways:
118
119    - 'print == true': Broken across multiple pages if necessary.
120
121    - 'print == false': In a single region that the user may scroll around if
122      needed.
123
124    Normally 'output_item' corresponds to a single rendering.  There is a
125    special case when 'print == true' and 'output_item' is a table_item with
126    multiple layers and 'item->pt->table_look->print_all_layers == true'.  In
127    that case, each layer is rendered separately from the FSM's internal point
128    of view; from the client's point of view, it is all one operation.
129 */
130 struct xr_fsm
131   {
132     struct xr_fsm_style *style;
133     struct output_item *item;
134     bool print;
135
136     /* Print mode only. */
137     bool done;
138
139     /* Table items only. */
140     size_t *layer_indexes;
141     struct render_params rp;
142     struct render_pager *p;
143     cairo_t *cairo;             /* XXX should this be here?! */
144   };
145
146 /* The unit used for internal measurements is inch/(72 * XR_POINT).
147    (Thus, XR_POINT units represent one point.) */
148 #define XR_POINT PANGO_SCALE
149
150 /* Conversions to and from points. */
151 static double
152 xr_to_pt (int x)
153 {
154   return x / (double) XR_POINT;
155 }
156
157 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
158 static int
159 px_to_xr (int x)
160 {
161   return x * (PANGO_SCALE * 72 / 96);
162 }
163
164 static int
165 pango_to_xr (int pango)
166 {
167   return (XR_POINT != PANGO_SCALE
168           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
169           : pango);
170 }
171
172 static int
173 xr_to_pango (int xr)
174 {
175   return (XR_POINT != PANGO_SCALE
176           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
177           : xr);
178 }
179
180 /* Dimensions for drawing lines in tables. */
181 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
182 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
183
184 static void
185 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
186                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
187                 int *width, int *height, int *brk);
188
189 static void
190 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
191 {
192   cairo_set_source_rgba (cairo,
193                          color->r / 255., color->g / 255., color->b / 255.,
194                          color->alpha / 255.);
195 }
196
197 static void
198 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
199               const struct cell_color *color)
200 {
201   cairo_new_path (xr->cairo);
202   cairo_set_line_width (
203     xr->cairo,
204     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
205               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
206               : XR_LINE_WIDTH));
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 (y1));
209
210   if (!xr->style->use_system_colors)
211     xr_set_source_rgba (xr->cairo, color);
212   if (style == RENDER_LINE_DASHED)
213     cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
214   cairo_stroke (xr->cairo);
215   if (style == RENDER_LINE_DASHED)
216     cairo_set_dash (xr->cairo, NULL, 0, 0);
217 }
218
219 static void UNUSED
220 xr_draw_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
221 {
222   cairo_new_path (xr->cairo);
223   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
224   cairo_close_path (xr->cairo);
225   cairo_stroke (xr->cairo);
226   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
227   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0));
228   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
229   cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1));
230 }
231
232 static void
233 fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
234 {
235   cairo_new_path (xr->cairo);
236   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
237   cairo_rectangle (xr->cairo,
238                    xr_to_pt (x0), xr_to_pt (y0),
239                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
240   cairo_fill (xr->cairo);
241 }
242
243 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
244    shortening it to X0...X1 if SHORTEN is true.
245    Draws a horizontal line X1...X3 at Y if RIGHT says so,
246    shortening it to X2...X3 if SHORTEN is true. */
247 static void
248 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
249                    enum render_line_style left, enum render_line_style right,
250                    const struct cell_color *left_color,
251                    const struct cell_color *right_color,
252                    bool shorten)
253 {
254   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
255       && cell_color_equal (left_color, right_color))
256     xr_draw_line (xr, x0, y, x3, y, left, left_color);
257   else
258     {
259       if (left != RENDER_LINE_NONE)
260         xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
261       if (right != RENDER_LINE_NONE)
262         xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
263     }
264 }
265
266 /* Draws a vertical line Y0...Y2 at X if TOP says so,
267    shortening it to Y0...Y1 if SHORTEN is true.
268    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
269    shortening it to Y2...Y3 if SHORTEN is true. */
270 static void
271 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
272                    enum render_line_style top, enum render_line_style bottom,
273                    const struct cell_color *top_color,
274                    const struct cell_color *bottom_color,
275                    bool shorten)
276 {
277   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
278       && cell_color_equal (top_color, bottom_color))
279     xr_draw_line (xr, x, y0, x, y3, top, top_color);
280   else
281     {
282       if (top != RENDER_LINE_NONE)
283         xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
284       if (bottom != RENDER_LINE_NONE)
285         xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
286     }
287 }
288
289 static void
290 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
291                enum render_line_style styles[TABLE_N_AXES][2],
292                struct cell_color colors[TABLE_N_AXES][2])
293 {
294   const int x0 = bb[H][0];
295   const int y0 = bb[V][0];
296   const int x3 = bb[H][1];
297   const int y3 = bb[V][1];
298   const int top = styles[H][0];
299   const int bottom = styles[H][1];
300
301   int start_side = render_direction_rtl();
302   int end_side = !start_side;
303   const int start_of_line = styles[V][start_side];
304   const int end_of_line   = styles[V][end_side];
305   const struct cell_color *top_color = &colors[H][0];
306   const struct cell_color *bottom_color = &colors[H][1];
307   const struct cell_color *start_color = &colors[V][start_side];
308   const struct cell_color *end_color = &colors[V][end_side];
309
310   /* The algorithm here is somewhat subtle, to allow it to handle
311      all the kinds of intersections that we need.
312
313      Three additional ordinates are assigned along the x axis.  The
314      first is xc, midway between x0 and x3.  The others are x1 and
315      x2; for a single vertical line these are equal to xc, and for
316      a double vertical line they are the ordinates of the left and
317      right half of the double line.
318
319      yc, y1, and y2 are assigned similarly along the y axis.
320
321      The following diagram shows the coordinate system and output
322      for double top and bottom lines, single left line, and no
323      right line:
324
325                  x0       x1 xc  x2      x3
326                y0 ________________________
327                   |        #     #       |
328                   |        #     #       |
329                   |        #     #       |
330                   |        #     #       |
331                   |        #     #       |
332      y1 = y2 = yc |#########     #       |
333                   |        #     #       |
334                   |        #     #       |
335                   |        #     #       |
336                   |        #     #       |
337                y3 |________#_____#_______|
338   */
339   struct xr_fsm *xr = xr_;
340
341   /* Offset from center of each line in a pair of double lines. */
342   int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
343
344   /* Are the lines along each axis single or double?
345      (It doesn't make sense to have different kinds of line on the
346      same axis, so we don't try to gracefully handle that case.) */
347   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
348   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
349
350   /* When horizontal lines are doubled,
351      the left-side line along y1 normally runs from x0 to x2,
352      and the right-side line along y1 from x3 to x1.
353      If the top-side line is also doubled, we shorten the y1 lines,
354      so that the left-side line runs only to x1,
355      and the right-side line only to x2.
356      Otherwise, the horizontal line at y = y1 below would cut off
357      the intersection, which looks ugly:
358                x0       x1     x2      x3
359              y0 ________________________
360                 |        #     #       |
361                 |        #     #       |
362                 |        #     #       |
363                 |        #     #       |
364              y1 |#########     ########|
365                 |                      |
366                 |                      |
367              y2 |######################|
368                 |                      |
369                 |                      |
370              y3 |______________________|
371      It is more of a judgment call when the horizontal line is
372      single.  We actually choose to cut off the line anyhow, as
373      shown in the first diagram above.
374   */
375   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
376   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
377   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
378   int horz_line_ofs = double_vert ? double_line_ofs : 0;
379   int xc = (x0 + x3) / 2;
380   int x1 = xc - horz_line_ofs;
381   int x2 = xc + horz_line_ofs;
382
383   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
384   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
385   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
386   int vert_line_ofs = double_horz ? double_line_ofs : 0;
387   int yc = (y0 + y3) / 2;
388   int y1 = yc - vert_line_ofs;
389   int y2 = yc + vert_line_ofs;
390
391   if (!double_horz)
392     xr_draw_horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
393                        start_color, end_color, shorten_yc_line);
394   else
395     {
396       xr_draw_horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
397                          start_color, end_color, shorten_y1_lines);
398       xr_draw_horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
399                          start_color, end_color, shorten_y2_lines);
400     }
401
402   if (!double_vert)
403     xr_draw_vert_line (xr, y0, y1, y2, y3, xc, top, bottom,
404                        top_color, bottom_color, shorten_xc_line);
405   else
406     {
407       xr_draw_vert_line (xr, y0, y1, y2, y3, x1, top, bottom,
408                          top_color, bottom_color, shorten_x1_lines);
409       xr_draw_vert_line (xr, y0, y1, y2, y3, x2, top, bottom,
410                          top_color, bottom_color, shorten_x2_lines);
411     }
412 }
413
414 static void
415 xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
416                         int *min_width, int *max_width)
417 {
418   struct xr_fsm *xr = xr_;
419   int bb[TABLE_N_AXES][2];
420   int clip[TABLE_N_AXES][2];
421   int h;
422
423   bb[H][0] = 0;
424   bb[H][1] = INT_MAX;
425   bb[V][0] = 0;
426   bb[V][1] = INT_MAX;
427   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
428   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
429
430   bb[H][1] = 1;
431   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
432
433   const int (*margin)[2] = cell->cell_style->margin;
434   if (*min_width > 0)
435     *min_width += px_to_xr (margin[H][0] + margin[H][1]);
436   if (*max_width > 0)
437     *max_width += px_to_xr (margin[H][0] + margin[H][1]);
438 }
439
440 static int
441 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
442 {
443   struct xr_fsm *xr = xr_;
444
445   const int (*margin)[2] = cell->cell_style->margin;
446
447   int bb[TABLE_N_AXES][2] = {
448     [H][0] = 0,
449     [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
450     [V][0] = 0,
451     [V][1] = INT_MAX,
452   };
453
454   int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
455
456   int w, h;
457   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
458   h += px_to_xr (margin[V][0] + margin[V][1]);
459   return h;
460 }
461
462 static void xr_clip (struct xr_fsm *, int clip[TABLE_N_AXES][2]);
463
464 static void
465 xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
466                int bb[TABLE_N_AXES][2], int valign_offset,
467                int spill[TABLE_N_AXES][2],
468                int clip[TABLE_N_AXES][2])
469 {
470   struct xr_fsm *xr = xr_;
471   int w, h, brk;
472
473   const struct cell_color *bg = &cell->font_style->bg[color_idx];
474   if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
475     {
476       cairo_save (xr->cairo);
477       int bg_clip[TABLE_N_AXES][2];
478       for (int axis = 0; axis < TABLE_N_AXES; axis++)
479         {
480           bg_clip[axis][0] = clip[axis][0];
481           if (bb[axis][0] == clip[axis][0])
482             bg_clip[axis][0] -= spill[axis][0];
483
484           bg_clip[axis][1] = clip[axis][1];
485           if (bb[axis][1] == clip[axis][1])
486             bg_clip[axis][1] += spill[axis][1];
487         }
488       xr_clip (xr, bg_clip);
489       xr_set_source_rgba (xr->cairo, bg);
490       fill_rectangle (xr,
491                       bb[H][0] - spill[H][0],
492                       bb[V][0] - spill[V][0],
493                       bb[H][1] + spill[H][1],
494                       bb[V][1] + spill[V][1]);
495       cairo_restore (xr->cairo);
496     }
497   cairo_save (xr->cairo);
498   if (!xr->style->use_system_colors)
499     xr_set_source_rgba (xr->cairo, &cell->font_style->fg[color_idx]);
500
501   bb[V][0] += valign_offset;
502
503   for (int axis = 0; axis < TABLE_N_AXES; axis++)
504     {
505       bb[axis][0] += px_to_xr (cell->cell_style->margin[axis][0]);
506       bb[axis][1] -= px_to_xr (cell->cell_style->margin[axis][1]);
507     }
508   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
509     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
510   cairo_restore (xr->cairo);
511 }
512
513 static int
514 xrr_adjust_break (void *xr_, const struct table_cell *cell,
515                   int width, int height)
516 {
517   struct xr_fsm *xr = xr_;
518
519   if (xrr_measure_cell_height (xr_, cell, width) < height)
520     return -1;
521
522   const int (*margin)[2] = cell->cell_style->margin;
523
524   int bb[TABLE_N_AXES][2] = {
525     [H][0] = 0,
526     [V][0] = 0,
527     [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
528     [V][1] = height - px_to_xr (margin[V][0] + margin[V][1]),
529   };
530   if (bb[H][1] <= 0)
531     return 0;
532
533   int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
534
535   int w, h, brk;
536   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
537   return brk;
538 }
539
540 static void
541 xrr_scale (void *xr_, double scale)
542 {
543   struct xr_fsm *xr = xr_;
544   cairo_scale (xr->cairo, scale, scale);
545 }
546 \f
547 static void
548 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
549 {
550   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
551     {
552       double x0 = xr_to_pt (clip[H][0]);
553       double y0 = xr_to_pt (clip[V][0]);
554       double x1 = xr_to_pt (clip[H][1]);
555       double y1 = xr_to_pt (clip[V][1]);
556
557       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
558       cairo_clip (xr->cairo);
559     }
560 }
561
562 static void
563 add_attr (PangoAttrList *list, PangoAttribute *attr,
564           guint start_index, guint end_index)
565 {
566   attr->start_index = start_index;
567   attr->end_index = end_index;
568   pango_attr_list_insert (list, attr);
569 }
570
571 static void
572 markup_escape (struct string *out, bool markup, const char *in, size_t len)
573 {
574   if (!markup)
575     {
576       ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
577       return;
578     }
579
580   while (len-- > 0)
581     {
582       int c = *in++;
583       switch (c)
584         {
585         case 0:
586           return;
587         case '&':
588           ds_put_cstr (out, "&amp;");
589           break;
590         case '<':
591           ds_put_cstr (out, "&lt;");
592           break;
593         case '>':
594           ds_put_cstr (out, "&gt;");
595           break;
596         default:
597           ds_put_byte (out, c);
598           break;
599         }
600     }
601 }
602
603 static int
604 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
605 {
606   int size[TABLE_N_AXES];
607   pango_layout_get_size (layout, &size[H], &size[V]);
608   return size[axis];
609 }
610
611 static PangoFontDescription *
612 parse_font (const char *font, int default_size, bool bold, bool italic)
613 {
614   if (!c_strcasecmp (font, "Monospaced"))
615     font = "Monospace";
616
617   PangoFontDescription *desc = pango_font_description_from_string (font);
618   if (desc == NULL)
619     return NULL;
620
621   /* If the font description didn't include an explicit font size, then set it
622      to DEFAULT_SIZE, which is in inch/72000 units. */
623   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
624     pango_font_description_set_size (desc,
625                                      (default_size / 1000.0) * PANGO_SCALE);
626
627   pango_font_description_set_weight (desc, (bold
628                                             ? PANGO_WEIGHT_BOLD
629                                             : PANGO_WEIGHT_NORMAL));
630   pango_font_description_set_style (desc, (italic
631                                            ? PANGO_STYLE_ITALIC
632                                            : PANGO_STYLE_NORMAL));
633
634   return desc;
635 }
636
637 static int
638 xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
639                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
640                      int *widthp, int *brk)
641 {
642   const struct pivot_table *pt = to_table_item (xr->item)->pt;
643   const struct font_style *font_style = cell->font_style;
644   const struct cell_style *cell_style = cell->cell_style;
645   unsigned int options = cell->options;
646
647   enum table_axis X = options & TAB_ROTATE ? V : H;
648   enum table_axis Y = !X;
649   int R = options & TAB_ROTATE ? 0 : 1;
650
651   PangoFontDescription *desc = NULL;
652   if (font_style->typeface)
653       desc = parse_font (
654         font_style->typeface,
655         font_style->size ? font_style->size * 1000 : 10000,
656         font_style->bold, font_style->italic);
657   if (!desc)
658     desc = xr->style->font;
659
660   assert (xr->cairo);
661   PangoContext *context = pango_cairo_create_context (xr->cairo);
662   pango_cairo_context_set_resolution (context, xr->style->font_resolution);
663   PangoLayout *layout = pango_layout_new (context);
664   g_object_unref (context);
665
666   pango_layout_set_font_description (layout, desc);
667
668   struct string body = DS_EMPTY_INITIALIZER;
669   bool numeric = pivot_value_format_body (cell->value, pt, &body);
670
671   enum table_halign halign = table_halign_interpret (
672     cell->cell_style->halign, numeric);
673
674   if (cell_style->halign == TABLE_HALIGN_DECIMAL
675       && !(cell->options & TAB_ROTATE))
676     {
677       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
678
679       const char *decimal = strrchr (ds_cstr (&body),
680                                      cell_style->decimal_char);
681       if (decimal)
682         {
683           pango_layout_set_text (layout, decimal, strlen (decimal));
684           pango_layout_set_width (layout, -1);
685           margin_adjustment += get_layout_dimension (layout, H);
686         }
687
688       if (margin_adjustment < 0)
689         bb[H][1] += margin_adjustment;
690     }
691
692   PangoAttrList *attrs = NULL;
693
694   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
695      Pango's implementation of it): it will break after a period or a comma
696      that precedes a digit, e.g. in ".000" it will break after the period.
697      This code looks for such a situation and inserts a U+2060 WORD JOINER
698      to prevent the break.
699
700      This isn't necessary when the decimal point is between two digits
701      (e.g. "0.000" won't be broken) or when the display width is not limited so
702      that word wrapping won't happen.
703
704      It isn't necessary to look for more than one period or comma, as would
705      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
706      are present then there will always be a digit on both sides of every
707      period and comma. */
708   bool markup = cell->font_style->markup;
709   if (markup)
710     {
711       PangoAttrList *new_attrs;
712       char *new_text;
713       if (pango_parse_markup (ds_cstr (&body), -1, 0,
714                               &new_attrs, &new_text, NULL, NULL))
715         {
716           attrs = new_attrs;
717           ds_destroy (&body);
718           body.ss = ss_cstr (new_text);
719           body.capacity = body.ss.length;
720         }
721       else
722         {
723           /* XXX should we report the error? */
724         }
725     }
726   else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
727     {
728       const char *text = ds_cstr (&body);
729       const char *decimal = text + strcspn (text, ".,");
730       if (decimal[0]
731           && c_isdigit (decimal[1])
732           && (decimal == text || !c_isdigit (decimal[-1])))
733         {
734           struct string tmp = DS_EMPTY_INITIALIZER;
735           ds_extend (&tmp, ds_length (&body) + 16);
736           markup_escape (&tmp, markup, text, decimal - text + 1);
737           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
738           markup_escape (&tmp, markup, decimal + 1, -1);
739           ds_swap (&tmp, &body);
740           ds_destroy (&tmp);
741         }
742     }
743
744   if (font_style->underline)
745     {
746       if (!attrs)
747         attrs = pango_attr_list_new ();
748       pango_attr_list_insert (attrs, pango_attr_underline_new (
749                                 PANGO_UNDERLINE_SINGLE));
750     }
751
752   const struct pivot_value *value = cell->value;
753   if (value->n_footnotes || value->n_subscripts)
754     {
755       size_t subscript_ofs = ds_length (&body);
756       for (size_t i = 0; i < value->n_subscripts; i++)
757         {
758           if (i)
759             ds_put_byte (&body, ',');
760           ds_put_cstr (&body, value->subscripts[i]);
761         }
762
763       size_t footnote_ofs = ds_length (&body);
764       for (size_t i = 0; i < value->n_footnotes; i++)
765         {
766           if (i)
767             ds_put_byte (&body, ',');
768
769           size_t idx = value->footnote_indexes[i];
770           pivot_footnote_format_marker (pt->footnotes[idx], pt, &body);
771         }
772
773       /* Allow footnote markers to occupy the right margin.  That way, numbers
774          in the column are still aligned. */
775       if (value->n_footnotes && halign == TABLE_HALIGN_RIGHT)
776         {
777           /* Measure the width of the footnote marker, so we know how much we
778              need to make room for. */
779           pango_layout_set_text (layout, ds_cstr (&body) + footnote_ofs,
780                                  ds_length (&body) - footnote_ofs);
781
782           PangoAttrList *fn_attrs = pango_attr_list_new ();
783           pango_attr_list_insert (
784             fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
785           pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
786           pango_layout_set_attributes (layout, fn_attrs);
787           pango_attr_list_unref (fn_attrs);
788           int footnote_width = get_layout_dimension (layout, X);
789
790           /* Bound the adjustment by the width of the right margin. */
791           int right_margin = px_to_xr (cell_style->margin[X][R]);
792           int footnote_adjustment = MIN (footnote_width, right_margin);
793
794           /* Adjust the bounding box. */
795           if (options & TAB_ROTATE)
796             footnote_adjustment = -footnote_adjustment;
797           bb[X][R] += footnote_adjustment;
798
799           /* Clean up. */
800           pango_layout_set_attributes (layout, NULL);
801         }
802
803       /* Set attributes. */
804       if (!attrs)
805         attrs = pango_attr_list_new ();
806       add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
807                 PANGO_ATTR_INDEX_TO_TEXT_END);
808       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
809                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
810       if (value->n_subscripts)
811         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
812                   footnote_ofs - subscript_ofs);
813       if (value->n_footnotes)
814         {
815           bool superscript = pt->look->footnote_marker_superscripts;
816           add_attr (attrs, pango_attr_rise_new (superscript ? 3000 : -3000),
817                     footnote_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
818         }
819     }
820
821   /* Set the attributes, if any. */
822   if (attrs)
823     {
824       pango_layout_set_attributes (layout, attrs);
825       pango_attr_list_unref (attrs);
826     }
827
828   /* Set the text. */
829   pango_layout_set_text (layout, ds_cstr (&body), ds_length (&body));
830
831   pango_layout_set_alignment (layout,
832                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
833                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
834                                : PANGO_ALIGN_CENTER));
835   pango_layout_set_width (
836     layout,
837     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
838   pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
839
840   int size[TABLE_N_AXES];
841   pango_layout_get_size (layout, &size[H], &size[V]);
842
843   if (clip[H][0] != clip[H][1])
844     {
845       cairo_save (xr->cairo);
846       if (!(options & TAB_ROTATE))
847         xr_clip (xr, clip);
848       if (options & TAB_ROTATE)
849         {
850           int extra = bb[H][1] - bb[H][0] - size[V];
851           int halign_offset = extra > 0 ? extra / 2 : 0;
852           cairo_translate (xr->cairo,
853                            xr_to_pt (bb[H][0] + halign_offset),
854                            xr_to_pt (bb[V][1]));
855           cairo_rotate (xr->cairo, -M_PI_2);
856         }
857       else
858         cairo_translate (xr->cairo,
859                          xr_to_pt (bb[H][0]),
860                          xr_to_pt (bb[V][0]));
861       pango_cairo_show_layout (xr->cairo, layout);
862
863       /* If enabled, this draws a blue rectangle around the extents of each
864          line of text, which can be rather useful for debugging layout
865          issues. */
866       if (0)
867         {
868           PangoLayoutIter *iter;
869           iter = pango_layout_get_iter (layout);
870           do
871             {
872               PangoRectangle extents;
873
874               pango_layout_iter_get_line_extents (iter, &extents, NULL);
875               cairo_save (xr->cairo);
876               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
877               xr_draw_rectangle (
878                 xr,
879                 pango_to_xr (extents.x),
880                 pango_to_xr (extents.y),
881                 pango_to_xr (extents.x + extents.width),
882                 pango_to_xr (extents.y + extents.height));
883               cairo_restore (xr->cairo);
884             }
885           while (pango_layout_iter_next_line (iter));
886           pango_layout_iter_free (iter);
887         }
888
889       cairo_restore (xr->cairo);
890     }
891
892   int w = pango_to_xr (size[X]);
893   int h = pango_to_xr (size[Y]);
894   if (w > *widthp)
895     *widthp = w;
896   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
897     {
898       PangoLayoutIter *iter;
899       int best = 0;
900
901       /* Choose a breakpoint between lines instead of in the middle of one. */
902       iter = pango_layout_get_iter (layout);
903       do
904         {
905           PangoRectangle extents;
906           int y0, y1;
907           int bottom;
908
909           pango_layout_iter_get_line_extents (iter, NULL, &extents);
910           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
911           extents.x = pango_to_xr (extents.x);
912           extents.y = pango_to_xr (y0);
913           extents.width = pango_to_xr (extents.width);
914           extents.height = pango_to_xr (y1 - y0);
915           bottom = bb[V][0] + extents.y + extents.height;
916           if (bottom < bb[V][1])
917             {
918               if (brk && clip[H][0] != clip[H][1])
919                 best = bottom;
920               if (brk)
921                 *brk = bottom;
922             }
923           else
924             break;
925         }
926       while (pango_layout_iter_next_line (iter));
927       pango_layout_iter_free (iter);
928
929       /* If enabled, draws a green line across the chosen breakpoint, which can
930          be useful for debugging issues with breaking.  */
931       if (0)
932         {
933           if (best)
934             xr_draw_line (xr, 0, best,
935                           xr->style->size[H], best,
936                           RENDER_LINE_SINGLE,
937                           &(struct cell_color) CELL_COLOR (0, 255, 0));
938         }
939     }
940
941   pango_layout_set_attributes (layout, NULL);
942
943   if (desc != xr->style->font)
944     pango_font_description_free (desc);
945   g_object_unref (G_OBJECT (layout));
946   ds_destroy (&body);
947
948   return h;
949 }
950
951 static void
952 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
953                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
954                 int *width, int *height, int *brk)
955 {
956   *width = 0;
957   *height = 0;
958
959   /* If enabled, draws a blue rectangle around the cell extents, which can be
960      useful for debugging layout. */
961   if (0)
962     {
963       if (clip[H][0] != clip[H][1])
964         {
965           cairo_save (xr->cairo);
966           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
967           xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
968           cairo_restore (xr->cairo);
969         }
970     }
971
972   if (brk)
973     *brk = bb[V][0];
974   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
975 }
976
977 #define CHART_WIDTH 500
978 #define CHART_HEIGHT 375
979
980 static struct xr_fsm *
981 xr_fsm_create (const struct output_item *item_,
982                const struct xr_fsm_style *style, cairo_t *cr,
983                bool print)
984 {
985   if (is_page_setup_item (item_)
986       || is_group_open_item (item_)
987       || is_group_close_item (item_))
988     return NULL;
989
990   struct output_item *item;
991   if (is_table_item (item_)
992       || is_chart_item (item_)
993       || is_image_item (item_)
994       || is_page_eject_item (item_))
995     item = output_item_ref (item_);
996   else if (is_message_item (item_))
997     item = table_item_super (
998       text_item_to_table_item (
999         message_item_to_text_item (
1000           to_message_item (
1001             output_item_ref (item_)))));
1002   else if (is_text_item (item_))
1003     {
1004       if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1005         return NULL;
1006
1007       item = table_item_super (
1008         text_item_to_table_item (
1009           to_text_item (
1010             output_item_ref (item_))));
1011     }
1012   else if (is_group_open_item (item_))
1013     {
1014       item = table_item_super (
1015         text_item_to_table_item (
1016           text_item_create (TEXT_ITEM_TITLE,
1017                             to_group_open_item (item_)->command_name,
1018                             NULL)));
1019     }
1020   else
1021     NOT_REACHED ();
1022   assert (is_table_item (item)
1023           || is_chart_item (item)
1024           || is_image_item (item)
1025           || is_page_eject_item (item));
1026
1027   size_t *layer_indexes = NULL;
1028   if (is_table_item (item))
1029     {
1030       const struct table_item *table_item = to_table_item (item);
1031       layer_indexes = pivot_output_next_layer (table_item->pt, NULL, print);
1032       if (!layer_indexes)
1033         return NULL;
1034     }
1035
1036   static const struct render_ops xrr_render_ops = {
1037     .measure_cell_width = xrr_measure_cell_width,
1038     .measure_cell_height = xrr_measure_cell_height,
1039     .adjust_break = xrr_adjust_break,
1040     .draw_line = xrr_draw_line,
1041     .draw_cell = xrr_draw_cell,
1042     .scale = xrr_scale,
1043   };
1044
1045   enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1046   static const int xr_line_widths[RENDER_N_LINES] =
1047     {
1048       [RENDER_LINE_NONE] = 0,
1049       [RENDER_LINE_SINGLE] = LW,
1050       [RENDER_LINE_DASHED] = LW,
1051       [RENDER_LINE_THICK] = LW * 2,
1052       [RENDER_LINE_THIN] = LW / 2,
1053       [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1054     };
1055
1056   struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1057   *fsm = (struct xr_fsm) {
1058     .style = xr_fsm_style_ref (style),
1059     .item = item,
1060     .print = print,
1061     .layer_indexes = layer_indexes,
1062     .rp = {
1063       .ops = &xrr_render_ops,
1064       .aux = fsm,
1065       .size = { [H] = style->size[H], [V] = style->size[V] },
1066       .line_widths = xr_line_widths,
1067       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1068       .supports_margins = true,
1069       .rtl = render_direction_rtl (),
1070       .printing = print,
1071     }
1072   };
1073
1074   /* Get font size. */
1075   PangoContext *context = pango_cairo_create_context (cr);
1076   pango_cairo_context_set_resolution (context, style->font_resolution);
1077   PangoLayout *layout = pango_layout_new (context);
1078   g_object_unref (context);
1079
1080   pango_layout_set_font_description (layout, style->font);
1081
1082   pango_layout_set_text (layout, "0", 1);
1083
1084   int char_size[TABLE_N_AXES];
1085   pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1086   for (int a = 0; a < TABLE_N_AXES; a++)
1087     {
1088       int csa = pango_to_xr (char_size[a]);
1089       fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1090     }
1091
1092   g_object_unref (G_OBJECT (layout));
1093
1094   if (is_table_item (item))
1095     {
1096       struct table_item *table_item = to_table_item (item);
1097
1098       fsm->cairo = cr;
1099       fsm->p = render_pager_create (&fsm->rp, table_item, fsm->layer_indexes);
1100       fsm->cairo = NULL;
1101     }
1102
1103   return fsm;
1104 }
1105
1106 struct xr_fsm *
1107 xr_fsm_create_for_printing (const struct output_item *item,
1108                             const struct xr_fsm_style *style, cairo_t *cr)
1109 {
1110   return xr_fsm_create (item, style, cr, true);
1111 }
1112
1113 void
1114 xr_fsm_destroy (struct xr_fsm *fsm)
1115 {
1116   if (fsm)
1117     {
1118       xr_fsm_style_unref (fsm->style);
1119       output_item_unref (fsm->item);
1120       free (fsm->layer_indexes);
1121       render_pager_destroy (fsm->p);
1122       assert (!fsm->cairo);
1123       free (fsm);
1124     }
1125 }
1126 \f
1127 /* Scrolling API. */
1128
1129 struct xr_fsm *
1130 xr_fsm_create_for_scrolling (const struct output_item *item,
1131                              const struct xr_fsm_style *style, cairo_t *cr)
1132 {
1133   return xr_fsm_create (item, style, cr, false);
1134 }
1135
1136 void
1137 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1138 {
1139   assert (!fsm->print);
1140
1141   int w, h;
1142
1143   if (is_table_item (fsm->item))
1144     {
1145       fsm->cairo = cr;
1146       w = render_pager_get_size (fsm->p, H) / XR_POINT;
1147       h = render_pager_get_size (fsm->p, V) / XR_POINT;
1148       fsm->cairo = NULL;
1149     }
1150   else if (is_chart_item (fsm->item))
1151     {
1152       w = CHART_WIDTH;
1153       h = CHART_HEIGHT;
1154     }
1155   else if (is_image_item (fsm->item))
1156     {
1157       cairo_surface_t *image = to_image_item (fsm->item)->image;
1158       w = cairo_image_surface_get_width (image);
1159       h = cairo_image_surface_get_height (image);
1160     }
1161   else
1162     NOT_REACHED ();
1163
1164   if (wp)
1165     *wp = w;
1166   if (hp)
1167     *hp = h;
1168 }
1169
1170 void
1171 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1172 {
1173   assert (!fsm->print);
1174   xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1175 }
1176
1177 static int
1178 mul_XR_POINT (int x)
1179 {
1180   return (x >= INT_MAX / XR_POINT ? INT_MAX
1181           : x <= INT_MIN / XR_POINT ? INT_MIN
1182           : x * XR_POINT);
1183 }
1184
1185 static void
1186 draw_image (cairo_surface_t *image, cairo_t *cr)
1187 {
1188   cairo_save (cr);
1189   cairo_set_source_surface (cr, image, 0, 0);
1190   cairo_rectangle (cr, 0, 0, cairo_image_surface_get_width (image),
1191                    cairo_image_surface_get_height (image));
1192   cairo_clip (cr);
1193   cairo_paint (cr);
1194   cairo_restore (cr);
1195 }
1196
1197 void
1198 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1199                     int x, int y, int w, int h)
1200 {
1201   assert (!fsm->print);
1202   if (is_table_item (fsm->item))
1203     {
1204       fsm->cairo = cr;
1205       render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1206                                 mul_XR_POINT (w), mul_XR_POINT (h));
1207       fsm->cairo = NULL;
1208     }
1209   else if (is_image_item (fsm->item))
1210     draw_image (to_image_item (fsm->item)->image, cr);
1211   else if (is_chart_item (fsm->item))
1212     xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1213   else if (is_page_eject_item (fsm->item))
1214     {
1215       /* Nothing to do. */
1216     }
1217   else
1218     NOT_REACHED ();
1219 }
1220 \f
1221 /* Printing API. */
1222
1223 static int
1224 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1225 {
1226   struct table_item *table_item = to_table_item (fsm->item);
1227   int used = render_pager_draw_next (fsm->p, space);
1228   if (!render_pager_has_next (fsm->p))
1229     {
1230       render_pager_destroy (fsm->p);
1231
1232       fsm->layer_indexes = pivot_output_next_layer (table_item->pt,
1233                                                     fsm->layer_indexes, true);
1234       if (fsm->layer_indexes)
1235         {
1236           fsm->p = render_pager_create (&fsm->rp, table_item,
1237                                         fsm->layer_indexes);
1238           if (table_item->pt->look->paginate_layers)
1239             used = space;
1240           else
1241             used += fsm->style->object_spacing;
1242         }
1243       else
1244         {
1245           fsm->p = NULL;
1246           fsm->done = true;
1247         }
1248     }
1249   return MIN (used, space);
1250 }
1251
1252 static int
1253 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1254 {
1255   const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1256   if (space < chart_height)
1257     return 0;
1258
1259   fsm->done = true;
1260   xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1261                  xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1262   return chart_height;
1263 }
1264
1265 static int
1266 xr_fsm_draw_image (struct xr_fsm *fsm, int space)
1267 {
1268   cairo_surface_t *image = to_image_item (fsm->item)->image;
1269   int width = cairo_image_surface_get_width (image) * XR_POINT;
1270   int height = cairo_image_surface_get_height (image) * XR_POINT;
1271   if (!width || !height)
1272     goto error;
1273
1274   if (height > fsm->rp.size[V])
1275     {
1276       double scale = fsm->rp.size[V] / (double) height;
1277       width *= scale;
1278       height *= scale;
1279       if (!width || !height)
1280         goto error;
1281
1282       cairo_scale (fsm->cairo, scale, scale);
1283     }
1284
1285   if (width > fsm->rp.size[H])
1286     {
1287       double scale = fsm->rp.size[H] / (double) width;
1288       width *= scale;
1289       height *= scale;
1290       if (!width || !height)
1291         goto error;
1292
1293       cairo_scale (fsm->cairo, scale, scale);
1294     }
1295
1296   if (space < height)
1297     return 0;
1298
1299   draw_image (image, fsm->cairo);
1300   fsm->done = true;
1301   return height;
1302
1303 error:
1304   fsm->done = true;
1305   return 0;
1306 }
1307
1308 static int
1309 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1310 {
1311   if (space >= fsm->rp.size[V])
1312     fsm->done = true;
1313   return 0;
1314 }
1315
1316 int
1317 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1318 {
1319   assert (fsm->print);
1320
1321   if (fsm->done || space <= 0)
1322     return 0;
1323
1324   cairo_save (cr);
1325   fsm->cairo = cr;
1326   int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1327               : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1328               : is_image_item (fsm->item) ? xr_fsm_draw_image (fsm, space)
1329               : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1330               : (abort (), 0));
1331   fsm->cairo = NULL;
1332   cairo_restore (cr);
1333
1334   return used;
1335 }
1336
1337 bool
1338 xr_fsm_is_empty (const struct xr_fsm *fsm)
1339 {
1340   assert (fsm->print);
1341
1342   return fsm->done;
1343 }