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