pivot-table: Implement hiding footnotes.
[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       size_t n_footnotes = 0;
765       for (size_t i = 0; i < value->n_footnotes; i++)
766         {
767           const struct pivot_footnote *f
768             = pt->footnotes[value->footnote_indexes[i]];
769           if (f->show)
770             {
771               if (n_footnotes++)
772                 ds_put_byte (&body, ',');
773               pivot_footnote_format_marker (f, pt, &body);
774             }
775         }
776
777       /* Allow footnote markers to occupy the right margin.  That way, numbers
778          in the column are still aligned. */
779       if (value->n_footnotes && halign == TABLE_HALIGN_RIGHT)
780         {
781           /* Measure the width of the footnote marker, so we know how much we
782              need to make room for. */
783           pango_layout_set_text (layout, ds_cstr (&body) + footnote_ofs,
784                                  ds_length (&body) - footnote_ofs);
785
786           PangoAttrList *fn_attrs = pango_attr_list_new ();
787           pango_attr_list_insert (
788             fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
789           pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
790           pango_layout_set_attributes (layout, fn_attrs);
791           pango_attr_list_unref (fn_attrs);
792           int footnote_width = get_layout_dimension (layout, X);
793
794           /* Bound the adjustment by the width of the right margin. */
795           int right_margin = px_to_xr (cell_style->margin[X][R]);
796           int footnote_adjustment = MIN (footnote_width, right_margin);
797
798           /* Adjust the bounding box. */
799           if (options & TAB_ROTATE)
800             footnote_adjustment = -footnote_adjustment;
801           bb[X][R] += footnote_adjustment;
802
803           /* Clean up. */
804           pango_layout_set_attributes (layout, NULL);
805         }
806
807       /* Set attributes. */
808       if (!attrs)
809         attrs = pango_attr_list_new ();
810       add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
811                 PANGO_ATTR_INDEX_TO_TEXT_END);
812       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
813                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
814       if (value->n_subscripts)
815         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
816                   footnote_ofs - subscript_ofs);
817       if (value->n_footnotes)
818         {
819           bool superscript = pt->look->footnote_marker_superscripts;
820           add_attr (attrs, pango_attr_rise_new (superscript ? 3000 : -3000),
821                     footnote_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
822         }
823     }
824
825   /* Set the attributes, if any. */
826   if (attrs)
827     {
828       pango_layout_set_attributes (layout, attrs);
829       pango_attr_list_unref (attrs);
830     }
831
832   /* Set the text. */
833   pango_layout_set_text (layout, ds_cstr (&body), ds_length (&body));
834
835   pango_layout_set_alignment (layout,
836                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
837                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
838                                : PANGO_ALIGN_CENTER));
839   pango_layout_set_width (
840     layout,
841     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
842   pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
843
844   int size[TABLE_N_AXES];
845   pango_layout_get_size (layout, &size[H], &size[V]);
846
847   if (clip[H][0] != clip[H][1])
848     {
849       cairo_save (xr->cairo);
850       if (!(options & TAB_ROTATE))
851         xr_clip (xr, clip);
852       if (options & TAB_ROTATE)
853         {
854           int extra = bb[H][1] - bb[H][0] - size[V];
855           int halign_offset = extra > 0 ? extra / 2 : 0;
856           cairo_translate (xr->cairo,
857                            xr_to_pt (bb[H][0] + halign_offset),
858                            xr_to_pt (bb[V][1]));
859           cairo_rotate (xr->cairo, -M_PI_2);
860         }
861       else
862         cairo_translate (xr->cairo,
863                          xr_to_pt (bb[H][0]),
864                          xr_to_pt (bb[V][0]));
865       pango_cairo_show_layout (xr->cairo, layout);
866
867       /* If enabled, this draws a blue rectangle around the extents of each
868          line of text, which can be rather useful for debugging layout
869          issues. */
870       if (0)
871         {
872           PangoLayoutIter *iter;
873           iter = pango_layout_get_iter (layout);
874           do
875             {
876               PangoRectangle extents;
877
878               pango_layout_iter_get_line_extents (iter, &extents, NULL);
879               cairo_save (xr->cairo);
880               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
881               xr_draw_rectangle (
882                 xr,
883                 pango_to_xr (extents.x),
884                 pango_to_xr (extents.y),
885                 pango_to_xr (extents.x + extents.width),
886                 pango_to_xr (extents.y + extents.height));
887               cairo_restore (xr->cairo);
888             }
889           while (pango_layout_iter_next_line (iter));
890           pango_layout_iter_free (iter);
891         }
892
893       cairo_restore (xr->cairo);
894     }
895
896   int w = pango_to_xr (size[X]);
897   int h = pango_to_xr (size[Y]);
898   if (w > *widthp)
899     *widthp = w;
900   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
901     {
902       PangoLayoutIter *iter;
903       int best = 0;
904
905       /* Choose a breakpoint between lines instead of in the middle of one. */
906       iter = pango_layout_get_iter (layout);
907       do
908         {
909           PangoRectangle extents;
910           int y0, y1;
911           int bottom;
912
913           pango_layout_iter_get_line_extents (iter, NULL, &extents);
914           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
915           extents.x = pango_to_xr (extents.x);
916           extents.y = pango_to_xr (y0);
917           extents.width = pango_to_xr (extents.width);
918           extents.height = pango_to_xr (y1 - y0);
919           bottom = bb[V][0] + extents.y + extents.height;
920           if (bottom < bb[V][1])
921             {
922               if (brk && clip[H][0] != clip[H][1])
923                 best = bottom;
924               if (brk)
925                 *brk = bottom;
926             }
927           else
928             break;
929         }
930       while (pango_layout_iter_next_line (iter));
931       pango_layout_iter_free (iter);
932
933       /* If enabled, draws a green line across the chosen breakpoint, which can
934          be useful for debugging issues with breaking.  */
935       if (0)
936         {
937           if (best)
938             xr_draw_line (xr, 0, best,
939                           xr->style->size[H], best,
940                           RENDER_LINE_SINGLE,
941                           &(struct cell_color) CELL_COLOR (0, 255, 0));
942         }
943     }
944
945   pango_layout_set_attributes (layout, NULL);
946
947   if (desc != xr->style->font)
948     pango_font_description_free (desc);
949   g_object_unref (G_OBJECT (layout));
950   ds_destroy (&body);
951
952   return h;
953 }
954
955 static void
956 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
957                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
958                 int *width, int *height, int *brk)
959 {
960   *width = 0;
961   *height = 0;
962
963   /* If enabled, draws a blue rectangle around the cell extents, which can be
964      useful for debugging layout. */
965   if (0)
966     {
967       if (clip[H][0] != clip[H][1])
968         {
969           cairo_save (xr->cairo);
970           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
971           xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
972           cairo_restore (xr->cairo);
973         }
974     }
975
976   if (brk)
977     *brk = bb[V][0];
978   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
979 }
980
981 #define CHART_WIDTH 500
982 #define CHART_HEIGHT 375
983
984 static struct xr_fsm *
985 xr_fsm_create (const struct output_item *item_,
986                const struct xr_fsm_style *style, cairo_t *cr,
987                bool print)
988 {
989   if (is_page_setup_item (item_)
990       || is_group_open_item (item_)
991       || is_group_close_item (item_))
992     return NULL;
993
994   struct output_item *item;
995   if (is_table_item (item_)
996       || is_chart_item (item_)
997       || is_image_item (item_)
998       || is_page_eject_item (item_))
999     item = output_item_ref (item_);
1000   else if (is_message_item (item_))
1001     item = table_item_super (
1002       text_item_to_table_item (
1003         message_item_to_text_item (
1004           to_message_item (
1005             output_item_ref (item_)))));
1006   else if (is_text_item (item_))
1007     {
1008       if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1009         return NULL;
1010
1011       item = table_item_super (
1012         text_item_to_table_item (
1013           to_text_item (
1014             output_item_ref (item_))));
1015     }
1016   else if (is_group_open_item (item_))
1017     {
1018       item = table_item_super (
1019         text_item_to_table_item (
1020           text_item_create (TEXT_ITEM_TITLE,
1021                             to_group_open_item (item_)->command_name,
1022                             NULL)));
1023     }
1024   else
1025     NOT_REACHED ();
1026   assert (is_table_item (item)
1027           || is_chart_item (item)
1028           || is_image_item (item)
1029           || is_page_eject_item (item));
1030
1031   size_t *layer_indexes = NULL;
1032   if (is_table_item (item))
1033     {
1034       const struct table_item *table_item = to_table_item (item);
1035       layer_indexes = pivot_output_next_layer (table_item->pt, NULL, print);
1036       if (!layer_indexes)
1037         return NULL;
1038     }
1039
1040   static const struct render_ops xrr_render_ops = {
1041     .measure_cell_width = xrr_measure_cell_width,
1042     .measure_cell_height = xrr_measure_cell_height,
1043     .adjust_break = xrr_adjust_break,
1044     .draw_line = xrr_draw_line,
1045     .draw_cell = xrr_draw_cell,
1046     .scale = xrr_scale,
1047   };
1048
1049   enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1050   static const int xr_line_widths[RENDER_N_LINES] =
1051     {
1052       [RENDER_LINE_NONE] = 0,
1053       [RENDER_LINE_SINGLE] = LW,
1054       [RENDER_LINE_DASHED] = LW,
1055       [RENDER_LINE_THICK] = LW * 2,
1056       [RENDER_LINE_THIN] = LW / 2,
1057       [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1058     };
1059
1060   struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1061   *fsm = (struct xr_fsm) {
1062     .style = xr_fsm_style_ref (style),
1063     .item = item,
1064     .print = print,
1065     .layer_indexes = layer_indexes,
1066     .rp = {
1067       .ops = &xrr_render_ops,
1068       .aux = fsm,
1069       .size = { [H] = style->size[H], [V] = style->size[V] },
1070       .line_widths = xr_line_widths,
1071       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1072       .supports_margins = true,
1073       .rtl = render_direction_rtl (),
1074       .printing = print,
1075     }
1076   };
1077
1078   /* Get font size. */
1079   PangoContext *context = pango_cairo_create_context (cr);
1080   pango_cairo_context_set_resolution (context, style->font_resolution);
1081   PangoLayout *layout = pango_layout_new (context);
1082   g_object_unref (context);
1083
1084   pango_layout_set_font_description (layout, style->font);
1085
1086   pango_layout_set_text (layout, "0", 1);
1087
1088   int char_size[TABLE_N_AXES];
1089   pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1090   for (int a = 0; a < TABLE_N_AXES; a++)
1091     {
1092       int csa = pango_to_xr (char_size[a]);
1093       fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1094     }
1095
1096   g_object_unref (G_OBJECT (layout));
1097
1098   if (is_table_item (item))
1099     {
1100       struct table_item *table_item = to_table_item (item);
1101
1102       fsm->cairo = cr;
1103       fsm->p = render_pager_create (&fsm->rp, table_item, fsm->layer_indexes);
1104       fsm->cairo = NULL;
1105     }
1106
1107   return fsm;
1108 }
1109
1110 struct xr_fsm *
1111 xr_fsm_create_for_printing (const struct output_item *item,
1112                             const struct xr_fsm_style *style, cairo_t *cr)
1113 {
1114   return xr_fsm_create (item, style, cr, true);
1115 }
1116
1117 void
1118 xr_fsm_destroy (struct xr_fsm *fsm)
1119 {
1120   if (fsm)
1121     {
1122       xr_fsm_style_unref (fsm->style);
1123       output_item_unref (fsm->item);
1124       free (fsm->layer_indexes);
1125       render_pager_destroy (fsm->p);
1126       assert (!fsm->cairo);
1127       free (fsm);
1128     }
1129 }
1130 \f
1131 /* Scrolling API. */
1132
1133 struct xr_fsm *
1134 xr_fsm_create_for_scrolling (const struct output_item *item,
1135                              const struct xr_fsm_style *style, cairo_t *cr)
1136 {
1137   return xr_fsm_create (item, style, cr, false);
1138 }
1139
1140 void
1141 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1142 {
1143   assert (!fsm->print);
1144
1145   int w, h;
1146
1147   if (is_table_item (fsm->item))
1148     {
1149       fsm->cairo = cr;
1150       w = render_pager_get_size (fsm->p, H) / XR_POINT;
1151       h = render_pager_get_size (fsm->p, V) / XR_POINT;
1152       fsm->cairo = NULL;
1153     }
1154   else if (is_chart_item (fsm->item))
1155     {
1156       w = CHART_WIDTH;
1157       h = CHART_HEIGHT;
1158     }
1159   else if (is_image_item (fsm->item))
1160     {
1161       cairo_surface_t *image = to_image_item (fsm->item)->image;
1162       w = cairo_image_surface_get_width (image);
1163       h = cairo_image_surface_get_height (image);
1164     }
1165   else
1166     NOT_REACHED ();
1167
1168   if (wp)
1169     *wp = w;
1170   if (hp)
1171     *hp = h;
1172 }
1173
1174 void
1175 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1176 {
1177   assert (!fsm->print);
1178   xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1179 }
1180
1181 static int
1182 mul_XR_POINT (int x)
1183 {
1184   return (x >= INT_MAX / XR_POINT ? INT_MAX
1185           : x <= INT_MIN / XR_POINT ? INT_MIN
1186           : x * XR_POINT);
1187 }
1188
1189 static void
1190 draw_image (cairo_surface_t *image, cairo_t *cr)
1191 {
1192   cairo_save (cr);
1193   cairo_set_source_surface (cr, image, 0, 0);
1194   cairo_rectangle (cr, 0, 0, cairo_image_surface_get_width (image),
1195                    cairo_image_surface_get_height (image));
1196   cairo_clip (cr);
1197   cairo_paint (cr);
1198   cairo_restore (cr);
1199 }
1200
1201 void
1202 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1203                     int x, int y, int w, int h)
1204 {
1205   assert (!fsm->print);
1206   if (is_table_item (fsm->item))
1207     {
1208       fsm->cairo = cr;
1209       render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1210                                 mul_XR_POINT (w), mul_XR_POINT (h));
1211       fsm->cairo = NULL;
1212     }
1213   else if (is_image_item (fsm->item))
1214     draw_image (to_image_item (fsm->item)->image, cr);
1215   else if (is_chart_item (fsm->item))
1216     xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1217   else if (is_page_eject_item (fsm->item))
1218     {
1219       /* Nothing to do. */
1220     }
1221   else
1222     NOT_REACHED ();
1223 }
1224 \f
1225 /* Printing API. */
1226
1227 static int
1228 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1229 {
1230   struct table_item *table_item = to_table_item (fsm->item);
1231   int used = render_pager_draw_next (fsm->p, space);
1232   if (!render_pager_has_next (fsm->p))
1233     {
1234       render_pager_destroy (fsm->p);
1235
1236       fsm->layer_indexes = pivot_output_next_layer (table_item->pt,
1237                                                     fsm->layer_indexes, true);
1238       if (fsm->layer_indexes)
1239         {
1240           fsm->p = render_pager_create (&fsm->rp, table_item,
1241                                         fsm->layer_indexes);
1242           if (table_item->pt->look->paginate_layers)
1243             used = space;
1244           else
1245             used += fsm->style->object_spacing;
1246         }
1247       else
1248         {
1249           fsm->p = NULL;
1250           fsm->done = true;
1251         }
1252     }
1253   return MIN (used, space);
1254 }
1255
1256 static int
1257 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1258 {
1259   const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1260   if (space < chart_height)
1261     return 0;
1262
1263   fsm->done = true;
1264   xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1265                  xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1266   return chart_height;
1267 }
1268
1269 static int
1270 xr_fsm_draw_image (struct xr_fsm *fsm, int space)
1271 {
1272   cairo_surface_t *image = to_image_item (fsm->item)->image;
1273   int width = cairo_image_surface_get_width (image) * XR_POINT;
1274   int height = cairo_image_surface_get_height (image) * XR_POINT;
1275   if (!width || !height)
1276     goto error;
1277
1278   if (height > fsm->rp.size[V])
1279     {
1280       double scale = fsm->rp.size[V] / (double) height;
1281       width *= scale;
1282       height *= scale;
1283       if (!width || !height)
1284         goto error;
1285
1286       cairo_scale (fsm->cairo, scale, scale);
1287     }
1288
1289   if (width > fsm->rp.size[H])
1290     {
1291       double scale = fsm->rp.size[H] / (double) width;
1292       width *= scale;
1293       height *= scale;
1294       if (!width || !height)
1295         goto error;
1296
1297       cairo_scale (fsm->cairo, scale, scale);
1298     }
1299
1300   if (space < height)
1301     return 0;
1302
1303   draw_image (image, fsm->cairo);
1304   fsm->done = true;
1305   return height;
1306
1307 error:
1308   fsm->done = true;
1309   return 0;
1310 }
1311
1312 static int
1313 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1314 {
1315   if (space >= fsm->rp.size[V])
1316     fsm->done = true;
1317   return 0;
1318 }
1319
1320 int
1321 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1322 {
1323   assert (fsm->print);
1324
1325   if (fsm->done || space <= 0)
1326     return 0;
1327
1328   cairo_save (cr);
1329   fsm->cairo = cr;
1330   int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1331               : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1332               : is_image_item (fsm->item) ? xr_fsm_draw_image (fsm, space)
1333               : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1334               : (abort (), 0));
1335   fsm->cairo = NULL;
1336   cairo_restore (cr);
1337
1338   return used;
1339 }
1340
1341 bool
1342 xr_fsm_is_empty (const struct xr_fsm *fsm)
1343 {
1344   assert (fsm->print);
1345
1346   return fsm->done;
1347 }