output: Add footnote support.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Sep 2014 23:41:08 +0000 (16:41 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Sep 2014 23:41:08 +0000 (16:41 -0700)
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.

15 files changed:
Smake
src/output/ascii.c
src/output/cairo.c
src/output/csv.c
src/output/html.c
src/output/odt.c
src/output/render.c
src/output/render.h
src/output/tab.c
src/output/tab.h
src/output/table-casereader.c
src/output/table-provider.h
src/output/table.c
tests/output/render-test.c
tests/output/render.at

diff --git a/Smake b/Smake
index d8ddb389faa05115c992966bc2413019b4bde81e..b27456bced1c709447c3f1efec04615c3c9e220b 100644 (file)
--- a/Smake
+++ b/Smake
@@ -76,6 +76,7 @@ GNULIB_MODULES = \
        stpcpy \
        strerror \
        strftime \
+       strsep \
        strtod \
        strtok_r \
        sys_stat \
index 76fb51544a965adf90e5931cfcc37e64e1c029d6..562cd72ea76b75df68b0d352bf39c6bf6f28e90e 100644 (file)
@@ -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;
 }
index 0f92ce9fe6fb627e25d3e2354e39c5e563894796..fa9b2e18725e89c5f8c6edd9ccf9d7e6ffda8495 100644 (file)
@@ -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)
 }
 \f
 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;
 }
 \f
@@ -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];
 }
index bd61c51a62e63175b66cacd3dc69a6a5e24dd08d..d2f2ac3b634349f3dbf6b8c255807a8b03a76838 100644 (file)
@@ -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))
     {
index 82478d189bdd8dd44f87e87023287532b2c6e515..19d1085e4178d20490b8a10cee5d030fea1245a2 100644 (file)
@@ -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 ("<TABLE><TBODY VALIGN=\"TOP\">\n", html->file);
+  fputs ("<TABLE>", 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, "<TFOOT><TR><TD COLSPAN=%d>",
+                             table_nc (t));
+                  else
+                    fputs ("\n<BR>", html->file);
+                  str_format_26adic (++footnote_idx, false, marker, sizeof marker);
+                  fprintf (html->file, "<SUP>%s</SUP> ", marker);
+                  escape_string (html->file, c->footnotes[i],
+                                 strlen (c->footnotes[i]), " ", "<BR>");
+                }
+            }
+          x = cell.d[TABLE_HORZ][1];
+          table_cell_free (&cell);
+        }
+    }
+  if (footnote_idx)
+    {
+      fputs ("</TD></TR></TFOOT>\n", html->file);
+      footnote_idx = 0;
+    }
+
+  fputs ("<TBODY VALIGN=\"TOP\">\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 ("  <TR>\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 ("<EM>", html->file);
@@ -483,6 +532,22 @@ html_output_table (struct html_driver *html, const struct table_item *item)
                     }
                   if (c->options & TAB_EMPH)
                     fputs ("</EM>", html->file);
+
+                  if (c->n_footnotes > 0)
+                    {
+                      fputs ("<SUP>", 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 ("</SUP>", 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 </TH> or </TD>. */
           fprintf (html->file, "</%s>\n", tag);
 
+          x = cell.d[TABLE_HORZ][1];
           table_cell_free (&cell);
         }
       fputs ("  </TR>\n", html->file);
index 0d4625c8d4d39ba61046c838aa632fa6d748e811..d0860585cd40c35219bfad9533803770e74613bd 100644 (file)
@@ -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 */
            }
index 734914dbf5b039835c961361d7a9fac33361e0d0..f41f504df0a90cc4e73a5941d3c238fb8ecbe17e 100644 (file)
@@ -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;
 }
 \f
+/* 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 ();
+}
+\f
 /* 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)
index 14f87b50c1bf404cdbeeb1b8db49eecc8580038a..c3f852f80a6b811e64c363c78279bea7516b68c8 100644 (file)
@@ -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. */
index bb6b82a2da5e843e47b3ac123eb26d23caead909..8ef077e38fae99f8a8b2d8c54b4c3b22f541e296 100644 (file)
@@ -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];
index a973b0414bee0368cf15d9a6dcc56c90a976706d..76be9d4f65949cd05114b2563186991d2b18e858 100644 (file)
@@ -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,
index a6a370f1bd7a41af5ef6de75fe0893aaa4637fd4..485014dc81a20eac816f91ed34435033d2910676 100644 (file)
@@ -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)
index 35a4b6cebb7164c27444c8afb786351edf42a2b7..b64410c22703b33f7f4b98bc49a624f016d0dfbc 100644 (file)
@@ -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. */
index 5dc5651fd940c0b86c5d9d6d38d5e5408fb2646f..b7afb8f81f24fe4542bedbd6f9e484302de1f3cf 100644 (file)
@@ -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;
 }
index 38940baa0c9c6bcf1749ea1d3b492522e539e7ca..d051200c3cadba4f1f4c00a157755694701e45eb 100644 (file)
@@ -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 <base>.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 <base>.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;
index 388674ba3915283c36e62b54317f7f899078db91..9aaef8cf8e702b3f8ca6d7ef9f9b2c0d1adc223c 100644 (file)
@@ -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])