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