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