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