From: Ben Pfaff Date: Sun, 29 Dec 2019 05:39:50 +0000 (+0000) Subject: output: Refine support for footnotes, subscripts, and superscripts. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0c15548837a299334adf32401f9d8dc2806cb320;p=pspp output: Refine support for footnotes, subscripts, and superscripts. The code for all this wasn't fully plumbed through from the pivot table code down to the drivers. This commit fixes all that up. --- diff --git a/src/output/ascii.c b/src/output/ascii.c index 899c2751b8..e41e128c6d 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -615,7 +615,8 @@ ascii_measure_cell_width (void *a_, const struct table_cell *cell, clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; ascii_layout_cell (a, cell, bb, clip, max_width, &h); - if (cell->n_footnotes || strchr (cell->text, ' ')) + if (cell->n_footnotes || strchr (cell->text, ' ') + || cell->n_subscripts || cell->superscript) { bb[H][1] = 1; ascii_layout_cell (a, cell, bb, clip, min_width, &h); @@ -809,10 +810,14 @@ text_draw (struct ascii_driver *a, enum table_halign halign, int options, } static char * -add_footnote_markers (const char *text, const struct table_cell *cell) +add_markers (const char *text, const struct table_cell *cell) { struct string s = DS_EMPTY_INITIALIZER; ds_put_cstr (&s, text); + for (size_t i = 0; i < cell->n_subscripts; i++) + ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]); + if (cell->superscript) + ds_put_format (&s, "^%s", cell->superscript); for (size_t i = 0; i < cell->n_footnotes; i++) ds_put_format (&s, "[%s]", cell->footnotes[i]->marker); return ds_steal_cstr (&s); @@ -831,11 +836,11 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, ? output_get_text_from_markup (cell->text) : cell->text); - /* Append footnote markers if any. */ + /* Append footnotes, subscripts, superscript if any. */ const char *text; - if (cell->n_footnotes) + if (cell->n_footnotes || cell->n_subscripts || cell->superscript) { - text = add_footnote_markers (plain_text, cell); + text = add_markers (plain_text, cell); if (plain_text != cell->text) free (CONST_CAST (char *, plain_text)); } diff --git a/src/output/cairo.c b/src/output/cairo.c index 2b6ae706c5..b1a7e27368 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -1422,31 +1422,45 @@ xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2]) } static void -add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index) +add_attr (PangoAttrList *list, PangoAttribute *attr, + guint start_index, guint end_index) { attr->start_index = start_index; + attr->end_index = end_index; pango_attr_list_insert (list, attr); } static void -markup_escape (const char *in, struct string *out) -{ - for (int c = *in++; c; c = *in++) - switch (c) - { - 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; - } +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 @@ -1510,6 +1524,7 @@ xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell, } 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 @@ -1525,7 +1540,23 @@ xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell, 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_ROTATE || bb[H][1] != INT_MAX) + 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] @@ -1533,93 +1564,106 @@ xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell, && (decimal == text || !c_isdigit (decimal[-1]))) { ds_extend (&tmp, strlen (text) + 16); - ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1)); + markup_escape (&tmp, options, text, decimal - text + 1); ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */); - ds_put_cstr (&tmp, decimal + 1); + markup_escape (&tmp, options, decimal + 1, -1); } } - if (cell->n_footnotes) + if (font_style->underline) { - int footnote_adjustment; - if (cell->n_footnotes == 1 && halign == TABLE_HALIGN_RIGHT) - { - const char *marker = cell->footnotes[0]->marker; - pango_layout_set_text (font->layout, marker, strlen (marker)); - - PangoAttrList *attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); - pango_attr_list_insert (attrs, pango_attr_rise_new (3000)); - pango_layout_set_attributes (font->layout, attrs); - pango_attr_list_unref (attrs); - - int w = get_layout_dimension (font->layout, X); - int right_margin = px_to_xr (cell_style->margin[X][R]); - footnote_adjustment = MIN (w, right_margin); - - pango_layout_set_attributes (font->layout, NULL); - } - else - footnote_adjustment = px_to_xr (cell_style->margin[X][R]); - - if (R) - bb[X][R] += footnote_adjustment; - else - bb[X][R] -= footnote_adjustment; + 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); - ds_put_cstr (&tmp, text); + 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 initial_length = ds_length (&tmp); + 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); + } - const char *marker = cell->footnotes[i]->marker; - if (options & TAB_MARKUP) - markup_escape (marker, &tmp); - else - ds_put_cstr (&tmp, 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); } - if (options & TAB_MARKUP) - pango_layout_set_markup (font->layout, - ds_cstr (&tmp), ds_length (&tmp)); - else - pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp)); - - PangoAttrList *attrs = pango_attr_list_new (); - if (font_style->underline) - pango_attr_list_insert (attrs, pango_attr_underline_new ( - PANGO_UNDERLINE_SINGLE)); - add_attr_with_start (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL), initial_length); - add_attr_with_start (attrs, pango_attr_rise_new (3000), initial_length); - add_attr_with_start ( - attrs, pango_attr_font_desc_new (font->desc), initial_length); + /* 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); } - else - { - const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp); - if (options & TAB_MARKUP) - pango_layout_set_markup (font->layout, content, -1); - else - pango_layout_set_text (font->layout, content, -1); - if (font_style->underline) - { - PangoAttrList *attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_underline_new ( - PANGO_UNDERLINE_SINGLE)); - 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, diff --git a/src/output/csv.c b/src/output/csv.c index 6c2f7495c4..1c6662793f 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -227,7 +227,8 @@ csv_submit (struct output_driver *driver, if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0]) csv_output_field (csv, ""); - else if (!(cell.options & TAB_MARKUP) && !cell.n_footnotes) + else if (!(cell.options & TAB_MARKUP) && !cell.n_footnotes + && !cell.n_subscripts && !cell.superscript) csv_output_field (csv, cell.text); else { @@ -242,6 +243,12 @@ csv_submit (struct output_driver *driver, else ds_put_cstr (&s, cell.text); + if (cell.n_subscripts) + for (size_t i = 0; i < cell.n_subscripts; i++) + ds_put_format (&s, "%c%s", + i ? ',' : '_', cell.subscripts[i]); + if (cell.superscript) + ds_put_format (&s, "^%s", cell.superscript); csv_format_footnotes (cell.footnotes, cell.n_footnotes, &s); csv_output_field (csv, ds_cstr (&s)); ds_destroy (&s); diff --git a/src/output/html.c b/src/output/html.c index 5233fc8fe5..989476ecaa 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -66,8 +66,7 @@ struct html_driver static const struct output_driver_class html_driver_class; static void html_output_table (struct html_driver *, const struct table_item *); -static void escape_string (FILE *file, - const char *text, size_t length, +static void escape_string (FILE *file, const char *text, const char *space, const char *newline); static void print_title_tag (FILE *file, const char *name, const char *content); @@ -201,7 +200,7 @@ print_title_tag (FILE *file, const char *name, const char *content) if (content != NULL) { fprintf (file, "<%s>", name); - escape_string (file, content, strlen (content), " ", " - "); + escape_string (file, content, " ", " - "); fprintf (file, "\n", name); } } @@ -275,7 +274,7 @@ html_submit (struct output_driver *driver, case TEXT_ITEM_SYNTAX: fprintf (html->file, "
");
-          escape_string (html->file, s, strlen (s), " ", "
"); + escape_string (html->file, s, " ", "
"); fprintf (html->file, "
\n"); break; @@ -297,20 +296,20 @@ html_submit (struct output_driver *driver, } } -/* Write LENGTH characters in TEXT to file F, escaping characters as necessary - for HTML. Spaces are replaced by SPACE, which should be " " or " " - New-lines are replaced by NEWLINE, which might be "
" or "\n" or - something else appropriate. */ +/* Write TEXT to file F, escaping characters as necessary for HTML. Spaces are + replaced by SPACE, which should be " " or " " New-lines are replaced by + NEWLINE, which might be "
" or "\n" or something else appropriate. */ static void -escape_string (FILE *file, - const char *text, size_t length, +escape_string (FILE *file, const char *text, const char *space, const char *newline) { - while (length-- > 0) + for (;;) { char c = *text++; switch (c) { + case 0: + return; case '\n': fputs (newline, file); break; @@ -336,6 +335,18 @@ escape_string (FILE *file, } } +static void +escape_tag (FILE *file, const char *tag, + const char *text, const char *space, const char *newline) +{ + if (!text || !*text) + return; + + fprintf (file, "<%s>", tag); + escape_string (file, text, space, newline); + fprintf (file, "", tag); +} + static const char * border_to_css (int border) { @@ -403,8 +414,7 @@ html_put_footnote_markers (struct html_driver *html, if (i > 0) putc (',', html->file); - escape_string (html->file, f->marker, - strlen (f->marker), " ", "
"); + escape_string (html->file, f->marker, " ", "
"); } fputs ("", html->file); } @@ -414,8 +424,7 @@ static void html_put_table_item_text (struct html_driver *html, const struct table_item_text *text) { - escape_string (html->file, text->content, strlen (text->content), - " ", "
"); + escape_string (html->file, text->content, " ", "
"); html_put_footnote_markers (html, text->footnotes, text->n_footnotes); } @@ -429,8 +438,7 @@ html_put_table_item_layers (struct html_driver *html, fputs ("
\n", html->file); const struct table_item_layer *layer = &layers->layers[i]; - escape_string (html->file, layer->content, strlen (layer->content), - " ", "
"); + escape_string (html->file, layer->content, " ", "
"); html_put_footnote_markers (html, layer->footnotes, layer->n_footnotes); } } @@ -456,12 +464,8 @@ html_output_table (struct html_driver *html, const struct table_item *item) for (size_t i = 0; i < n_footnotes; i++) { put_tfoot (html, t, &tfoot); - fputs ("", html->file); - escape_string (html->file, f[i]->marker, strlen (f[i]->marker), - " ", "
"); - fputs ("
", html->file); - escape_string (html->file, f[i]->content, strlen (f[i]->content), - " ", "
"); + escape_tag (html->file, "SUP", f[i]->marker, " ", "
"); + escape_string (html->file, f[i]->content, " ", "
"); } free (f); if (tfoot) @@ -569,17 +573,27 @@ html_output_table (struct html_driver *html, const struct table_item *item) /* Output cell contents. */ const char *s = cell.text; if (cell.options & TAB_FIX) - { - fputs ("", html->file); - escape_string (html->file, s, strlen (s), " ", "
"); - fputs ("
", html->file); - } + escape_tag (html->file, "TT", s, " ", "
"); else { s += strspn (s, CC_SPACES); - escape_string (html->file, s, strlen (s), " ", "
"); + escape_string (html->file, s, " ", "
"); } + if (cell.n_subscripts) + { + fputs ("", html->file); + for (size_t i = 0; i < cell.n_subscripts; i++) + { + if (i) + putc (',', html->file); + escape_string (html->file, cell.subscripts[i], + " ", "
"); + } + fputs ("
", html->file); + } + if (cell.superscript) + escape_tag (html->file, "SUP", cell.superscript, " ", "
"); html_put_footnote_markers (html, cell.footnotes, cell.n_footnotes); /* Output or . */ diff --git a/src/output/pivot-output.c b/src/output/pivot-output.c index f90a953e5d..fc41cb870f 100644 --- a/src/output/pivot-output.c +++ b/src/output/pivot-output.c @@ -124,6 +124,10 @@ fill_cell (struct table *t, int x1, int y1, int x2, int y2, for (size_t i = 0; i < value->n_footnotes; i++) table_add_footnote (t, x1, y1, footnotes[value->footnotes[i]->idx]); + + if (value->n_subscripts) + table_add_subscripts (t, x1, y1, + value->subscripts, value->n_subscripts); } } diff --git a/src/output/pivot-table.c b/src/output/pivot-table.c index d4649292ac..ead1a1cc47 100644 --- a/src/output/pivot-table.c +++ b/src/output/pivot-table.c @@ -1891,8 +1891,11 @@ pivot_value_format (const struct pivot_value *value, { pivot_value_format_body ( value, show_values, show_variables, out); - if (value->subscript) - ds_put_format (out, "_%s", value->subscript); + if (value->n_subscripts) + { + for (size_t i = 0; i < value->n_subscripts; i++) + ds_put_format (out, "%c%s", i ? ',' : '_', value->subscripts[i]); + } if (value->superscript) ds_put_format (out, "^%s", value->superscript); @@ -1929,7 +1932,12 @@ pivot_value_destroy (struct pivot_value *value) /* Do not free the elements of footnotes because VALUE does not own them. */ free (value->footnotes); - free (value->subscript); + + for (size_t i = 0; i < value->n_subscripts; i++) + free (value->subscripts[i]); + free (value->subscripts); + + free (value->superscript); switch (value->type) { diff --git a/src/output/pivot-table.h b/src/output/pivot-table.h index 096e50a1eb..f2f0c564ab 100644 --- a/src/output/pivot-table.h +++ b/src/output/pivot-table.h @@ -599,7 +599,10 @@ struct pivot_value { struct font_style *font_style; struct cell_style *cell_style; - char *subscript; + + char **subscripts; + size_t n_subscripts; + char *superscript; const struct pivot_footnote **footnotes; diff --git a/src/output/table-provider.h b/src/output/table-provider.h index d961d43e10..711f84d6b3 100644 --- a/src/output/table-provider.h +++ b/src/output/table-provider.h @@ -55,11 +55,11 @@ struct table_cell unsigned int options; /* TAB_*. */ char *text; /* A paragraph of text. */ - - /* Optional footnote(s). */ + char **subscripts; + size_t n_subscripts; + char *superscript; const struct footnote **footnotes; size_t n_footnotes; - const struct area_style *style; }; diff --git a/src/output/table.c b/src/output/table.c index b1707a84d8..01ab1bffc0 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -580,14 +580,11 @@ add_joined_cell (struct table *table, int x1, int y1, int x2, int y2, x1, y1, x2, y2); struct table_cell *cell = pool_alloc (table->container, sizeof *cell); - cell->d[TABLE_HORZ][0] = x1; - cell->d[TABLE_VERT][0] = y1; - cell->d[TABLE_HORZ][1] = ++x2; - cell->d[TABLE_VERT][1] = ++y2; - cell->options = opt; - cell->footnotes = NULL; - cell->n_footnotes = 0; - cell->style = NULL; + *cell = (struct table_cell) { + .d = { [TABLE_HORZ] = { x1, ++x2 }, + [TABLE_VERT] = { y1, ++y2 } }, + .options = opt, + }; void **cc = &table->cc[x1 + y1 * table_nc (table)]; unsigned short *ct = &table->ct[x1 + y1 * table_nc (table)]; @@ -639,6 +636,29 @@ get_joined_cell (struct table *table, int x, int y) return cell; } +/* Sets the subscripts for column X, row Y in TABLE. */ +void +table_add_subscripts (struct table *table, int x, int y, + char **subscripts, size_t n_subscripts) +{ + struct table_cell *cell = get_joined_cell (table, x, y); + + cell->n_subscripts = n_subscripts; + cell->subscripts = pool_nalloc (table->container, n_subscripts, + sizeof *cell->subscripts); + for (size_t i = 0; i < n_subscripts; i++) + cell->subscripts[i] = pool_strdup (table->container, subscripts[i]); +} + +/* Sets the superscript for column X, row Y in TABLE. */ +void +table_add_superscript (struct table *table, int x, int y, + const char *superscript) +{ + get_joined_cell (table, x, y)->superscript + = pool_strdup (table->container, superscript); +} + /* Create a footnote in TABLE with MARKER (e.g. "a") as its marker and CONTENT as its content. The footnote will be styled as STYLE, which is mandatory. IDX must uniquely identify the footnote within TABLE. diff --git a/src/output/table.h b/src/output/table.h index 9f84f5dd3f..0cdeef1b4e 100644 --- a/src/output/table.h +++ b/src/output/table.h @@ -283,10 +283,14 @@ void table_text (struct table *, int c, int r, unsigned opt, const char *); void table_text_format (struct table *, int c, int r, unsigned opt, const char *, ...) PRINTF_FORMAT (5, 6); - void table_joint_text (struct table *, int x1, int y1, int x2, int y2, unsigned opt, const char *); +void table_add_subscripts (struct table *, int x, int y, + char **subscripts, size_t n_subscripts); +void table_add_superscript (struct table *, int x, int y, + const char *superscript); + /* Footnotes. Use table_create_footnote() to create the footnotes themselves, then use