0769f47c0172a1b55bf18f58dc7f08f2cde79733
[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_scale != b->font_scale)
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 * xr->style->font_scale : 10000,
612         font_style->bold, font_style->italic);
613   if (!desc)
614     desc = xr->style->fonts[font_type];
615
616   assert (xr->cairo);
617   PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
618   pango_layout_set_font_description (layout, desc);
619
620   const char *text = cell->text;
621   enum table_halign halign = table_halign_interpret (
622     cell_style->halign, cell->options & TAB_NUMERIC);
623   if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
624     {
625       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
626
627       const char *decimal = strrchr (text, cell_style->decimal_char);
628       if (decimal)
629         {
630           pango_layout_set_text (layout, decimal, strlen (decimal));
631           pango_layout_set_width (layout, -1);
632           margin_adjustment += get_layout_dimension (layout, H);
633         }
634
635       if (margin_adjustment < 0)
636         bb[H][1] += margin_adjustment;
637     }
638
639   struct string tmp = DS_EMPTY_INITIALIZER;
640   PangoAttrList *attrs = NULL;
641
642   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
643      Pango's implementation of it): it will break after a period or a comma
644      that precedes a digit, e.g. in ".000" it will break after the period.
645      This code looks for such a situation and inserts a U+2060 WORD JOINER
646      to prevent the break.
647
648      This isn't necessary when the decimal point is between two digits
649      (e.g. "0.000" won't be broken) or when the display width is not limited so
650      that word wrapping won't happen.
651
652      It isn't necessary to look for more than one period or comma, as would
653      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
654      are present then there will always be a digit on both sides of every
655      period and comma. */
656   if (options & TAB_MARKUP)
657     {
658       PangoAttrList *new_attrs;
659       char *new_text;
660       if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
661         {
662           attrs = new_attrs;
663           tmp.ss = ss_cstr (new_text);
664           tmp.capacity = tmp.ss.length;
665         }
666       else
667         {
668           /* XXX should we report the error? */
669           ds_put_cstr (&tmp, text);
670         }
671     }
672   else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
673     {
674       const char *decimal = text + strcspn (text, ".,");
675       if (decimal[0]
676           && c_isdigit (decimal[1])
677           && (decimal == text || !c_isdigit (decimal[-1])))
678         {
679           ds_extend (&tmp, strlen (text) + 16);
680           markup_escape (&tmp, options, text, decimal - text + 1);
681           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
682           markup_escape (&tmp, options, decimal + 1, -1);
683         }
684     }
685
686   if (font_style->underline)
687     {
688       if (!attrs)
689         attrs = pango_attr_list_new ();
690       pango_attr_list_insert (attrs, pango_attr_underline_new (
691                                 PANGO_UNDERLINE_SINGLE));
692     }
693
694   if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
695     {
696       /* If we haven't already put TEXT into tmp, do it now. */
697       if (ds_is_empty (&tmp))
698         {
699           ds_extend (&tmp, strlen (text) + 16);
700           markup_escape (&tmp, options, text, -1);
701         }
702
703       size_t subscript_ofs = ds_length (&tmp);
704       for (size_t i = 0; i < cell->n_subscripts; i++)
705         {
706           if (i)
707             ds_put_byte (&tmp, ',');
708           ds_put_cstr (&tmp, cell->subscripts[i]);
709         }
710
711       size_t superscript_ofs = ds_length (&tmp);
712       if (cell->superscript)
713         ds_put_cstr (&tmp, cell->superscript);
714
715       size_t footnote_ofs = ds_length (&tmp);
716       for (size_t i = 0; i < cell->n_footnotes; i++)
717         {
718           if (i)
719             ds_put_byte (&tmp, ',');
720           ds_put_cstr (&tmp, cell->footnotes[i]->marker);
721         }
722
723       /* Allow footnote markers to occupy the right margin.  That way, numbers
724          in the column are still aligned. */
725       if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
726         {
727           /* Measure the width of the footnote marker, so we know how much we
728              need to make room for. */
729           pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
730                                  ds_length (&tmp) - footnote_ofs);
731
732           PangoAttrList *fn_attrs = pango_attr_list_new ();
733           pango_attr_list_insert (
734             fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
735           pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
736           pango_layout_set_attributes (layout, fn_attrs);
737           pango_attr_list_unref (fn_attrs);
738           int footnote_width = get_layout_dimension (layout, X);
739
740           /* Bound the adjustment by the width of the right margin. */
741           int right_margin = px_to_xr (cell_style->margin[X][R]);
742           int footnote_adjustment = MIN (footnote_width, right_margin);
743
744           /* Adjust the bounding box. */
745           if (options & TAB_ROTATE)
746             footnote_adjustment = -footnote_adjustment;
747           bb[X][R] += footnote_adjustment;
748
749           /* Clean up. */
750           pango_layout_set_attributes (layout, NULL);
751         }
752
753       /* Set attributes. */
754       if (!attrs)
755         attrs = pango_attr_list_new ();
756       add_attr (attrs, pango_attr_font_desc_new (desc), subscript_ofs,
757                 PANGO_ATTR_INDEX_TO_TEXT_END);
758       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
759                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
760       if (cell->n_subscripts)
761         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
762                   superscript_ofs - subscript_ofs);
763       if (cell->superscript || cell->n_footnotes)
764         add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
765                   PANGO_ATTR_INDEX_TO_TEXT_END);
766     }
767
768   /* Set the attributes, if any. */
769   if (attrs)
770     {
771       pango_layout_set_attributes (layout, attrs);
772       pango_attr_list_unref (attrs);
773     }
774
775   /* Set the text. */
776   if (ds_is_empty (&tmp))
777     pango_layout_set_text (layout, text, -1);
778   else
779     pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
780   ds_destroy (&tmp);
781
782   pango_layout_set_alignment (layout,
783                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
784                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
785                                : PANGO_ALIGN_CENTER));
786   pango_layout_set_width (
787     layout,
788     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
789   pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
790
791   if (clip[H][0] != clip[H][1])
792     {
793       cairo_save (xr->cairo);
794       if (!(options & TAB_ROTATE))
795         xr_clip (xr, clip);
796       if (options & TAB_ROTATE)
797         {
798           cairo_translate (xr->cairo,
799                            xr_to_pt (bb[H][0]),
800                            xr_to_pt (bb[V][1]));
801           cairo_rotate (xr->cairo, -M_PI_2);
802         }
803       else
804         cairo_translate (xr->cairo,
805                          xr_to_pt (bb[H][0]),
806                          xr_to_pt (bb[V][0]));
807       pango_cairo_show_layout (xr->cairo, layout);
808
809       /* If enabled, this draws a blue rectangle around the extents of each
810          line of text, which can be rather useful for debugging layout
811          issues. */
812       if (0)
813         {
814           PangoLayoutIter *iter;
815           iter = pango_layout_get_iter (layout);
816           do
817             {
818               PangoRectangle extents;
819
820               pango_layout_iter_get_line_extents (iter, &extents, NULL);
821               cairo_save (xr->cairo);
822               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
823               xr_draw_rectangle (
824                 xr,
825                 pango_to_xr (extents.x),
826                 pango_to_xr (extents.y),
827                 pango_to_xr (extents.x + extents.width),
828                 pango_to_xr (extents.y + extents.height));
829               cairo_restore (xr->cairo);
830             }
831           while (pango_layout_iter_next_line (iter));
832           pango_layout_iter_free (iter);
833         }
834
835       cairo_restore (xr->cairo);
836     }
837
838   int size[TABLE_N_AXES];
839   pango_layout_get_size (layout, &size[H], &size[V]);
840   int w = pango_to_xr (size[X]);
841   int h = pango_to_xr (size[Y]);
842   if (w > *widthp)
843     *widthp = w;
844   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
845     {
846       PangoLayoutIter *iter;
847       int best = 0;
848
849       /* Choose a breakpoint between lines instead of in the middle of one. */
850       iter = pango_layout_get_iter (layout);
851       do
852         {
853           PangoRectangle extents;
854           int y0, y1;
855           int bottom;
856
857           pango_layout_iter_get_line_extents (iter, NULL, &extents);
858           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
859           extents.x = pango_to_xr (extents.x);
860           extents.y = pango_to_xr (y0);
861           extents.width = pango_to_xr (extents.width);
862           extents.height = pango_to_xr (y1 - y0);
863           bottom = bb[V][0] + extents.y + extents.height;
864           if (bottom < bb[V][1])
865             {
866               if (brk && clip[H][0] != clip[H][1])
867                 best = bottom;
868               if (brk)
869                 *brk = bottom;
870             }
871           else
872             break;
873         }
874       while (pango_layout_iter_next_line (iter));
875       pango_layout_iter_free (iter);
876
877       /* If enabled, draws a green line across the chosen breakpoint, which can
878          be useful for debugging issues with breaking.  */
879       if (0)
880         {
881           if (best)
882             xr_draw_line (xr, 0, best,
883                           xr->style->size[H], best,
884                           RENDER_LINE_SINGLE,
885                           &(struct cell_color) CELL_COLOR (0, 255, 0));
886         }
887     }
888
889   pango_layout_set_attributes (layout, NULL);
890
891   if (desc != xr->style->fonts[font_type])
892     pango_font_description_free (desc);
893   g_object_unref (G_OBJECT (layout));
894
895   return h;
896 }
897
898 static void
899 xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
900                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
901                 int *width, int *height, int *brk)
902 {
903   *width = 0;
904   *height = 0;
905
906   /* If enabled, draws a blue rectangle around the cell extents, which can be
907      useful for debugging layout. */
908   if (0)
909     {
910       if (clip[H][0] != clip[H][1])
911         {
912           cairo_save (xr->cairo);
913           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
914           xr_draw_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
915           cairo_restore (xr->cairo);
916         }
917     }
918
919   if (brk)
920     *brk = bb[V][0];
921   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
922 }
923 \f
924 #if 0
925 static bool
926 xr_table_render (struct xr_render_fsm *fsm, struct xr_fsm *xr)
927 {
928   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
929
930   while (render_pager_has_next (ts->p))
931     {
932       int used;
933
934       used = render_pager_draw_next (ts->p, xr->length);
935       if (!used)
936         {
937           assert (xr->y > 0);
938           return true;
939         }
940       else
941         xr->y += used;
942     }
943   return false;
944 }
945
946 static void
947 xr_table_destroy (struct xr_render_fsm *fsm)
948 {
949   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
950
951   render_pager_destroy (ts->p);
952   free (ts);
953 }
954
955 static struct xr_render_fsm *
956 xr_render_table (struct xr_fsm *xr, struct table_item *table_item)
957 {
958   struct xr_table_state *ts;
959
960   ts = xmalloc (sizeof *ts);
961   ts->fsm.render = xr_table_render;
962   ts->fsm.destroy = xr_table_destroy;
963
964   if (xr->y > 0)
965     xr->y += xr->char_height;
966
967   ts->p = render_pager_create (xr->params, table_item);
968   table_item_unref (table_item);
969
970   return &ts->fsm;
971 }
972 \f
973 static bool
974 xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_fsm *xr)
975 {
976   return xr->y > 0;
977 }
978
979 static void
980 xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
981 {
982   /* Nothing to do. */
983 }
984
985 static struct xr_render_fsm *
986 xr_render_eject (void)
987 {
988   static struct xr_render_fsm eject_renderer =
989     {
990       xr_eject_render,
991       xr_eject_destroy
992     };
993
994   return &eject_renderer;
995 }
996 \f
997 #define CHART_WIDTH 500
998 #define CHART_HEIGHT 375
999
1000 static struct xr_render_fsm *
1001 xr_render_text (struct xr_fsm *xr, const struct text_item *text_item)
1002 {
1003   enum text_item_type type = text_item_get_type (text_item);
1004
1005   switch (type)
1006     {
1007     case TEXT_ITEM_PAGE_TITLE:
1008       break;
1009
1010     case TEXT_ITEM_EJECT_PAGE:
1011       if (xr->y > 0)
1012         return xr_render_eject ();
1013       break;
1014
1015     default:
1016       return xr_render_table (
1017         xr, text_item_to_table_item (text_item_ref (text_item)));
1018     }
1019
1020   return NULL;
1021 }
1022 #endif
1023
1024 #define CHART_WIDTH 500
1025 #define CHART_HEIGHT 375
1026
1027 struct xr_fsm *
1028 xr_fsm_create (const struct output_item *item_,
1029                const struct xr_fsm_style *style, cairo_t *cr)
1030 {
1031   if (is_page_setup_item (item_)
1032       || is_group_open_item (item_)
1033       || is_group_close_item (item_))
1034     return NULL;
1035
1036   struct output_item *item;
1037   if (is_table_item (item_)
1038       || is_chart_item (item_)
1039       || is_page_eject_item (item_))
1040     item = output_item_ref (item_);
1041   else if (is_message_item (item_))
1042     item = table_item_super (
1043       text_item_to_table_item (
1044         message_item_to_text_item (
1045           to_message_item (
1046             output_item_ref (item_)))));
1047   else if (is_text_item (item_))
1048     {
1049       if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
1050         return NULL;
1051
1052       item = table_item_super (
1053         text_item_to_table_item (
1054           to_text_item (
1055             output_item_ref (item_))));
1056     }
1057   else if (is_group_open_item (item_))
1058     {
1059       item = table_item_super (
1060         text_item_to_table_item (
1061           text_item_create (TEXT_ITEM_TITLE,
1062                             to_group_open_item (item_)->command_name)));
1063     }
1064   else
1065     NOT_REACHED ();
1066   assert (is_table_item (item)
1067           || is_chart_item (item)
1068           || is_page_eject_item (item));
1069
1070   static const struct render_ops xrr_render_ops = {
1071     .measure_cell_width = xrr_measure_cell_width,
1072     .measure_cell_height = xrr_measure_cell_height,
1073     .adjust_break = xrr_adjust_break,
1074     .draw_line = xrr_draw_line,
1075     .draw_cell = xrr_draw_cell,
1076   };
1077
1078   enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
1079   static const int xr_line_widths[RENDER_N_LINES] =
1080     {
1081       [RENDER_LINE_NONE] = 0,
1082       [RENDER_LINE_SINGLE] = LW,
1083       [RENDER_LINE_DASHED] = LW,
1084       [RENDER_LINE_THICK] = LW * 2,
1085       [RENDER_LINE_THIN] = LW / 2,
1086       [RENDER_LINE_DOUBLE] = 2 * LW + LS,
1087     };
1088
1089   struct xr_fsm *fsm = xmalloc (sizeof *fsm);
1090   *fsm = (struct xr_fsm) {
1091     .style = xr_fsm_style_ref (style),
1092     .item = item,
1093     .rp = {
1094       .ops = &xrr_render_ops,
1095       .aux = fsm,
1096       .size = { [H] = style->size[H], [V] = style->size[V] },
1097       /* XXX font_size */
1098       .line_widths = xr_line_widths,
1099       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
1100       .supports_margins = true,
1101       .rtl = render_direction_rtl (),
1102     }
1103   };
1104
1105   if (is_table_item (item))
1106     {
1107       fsm->cairo = cr;
1108       fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
1109       fsm->cairo = NULL;
1110     }
1111
1112   for (int i = 0; i < XR_N_FONTS; i++)
1113     {
1114       PangoLayout *layout = pango_cairo_create_layout (cr);
1115       pango_layout_set_font_description (layout, style->fonts[i]);
1116
1117       pango_layout_set_text (layout, "0", 1);
1118
1119       int char_size[TABLE_N_AXES];
1120       pango_layout_get_size (layout, &char_size[H], &char_size[V]);
1121       for (int j = 0; j < TABLE_N_AXES; j++)
1122         {
1123           int csj = pango_to_xr (char_size[j]);
1124           fsm->rp.font_size[j] = MAX (fsm->rp.font_size[j], csj);
1125         }
1126
1127       g_object_unref (G_OBJECT (layout));
1128     }
1129
1130   return fsm;
1131 }
1132
1133 void
1134 xr_fsm_destroy (struct xr_fsm *fsm)
1135 {
1136   if (fsm)
1137     {
1138       xr_fsm_style_unref (fsm->style);
1139       output_item_unref (fsm->item);
1140       render_pager_destroy (fsm->p);
1141       assert (!fsm->cairo);
1142       free (fsm);
1143     }
1144 }
1145
1146
1147 /* This is primarily meant for use with screen rendering since the result is a
1148    fixed value for charts. */
1149 void
1150 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
1151 {
1152   int w, h;
1153
1154   if (is_table_item (fsm->item))
1155     {
1156       fsm->cairo = cr;
1157       w = render_pager_get_size (fsm->p, H) / XR_POINT;
1158       h = render_pager_get_size (fsm->p, V) / XR_POINT;
1159       fsm->cairo = NULL;
1160     }
1161   else if (is_chart_item (fsm->item))
1162     {
1163       w = CHART_WIDTH;
1164       h = CHART_HEIGHT;
1165     }
1166   else
1167     NOT_REACHED ();
1168
1169   if (wp)
1170     *wp = w;
1171   if (hp)
1172     *hp = h;
1173 }
1174
1175 static int
1176 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
1177 {
1178   int used = 0;
1179   while (render_pager_has_next (fsm->p))
1180     {
1181       int chunk = render_pager_draw_next (fsm->p, space - used);
1182       if (!chunk)
1183         return used;
1184
1185       used += chunk;
1186       cairo_translate (fsm->cairo, 0, chunk);
1187     }
1188   return used;
1189 }
1190
1191 static void
1192 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1193                double width, double height)
1194 {
1195   struct xrchart_geometry geom;
1196
1197   cairo_translate (cr, 0, height);
1198   cairo_scale (cr, 1.0, -1.0);
1199   xrchart_geometry_init (cr, &geom, width, height);
1200   if (is_boxplot (chart_item))
1201     xrchart_draw_boxplot (chart_item, cr, &geom);
1202   else if (is_histogram_chart (chart_item))
1203     xrchart_draw_histogram (chart_item, cr, &geom);
1204   else if (is_np_plot_chart (chart_item))
1205     xrchart_draw_np_plot (chart_item, cr, &geom);
1206   else if (is_piechart (chart_item))
1207     xrchart_draw_piechart (chart_item, cr, &geom);
1208   else if (is_barchart (chart_item))
1209     xrchart_draw_barchart (chart_item, cr, &geom);
1210   else if (is_roc_chart (chart_item))
1211     xrchart_draw_roc (chart_item, cr, &geom);
1212   else if (is_scree (chart_item))
1213     xrchart_draw_scree (chart_item, cr, &geom);
1214   else if (is_spreadlevel_plot_chart (chart_item))
1215     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1216   else if (is_scatterplot_chart (chart_item))
1217     xrchart_draw_scatterplot (chart_item, cr, &geom);
1218   else
1219     NOT_REACHED ();
1220   xrchart_geometry_free (cr, &geom);
1221 }
1222
1223 static int
1224 xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
1225 {
1226   const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
1227   if (space < chart_height)
1228     return 0;
1229
1230   fsm->done = true;
1231   xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
1232                  xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
1233   return chart_height;
1234 }
1235
1236 static int
1237 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
1238 {
1239   if (space >= fsm->rp.size[V])
1240     fsm->done = true;
1241   return 0;
1242 }
1243
1244 void
1245 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
1246 {
1247   xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
1248 }
1249
1250 static int
1251 mul_XR_POINT (int x)
1252 {
1253   return (x >= INT_MAX / XR_POINT ? INT_MAX
1254           : x <= INT_MIN / XR_POINT ? INT_MIN
1255           : x * XR_POINT);
1256 }
1257
1258 void
1259 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
1260                     int x, int y, int w, int h)
1261 {
1262   if (is_table_item (fsm->item))
1263     {
1264       fsm->cairo = cr;
1265       render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
1266                                 mul_XR_POINT (w), mul_XR_POINT (h));
1267       fsm->cairo = NULL;
1268     }
1269   else if (is_chart_item (fsm->item))
1270     xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
1271   else if (is_page_eject_item (fsm->item))
1272     {
1273       /* Nothing to do. */
1274     }
1275   else
1276     NOT_REACHED ();
1277 }
1278
1279 int
1280 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
1281 {
1282   if (xr_fsm_is_empty (fsm))
1283     return 0;
1284
1285   cairo_save (cr);
1286   fsm->cairo = cr;
1287   int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
1288               : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
1289               : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
1290               : (abort (), 0));
1291   fsm->cairo = NULL;
1292   cairo_restore (cr);
1293
1294   return used;
1295 }
1296
1297
1298 bool
1299 xr_fsm_is_empty (const struct xr_fsm *fsm)
1300 {
1301   return (is_table_item (fsm->item)
1302           ? !render_pager_has_next (fsm->p)
1303           : fsm->done);
1304 }