+static void
+markup_escape (struct string *out, unsigned int options,
+ const char *in, size_t len)
+{
+ if (!(options & TAB_MARKUP))
+ {
+ ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
+ return;
+ }
+
+ while (len-- > 0)
+ {
+ int c = *in++;
+ switch (c)
+ {
+ case 0:
+ return;
+ case '&':
+ ds_put_cstr (out, "&");
+ break;
+ case '<':
+ ds_put_cstr (out, "<");
+ break;
+ case '>':
+ ds_put_cstr (out, ">");
+ break;
+ default:
+ ds_put_byte (out, c);
+ break;
+ }
+ }
+}
+
+static int
+get_layout_dimension (PangoLayout *layout, enum table_axis axis)
+{
+ int size[TABLE_N_AXES];
+ pango_layout_get_size (layout, &size[H], &size[V]);
+ return size[axis];
+}
+
+static int
+xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell,
+ int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
+ int *widthp, int *brk)
+{
+ const struct font_style *font_style = &cell->style->font_style;
+ const struct cell_style *cell_style = &cell->style->cell_style;
+ unsigned int options = cell->options;
+
+ enum table_axis X = options & TAB_ROTATE ? V : H;
+ enum table_axis Y = !X;
+ int R = options & TAB_ROTATE ? 0 : 1;
+
+ struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
+ : &xr->fonts[XR_FONT_PROPORTIONAL]);
+ struct xr_font local_font;
+ if (font_style->typeface)
+ {
+ PangoFontDescription *desc = parse_font (
+ font_style->typeface,
+ font_style->size ? font_style->size * 1000 * xr->font_scale : 10000,
+ font_style->bold, font_style->italic);
+ if (desc)
+ {
+ PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
+ pango_layout_set_font_description (layout, desc);
+
+ local_font.desc = desc;
+ local_font.layout = layout;
+ font = &local_font;
+ }
+ }
+
+ const char *text = cell->text;
+ enum table_halign halign = table_halign_interpret (
+ cell_style->halign, cell->options & TAB_NUMERIC);
+ if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
+ {
+ int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
+
+ const char *decimal = strrchr (text, cell_style->decimal_char);
+ if (decimal)
+ {
+ pango_layout_set_text (font->layout, decimal, strlen (decimal));
+ pango_layout_set_width (font->layout, -1);
+ margin_adjustment += get_layout_dimension (font->layout, H);
+ }
+
+ if (margin_adjustment < 0)
+ bb[H][1] += margin_adjustment;
+ }
+
+ struct string tmp = DS_EMPTY_INITIALIZER;
+ PangoAttrList *attrs = NULL;
+
+ /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
+ Pango's implementation of it): it will break after a period or a comma
+ that precedes a digit, e.g. in ".000" it will break after the period.
+ This code looks for such a situation and inserts a U+2060 WORD JOINER
+ to prevent the break.
+
+ This isn't necessary when the decimal point is between two digits
+ (e.g. "0.000" won't be broken) or when the display width is not limited so
+ that word wrapping won't happen.
+
+ It isn't necessary to look for more than one period or comma, as would
+ happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
+ are present then there will always be a digit on both sides of every
+ period and comma. */
+ if (options & TAB_MARKUP)
+ {
+ PangoAttrList *new_attrs;
+ char *new_text;
+ if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
+ {
+ attrs = new_attrs;
+ tmp.ss = ss_cstr (new_text);
+ tmp.capacity = tmp.ss.length;
+ }
+ else
+ {
+ /* XXX should we report the error? */
+ ds_put_cstr (&tmp, text);
+ }
+ }
+ else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
+ {
+ const char *decimal = text + strcspn (text, ".,");
+ if (decimal[0]
+ && c_isdigit (decimal[1])
+ && (decimal == text || !c_isdigit (decimal[-1])))
+ {
+ ds_extend (&tmp, strlen (text) + 16);
+ markup_escape (&tmp, options, text, decimal - text + 1);
+ ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
+ markup_escape (&tmp, options, decimal + 1, -1);
+ }
+ }
+
+ if (font_style->underline)
+ {
+ if (!attrs)
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_underline_new (
+ PANGO_UNDERLINE_SINGLE));
+ }
+
+ if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
+ {
+ /* If we haven't already put TEXT into tmp, do it now. */
+ if (ds_is_empty (&tmp))
+ {
+ ds_extend (&tmp, strlen (text) + 16);
+ markup_escape (&tmp, options, text, -1);
+ }
+
+ size_t subscript_ofs = ds_length (&tmp);
+ for (size_t i = 0; i < cell->n_subscripts; i++)
+ {
+ if (i)
+ ds_put_byte (&tmp, ',');
+ ds_put_cstr (&tmp, cell->subscripts[i]);
+ }
+
+ size_t superscript_ofs = ds_length (&tmp);
+ if (cell->superscript)
+ ds_put_cstr (&tmp, cell->superscript);
+
+ size_t footnote_ofs = ds_length (&tmp);
+ for (size_t i = 0; i < cell->n_footnotes; i++)
+ {
+ if (i)
+ ds_put_byte (&tmp, ',');
+ ds_put_cstr (&tmp, cell->footnotes[i]->marker);
+ }
+
+ /* Allow footnote markers to occupy the right margin. That way, numbers
+ in the column are still aligned. */
+ if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
+ {
+ /* Measure the width of the footnote marker, so we know how much we
+ need to make room for. */
+ pango_layout_set_text (font->layout, ds_cstr (&tmp) + footnote_ofs,
+ ds_length (&tmp) - footnote_ofs);
+
+ PangoAttrList *fn_attrs = pango_attr_list_new ();
+ pango_attr_list_insert (
+ fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
+ pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
+ pango_layout_set_attributes (font->layout, fn_attrs);
+ pango_attr_list_unref (fn_attrs);
+ int footnote_width = get_layout_dimension (font->layout, X);
+
+ /* Bound the adjustment by the width of the right margin. */
+ int right_margin = px_to_xr (cell_style->margin[X][R]);
+ int footnote_adjustment = MIN (footnote_width, right_margin);
+
+ /* Adjust the bounding box. */
+ if (options & TAB_ROTATE)
+ footnote_adjustment = -footnote_adjustment;
+ bb[X][R] += footnote_adjustment;
+
+ /* Clean up. */
+ pango_layout_set_attributes (font->layout, NULL);
+ }
+
+ /* Set attributes. */
+ if (!attrs)
+ attrs = pango_attr_list_new ();
+ add_attr (attrs, pango_attr_font_desc_new (font->desc), subscript_ofs,
+ PANGO_ATTR_INDEX_TO_TEXT_END);
+ add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
+ subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
+ if (cell->n_subscripts)
+ add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
+ superscript_ofs - subscript_ofs);
+ if (cell->superscript || cell->n_footnotes)
+ add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
+ PANGO_ATTR_INDEX_TO_TEXT_END);
+ }
+
+ /* Set the attributes, if any. */
+ if (attrs)
+ {
+ pango_layout_set_attributes (font->layout, attrs);
+ pango_attr_list_unref (attrs);
+ }
+
+ /* Set the text. */
+ if (ds_is_empty (&tmp))
+ pango_layout_set_text (font->layout, text, -1);
+ else
+ pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
+ ds_destroy (&tmp);
+
+ pango_layout_set_alignment (font->layout,
+ (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
+ : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
+ : PANGO_ALIGN_CENTER));
+ pango_layout_set_width (