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