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