csv: Change footnote format.
[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   if (old->font)
78     new->font = pango_font_description_copy (old->font);
79
80   return new;
81 }
82
83 void
84 xr_fsm_style_unref (struct xr_fsm_style *style)
85 {
86   if (style)
87     {
88       assert (style->ref_cnt > 0);
89       if (!--style->ref_cnt)
90         {
91           pango_font_description_free (style->font);
92           free (style);
93         }
94     }
95 }
96
97 bool
98 xr_fsm_style_equals (const struct xr_fsm_style *a,
99                      const struct xr_fsm_style *b)
100 {
101   if (a->size[H] != b->size[H]
102       || a->size[V] != b->size[V]
103       || a->min_break[H] != b->min_break[H]
104       || a->min_break[V] != b->min_break[V]
105       || !pango_font_description_equal (a->font, b->font)
106       || a->use_system_colors != b->use_system_colors
107       || a->font_resolution != b->font_resolution)
108     return false;
109
110   return true;
111 }
112 \f
113 struct xr_fsm
114   {
115     struct xr_fsm_style *style;
116     struct output_item *item;
117
118     /* Table items only. */
119     struct render_params rp;
120     struct render_pager *p;
121     cairo_t *cairo;             /* XXX should this be here?! */
122
123     /* Chart and page-eject items only. */
124     bool done;
125   };
126
127 /* The unit used for internal measurements is inch/(72 * XR_POINT).
128    (Thus, XR_POINT units represent one point.) */
129 #define XR_POINT PANGO_SCALE
130
131 /* Conversions to and from points. */
132 static double
133 xr_to_pt (int x)
134 {
135   return x / (double) XR_POINT;
136 }
137
138 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
139 static int
140 px_to_xr (int x)
141 {
142   return x * (PANGO_SCALE * 72 / 96);
143 }
144
145 static int
146 pango_to_xr (int pango)
147 {
148   return (XR_POINT != PANGO_SCALE
149           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
150           : pango);
151 }
152
153 static int
154 xr_to_pango (int xr)
155 {
156   return (XR_POINT != PANGO_SCALE
157           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
158           : xr);
159 }
160
161 /* Dimensions for drawing lines in tables. */
162 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
163 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
164
165 static void
166 xr_layout_cell (struct xr_fsm *, const struct table_cell *,
167                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
168                 int *width, int *height, int *brk);
169
170 static void
171 xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
172 {
173   cairo_set_source_rgba (cairo,
174                          color->r / 255., color->g / 255., color->b / 255.,
175                          color->alpha / 255.);
176 }
177
178 static void
179 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
180               const struct cell_color *color)
181 {
182   cairo_new_path (xr->cairo);
183   cairo_set_line_width (
184     xr->cairo,
185     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
186               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
187               : XR_LINE_WIDTH));
188   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
189   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
190
191   if (!xr->style->use_system_colors)
192     xr_set_source_rgba (xr->cairo, color);
193   if (style == RENDER_LINE_DASHED)
194     cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
195   cairo_stroke (xr->cairo);
196   if (style == RENDER_LINE_DASHED)
197     cairo_set_dash (xr->cairo, NULL, 0, 0);
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   PangoFontDescription *desc = NULL;
630   if (font_style->typeface)
631       desc = parse_font (
632         font_style->typeface,
633         font_style->size ? font_style->size * 1000 : 10000,
634         font_style->bold, font_style->italic);
635   if (!desc)
636     desc = xr->style->font;
637
638   assert (xr->cairo);
639   PangoContext *context = pango_cairo_create_context (xr->cairo);
640   pango_cairo_context_set_resolution (context, xr->style->font_resolution);
641   PangoLayout *layout = pango_layout_new (context);
642   g_object_unref (context);
643
644   pango_layout_set_font_description (layout, desc);
645
646   const char *text = cell->text;
647   enum table_halign halign = table_halign_interpret (
648     cell_style->halign, cell->options & TAB_NUMERIC);
649   if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
650     {
651       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
652
653       const char *decimal = strrchr (text, cell_style->decimal_char);
654       if (decimal)
655         {
656           pango_layout_set_text (layout, decimal, strlen (decimal));
657           pango_layout_set_width (layout, -1);
658           margin_adjustment += get_layout_dimension (layout, H);
659         }
660
661       if (margin_adjustment < 0)
662         bb[H][1] += margin_adjustment;
663     }
664
665   struct string tmp = DS_EMPTY_INITIALIZER;
666   PangoAttrList *attrs = NULL;
667
668   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
669      Pango's implementation of it): it will break after a period or a comma
670      that precedes a digit, e.g. in ".000" it will break after the period.
671      This code looks for such a situation and inserts a U+2060 WORD JOINER
672      to prevent the break.
673
674      This isn't necessary when the decimal point is between two digits
675      (e.g. "0.000" won't be broken) or when the display width is not limited so
676      that word wrapping won't happen.
677
678      It isn't necessary to look for more than one period or comma, as would
679      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
680      are present then there will always be a digit on both sides of every
681      period and comma. */
682   if (options & TAB_MARKUP)
683     {
684       PangoAttrList *new_attrs;
685       char *new_text;
686       if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
687         {
688           attrs = new_attrs;
689           tmp.ss = ss_cstr (new_text);
690           tmp.capacity = tmp.ss.length;
691         }
692       else
693         {
694           /* XXX should we report the error? */
695           ds_put_cstr (&tmp, text);
696         }
697     }
698   else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
699     {
700       const char *decimal = text + strcspn (text, ".,");
701       if (decimal[0]
702           && c_isdigit (decimal[1])
703           && (decimal == text || !c_isdigit (decimal[-1])))
704         {
705           ds_extend (&tmp, strlen (text) + 16);
706           markup_escape (&tmp, options, text, decimal - text + 1);
707           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
708           markup_escape (&tmp, options, decimal + 1, -1);
709         }
710     }
711
712   if (font_style->underline)
713     {
714       if (!attrs)
715         attrs = pango_attr_list_new ();
716       pango_attr_list_insert (attrs, pango_attr_underline_new (
717                                 PANGO_UNDERLINE_SINGLE));
718     }
719
720   if (cell->n_footnotes || cell->n_subscripts)
721     {
722       /* If we haven't already put TEXT into tmp, do it now. */
723       if (ds_is_empty (&tmp))
724         {
725           ds_extend (&tmp, strlen (text) + 16);
726           markup_escape (&tmp, options, text, -1);
727         }
728
729       size_t subscript_ofs = ds_length (&tmp);
730       for (size_t i = 0; i < cell->n_subscripts; i++)
731         {
732           if (i)
733             ds_put_byte (&tmp, ',');
734           ds_put_cstr (&tmp, cell->subscripts[i]);
735         }
736
737       size_t footnote_ofs = ds_length (&tmp);
738       for (size_t i = 0; i < cell->n_footnotes; i++)
739         {
740           if (i)
741             ds_put_byte (&tmp, ',');
742           ds_put_cstr (&tmp, cell->footnotes[i]->marker);
743         }
744
745       /* Allow footnote markers to occupy the right margin.  That way, numbers
746          in the column are still aligned. */
747       if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
748         {
749           /* Measure the width of the footnote marker, so we know how much we
750              need to make room for. */
751           pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
752                                  ds_length (&tmp) - footnote_ofs);
753
754           PangoAttrList *fn_attrs = pango_attr_list_new ();
755           pango_attr_list_insert (
756             fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
757           pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
758           pango_layout_set_attributes (layout, fn_attrs);
759           pango_attr_list_unref (fn_attrs);
760           int footnote_width = get_layout_dimension (layout, X);
761
762           /* Bound the adjustment by the width of the right margin. */
763           int right_margin = px_to_xr (cell_style->margin[X][R]);
764           int footnote_adjustment = MIN (footnote_width, right_margin);
765
766           /* Adjust the bounding box. */
767           if (options & TAB_ROTATE)
768             footnote_adjustment = -footnote_adjustment;
769           bb[X][R] += footnote_adjustment;
770
771           /* Clean up. */
772           pango_layout_set_attributes (layout, NULL);
773         }
774
775       /* Set attributes. */
776       if (!attrs)
777         attrs = pango_attr_list_new ();
778       add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
779                 PANGO_ATTR_INDEX_TO_TEXT_END);
780       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
781                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
782       if (cell->n_subscripts)
783         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
784                   footnote_ofs - subscript_ofs);
785       if (cell->n_footnotes)
786         add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
787                   PANGO_ATTR_INDEX_TO_TEXT_END);
788     }
789
790   /* Set the attributes, if any. */
791   if (attrs)
792     {
793       pango_layout_set_attributes (layout, attrs);
794       pango_attr_list_unref (attrs);
795     }
796
797   /* Set the text. */
798   if (ds_is_empty (&tmp))
799     pango_layout_set_text (layout, text, -1);
800   else
801     pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
802   ds_destroy (&tmp);
803
804   pango_layout_set_alignment (layout,
805                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
806                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
807                                : PANGO_ALIGN_CENTER));
808   pango_layout_set_width (
809     layout,
810     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
811   pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
812
813   int size[TABLE_N_AXES];
814   pango_layout_get_size (layout, &size[H], &size[V]);
815
816   if (clip[H][0] != clip[H][1])
817     {
818       cairo_save (xr->cairo);
819       if (!(options & TAB_ROTATE))
820         xr_clip (xr, clip);
821       if (options & TAB_ROTATE)
822         {
823           int extra = bb[H][1] - bb[H][0] - size[V];
824           int halign_offset = extra > 0 ? extra / 2 : 0;
825           cairo_translate (xr->cairo,
826                            xr_to_pt (bb[H][0] + halign_offset),
827                            xr_to_pt (bb[V][1]));
828           cairo_rotate (xr->cairo, -M_PI_2);
829         }
830       else
831         cairo_translate (xr->cairo,
832                          xr_to_pt (bb[H][0]),
833                          xr_to_pt (bb[V][0]));
834       pango_cairo_show_layout (xr->cairo, layout);
835
836       /* If enabled, this draws a blue rectangle around the extents of each
837          line of text, which can be rather useful for debugging layout
838          issues. */
839       if (0)
840         {
841           PangoLayoutIter *iter;
842           iter = pango_layout_get_iter (layout);
843           do
844             {
845               PangoRectangle extents;
846
847               pango_layout_iter_get_line_extents (iter, &extents, NULL);
848               cairo_save (xr->cairo);
849               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
850               xr_draw_rectangle (
851                 xr,
852                 pango_to_xr (extents.x),
853                 pango_to_xr (extents.y),
854                 pango_to_xr (extents.x + extents.width),
855                 pango_to_xr (extents.y + extents.height));
856               cairo_restore (xr->cairo);
857             }
858           while (pango_layout_iter_next_line (iter));
859           pango_layout_iter_free (iter);
860         }
861
862       cairo_restore (xr->cairo);
863     }
864
865   int w = pango_to_xr (size[X]);
866   int h = pango_to_xr (size[Y]);
867   if (w > *widthp)
868     *widthp = w;
869   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
870     {
871       PangoLayoutIter *iter;
872       int best = 0;
873
874       /* Choose a breakpoint between lines instead of in the middle of one. */
875       iter = pango_layout_get_iter (layout);
876       do
877         {
878           PangoRectangle extents;
879           int y0, y1;
880           int bottom;
881
882           pango_layout_iter_get_line_extents (iter, NULL, &extents);
883           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
884           extents.x = pango_to_xr (extents.x);
885           extents.y = pango_to_xr (y0);
886           extents.width = pango_to_xr (extents.width);
887           extents.height = pango_to_xr (y1 - y0);
888           bottom = bb[V][0] + extents.y + extents.height;
889           if (bottom < bb[V][1])
890             {
891               if (brk && clip[H][0] != clip[H][1])
892                 best = bottom;
893               if (brk)
894                 *brk = bottom;
895             }
896           else
897             break;
898         }
899       while (pango_layout_iter_next_line (iter));
900       pango_layout_iter_free (iter);
901
902       /* If enabled, draws a green line across the chosen breakpoint, which can
903          be useful for debugging issues with breaking.  */
904       if (0)
905         {
906           if (best)
907             xr_draw_line (xr, 0, best,
908                           xr->style->size[H], best,
909                           RENDER_LINE_SINGLE,
910                           &(struct cell_color) CELL_COLOR (0, 255, 0));
911         }
912     }
913
914   pango_layout_set_attributes (layout, NULL);
915
916   if (desc != xr->style->font)
917     pango_font_description_free (desc);
918   g_object_unref (G_OBJECT (layout));
919
920   return h;
921 }
922
923 static void
924 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
925                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
926                 int *width, int *height, int *brk)
927 {
928   *width = 0;
929   *height = 0;
930
931   /* If enabled, draws a blue rectangle around the cell extents, which can be
932      useful for debugging layout. */
933   if (0)
934     {
935       if (clip[H][0] != clip[H][1])
936         {
937           cairo_save (xr->cairo);
938           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
939           xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
940           cairo_restore (xr->cairo);
941         }
942     }
943
944   if (brk)
945     *brk = bb[V][0];
946   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
947 }
948
949 #define CHART_WIDTH 500
950 #define CHART_HEIGHT 375
951
952 struct xr_fsm *
953 xr_fsm_create (const struct output_item *item_,
954                const struct xr_fsm_style *style, cairo_t *cr)
955 {
956   if (is_page_setup_item (item_)
957       || is_group_open_item (item_)
958       || is_group_close_item (item_))
959     return NULL;
960
961   struct output_item *item;
962   if (is_table_item (item_)
963       || is_chart_item (item_)
964       || is_page_eject_item (item_))
965     item = output_item_ref (item_);
966   else if (is_message_item (item_))
967     item = table_item_super (
968       text_item_to_table_item (
969         message_item_to_text_item (
970           to_message_item (
971             output_item_ref (item_)))));
972   else if (is_text_item (item_))
973     {
974       if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
975         return NULL;
976
977       item = table_item_super (
978         text_item_to_table_item (
979           to_text_item (
980             output_item_ref (item_))));
981     }
982   else if (is_group_open_item (item_))
983     {
984       item = table_item_super (
985         text_item_to_table_item (
986           text_item_create (TEXT_ITEM_TITLE,
987                             to_group_open_item (item_)->command_name,
988                             NULL)));
989     }
990   else
991     NOT_REACHED ();
992   assert (is_table_item (item)
993           || is_chart_item (item)
994           || is_page_eject_item (item));
995
996   static const struct render_ops xrr_render_ops = {
997     .measure_cell_width = xrr_measure_cell_width,
998     .measure_cell_height = xrr_measure_cell_height,
999     .adjust_break = xrr_adjust_break,
1000     .draw_line = xrr_draw_line,
1001     .draw_cell = xrr_draw_cell,
1002     .scale = xrr_scale,
1003   };
1004
1005   enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1006   static const int xr_line_widths[RENDER_N_LINES] =
1007     {
1008       [RENDER_LINE_NONE] = 0,
1009       [RENDER_LINE_SINGLE] = LW,
1010       [RENDER_LINE_DASHED] = LW,
1011       [RENDER_LINE_THICK] = LW * 2,
1012       [RENDER_LINE_THIN] = LW / 2,
1013       [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1014     };
1015
1016   struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1017   *fsm = (struct xr_fsm) {
1018     .style = xr_fsm_style_ref (style),
1019     .item = item,
1020     .rp = {
1021       .ops = &xrr_render_ops,
1022       .aux = fsm,
1023       .size = { [H] = style->size[H], [V] = style->size[V] },
1024       .line_widths = xr_line_widths,
1025       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1026       .supports_margins = true,
1027       .rtl = render_direction_rtl (),
1028     }
1029   };
1030
1031   if (is_table_item (item))
1032     {
1033       fsm->cairo = cr;
1034       fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1035       fsm->cairo = NULL;
1036     }
1037
1038   /* Get font size. */
1039   PangoContext *context = pango_cairo_create_context (cr);
1040   pango_cairo_context_set_resolution (context, style->font_resolution);
1041   PangoLayout *layout = pango_layout_new (context);
1042   g_object_unref (context);
1043
1044   pango_layout_set_font_description (layout, style->font);
1045
1046   pango_layout_set_text (layout, "0", 1);
1047
1048   int char_size[TABLE_N_AXES];
1049   pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1050   for (int a = 0; a < TABLE_N_AXES; a++)
1051     {
1052       int csa = pango_to_xr (char_size[a]);
1053       fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
1054     }
1055
1056   g_object_unref (G_OBJECT (layout));
1057
1058   return fsm;
1059 }
1060
1061 void
1062 xr_fsm_destroy (struct xr_fsm *fsm)
1063 {
1064   if (fsm)
1065     {
1066       xr_fsm_style_unref (fsm->style);
1067       output_item_unref (fsm->item);
1068       render_pager_destroy (fsm->p);
1069       assert (!fsm->cairo);
1070       free (fsm);
1071     }
1072 }
1073
1074 /* This is primarily meant for use with screen rendering since the result is a
1075    fixed value for charts. */
1076 void
1077 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1078 {
1079   int w, h;
1080
1081   if (is_table_item (fsm->item))
1082     {
1083       fsm->cairo = cr;
1084       w = render_pager_get_size (fsm->p, H) / XR_POINT;
1085       h = render_pager_get_size (fsm->p, V) / XR_POINT;
1086       fsm->cairo = NULL;
1087     }
1088   else if (is_chart_item (fsm->item))
1089     {
1090       w = CHART_WIDTH;
1091       h = CHART_HEIGHT;
1092     }
1093   else
1094     NOT_REACHED ();
1095
1096   if (wp)
1097     *wp = w;
1098   if (hp)
1099     *hp = h;
1100 }
1101
1102 static int
1103 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1104 {
1105   return (render_pager_has_next (fsm->p)
1106           ? render_pager_draw_next (fsm->p, space)
1107           : 0);
1108 }
1109
1110 static int
1111 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1112 {
1113   const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1114   if (space < chart_height)
1115     return 0;
1116
1117   fsm->done = true;
1118   xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1119                  xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1120   return chart_height;
1121 }
1122
1123 static int
1124 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1125 {
1126   if (space >= fsm->rp.size[V])
1127     fsm->done = true;
1128   return 0;
1129 }
1130
1131 void
1132 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1133 {
1134   xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1135 }
1136
1137 static int
1138 mul_XR_POINT (int x)
1139 {
1140   return (x >= INT_MAX / XR_POINT ? INT_MAX
1141           : x <= INT_MIN / XR_POINT ? INT_MIN
1142           : x * XR_POINT);
1143 }
1144
1145 void
1146 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1147                     int x, int y, int w, int h)
1148 {
1149   if (is_table_item (fsm->item))
1150     {
1151       fsm->cairo = cr;
1152       render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1153                                 mul_XR_POINT (w), mul_XR_POINT (h));
1154       fsm->cairo = NULL;
1155     }
1156   else if (is_chart_item (fsm->item))
1157     xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1158   else if (is_page_eject_item (fsm->item))
1159     {
1160       /* Nothing to do. */
1161     }
1162   else
1163     NOT_REACHED ();
1164 }
1165
1166 int
1167 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1168 {
1169   if (xr_fsm_is_empty (fsm))
1170     return 0;
1171
1172   cairo_save (cr);
1173   fsm->cairo = cr;
1174   int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1175               : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1176               : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1177               : (abort (), 0));
1178   fsm->cairo = NULL;
1179   cairo_restore (cr);
1180
1181   return used;
1182 }
1183
1184
1185 bool
1186 xr_fsm_is_empty (const struct xr_fsm *fsm)
1187 {
1188   return (is_table_item (fsm->item)
1189           ? !render_pager_has_next (fsm->p)
1190           : fsm->done);
1191 }