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