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