From d3fef25674baf4f4e25502f257c680b5090535c6 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 13 Sep 2014 16:41:08 -0700 Subject: [PATCH] output: Add footnote support. The initials support for footnotes does not allow the client to specify the markers and does not merge footnotes with the same text so that they have the same markers. It might be necessary to implement one or both of those features. The footnote feature doesn't have any users yet, outside of the tests. --- Smake | 1 + src/output/ascii.c | 73 ++++++++--- src/output/cairo.c | 148 ++++++++++++++++++---- src/output/csv.c | 61 ++++++++- src/output/html.c | 72 ++++++++++- src/output/odt.c | 87 +++++++++---- src/output/render.c | 224 ++++++++++++++++++++++++++++------ src/output/render.h | 38 +++++- src/output/tab.c | 35 ++++++ src/output/tab.h | 3 + src/output/table-casereader.c | 3 +- src/output/table-provider.h | 4 + src/output/table.c | 2 + tests/output/render-test.c | 29 ++++- tests/output/render.at | 42 +++++++ 15 files changed, 709 insertions(+), 113 deletions(-) diff --git a/Smake b/Smake index d8ddb389fa..b27456bced 100644 --- a/Smake +++ b/Smake @@ -76,6 +76,7 @@ GNULIB_MODULES = \ stpcpy \ strerror \ strftime \ + strsep \ strtod \ strtok_r \ sys_stat \ diff --git a/src/output/ascii.c b/src/output/ascii.c index 76fb51544a..562cd72ea7 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -197,11 +197,11 @@ static bool ascii_open_page (struct ascii_driver *); static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2], enum render_line_style styles[TABLE_N_AXES][2]); static void ascii_measure_cell_width (void *, const struct table_cell *, - int *min, int *max); + int footnote_idx, int *min, int *max); static int ascii_measure_cell_height (void *, const struct table_cell *, - int width); + int footnote_idx, int width); static void ascii_draw_cell (void *, const struct table_cell *, - int bb[TABLE_N_AXES][2], + int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); static void @@ -569,6 +569,7 @@ static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1, int n); static void ascii_layout_cell (struct ascii_driver *, const struct table_cell *, + int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *width, int *height); @@ -609,7 +610,7 @@ ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2], static void ascii_measure_cell_width (void *a_, const struct table_cell *cell, - int *min_width, int *max_width) + int footnote_idx, int *min_width, int *max_width) { struct ascii_driver *a = a_; int bb[TABLE_N_AXES][2]; @@ -621,21 +622,23 @@ ascii_measure_cell_width (void *a_, const struct table_cell *cell, bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - ascii_layout_cell (a, cell, bb, clip, max_width, &h); + ascii_layout_cell (a, cell, footnote_idx, bb, clip, max_width, &h); if (cell->n_contents != 1 || cell->contents[0].table + || cell->contents[0].n_footnotes || strchr (cell->contents[0].text, ' ')) { bb[H][1] = 1; - ascii_layout_cell (a, cell, bb, clip, min_width, &h); + ascii_layout_cell (a, cell, footnote_idx, bb, clip, min_width, &h); } else *min_width = *max_width; } static int -ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width) +ascii_measure_cell_height (void *a_, const struct table_cell *cell, + int footnote_idx, int width) { struct ascii_driver *a = a_; int bb[TABLE_N_AXES][2]; @@ -647,18 +650,18 @@ ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width) bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - ascii_layout_cell (a, cell, bb, clip, &w, &h); + ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h); return h; } static void -ascii_draw_cell (void *a_, const struct table_cell *cell, +ascii_draw_cell (void *a_, const struct table_cell *cell, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) { struct ascii_driver *a = a_; int w, h; - ascii_layout_cell (a, cell, bb, clip, &w, &h); + ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h); } static char * @@ -799,22 +802,47 @@ text_draw (struct ascii_driver *a, unsigned int options, static int ascii_layout_cell_text (struct ascii_driver *a, - const struct cell_contents *contents, + const struct cell_contents *contents, int *footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *widthp) { - size_t length = strlen (contents->text); + size_t length; + const char *text; char *breaks; int bb_width; size_t pos; int y; y = bb[V][0]; - if (length == 0) - return y; + length = strlen (contents->text); + if (contents->n_footnotes) + { + struct string s; + int i; + + ds_init_empty (&s); + ds_extend (&s, length + contents->n_footnotes * 4); + ds_put_cstr (&s, contents->text); + for (i = 0; i < contents->n_footnotes; i++) + { + char marker[10]; + + str_format_26adic (++*footnote_idx, false, marker, sizeof marker); + ds_put_format (&s, "[%s]", marker); + } + + length = ds_length (&s); + text = ds_steal_cstr (&s); + } + else + { + if (length == 0) + return y; + text = contents->text; + } breaks = xmalloc (length + 1); - u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length, + u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length, "UTF-8", breaks); breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE); @@ -823,7 +851,7 @@ ascii_layout_cell_text (struct ascii_driver *a, bb_width = bb[H][1] - bb[H][0]; for (y = bb[V][0]; y < bb[V][1] && pos < length; y++) { - const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos); + const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos); const char *b = breaks + pos; size_t n = length - pos; @@ -890,6 +918,8 @@ ascii_layout_cell_text (struct ascii_driver *a, } free (breaks); + if (text != contents->text) + free (CONST_CAST (char *, text)); return y; } @@ -897,6 +927,7 @@ ascii_layout_cell_text (struct ascii_driver *a, static int ascii_layout_subtable (struct ascii_driver *a, const struct cell_contents *contents, + int *footnote_idx UNUSED /* XXX */, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED, int *widthp) { @@ -958,6 +989,7 @@ ascii_layout_subtable (struct ascii_driver *a, static void ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, + int footnote_idx, int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *widthp, int *heightp) { @@ -981,9 +1013,11 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, } if (contents->text) - bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp); + bb[V][0] = ascii_layout_cell_text (a, contents, &footnote_idx, + bb, clip, widthp); else - bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp); + bb[V][0] = ascii_layout_subtable (a, contents, &footnote_idx, + bb, clip, widthp); } *heightp = bb[V][0] - bb_[V][0]; } @@ -1005,6 +1039,7 @@ ascii_test_write (struct output_driver *driver, contents.options = options | TAB_LEFT; contents.text = CONST_CAST (char *, s); contents.table = NULL; + contents.n_footnotes = 0; memset (&cell, 0, sizeof cell); cell.contents = &contents; @@ -1015,7 +1050,7 @@ ascii_test_write (struct output_driver *driver, bb[TABLE_VERT][0] = y; bb[TABLE_VERT][1] = a->length; - ascii_layout_cell (a, &cell, bb, bb, &width, &height); + ascii_layout_cell (a, &cell, 0, bb, bb, &width, &height); a->y = 1; } diff --git a/src/output/cairo.c b/src/output/cairo.c index 0f92ce9fe6..fa9b2e1872 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -89,6 +89,7 @@ enum xr_font_type XR_FONT_PROPORTIONAL, XR_FONT_EMPHASIS, XR_FONT_FIXED, + XR_FONT_MARKER, XR_N_FONTS }; @@ -131,6 +132,8 @@ struct xr_driver int line_space; /* Space between lines. */ int line_width; /* Width of lines. */ + int cell_margin; + int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ struct xr_color bg; /* Background color */ @@ -157,13 +160,13 @@ static void xr_driver_run_fsm (struct xr_driver *); static void xr_draw_line (void *, int bb[TABLE_N_AXES][2], enum render_line_style styles[TABLE_N_AXES][2]); static void xr_measure_cell_width (void *, const struct table_cell *, - int *min, int *max); + int footnote_idx, int *min, int *max); static int xr_measure_cell_height (void *, const struct table_cell *, - int width); -static void xr_draw_cell (void *, const struct table_cell *, + int footnote_idx, int width); +static void xr_draw_cell (void *, const struct table_cell *, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); -static int xr_adjust_break (void *, const struct table_cell *, +static int xr_adjust_break (void *, const struct table_cell *, int footnote_idx, int width, int height); static struct xr_render_fsm *xr_render_output_item ( @@ -280,8 +283,10 @@ apply_options (struct xr_driver *xr, struct string_map *o) "serif", font_size); xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font", "serif italic", font_size); + xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "serif", + font_size * PANGO_SCALE_X_SMALL); - xr->line_gutter = parse_dimension (opt (d, o, "gutter", "3pt")) * scale; + xr->line_gutter = 0; xr->line_space = XR_POINT; xr->line_width = XR_POINT / 2; xr->page_number = 0; @@ -363,6 +368,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) xr->char_width = MAX (xr->char_width, pango_to_xr (char_width)); xr->char_height = MAX (xr->char_height, pango_to_xr (char_height)); } + xr->cell_margin = xr->char_width; if (xr->params == NULL) { @@ -627,7 +633,7 @@ xr_driver_run_fsm (struct xr_driver *xr) } static void -xr_layout_cell (struct xr_driver *, const struct table_cell *, +xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *width, int *height, int *brk); @@ -805,7 +811,7 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2], static void xr_measure_cell_width (void *xr_, const struct table_cell *cell, - int *min_width, int *max_width) + int footnote_idx, int *min_width, int *max_width) { struct xr_driver *xr = xr_; int bb[TABLE_N_AXES][2]; @@ -817,14 +823,20 @@ xr_measure_cell_width (void *xr_, const struct table_cell *cell, bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL); bb[H][1] = 1; - xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL); + + if (*min_width > 0) + *min_width += xr->cell_margin * 2; + if (*max_width > 0) + *max_width += xr->cell_margin * 2; } static int -xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width) +xr_measure_cell_height (void *xr_, const struct table_cell *cell, + int footnote_idx, int width) { struct xr_driver *xr = xr_; int bb[TABLE_N_AXES][2]; @@ -832,26 +844,32 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width) int w, h; bb[H][0] = 0; - bb[H][1] = width; + bb[H][1] = width - xr->cell_margin * 2; + if (bb[H][1] <= 0) + return 0; bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL); return h; } static void -xr_draw_cell (void *xr_, const struct table_cell *cell, +xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) { struct xr_driver *xr = xr_; int w, h, brk; - xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); + bb[H][0] += xr->cell_margin; + bb[H][1] -= xr->cell_margin; + if (bb[H][0] >= bb[H][1]) + return; + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk); } static int -xr_adjust_break (void *xr_, const struct table_cell *cell, +xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx, int width, int height) { struct xr_driver *xr = xr_; @@ -859,15 +877,17 @@ xr_adjust_break (void *xr_, const struct table_cell *cell, int clip[TABLE_N_AXES][2]; int w, h, brk; - if (xr_measure_cell_height (xr_, cell, width) < height) + if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height) return -1; bb[H][0] = 0; - bb[H][1] = width; + bb[H][1] = width - 2 * xr->cell_margin; + if (bb[H][1] <= 0) + return 0; bb[V][0] = 0; bb[V][1] = height; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk); return brk; } @@ -886,21 +906,99 @@ 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) +{ + attr->start_index = start_index; + pango_attr_list_insert (list, attr); +} + static int xr_layout_cell_text (struct xr_driver *xr, - const struct cell_contents *contents, + const struct cell_contents *contents, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int y, int *widthp, int *brk) { unsigned int options = contents->options; struct xr_font *font; + bool merge_footnotes; + size_t length; int w, h; + if (contents->n_footnotes == 0) + merge_footnotes = false; + else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT) + { + PangoAttrList *attrs; + char marker[16]; + + font = &xr->fonts[XR_FONT_MARKER]; + + str_format_26adic (footnote_idx + 1, false, marker, sizeof marker); + pango_layout_set_text (font->layout, marker, strlen (marker)); + + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_rise_new (7000)); + pango_layout_set_attributes (font->layout, attrs); + pango_attr_list_unref (attrs); + + pango_layout_get_size (font->layout, &w, &h); + merge_footnotes = w > xr->cell_margin; + if (!merge_footnotes && clip[H][0] != clip[H][1]) + { + cairo_save (xr->cairo); + xr_clip (xr, clip); + cairo_translate (xr->cairo, + xr_to_pt (bb[H][1] + xr->x), + xr_to_pt (y + xr->y)); + pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT); + pango_layout_set_width (font->layout, -1); + pango_cairo_show_layout (xr->cairo, font->layout); + cairo_restore (xr->cairo); + } + + pango_layout_set_attributes (font->layout, NULL); + } + else + merge_footnotes = true; + font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] : &xr->fonts[XR_FONT_PROPORTIONAL]); - pango_layout_set_text (font->layout, contents->text, -1); + length = strlen (contents->text); + if (merge_footnotes) + { + PangoAttrList *attrs; + struct string s; + size_t i; + + bb[H][1] += xr->cell_margin; + + ds_init_empty (&s); + ds_extend (&s, length + contents->n_footnotes * 10); + ds_put_cstr (&s, contents->text); + for (i = 0; i < contents->n_footnotes; i++) + { + char marker[16]; + + if (i > 0) + ds_put_byte (&s, ','); + str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker); + ds_put_cstr (&s, marker); + } + pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s)); + ds_destroy (&s); + + attrs = pango_attr_list_new (); + add_attr_with_start (attrs, pango_attr_rise_new (7000), length); + add_attr_with_start ( + attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length); + pango_layout_set_attributes (font->layout, attrs); + pango_attr_list_unref (attrs); + } + else + pango_layout_set_text (font->layout, contents->text, -1); pango_layout_set_alignment ( font->layout, @@ -998,12 +1096,15 @@ xr_layout_cell_text (struct xr_driver *xr, } } } + + pango_layout_set_attributes (font->layout, NULL); return y + h; } static int xr_layout_cell_subtable (struct xr_driver *xr, const struct cell_contents *contents, + int footnote_idx UNUSED, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *widthp, int *brk) { @@ -1077,6 +1178,7 @@ xr_layout_cell_subtable (struct xr_driver *xr, static void xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, + int footnote_idx, int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *width, int *height, int *brk) { @@ -1123,10 +1225,12 @@ xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, } if (contents->text) - bb[V][0] = xr_layout_cell_text (xr, contents, bb, clip, + bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip, bb[V][0], width, brk); else - bb[V][0] = xr_layout_cell_subtable (xr, contents, bb, clip, width, brk); + bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx, + bb, clip, width, brk); + footnote_idx += contents->n_footnotes; } *height = bb[V][0] - bb_[V][0]; } diff --git a/src/output/csv.c b/src/output/csv.c index bd61c51a62..d2f2ac3b63 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -261,6 +261,7 @@ csv_submit (struct output_driver *driver, struct table_item *table_item = to_table_item (output_item); const char *caption = table_item_get_caption (table_item); const struct table *t = table_item_get_table (table_item); + int footnote_idx; int x, y; csv_put_separator (csv); @@ -271,6 +272,7 @@ csv_submit (struct output_driver *driver, putc ('\n', csv->file); } + footnote_idx = 0; for (y = 0; y < table_nr (t); y++) { for (x = 0; x < table_nc (t); x++) @@ -284,7 +286,9 @@ 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.n_contents == 1 && cell.contents[0].text != NULL) + else if (cell.n_contents == 1 + && cell.contents[0].text != NULL + && cell.contents[0].n_footnotes == 0) csv_output_field (csv, cell.contents[0].text); else { @@ -294,13 +298,25 @@ csv_submit (struct output_driver *driver, ds_init_empty (&s); for (i = 0; i < cell.n_contents; i++) { + const struct cell_contents *c = &cell.contents[i]; + int j; + if (i > 0) ds_put_cstr (&s, "\n\n"); - if (cell.contents[i].text != NULL) - ds_put_cstr (&s, cell.contents[i].text); + if (c->text != NULL) + ds_put_cstr (&s, c->text); else - csv_output_subtable (csv, &s, cell.contents[i].table); + csv_output_subtable (csv, &s, c->table); + + for (j = 0; j < c->n_footnotes; j++) + { + char marker[16]; + + str_format_26adic (++footnote_idx, false, + marker, sizeof marker); + ds_put_format (&s, "[%s]", marker); + } } csv_output_field (csv, ds_cstr (&s)); ds_destroy (&s); @@ -310,6 +326,43 @@ csv_submit (struct output_driver *driver, } putc ('\n', csv->file); } + + if (footnote_idx) + { + size_t i; + + fputs ("\nFootnotes:\n", csv->file); + + footnote_idx = 0; + for (y = 0; y < table_nr (t); y++) + { + struct table_cell cell; + for (x = 0; x < table_nc (t); x = cell.d[TABLE_HORZ][1]) + { + table_get_cell (t, x, y, &cell); + + if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0]) + for (i = 0; i < cell.n_contents; i++) + { + const struct cell_contents *c = &cell.contents[i]; + int j; + + for (j = 0; j < c->n_footnotes; j++) + { + char marker[16]; + + str_format_26adic (++footnote_idx, false, + marker, sizeof marker); + csv_output_field (csv, marker); + fputs (csv->separator, csv->file); + csv_output_field (csv, c->footnotes[j]); + putc ('\n', csv->file); + } + } + table_cell_free (&cell); + } + } + } } else if (is_text_item (output_item)) { diff --git a/src/output/html.c b/src/output/html.c index 82478d189b..19d1085e41 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -378,9 +378,55 @@ html_output_table (struct html_driver *html, const struct table_item *item) { const struct table *t = table_item_get_table (item); const char *caption = table_item_get_caption (item); - int x, y; + int footnote_idx = 0; + int y; - fputs ("\n", html->file); + fputs ("
", html->file); + + footnote_idx = 0; + for (y = 0; y < table_nr (t); y++) + { + int x; + + for (x = 0; x < table_nc (t); ) + { + const struct cell_contents *c; + struct table_cell cell; + + table_get_cell (t, x, y, &cell); + if (y != cell.d[TABLE_VERT][0]) + continue; + + for (c = cell.contents; c < &cell.contents[cell.n_contents]; c++) + { + int i; + + for (i = 0; i < c->n_footnotes; i++) + { + char marker[16]; + + if (!footnote_idx) + fprintf (html->file, "\n", html->file); + footnote_idx = 0; + } + + fputs ("\n", html->file); if (caption != NULL) { @@ -391,8 +437,10 @@ html_output_table (struct html_driver *html, const struct table_item *item) for (y = 0; y < table_nr (t); y++) { + int x; + fputs (" \n", html->file); - for (x = 0; x < table_nc (t); x++) + for (x = 0; x < table_nc (t); ) { const struct cell_contents *c; struct table_cell cell; @@ -467,6 +515,7 @@ html_output_table (struct html_driver *html, const struct table_item *item) if (c->text) { const char *s = c->text; + int i; if (c->options & TAB_EMPH) fputs ("", html->file); @@ -483,6 +532,22 @@ html_output_table (struct html_driver *html, const struct table_item *item) } if (c->options & TAB_EMPH) fputs ("", html->file); + + if (c->n_footnotes > 0) + { + fputs ("", html->file); + for (i = 0; i < c->n_footnotes; i++) + { + char marker[16]; + + if (i > 0) + putc (',', html->file); + str_format_26adic (++footnote_idx, false, + marker, sizeof marker); + fputs (marker, html->file); + } + fputs ("", html->file); + } } else html_output_table (html, c->table); @@ -491,6 +556,7 @@ html_output_table (struct html_driver *html, const struct table_item *item) /* Output or . */ fprintf (html->file, "\n", tag); + x = cell.d[TABLE_HORZ][1]; table_cell_free (&cell); } fputs (" \n", html->file); diff --git a/src/output/odt.c b/src/output/odt.c index 0d4625c8d4..d0860585cd 100644 --- a/src/output/odt.c +++ b/src/output/odt.c @@ -71,6 +71,9 @@ struct odt_driver /* Name of current command. */ char *command_name; + + /* Number of footnotes so far. */ + int n_footnotes; }; static const struct output_driver_class odt_driver_class; @@ -386,30 +389,67 @@ odt_destroy (struct output_driver *driver) } static void -write_xml_with_line_breaks (xmlTextWriterPtr writer, char *line) +write_xml_with_line_breaks (struct odt_driver *odt, const char *line_) { - char *newline; - char *p; + xmlTextWriterPtr writer = odt->content_wtr; - for (p = line; *p; p = newline + 1) + if (!strchr (line_, '\n')) + xmlTextWriterWriteString (writer, _xml(line_)); + else { - newline = strchr (p, '\n'); + char *line = xstrdup (line_); + char *newline; + char *p; - if (!newline) + for (p = line; *p; p = newline + 1) { + newline = strchr (p, '\n'); + + if (!newline) + { + xmlTextWriterWriteString (writer, _xml(p)); + free (line); + return; + } + + if (newline > p && newline[-1] == '\r') + newline[-1] = '\0'; + else + *newline = '\0'; xmlTextWriterWriteString (writer, _xml(p)); - return; + xmlTextWriterWriteElement (writer, _xml("text:line-break"), _xml("")); } - - if (newline > p && newline[-1] == '\r') - newline[-1] = '\0'; - else - *newline = '\0'; - xmlTextWriterWriteString (writer, _xml(p)); - xmlTextWriterWriteElement (writer, _xml("text:line-break"), _xml("")); } } +static void +write_footnote (struct odt_driver *odt, const char *footnote) +{ + char marker[16]; + + xmlTextWriterStartElement (odt->content_wtr, _xml("text:note")); + xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:note-class"), + _xml("footnote")); + + xmlTextWriterStartElement (odt->content_wtr, _xml("text:note-citation")); + str_format_26adic (++odt->n_footnotes, false, marker, sizeof marker); + if (strlen (marker) > 1) + xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("text:label"), + "(%s)", marker); + else + xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:label"), + _xml(marker)); + xmlTextWriterEndElement (odt->content_wtr); + + xmlTextWriterStartElement (odt->content_wtr, _xml("text:note-body")); + xmlTextWriterStartElement (odt->content_wtr, _xml("text:p")); + write_xml_with_line_breaks (odt, footnote); + xmlTextWriterEndElement (odt->content_wtr); + xmlTextWriterEndElement (odt->content_wtr); + + xmlTextWriterEndElement (odt->content_wtr); +} + static void write_table (struct odt_driver *odt, const struct table_item *item) { @@ -480,6 +520,7 @@ write_table (struct odt_driver *odt, const struct table_item *item) for (i = 0; i < cell.n_contents; i++) { const struct cell_contents *contents = &cell.contents[i]; + int j; if (contents->text) { @@ -490,22 +531,16 @@ write_table (struct odt_driver *odt, const struct table_item *item) else xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents")); - if (strchr (contents->text, '\n')) - { - char *line = xstrdup (contents->text); - write_xml_with_line_breaks (odt->content_wtr, line); - free (line); - } - else - xmlTextWriterWriteString (odt->content_wtr, _xml(contents->text)); + write_xml_with_line_breaks (odt, contents->text); + + for (j = 0; j < contents->n_footnotes; j++) + write_footnote (odt, contents->footnotes[j]); xmlTextWriterEndElement (odt->content_wtr); /* text:p */ } else if (contents->table) - { - write_table (odt, contents->table); - continue; - } + write_table (odt, contents->table); + } xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */ } diff --git a/src/output/render.c b/src/output/render.c index 734914dbf5..f41f504df0 100644 --- a/src/output/render.c +++ b/src/output/render.c @@ -26,6 +26,7 @@ #include "libpspp/hash-functions.h" #include "libpspp/hmap.h" #include "output/render.h" +#include "output/tab.h" #include "output/table-item.h" #include "output/table.h" @@ -90,6 +91,15 @@ struct render_page entire page can overflow on all four sides!) */ struct hmap overflows; + /* Contains "struct render_footnote"s, one for each cell with one or more + footnotes. + + 'n_footnotes' is the number of footnotes in the table. There might be + more than hmap_count(&page->footnotes) because there can be more than + one footnote in a cell. */ + struct hmap footnotes; + size_t n_footnotes; + /* If a single column (or row) is too wide (or tall) to fit on a page reasonably, then render_break_next() will split a single row or column across multiple render_pages. This member indicates when this has @@ -301,9 +311,9 @@ struct render_overflow int overflow[TABLE_N_AXES][2]; }; -/* Returns a hash value for (X,Y). */ +/* Returns a hash value for (,Y). */ static unsigned int -hash_overflow (int x, int y) +hash_cell (int x, int y) { return hash_int (x + (y << 16), 0); } @@ -318,7 +328,7 @@ find_overflow (const struct render_page *page, int x, int y) const struct render_overflow *of; HMAP_FOR_EACH_WITH_HASH (of, struct render_overflow, node, - hash_overflow (x, y), &page->overflows) + hash_cell (x, y), &page->overflows) if (x == of->d[H] && y == of->d[V]) return of; } @@ -326,6 +336,55 @@ find_overflow (const struct render_page *page, int x, int y) return NULL; } +/* A footnote. */ +struct render_footnote + { + struct hmap_node node; + + /* The area of the table covered by the cell that has the footnote. + + d[H][0] is the leftmost column. + d[H][1] is the rightmost column, plus 1. + d[V][0] is the top row. + d[V][1] is the bottom row, plus 1. + + The cell in its original table might occupy a larger region. This + member reflects the size of the cell in the current render_page, after + trimming off any rows or columns due to page-breaking. */ + int d[TABLE_N_AXES][2]; + + /* The index of the first footnote in the cell. */ + int idx; + }; + +static int +count_footnotes (const struct table_cell *cell) +{ + size_t i; + int n; + + n = 0; + for (i = 0; i < cell->n_contents; i++) + n += cell->contents[i].n_footnotes; + return n; +} + +static int +find_footnote_idx (const struct table_cell *cell, const struct hmap *footnotes) +{ + const struct render_footnote *f; + + if (!count_footnotes (cell)) + return 0; + + HMAP_FOR_EACH_WITH_HASH (f, struct render_footnote, node, + hash_cell (cell->d[H][0], cell->d[V][0]), footnotes) + if (f->d[H][0] == cell->d[H][0] && f->d[V][0] == cell->d[V][0]) + return f->idx; + + NOT_REACHED (); +} + /* Row or column dimensions. Used to figure the size of a table in render_page_create() and discarded after that. */ struct render_row @@ -529,6 +588,8 @@ render_page_allocate (const struct render_params *params, } hmap_init (&page->overflows); + hmap_init (&page->footnotes); + page->n_footnotes = 0; memset (page->is_edge_cutoff, 0, sizeof page->is_edge_cutoff); return page; @@ -630,6 +691,8 @@ render_page_create (const struct render_params *params, struct render_row *rows; int table_widths[2]; int *rules[TABLE_N_AXES]; + struct hmap footnotes; + int footnote_idx; int nr, nc; int x, y; int i; @@ -651,7 +714,9 @@ render_page_create (const struct render_params *params, } /* Calculate minimum and maximum widths of cells that do not - span multiple columns. */ + span multiple columns. Assign footnote markers. */ + hmap_init (&footnotes); + footnote_idx = 0; for (i = 0; i < 2; i++) columns[i] = xzalloc (nc * sizeof *columns[i]); for (y = 0; y < nr; y++) @@ -660,15 +725,35 @@ render_page_create (const struct render_params *params, struct table_cell cell; table_get_cell (table, x, y, &cell); - if (y == cell.d[V][0] && table_cell_colspan (&cell) == 1) + if (y == cell.d[V][0]) { - int w[2]; - int i; + int n; - params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]); - for (i = 0; i < 2; i++) - if (columns[i][x].unspanned < w[i]) - columns[i][x].unspanned = w[i]; + if (table_cell_colspan (&cell) == 1) + { + int w[2]; + int i; + + params->measure_cell_width (params->aux, &cell, footnote_idx, + &w[MIN], &w[MAX]); + for (i = 0; i < 2; i++) + if (columns[i][x].unspanned < w[i]) + columns[i][x].unspanned = w[i]; + } + + n = count_footnotes (&cell); + if (n > 0) + { + struct render_footnote *f = xmalloc (sizeof *f); + f->d[H][0] = cell.d[H][0]; + f->d[H][1] = cell.d[H][1]; + f->d[V][0] = cell.d[V][0]; + f->d[V][1] = cell.d[V][1]; + f->idx = footnote_idx; + hmap_insert (&footnotes, &f->node, hash_cell (x, y)); + + footnote_idx += n; + } } x = cell.d[H][1]; table_cell_free (&cell); @@ -688,7 +773,9 @@ render_page_create (const struct render_params *params, { int w[2]; - params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]); + params->measure_cell_width (params->aux, &cell, + find_footnote_idx (&cell, &footnotes), + &w[MIN], &w[MAX]); for (i = 0; i < 2; i++) distribute_spanned_width (w[i], &columns[i][cell.d[H][0]], rules[H], table_cell_colspan (&cell)); @@ -737,7 +824,8 @@ render_page_create (const struct render_params *params, if (table_cell_rowspan (&cell) == 1) { int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); - int h = params->measure_cell_height (params->aux, &cell, w); + int h = params->measure_cell_height ( + params->aux, &cell, find_footnote_idx (&cell, &footnotes), w); if (h > r->unspanned) r->unspanned = r->width = h; } @@ -764,7 +852,8 @@ render_page_create (const struct render_params *params, if (y == cell.d[V][0] && table_cell_rowspan (&cell) > 1) { int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); - int h = params->measure_cell_height (params->aux, &cell, w); + int h = params->measure_cell_height ( + params->aux, &cell, find_footnote_idx (&cell, &footnotes), w); distribute_spanned_width (h, &rows[cell.d[V][0]], rules[V], table_cell_rowspan (&cell)); } @@ -789,6 +878,10 @@ render_page_create (const struct render_params *params, } } + hmap_swap (&page->footnotes, &footnotes); + hmap_destroy (&footnotes); + page->n_footnotes = footnote_idx; + free (rules[H]); free (rules[V]); @@ -957,7 +1050,8 @@ render_cell (const struct render_page *page, const int ofs[TABLE_N_AXES], } } - page->params->draw_cell (page->params->aux, cell, bb, clip); + page->params->draw_cell (page->params->aux, cell, + find_footnote_idx (cell, &page->footnotes), bb, clip); } /* Draws the cells of PAGE indicated in BB. */ @@ -1216,7 +1310,8 @@ render_break_next (struct render_break *b, int size) table_get_cell (page->table, x, z, &cell); w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); better_pixel = page->params->adjust_break ( - page->params->aux, &cell, w, pixel); + page->params->aux, &cell, + find_footnote_idx (&cell, &page->footnotes), w, pixel); x = cell.d[H][1]; table_cell_free (&cell); @@ -1311,21 +1406,22 @@ cell_is_breakable (const struct render_break *b, int cell) struct render_pager { - int width; + const struct render_params *params; + struct render_page **pages; - size_t cur_page, n_pages; + size_t n_pages, allocated_pages; + + size_t cur_page; struct render_break x_break; struct render_break y_break; }; static void -render_pager_add_table (struct render_pager *p, struct table *table, - const struct render_params *params, - size_t *allocated_pages) +render_pager_add_table (struct render_pager *p, struct table *table) { - if (p->n_pages >= *allocated_pages) - p->pages = x2nrealloc (p->pages, allocated_pages, sizeof *p->pages); - p->pages[p->n_pages++] = render_page_create (params, table); + if (p->n_pages >= p->allocated_pages) + p->pages = x2nrealloc (p->pages, &p->allocated_pages, sizeof *p->pages); + p->pages[p->n_pages++] = render_page_create (p->params, table); } static void @@ -1335,6 +1431,52 @@ render_pager_start_page (struct render_pager *p) render_break_init_empty (&p->y_break); } +static void +add_footnote_page (struct render_pager *p, const struct render_page *body) +{ + const struct table *table = body->table; + int nc = table_nc (table); + int nr = table_nr (table); + int footnote_idx = 0; + struct tab_table *t; + int x, y; + + if (!body->n_footnotes) + return; + + t = tab_create (2, body->n_footnotes); + for (y = 0; y < nr; y++) + for (x = 0; x < nc; ) + { + struct table_cell cell; + + table_get_cell (table, x, y, &cell); + if (y == cell.d[V][0]) + { + size_t i; + + for (i = 0; i < cell.n_contents; i++) + { + const struct cell_contents *cc = &cell.contents[i]; + size_t j; + + for (j = 0; j < cc->n_footnotes; j++) + { + const char *f = cc->footnotes[j]; + + tab_text (t, 0, footnote_idx, TAB_LEFT, ""); + tab_footnote (t, 0, footnote_idx, "(none)"); + tab_text (t, 1, footnote_idx, TAB_LEFT, f); + footnote_idx++; + } + } + } + x = cell.d[H][1]; + table_cell_free (&cell); + } + render_pager_add_table (p, &t->table); +} + /* Creates and returns a new render_pager for rendering TABLE_ITEM on the device with the given PARAMS. */ struct render_pager * @@ -1342,18 +1484,16 @@ render_pager_create (const struct render_params *params, const struct table_item *table_item) { struct render_pager *p; - size_t allocated_pages = 0; const char *caption; p = xzalloc (sizeof *p); - p->width = params->size[H]; + p->params = params; caption = table_item_get_caption (table_item); if (caption) - render_pager_add_table (p, table_from_string (TAB_LEFT, caption), params, - &allocated_pages); - render_pager_add_table (p, table_ref (table_item_get_table (table_item)), params, - &allocated_pages); + render_pager_add_table (p, table_from_string (TAB_LEFT, caption)); + render_pager_add_table (p, table_ref (table_item_get_table (table_item))); + add_footnote_page (p, p->pages[p->n_pages - 1]); render_pager_start_page (p); @@ -1400,7 +1540,7 @@ render_pager_has_next (const struct render_pager *p_) } else render_break_init (&p->y_break, - render_break_next (&p->x_break, p->width), V); + render_break_next (&p->x_break, p->params->size[H]), V); } return true; } @@ -1524,8 +1664,8 @@ static struct render_overflow *insert_overflow (struct render_page_selection *, const struct table_cell *); /* Creates and returns a new render_page whose contents are a subregion of - PAGE's contents. The new render_page includes cells Z0 through Z1 along - AXIS, plus any headers on AXIS. + PAGE's contents. The new render_page includes cells Z0 through Z1 + (exclusive) along AXIS, plus any headers on AXIS. If P0 is nonzero, then it is a number of pixels to exclude from the left or top (according to AXIS) of cell Z0. Similarly, P1 is a number of pixels to @@ -1541,6 +1681,7 @@ static struct render_page * render_page_select (const struct render_page *page, enum table_axis axis, int z0, int p0, int z1, int p1) { + const struct render_footnote *f; struct render_page_selection s; enum table_axis a = axis; enum table_axis b = !a; @@ -1707,6 +1848,21 @@ render_page_select (const struct render_page *page, enum table_axis axis, table_cell_free (&cell); } + /* Copy footnotes from PAGE into subpage. */ + HMAP_FOR_EACH (f, struct render_footnote, node, &page->footnotes) + if ((f->d[a][0] >= z0 && f->d[a][0] < z1) + || (f->d[a][1] - 1 >= z0 && f->d[a][1] - 1 < z1)) + { + struct render_footnote *nf = xmalloc (sizeof *nf); + nf->d[a][0] = MAX (z0, f->d[a][0]) - z0 + page->h[a][0]; + nf->d[a][1] = MIN (z1, f->d[a][1]) - z0 + page->h[a][0]; + nf->d[b][0] = f->d[b][0]; + nf->d[b][1] = f->d[b][1]; + nf->idx = f->idx; + hmap_insert (&subpage->footnotes, &nf->node, + hash_cell (nf->d[H][0], nf->d[V][0])); + } + return subpage; } @@ -1759,7 +1915,7 @@ insert_overflow (struct render_page_selection *s, of = xzalloc (sizeof *of); cell_to_subpage (s, cell, of->d); hmap_insert (&s->subpage->overflows, &of->node, - hash_overflow (of->d[H], of->d[V])); + hash_cell (of->d[H], of->d[V])); old = find_overflow (s->page, cell->d[H][0], cell->d[V][0]); if (old != NULL) diff --git a/src/output/render.h b/src/output/render.h index 14f87b50c1..c3f852f80a 100644 --- a/src/output/render.h +++ b/src/output/render.h @@ -31,18 +31,49 @@ enum render_line_style RENDER_N_LINES }; +/* Parameters for rendering a table_item to a device. + + + Coordinate system + ================= + + The rendering code assumes that larger 'x' is to the right and larger 'y' + toward the bottom of the page. + + The rendering code assumes that the table being rendered has its upper left + corner at (0,0) in device coordinates. This is usually not the case from + the driver's perspective, so the driver should expect to apply its own + offset to coordinates passed to callback functions. + + + Callback functions + ================== + + For each of the callback functions, AUX is passed as the 'aux' member of the + render_params structure. + + The device is expected to transform numerical footnote index numbers into + footnote markers. The existing drivers use str_format_26adic() to transform + index 0 to "a", index 1 to "b", and so on. The FOOTNOTE_IDX supplied to + each function is the footnote index number for the first footnote in the + cell. If a cell contains more than one footnote, then the additional + footnote indexes increase sequentially, e.g. the second footnote has index + FOOTNOTE_IDX + 1. +*/ struct render_params { /* Measures CELL's width. Stores in *MIN_WIDTH the minimum width required to avoid splitting a single word across multiple lines (normally, this is the width of the longest word in the cell) and in *MAX_WIDTH the - minimum width required to avoid line breaks other than at new-lines. */ + minimum width required to avoid line breaks other than at new-lines. + */ void (*measure_cell_width) (void *aux, const struct table_cell *cell, + int footnote_idx, int *min_width, int *max_width); /* Returns the height required to render CELL given a width of WIDTH. */ int (*measure_cell_height) (void *aux, const struct table_cell *cell, - int width); + int footnote_idx, int width); /* Given that there is space measuring WIDTH by HEIGHT to render CELL, where HEIGHT is insufficient to render the entire height of the cell, @@ -55,7 +86,7 @@ struct render_params Optional. If NULL, the rendering engine assumes that all breakpoints are acceptable. */ int (*adjust_break) (void *aux, const struct table_cell *cell, - int width, int height); + int footnote_idx, int width, int height); /* Draws a generalized intersection of lines in the rectangle whose top-left corner is (BB[TABLE_HORZ][0], BB[TABLE_VERT][0]) and whose @@ -75,6 +106,7 @@ struct render_params of the cell that lies within CLIP should actually be drawn, although BB should used to determine the layout of the cell. */ void (*draw_cell) (void *aux, const struct table_cell *cell, + int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); /* Auxiliary data passed to each of the above functions. */ diff --git a/src/output/tab.c b/src/output/tab.c index bb6b82a2da..8ef077e38f 100644 --- a/src/output/tab.c +++ b/src/output/tab.c @@ -59,6 +59,9 @@ struct tab_joined_cell struct table_item *subtable; } u; + + size_t n_footnotes; + char **footnotes; }; static const struct table_class tab_table_class; @@ -544,6 +547,8 @@ add_joined_cell (struct tab_table *table, int x1, int y1, int x2, int y2, j->d[TABLE_VERT][0] = y1 + table->row_ofs; j->d[TABLE_HORZ][1] = ++x2 + table->col_ofs; j->d[TABLE_VERT][1] = ++y2 + table->row_ofs; + j->n_footnotes = 0; + j->footnotes = NULL; { void **cc = &table->cc[x1 + y1 * table->cf]; @@ -597,6 +602,32 @@ tab_joint_text_format (struct tab_table *table, int x1, int y1, int x2, int y2, add_joined_cell (table, x1, y1, x2, y2, opt)->u.text = s; } +void +tab_footnote (struct tab_table *table, int x, int y, const char *format, ...) +{ + int index = x + y * table->cf; + unsigned char opt = table->ct[index]; + struct tab_joined_cell *j; + va_list args; + + if (opt & TAB_JOIN) + j = table->cc[index]; + else + { + char *text = table->cc[index]; + + j = add_joined_cell (table, x, y, x, y, table->ct[index]); + j->u.text = text ? text : xstrdup (""); + } + + j->footnotes = xrealloc (j->footnotes, + (j->n_footnotes + 1) * sizeof *j->footnotes); + + va_start (args, format); + j->footnotes[j->n_footnotes++] = pool_vasprintf (table->container, format, args); + va_end (args); +} + static void subtable_unref (void *subtable) { @@ -752,6 +783,7 @@ tab_get_cell (const struct table *table, int x, int y, struct table_cell *cell) cell->inline_contents.options = opt; cell->inline_contents.table = NULL; + cell->inline_contents.n_footnotes = 0; cell->destructor = NULL; if (opt & TAB_JOIN) @@ -777,6 +809,9 @@ tab_get_cell (const struct table *table, int x, int y, struct table_cell *cell) cell->inline_contents.text = jc->u.text; } + cell->inline_contents.footnotes = jc->footnotes; + cell->inline_contents.n_footnotes = jc->n_footnotes; + cell->d[TABLE_HORZ][0] = jc->d[TABLE_HORZ][0]; cell->d[TABLE_HORZ][1] = jc->d[TABLE_HORZ][1]; cell->d[TABLE_VERT][0] = jc->d[TABLE_VERT][0]; diff --git a/src/output/tab.h b/src/output/tab.h index a973b0414b..76be9d4f65 100644 --- a/src/output/tab.h +++ b/src/output/tab.h @@ -143,6 +143,9 @@ void tab_joint_text_format (struct tab_table *, int x1, int y1, int x2, int y2, unsigned opt, const char *, ...) PRINTF_FORMAT (7, 8); +void tab_footnote (struct tab_table *, int x, int y, const char *format, ...) + PRINTF_FORMAT (4, 5); + void tab_subtable (struct tab_table *, int x1, int y1, int x2, int y2, unsigned opt, struct table_item *subtable); void tab_subtable_bare (struct tab_table *, int x1, int y1, int x2, int y2, diff --git a/src/output/table-casereader.c b/src/output/table-casereader.c index a6a370f1bd..485014dc81 100644 --- a/src/output/table-casereader.c +++ b/src/output/table-casereader.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2011, 2013 Free Software Foundation, Inc. + Copyright (C) 2009, 2011, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -114,6 +114,7 @@ table_casereader_get_cell (const struct table *t, int x, int y, cell->n_contents = 1; cell->inline_contents.options = TAB_RIGHT; cell->inline_contents.table = NULL; + cell->inline_contents.n_footnotes = 0; if (tc->heading != NULL) { if (y == 0) diff --git a/src/output/table-provider.h b/src/output/table-provider.h index 35a4b6cebb..b64410c227 100644 --- a/src/output/table-provider.h +++ b/src/output/table-provider.h @@ -27,6 +27,10 @@ struct cell_contents /* Exactly one of these must be nonnull. */ char *text; /* A paragraph of text. */ struct table_item *table; /* A table nested within the cell. */ + + /* Optional footnote(s). */ + char **footnotes; + size_t n_footnotes; }; /* A cell in a table. */ diff --git a/src/output/table.c b/src/output/table.c index 5dc5651fd9..b7afb8f81f 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -321,6 +321,7 @@ table_string_get_cell (const struct table *ts_, int x UNUSED, int y UNUSED, cell->inline_contents.options = ts->options; cell->inline_contents.text = ts->string; cell->inline_contents.table = NULL; + cell->inline_contents.n_footnotes = 0; cell->n_contents = 1; cell->destructor = NULL; } @@ -398,6 +399,7 @@ table_nested_get_cell (const struct table *tn_, int x UNUSED, int y UNUSED, cell->inline_contents.options = TAB_LEFT; cell->inline_contents.text = NULL; cell->inline_contents.table = tn->inner; + cell->inline_contents.n_footnotes = 0; cell->n_contents = 1; cell->destructor = NULL; } diff --git a/tests/output/render-test.c b/tests/output/render-test.c index 38940baa0c..d051200c3c 100644 --- a/tests/output/render-test.c +++ b/tests/output/render-test.c @@ -56,6 +56,9 @@ static int render_stdout = true; /* --pdf: Also render PDF output. */ static int render_pdf; +/* --csv: Also render CSV output. */ +static int render_csv; + /* ASCII driver, for ASCII driver test mode. */ static struct output_driver *ascii_driver; @@ -206,6 +209,18 @@ configure_drivers (int width, int length, int min_break) } #endif + /* Render to .csv. */ + if (render_csv) + { + string_map_clear (&options); + string_map_insert_nocopy (&options, xstrdup ("output-file"), + xasprintf ("%s.csv", output_base)); + driver = output_driver_create (&options); + if (driver == NULL) + exit (EXIT_FAILURE); + output_driver_register (driver); + } + /* Render to .odt. */ string_map_replace_nocopy (&options, xstrdup ("output-file"), xasprintf ("%s.odt", output_base)); @@ -246,6 +261,7 @@ parse_options (int argc, char **argv) {"no-txt", no_argument, &render_txt, 0}, {"no-stdout", no_argument, &render_stdout, 0}, {"pdf", no_argument, &render_pdf, 1}, + {"csv", no_argument, &render_csv, 1}, {"output", required_argument, NULL, 'o'}, {"help", no_argument, NULL, OPT_HELP}, {NULL, 0, NULL, 0}, @@ -462,7 +478,18 @@ read_table (FILE *stream, struct table **tables, size_t n_tables) table_item_create (table, NULL)); } else - tab_joint_text (tab, c, r, c + cs - 1, r + rs - 1, opt, text); + { + char *pos = text; + char *content; + int i; + + for (i = 0; (content = strsep (&pos, "#")) != NULL; i++) + if (!i) + tab_joint_text (tab, c, r, c + cs - 1, r + rs - 1, opt, + content); + else + tab_footnote (tab, c, r, content); + } } return &tab->table; diff --git a/tests/output/render.at b/tests/output/render.at index 388674ba39..9aaef8cf8e 100644 --- a/tests/output/render.at +++ b/tests/output/render.at @@ -242,6 +242,48 @@ AT_CHECK([render-test input], [0], [dnl ]) AT_CLEANUP +AT_SETUP([joined rows and columns (with footnotes)]) +AT_KEYWORDS([render rendering footnote]) +AT_DATA([input], [3 3 +1*2 @abc#Approximation. +2*1 @d\ne\nf#This is a very long footnote that will have to wrap from one line to the next. Let's see if the rendering engine does it acceptably. +2*1 @g\nh\ni#One#Two#Three +@j +1*2 @klm +]) +AT_CHECK([render-test --csv input], [0], +[[+------------+----+ +| abc[a]| d| ++----------+-+ e| +| g|j|f[b]| +| h+-+----+ +|i[c][d][e]| klm| ++----------+------+ +[a] Approximation. +[b] This is a very long footnote that will have to wrap from one line to the + next. Let's see if the rendering engine does it acceptably. +[c] One +[d] Two +[e] Three +]]) +AT_CHECK([cat render.csv], [0], +[[abc[a],,"d +e +f[b]" +"g +h +i[c][d][e]",j, +,klm, + +Footnotes: +a,Approximation. +b,This is a very long footnote that will have to wrap from one line to the next. Let's see if the rendering engine does it acceptably. +c,One +d,Two +e,Three +]]) +AT_CLEANUP + AT_SETUP([6x6, joined rows and columns]) AT_KEYWORDS([render rendering]) AT_DATA([input], [WEAVE_6X6]) -- 2.30.2
", + table_nc (t)); + else + fputs ("\n
", html->file); + str_format_26adic (++footnote_idx, false, marker, sizeof marker); + fprintf (html->file, "%s ", marker); + escape_string (html->file, c->footnotes[i], + strlen (c->footnotes[i]), " ", "
"); + } + } + x = cell.d[TABLE_HORZ][1]; + table_cell_free (&cell); + } + } + if (footnote_idx) + { + fputs ("