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