output: Make table_item a pivot_table, table_cell a pivot_value.
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 1 Jan 2021 06:44:23 +0000 (22:44 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 2 Jan 2021 03:23:40 +0000 (19:23 -0800)
This greatly simplifies a lot of glue code and it brings the
internal representation of tables in output closer to the .spv
format representation, which will make better compatibility
possible in the future.

This commit gets rid of render-test, which was very specific
to the lower-level "table" representation, and replaces it
by pivot-table-test, which works in terms of the higher-level
pivot table representation.

37 files changed:
Smake
src/output/ascii.c
src/output/automake.mk
src/output/cairo-fsm.c
src/output/cairo-fsm.h
src/output/cairo-pager.c
src/output/cairo-pager.h
src/output/cairo.c
src/output/csv.c
src/output/html.c
src/output/odt.c
src/output/pivot-output.c
src/output/pivot-output.h [new file with mode: 0644]
src/output/pivot-table.c
src/output/pivot-table.h
src/output/render.c
src/output/render.h
src/output/spv/spv-dump.c
src/output/spv/spv-legacy-decoder.c
src/output/spv/spv-light-decoder.c
src/output/spv/spv-output.c
src/output/spv/spv-writer.c
src/output/table-item.c
src/output/table-item.h
src/output/table-provider.h
src/output/table.c
src/output/table.h
src/output/tex.c
src/output/text-item.c
src/ui/gui/psppire-output-view.c
tests/automake.mk
tests/output/look.stt [new file with mode: 0644]
tests/output/pivot-table-test.c [new file with mode: 0644]
tests/output/pivot-table.at [new file with mode: 0644]
tests/output/render-test.c [deleted file]
tests/output/render.at
utilities/pspp-output.c

diff --git a/Smake b/Smake
index 40eac89dd16873a7f753b82d48ec150fead27da4..3cf37f5df12a5e59bf5631a07c22b60b113c9dc0 100644 (file)
--- a/Smake
+++ b/Smake
@@ -47,6 +47,7 @@ GNULIB_MODULES = \
        environ \
        fatal-signal \
        fcntl \
+       fnmatch \
        fpieee \
        fprintf-posix \
        full-read \
index c058f23f8a645068ce33de1ca4eda607c339242c..46275bc363d61a22850933b6ff5f69cb48c86559 100644 (file)
@@ -54,6 +54,8 @@
 #include "output/driver-provider.h"
 #include "output/message-item.h"
 #include "output/options.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/render.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
@@ -319,6 +321,7 @@ struct ascii_driver
     int allocated_lines;        /* Number of lines allocated. */
     int chart_cnt;              /* Number of charts so far. */
     int object_cnt;             /* Number of objects so far. */
+    const struct pivot_table *pt;
     struct render_params params;
   };
 
@@ -441,6 +444,7 @@ ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
   a->params.line_widths = ascii_line_widths;
   a->params.supports_margins = false;
   a->params.rtl = render_direction_rtl ();
+  a->params.printing = true;
 
   if (!update_page_size (a, true))
     goto error;
@@ -558,21 +562,25 @@ static void
 ascii_output_table_item (struct ascii_driver *a,
                          const struct table_item *table_item)
 {
-  struct render_pager *p;
-
   update_page_size (a, false);
+  a->pt = table_item->pt;
 
-  if (a->object_cnt++)
-    putc ('\n', a->file);
-
-  p = render_pager_create (&a->params, table_item);
-  for (int i = 0; render_pager_has_next (p); i++)
+  size_t *layer_indexes;
+  PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, table_item->pt, true)
     {
-      if (i)
-        putc ('\n', a->file);
-      ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
+      struct render_pager *p = render_pager_create (&a->params, table_item,
+                                                    layer_indexes);
+      for (int i = 0; render_pager_has_next (p); i++)
+        {
+          if (a->object_cnt++)
+            putc ('\n', a->file);
+
+          ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
+        }
+      render_pager_destroy (p);
     }
-  render_pager_destroy (p);
+
+  a->pt = NULL;
 }
 
 static void
@@ -707,14 +715,8 @@ ascii_measure_cell_width (void *a_, const struct table_cell *cell,
   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
 
-  if (cell->n_footnotes || strchr (cell->text, ' ')
-      || cell->n_subscripts)
-    {
-      bb[H][1] = 1;
-      ascii_layout_cell (a, cell, bb, clip, min_width, &h);
-    }
-  else
-    *min_width = *max_width;
+  bb[H][1] = 1;
+  ascii_layout_cell (a, cell, bb, clip, min_width, &h);
 }
 
 static int
@@ -764,7 +766,7 @@ ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
 }
 
 static void
-text_draw (struct ascii_driver *a, enum table_halign halign, int options,
+text_draw (struct ascii_driver *a, enum table_halign halign, bool numeric,
            bool bold, bool underline,
            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
            int y, const uint8_t *string, int n, size_t width)
@@ -778,7 +780,7 @@ text_draw (struct ascii_driver *a, enum table_halign halign, int options,
   if (y < y0 || y >= y1)
     return;
 
-  switch (table_halign_interpret (halign, options & TAB_NUMERIC))
+  switch (table_halign_interpret (halign, numeric))
     {
     case TABLE_HALIGN_LEFT:
       x = bb[H][0];
@@ -902,18 +904,6 @@ text_draw (struct ascii_driver *a, enum table_halign halign, int options,
     }
 }
 
-static char *
-add_markers (const char *text, const struct table_cell *cell)
-{
-  struct string s = DS_EMPTY_INITIALIZER;
-  ds_put_cstr (&s, text);
-  for (size_t i = 0; i < cell->n_subscripts; i++)
-    ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
-  for (size_t i = 0; i < cell->n_footnotes; i++)
-    ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
-  return ds_steal_cstr (&s);
-}
-
 static void
 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
@@ -922,34 +912,21 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
   *widthp = 0;
   *heightp = 0;
 
-  /* Get the basic textual contents. */
-  const char *plain_text = (cell->options & TAB_MARKUP
-                            ? output_get_text_from_markup (cell->text)
-                            : cell->text);
-
-  /* Append footnotes, subscripts if any. */
-  const char *text;
-  if (cell->n_footnotes || cell->n_subscripts)
-    {
-      text = add_markers (plain_text, cell);
-      if (plain_text != cell->text)
-        free (CONST_CAST (char *, plain_text));
-    }
-  else
-    text = plain_text;
+  struct string body = DS_EMPTY_INITIALIZER;
+  bool numeric = pivot_value_format_body (cell->value, a->pt, &body);
 
   /* Calculate length; if it's zero, then there's nothing to do. */
-  size_t length = strlen (text);
-  if (!length)
+  if (ds_is_empty (&body))
     {
-      if (text != cell->text)
-        free (CONST_CAST (char *, text));
+      ds_destroy (&body);
       return;
     }
 
+  size_t length = ds_length (&body);
+  const uint8_t *text = CHAR_CAST (uint8_t *, ds_cstr (&body));
+
   char *breaks = xmalloc (length + 1);
-  u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
-                          "UTF-8", breaks);
+  u8_possible_linebreaks (text, length, "UTF-8", breaks);
   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
 
@@ -957,7 +934,7 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
   int bb_width = bb[H][1] - bb[H][0];
   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
     {
-      const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
+      const uint8_t *line = text + pos;
       const char *b = breaks + pos;
       size_t n = length - pos;
 
@@ -1008,9 +985,9 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
       width -= ofs - graph_ofs;
 
       /* Draw text. */
-      text_draw (a, cell->style->cell_style.halign, cell->options,
-                 cell->style->font_style.bold,
-                 cell->style->font_style.underline,
+      text_draw (a, cell->cell_style->halign, numeric,
+                 cell->font_style->bold,
+                 cell->font_style->underline,
                  bb, clip, y, line, graph_ofs, width);
 
       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
@@ -1028,8 +1005,7 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
     }
 
   free (breaks);
-  if (text != cell->text)
-    free (CONST_CAST (char *, text));
+  ds_destroy (&body);
 }
 
 void
@@ -1043,14 +1019,24 @@ ascii_test_write (struct output_driver *driver,
   if (!a->file)
     return;
 
-  struct table_area_style style = {
-    .cell_style.halign = TABLE_HALIGN_LEFT,
-    .font_style.bold = bold,
-    .font_style.underline = underline,
+  struct cell_style cell_style = { .halign = TABLE_HALIGN_LEFT };
+  struct font_style font_style = {
+    .bold = bold,
+    .underline = underline,
+  };
+  const struct pivot_value value = {
+    .type = PIVOT_VALUE_TEXT,
+    .text = {
+      .local = CONST_CAST (char *, s),
+      .c = CONST_CAST (char *, s),
+      .id = CONST_CAST (char *, s),
+      .user_provided = true,
+    },
   };
   struct table_cell cell = {
-    .text = CONST_CAST (char *, s),
-    .style = &style,
+    .value = &value,
+    .font_style = &font_style,
+    .cell_style = &cell_style,
   };
 
   bb[TABLE_HORZ][0] = x;
@@ -1058,7 +1044,13 @@ ascii_test_write (struct output_driver *driver,
   bb[TABLE_VERT][0] = y;
   bb[TABLE_VERT][1] = INT_MAX;
 
+  struct pivot_table pt = {
+    .show_values = SETTINGS_VALUE_SHOW_DEFAULT,
+    .show_variables = SETTINGS_VALUE_SHOW_DEFAULT,
+  };
+  a->pt = &pt;
   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
+  a->pt = NULL;
 }
 
 void
index e267089c7def10714a5fb3b769899d9acc92ab02..fb476f4649d6a0785e700e810423a66420811138 100644 (file)
@@ -76,6 +76,7 @@ src_output_liboutput_la_SOURCES = \
        src/output/page-setup-item.c \
        src/output/page-setup-item.h \
        src/output/pivot-output.c \
+       src/output/pivot-output.h \
        src/output/pivot-table.c \
        src/output/pivot-table.h \
        src/output/render.c \
index 02478fe00123956e1441e297943c684430258971..df8df7e68fec0b0f2303e8b156fffe587dd5185b 100644 (file)
@@ -41,6 +41,8 @@
 #include "output/message-item.h"
 #include "output/page-eject-item.h"
 #include "output/page-setup-item.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/render.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
@@ -104,24 +106,40 @@ xr_fsm_style_equals (const struct xr_fsm_style *a,
       || a->min_break[V] != b->min_break[V]
       || !pango_font_description_equal (a->font, b->font)
       || a->use_system_colors != b->use_system_colors
+      || a->object_spacing != b->object_spacing
       || a->font_resolution != b->font_resolution)
     return false;
 
   return true;
 }
 \f
+/* Renders a single output_item to an output device in one of two ways:
+
+   - 'print == true': Broken across multiple pages if necessary.
+
+   - 'print == false': In a single region that the user may scroll around if
+     needed.
+
+   Normally 'output_item' corresponds to a single rendering.  There is a
+   special case when 'print == true' and 'output_item' is a table_item with
+   multiple layers and 'item->pt->table_look->print_all_layers == true'.  In
+   that case, each layer is rendered separately from the FSM's internal point
+   of view; from the client's point of view, it is all one operation.
+*/
 struct xr_fsm
   {
     struct xr_fsm_style *style;
     struct output_item *item;
+    bool print;
+
+    /* Print mode only. */
+    bool done;
 
     /* Table items only. */
+    size_t *layer_indexes;
     struct render_params rp;
     struct render_pager *p;
     cairo_t *cairo;             /* XXX should this be here?! */
-
-    /* Chart and page-eject items only. */
-    bool done;
   };
 
 /* The unit used for internal measurements is inch/(72 * XR_POINT).
@@ -411,31 +429,32 @@ xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
   bb[H][1] = 1;
   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
 
+  const int (*margin)[2] = cell->cell_style->margin;
   if (*min_width > 0)
-    *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
-                            + cell->style->cell_style.margin[H][1]);
+    *min_width += px_to_xr (margin[H][0] + margin[H][1]);
   if (*max_width > 0)
-    *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
-                            + cell->style->cell_style.margin[H][1]);
+    *max_width += px_to_xr (margin[H][0] + margin[H][1]);
 }
 
 static int
 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
 {
   struct xr_fsm *xr = xr_;
-  int bb[TABLE_N_AXES][2];
-  int clip[TABLE_N_AXES][2];
-  int w, h;
 
-  bb[H][0] = 0;
-  bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
-                               + cell->style->cell_style.margin[H][1]);
-  bb[V][0] = 0;
-  bb[V][1] = INT_MAX;
-  clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
+  const int (*margin)[2] = cell->cell_style->margin;
+
+  int bb[TABLE_N_AXES][2] = {
+    [H][0] = 0,
+    [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
+    [V][0] = 0,
+    [V][1] = INT_MAX,
+  };
+
+  int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
+
+  int w, h;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
-  h += px_to_xr (cell->style->cell_style.margin[V][0]
-                 + cell->style->cell_style.margin[V][1]);
+  h += px_to_xr (margin[V][0] + margin[V][1]);
   return h;
 }
 
@@ -450,7 +469,7 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
   struct xr_fsm *xr = xr_;
   int w, h, brk;
 
-  const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
+  const struct cell_color *bg = &cell->font_style->bg[color_idx];
   if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
     {
       cairo_save (xr->cairo);
@@ -476,14 +495,14 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
     }
   cairo_save (xr->cairo);
   if (!xr->style->use_system_colors)
-    xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
+    xr_set_source_rgba (xr->cairo, &cell->font_style->fg[color_idx]);
 
   bb[V][0] += valign_offset;
 
   for (int axis = 0; axis < TABLE_N_AXES; axis++)
     {
-      bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
-      bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
+      bb[axis][0] += px_to_xr (cell->cell_style->margin[axis][0]);
+      bb[axis][1] -= px_to_xr (cell->cell_style->margin[axis][1]);
     }
   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
@@ -495,22 +514,24 @@ xrr_adjust_break (void *xr_, const struct table_cell *cell,
                   int width, int height)
 {
   struct xr_fsm *xr = xr_;
-  int bb[TABLE_N_AXES][2];
-  int clip[TABLE_N_AXES][2];
-  int w, h, brk;
 
   if (xrr_measure_cell_height (xr_, cell, width) < height)
     return -1;
 
-  bb[H][0] = 0;
-  bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
-                               + cell->style->cell_style.margin[H][1]);
+  const int (*margin)[2] = cell->cell_style->margin;
+
+  int bb[TABLE_N_AXES][2] = {
+    [H][0] = 0,
+    [V][0] = 0,
+    [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
+    [V][1] = height - px_to_xr (margin[V][0] + margin[V][1]),
+  };
   if (bb[H][1] <= 0)
     return 0;
-  bb[V][0] = 0;
-  bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
-                                + cell->style->cell_style.margin[V][1]);
-  clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
+
+  int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
+
+  int w, h, brk;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
   return brk;
 }
@@ -547,10 +568,9 @@ add_attr (PangoAttrList *list, PangoAttribute *attr,
 }
 
 static void
-markup_escape (struct string *out, unsigned int options,
-               const char *in, size_t len)
+markup_escape (struct string *out, bool markup, const char *in, size_t len)
 {
-  if (!(options & TAB_MARKUP))
+  if (!markup)
     {
       ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
       return;
@@ -618,8 +638,9 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
                      int *widthp, int *brk)
 {
-  const struct font_style *font_style = &cell->style->font_style;
-  const struct cell_style *cell_style = &cell->style->cell_style;
+  const struct pivot_table *pt = to_table_item (xr->item)->pt;
+  const struct font_style *font_style = cell->font_style;
+  const struct cell_style *cell_style = cell->cell_style;
   unsigned int options = cell->options;
 
   enum table_axis X = options & TAB_ROTATE ? V : H;
@@ -643,14 +664,19 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
 
   pango_layout_set_font_description (layout, desc);
 
-  const char *text = cell->text;
+  struct string body = DS_EMPTY_INITIALIZER;
+  bool numeric = pivot_value_format_body (cell->value, pt, &body);
+
   enum table_halign halign = table_halign_interpret (
-    cell_style->halign, cell->options & TAB_NUMERIC);
-  if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
+    cell->cell_style->halign, numeric);
+
+  if (cell_style->halign == TABLE_HALIGN_DECIMAL
+      && !(cell->options & TAB_ROTATE))
     {
       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
 
-      const char *decimal = strrchr (text, cell_style->decimal_char);
+      const char *decimal = strrchr (ds_cstr (&body),
+                                     cell_style->decimal_char);
       if (decimal)
         {
           pango_layout_set_text (layout, decimal, strlen (decimal));
@@ -662,7 +688,6 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
         bb[H][1] += margin_adjustment;
     }
 
-  struct string tmp = DS_EMPTY_INITIALIZER;
   PangoAttrList *attrs = NULL;
 
   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
@@ -679,33 +704,39 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
      are present then there will always be a digit on both sides of every
      period and comma. */
-  if (options & TAB_MARKUP)
+  bool markup = cell->font_style->markup;
+  if (markup)
     {
       PangoAttrList *new_attrs;
       char *new_text;
-      if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
+      if (pango_parse_markup (ds_cstr (&body), -1, 0,
+                              &new_attrs, &new_text, NULL, NULL))
         {
           attrs = new_attrs;
-          tmp.ss = ss_cstr (new_text);
-          tmp.capacity = tmp.ss.length;
+          ds_destroy (&body);
+          body.ss = ss_cstr (new_text);
+          body.capacity = body.ss.length;
         }
       else
         {
           /* XXX should we report the error? */
-          ds_put_cstr (&tmp, text);
         }
     }
   else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
     {
+      const char *text = ds_cstr (&body);
       const char *decimal = text + strcspn (text, ".,");
       if (decimal[0]
           && c_isdigit (decimal[1])
           && (decimal == text || !c_isdigit (decimal[-1])))
         {
-          ds_extend (&tmp, strlen (text) + 16);
-          markup_escape (&tmp, options, text, decimal - text + 1);
+          struct string tmp = DS_EMPTY_INITIALIZER;
+          ds_extend (&tmp, ds_length (&body) + 16);
+          markup_escape (&tmp, markup, text, decimal - text + 1);
           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
-          markup_escape (&tmp, options, decimal + 1, -1);
+          markup_escape (&tmp, markup, decimal + 1, -1);
+          ds_swap (&tmp, &body);
+          ds_destroy (&tmp);
         }
     }
 
@@ -717,39 +748,36 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                                 PANGO_UNDERLINE_SINGLE));
     }
 
-  if (cell->n_footnotes || cell->n_subscripts)
+  const struct pivot_value *value = cell->value;
+  if (value->n_footnotes || value->n_subscripts)
     {
-      /* If we haven't already put TEXT into tmp, do it now. */
-      if (ds_is_empty (&tmp))
-        {
-          ds_extend (&tmp, strlen (text) + 16);
-          markup_escape (&tmp, options, text, -1);
-        }
-
-      size_t subscript_ofs = ds_length (&tmp);
-      for (size_t i = 0; i < cell->n_subscripts; i++)
+      size_t subscript_ofs = ds_length (&body);
+      for (size_t i = 0; i < value->n_subscripts; i++)
         {
           if (i)
-            ds_put_byte (&tmp, ',');
-          ds_put_cstr (&tmp, cell->subscripts[i]);
+            ds_put_byte (&body, ',');
+          ds_put_cstr (&body, value->subscripts[i]);
         }
 
-      size_t footnote_ofs = ds_length (&tmp);
-      for (size_t i = 0; i < cell->n_footnotes; i++)
+      size_t footnote_ofs = ds_length (&body);
+      for (size_t i = 0; i < value->n_footnotes; i++)
         {
           if (i)
-            ds_put_byte (&tmp, ',');
-          ds_put_cstr (&tmp, cell->footnotes[i]->marker);
+            ds_put_byte (&body, ',');
+
+          size_t idx = value->footnote_indexes[i];
+          const struct pivot_footnote *f = pt->footnotes[idx];
+          pivot_value_format (f->marker, pt, &body);
         }
 
       /* Allow footnote markers to occupy the right margin.  That way, numbers
          in the column are still aligned. */
-      if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
+      if (value->n_footnotes && halign == TABLE_HALIGN_RIGHT)
         {
           /* Measure the width of the footnote marker, so we know how much we
              need to make room for. */
-          pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
-                                 ds_length (&tmp) - footnote_ofs);
+          pango_layout_set_text (layout, ds_cstr (&body) + footnote_ofs,
+                                 ds_length (&body) - footnote_ofs);
 
           PangoAttrList *fn_attrs = pango_attr_list_new ();
           pango_attr_list_insert (
@@ -779,10 +807,10 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                 PANGO_ATTR_INDEX_TO_TEXT_END);
       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
-      if (cell->n_subscripts)
+      if (value->n_subscripts)
         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
                   footnote_ofs - subscript_ofs);
-      if (cell->n_footnotes)
+      if (value->n_footnotes)
         add_attr (attrs, pango_attr_rise_new (3000), footnote_ofs,
                   PANGO_ATTR_INDEX_TO_TEXT_END);
     }
@@ -795,11 +823,7 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
     }
 
   /* Set the text. */
-  if (ds_is_empty (&tmp))
-    pango_layout_set_text (layout, text, -1);
-  else
-    pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
-  ds_destroy (&tmp);
+  pango_layout_set_text (layout, ds_cstr (&body), ds_length (&body));
 
   pango_layout_set_alignment (layout,
                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
@@ -916,6 +940,7 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
   if (desc != xr->style->font)
     pango_font_description_free (desc);
   g_object_unref (G_OBJECT (layout));
+  ds_destroy (&body);
 
   return h;
 }
@@ -949,9 +974,10 @@ xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
 #define CHART_WIDTH 500
 #define CHART_HEIGHT 375
 
-struct xr_fsm *
+static struct xr_fsm *
 xr_fsm_create (const struct output_item *item_,
-               const struct xr_fsm_style *style, cairo_t *cr)
+               const struct xr_fsm_style *style, cairo_t *cr,
+               bool print)
 {
   if (is_page_setup_item (item_)
       || is_group_open_item (item_)
@@ -993,6 +1019,15 @@ xr_fsm_create (const struct output_item *item_,
           || is_chart_item (item)
           || is_page_eject_item (item));
 
+  size_t *layer_indexes = NULL;
+  if (is_table_item (item))
+    {
+      const struct table_item *table_item = to_table_item (item);
+      layer_indexes = pivot_output_next_layer (table_item->pt, NULL, print);
+      if (!layer_indexes)
+        return NULL;
+    }
+
   static const struct render_ops xrr_render_ops = {
     .measure_cell_width = xrr_measure_cell_width,
     .measure_cell_height = xrr_measure_cell_height,
@@ -1017,6 +1052,8 @@ xr_fsm_create (const struct output_item *item_,
   *fsm = (struct xr_fsm) {
     .style = xr_fsm_style_ref (style),
     .item = item,
+    .print = print,
+    .layer_indexes = layer_indexes,
     .rp = {
       .ops = &xrr_render_ops,
       .aux = fsm,
@@ -1025,16 +1062,10 @@ xr_fsm_create (const struct output_item *item_,
       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
       .supports_margins = true,
       .rtl = render_direction_rtl (),
+      .printing = print,
     }
   };
 
-  if (is_table_item (item))
-    {
-      fsm->cairo = cr;
-      fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
-      fsm->cairo = NULL;
-    }
-
   /* Get font size. */
   PangoContext *context = pango_cairo_create_context (cr);
   pango_cairo_context_set_resolution (context, style->font_resolution);
@@ -1055,9 +1086,25 @@ xr_fsm_create (const struct output_item *item_,
 
   g_object_unref (G_OBJECT (layout));
 
+  if (is_table_item (item))
+    {
+      struct table_item *table_item = to_table_item (item);
+
+      fsm->cairo = cr;
+      fsm->p = render_pager_create (&fsm->rp, table_item, fsm->layer_indexes);
+      fsm->cairo = NULL;
+    }
+
   return fsm;
 }
 
+struct xr_fsm *
+xr_fsm_create_for_printing (const struct output_item *item,
+                            const struct xr_fsm_style *style, cairo_t *cr)
+{
+  return xr_fsm_create (item, style, cr, true);
+}
+
 void
 xr_fsm_destroy (struct xr_fsm *fsm)
 {
@@ -1065,17 +1112,27 @@ xr_fsm_destroy (struct xr_fsm *fsm)
     {
       xr_fsm_style_unref (fsm->style);
       output_item_unref (fsm->item);
+      free (fsm->layer_indexes);
       render_pager_destroy (fsm->p);
       assert (!fsm->cairo);
       free (fsm);
     }
 }
+\f
+/* Scrolling API. */
+
+struct xr_fsm *
+xr_fsm_create_for_scrolling (const struct output_item *item,
+                             const struct xr_fsm_style *style, cairo_t *cr)
+{
+  return xr_fsm_create (item, style, cr, false);
+}
 
-/* This is primarily meant for use with screen rendering since the result is a
-   fixed value for charts. */
 void
 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
 {
+  assert (!fsm->print);
+
   int w, h;
 
   if (is_table_item (fsm->item))
@@ -1099,38 +1156,10 @@ xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
     *hp = h;
 }
 
-static int
-xr_fsm_draw_table (struct xr_fsm *fsm, int space)
-{
-  return (render_pager_has_next (fsm->p)
-          ? render_pager_draw_next (fsm->p, space)
-          : 0);
-}
-
-static int
-xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
-{
-  const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
-  if (space < chart_height)
-    return 0;
-
-  fsm->done = true;
-  xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
-                 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
-  return chart_height;
-}
-
-static int
-xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
-{
-  if (space >= fsm->rp.size[V])
-    fsm->done = true;
-  return 0;
-}
-
 void
 xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
 {
+  assert (!fsm->print);
   xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
 }
 
@@ -1146,6 +1175,7 @@ void
 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
                     int x, int y, int w, int h)
 {
+  assert (!fsm->print);
   if (is_table_item (fsm->item))
     {
       fsm->cairo = cr;
@@ -1162,11 +1192,65 @@ xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
   else
     NOT_REACHED ();
 }
+\f
+/* Printing API. */
+
+static int
+xr_fsm_draw_table (struct xr_fsm *fsm, int space)
+{
+  struct table_item *table_item = to_table_item (fsm->item);
+  int used = render_pager_draw_next (fsm->p, space);
+  if (!render_pager_has_next (fsm->p))
+    {
+      render_pager_destroy (fsm->p);
+
+      fsm->layer_indexes = pivot_output_next_layer (table_item->pt,
+                                                    fsm->layer_indexes, true);
+      if (fsm->layer_indexes)
+        {
+          fsm->p = render_pager_create (&fsm->rp, table_item,
+                                        fsm->layer_indexes);
+          if (table_item->pt->look->paginate_layers)
+            used = space;
+          else
+            used += fsm->style->object_spacing;
+        }
+      else
+        {
+          fsm->p = NULL;
+          fsm->done = true;
+        }
+    }
+  return MIN (used, space);
+}
+
+static int
+xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
+{
+  const int chart_height = 0.8 * MIN (fsm->rp.size[H], fsm->rp.size[V]);
+  if (space < chart_height)
+    return 0;
+
+  fsm->done = true;
+  xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
+                 xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
+  return chart_height;
+}
+
+static int
+xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
+{
+  if (space >= fsm->rp.size[V])
+    fsm->done = true;
+  return 0;
+}
 
 int
 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
 {
-  if (xr_fsm_is_empty (fsm))
+  assert (fsm->print);
+
+  if (fsm->done)
     return 0;
 
   cairo_save (cr);
@@ -1181,11 +1265,10 @@ xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
   return used;
 }
 
-
 bool
 xr_fsm_is_empty (const struct xr_fsm *fsm)
 {
-  return (is_table_item (fsm->item)
-          ? !render_pager_has_next (fsm->p)
-          : fsm->done);
+  assert (fsm->print);
+
+  return fsm->done;
 }
index cf2da691f45fc0661dd3d499c13f73d745e115bd..e8fe5b7b3cf33940c00273deffb519c3c2a54062 100644 (file)
@@ -42,6 +42,8 @@ struct xr_fsm_style
     struct cell_color fg;
     bool use_system_colors;
 
+    int object_spacing;
+
     /* Resolution, in units per inch, used for measuring font "points".  If
        this is 72.0, for example, then 1pt = 1 device unit, which is
        appropriate for rendering to a surface created by
@@ -55,16 +57,20 @@ void xr_fsm_style_unref (struct xr_fsm_style *);
 bool xr_fsm_style_equals (const struct xr_fsm_style *,
                           const struct xr_fsm_style *);
 
-struct xr_fsm *xr_fsm_create (const struct output_item *,
-                              const struct xr_fsm_style *,
-                              cairo_t *);
-void xr_fsm_destroy (struct xr_fsm *);
-
+/* Interface used for rendering output items in a single on-screen region. */
+struct xr_fsm *xr_fsm_create_for_scrolling (const struct output_item *,
+                                            const struct xr_fsm_style *,
+                                            cairo_t *);
 void xr_fsm_measure (struct xr_fsm *, cairo_t *, int *w, int *h);
 void xr_fsm_draw_all (struct xr_fsm *, cairo_t *);
 void xr_fsm_draw_region (struct xr_fsm *, cairo_t *,
                          int x, int y, int w, int h);
 
+/* Interface used for rendering output items to a series of printed pages. */
+struct xr_fsm *xr_fsm_create_for_printing (const struct output_item *,
+                                           const struct xr_fsm_style *,
+                                           cairo_t *);
+void xr_fsm_destroy (struct xr_fsm *);
 int xr_fsm_draw_slice (struct xr_fsm *, cairo_t *, int space);
 bool xr_fsm_is_empty (const struct xr_fsm *);
 
index d8dda89c3197b79a2d03526d5970e0b931c00012..311a3b546afb7a82b188e24d99d96a55b8a31929 100644 (file)
@@ -95,8 +95,7 @@ xr_page_style_equals (const struct xr_page_style *a,
     if (!page_heading_equals (&a->headings[i], &b->headings[i]))
       return false;
 
-  return (a->initial_page_number == b->initial_page_number
-          && a->object_spacing == b->object_spacing);
+  return a->initial_page_number == b->initial_page_number;
 }
 \f
 struct xr_pager
@@ -219,7 +218,7 @@ xr_measure_headings (const struct xr_page_style *ps,
                                    &ps->headings[i], -1, fs->size[H], 0,
                                    fs->font_resolution);
       if (*h)
-        *h += ps->object_spacing;
+        *h += fs->object_spacing;
     }
   cairo_destroy (cairo);
   cairo_surface_destroy (surface);
@@ -318,7 +317,7 @@ xr_pager_add_page (struct xr_pager *p, cairo_t *cr)
 
   if (p->heading_heights[1])
     xr_render_page_heading (cr, fs->font, &ps->headings[1], page_number,
-                            fs->size[H], fs->size[V] + ps->object_spacing,
+                            fs->size[H], fs->size[V] + fs->object_spacing,
                             fs->font_resolution);
 
   cairo_surface_t *surface = cairo_get_target (cr);
@@ -395,7 +394,7 @@ xr_pager_run (struct xr_pager *p)
                 }
             }
 
-          p->fsm = xr_fsm_create (p->item, p->fsm_style, p->cr);
+          p->fsm = xr_fsm_create_for_printing (p->item, p->fsm_style, p->cr);
           if (!p->fsm)
             {
               output_item_unref (p->item);
@@ -417,7 +416,7 @@ xr_pager_run (struct xr_pager *p)
               free (attrs);
             }
 
-          int spacing = p->page_style->object_spacing;
+          int spacing = p->fsm_style->object_spacing;
           int chunk = xr_fsm_draw_slice (p->fsm, p->cr,
                                          p->fsm_style->size[V] - p->y);
           p->y += chunk + spacing;
index bd7fa55125115a0b5ab3377cd25af3d2990554cd..361c29798b549e4f5caf847eb0a2aa8a085bfd88 100644 (file)
@@ -38,7 +38,6 @@ struct xr_page_style
     struct page_heading headings[2]; /* Top and bottom headings. */
 
     int initial_page_number;
-    int object_spacing;
 
     /* Whether to include an outline in PDF output.  (The only reason I know to
        omit it is to avoid a Cairo bug that caused crashes in some cases.) */
index 0aa35b66cc69d8fc3aaf8200d71aa337e8b12116..1c043bb93d3418a7d0948eafe09107d9397ec64c 100644 (file)
@@ -254,7 +254,6 @@ xr_allocate (const char *name, int device_type,
     },
 
     .initial_page_number = 1,
-    .object_spacing = object_spacing,
     .include_outline = include_outline,
   };
 
@@ -266,6 +265,7 @@ xr_allocate (const char *name, int device_type,
     .font = font,
     .fg = fg,
     .use_system_colors = systemcolors,
+    .object_spacing = object_spacing,
     .font_resolution = font_resolution,
   };
 
@@ -574,7 +574,6 @@ xr_update_page_setup (struct output_driver *driver,
     },
 
     .initial_page_number = setup->initial_page_number,
-    .object_spacing = setup->object_spacing * 72 * XR_POINT,
     .include_outline = old_ps->include_outline,
   };
   for (size_t i = 0; i < 2; i++)
@@ -593,6 +592,7 @@ xr_update_page_setup (struct output_driver *driver,
     .font = pango_font_description_copy (old_fs->font),
     .fg = old_fs->fg,
     .use_system_colors = old_fs->use_system_colors,
+    .object_spacing = setup->object_spacing * 72 * XR_POINT,
     .font_resolution = old_fs->font_resolution,
   };
   xr_fsm_style_unref (old_fs);
index 4940d3c18cf1a212df3b610214d085b9c349dce2..69f12b16f295ef9790487ce78192d391c7649610 100644 (file)
@@ -31,6 +31,8 @@
 #include "output/options.h"
 #include "output/message-item.h"
 #include "output/page-eject-item.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/table-item.h"
 #include "output/table-provider.h"
 
@@ -175,51 +177,72 @@ csv_output_lines (struct csv_driver *csv, const char *text_)
 }
 
 static void
-csv_format_footnotes (struct footnote **f, size_t n, struct string *s)
+csv_output_table_cell (struct csv_driver *csv, const struct pivot_table *pt,
+                       const struct table_cell *cell, const char *leader)
 {
-  for (size_t i = 0; i < n; i++)
-    ds_put_format (s, "[%s]", f[i]->marker);
+  struct string s = DS_EMPTY_INITIALIZER;
+  if (leader)
+    ds_put_format (&s, "%s: ", leader);
+  pivot_value_format (cell->value, pt, &s);
+  if (cell->font_style->markup)
+    {
+      char *t = output_get_text_from_markup (ds_cstr (&s));
+      ds_assign_cstr (&s, t);
+      free (t);
+    }
+  csv_output_field (csv, ds_cstr (&s));
+  ds_destroy (&s);
 }
 
 static void
-csv_output_table_cell (struct csv_driver *csv, const struct table_cell *cell,
-                       const char *leader)
+csv_output_table__ (struct csv_driver *csv, const struct pivot_table *pt,
+                    const struct table *t, const char *leader)
 {
-  if (!cell)
+  if (!t)
     return;
 
-  if (!(cell->options & TAB_MARKUP) && !cell->n_footnotes
-      && !cell->n_subscripts && !leader)
-    csv_output_field (csv, cell->text);
-  else
+  for (int y = 0; y < t->n[TABLE_VERT]; y++)
     {
-      struct string s = DS_EMPTY_INITIALIZER;
+      for (int x = 0; x < t->n[TABLE_HORZ]; x++)
+        {
+          struct table_cell cell;
 
-      if (leader)
-        ds_put_format (&s, "%s: ", leader);
+          table_get_cell (t, x, y, &cell);
 
-      if (cell->options & TAB_MARKUP)
-        {
-          char *t = output_get_text_from_markup (cell->text);
-          ds_put_cstr (&s, t);
-          free (t);
+          if (x > 0)
+            fputs (csv->separator, csv->file);
+
+          if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
+            csv_output_field (csv, "");
+          else
+            csv_output_table_cell (csv, pt, &cell, !x ? leader : NULL);
         }
-      else
-        ds_put_cstr (&s, cell->text);
-
-      if (cell->n_subscripts)
-        for (size_t i = 0; i < cell->n_subscripts; i++)
-          ds_put_format (&s, "%c%s",
-                         i ? ',' : '_', cell->subscripts[i]);
-      csv_format_footnotes (cell->footnotes, cell->n_footnotes, &s);
-      csv_output_field (csv, ds_cstr (&s));
-      ds_destroy (&s);
-
-      if (leader)
-        putc ('\n', csv->file);
+      putc ('\n', csv->file);
     }
 }
 
+static void
+csv_output_table_layer (struct csv_driver *csv, const struct pivot_table *pt,
+                        const size_t *layer_indexes)
+{
+  struct table *title, *layers, *body, *caption, *footnotes;
+  pivot_output (pt, layer_indexes, true, &title, &layers, &body,
+                &caption, &footnotes, NULL, NULL);
+
+  csv_put_separator (csv);
+  csv_output_table__ (csv, pt, title, "Table");
+  csv_output_table__ (csv, pt, layers, "Layer");
+  csv_output_table__ (csv, pt, body, NULL);
+  csv_output_table__ (csv, pt, caption, "Caption");
+  csv_output_table__ (csv, pt, footnotes, "Footnote");
+
+  table_unref (title);
+  table_unref (layers);
+  table_unref (body);
+  table_unref (caption);
+  table_unref (footnotes);
+}
+
 static void
 csv_submit (struct output_driver *driver,
             const struct output_item *output_item)
@@ -228,52 +251,11 @@ csv_submit (struct output_driver *driver,
 
   if (is_table_item (output_item))
     {
-      struct table_item *table_item = to_table_item (output_item);
-      const struct table *t = table_item_get_table (table_item);
-      int x, y;
+      const struct pivot_table *pt = to_table_item (output_item)->pt;
 
-      csv_put_separator (csv);
-
-      if (csv->titles)
-        csv_output_table_cell (csv, table_item_get_title (table_item), "Table");
-
-      for (y = 0; y < t->n[TABLE_VERT]; y++)
-        {
-          for (x = 0; x < t->n[TABLE_HORZ]; x++)
-            {
-              struct table_cell cell;
-
-              table_get_cell (t, x, y, &cell);
-
-              if (x > 0)
-                fputs (csv->separator, csv->file);
-
-              if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
-                csv_output_field (csv, "");
-              else
-                csv_output_table_cell (csv, &cell, NULL);
-            }
-          putc ('\n', csv->file);
-        }
-
-      if (csv->captions)
-        csv_output_table_cell (csv, table_item_get_caption (table_item),
-                               "Caption");
-
-      struct footnote **f;
-      size_t n_footnotes = table_collect_footnotes (table_item, &f);
-      if (n_footnotes)
-        {
-          for (size_t i = 0; i < n_footnotes; i++)
-            {
-              char *s = xasprintf ("Footnote: %s. %s", f[i]->marker, f[i]->content);
-              csv_output_field (csv, s);
-              free (s);
-              putc ('\n', csv->file);
-            }
-
-          free (f);
-        }
+      size_t *layer_indexes;
+      PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, pt, true)
+        csv_output_table_layer (csv, pt, layer_indexes);
     }
   else if (is_text_item (output_item))
     {
index f731686ce78711ab1dc0fdfaf7da6b7a2701127a..ecaf729dd74e4377142dc7f40ec6669ce8df0379 100644 (file)
@@ -41,6 +41,8 @@
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/output-item-provider.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/table-provider.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
@@ -364,18 +366,6 @@ escape_string (FILE *file, const char *text,
     }
 }
 
-static void
-escape_tag (FILE *file, const char *tag,
-            const char *text, const char *space, const char *newline)
-{
-  if (!text || !*text)
-    return;
-
-  fprintf (file, "<%s>", tag);
-  escape_string (file, text, space, newline);
-  fprintf (file, "</%s>", tag);
-}
-
 static const char *
 border_to_css (int border)
 {
@@ -480,88 +470,20 @@ put_border (const struct table *table, const struct table_cell *cell,
 }
 
 static void
-put_tfoot (struct html_driver *html, const struct table *t, bool *tfoot)
-{
-  if (!*tfoot)
-    {
-      fputs ("<tfoot>\n", html->file);
-      fputs ("<tr>\n", html->file);
-      fprintf (html->file, "<td colspan=%d>\n", t->n[H]);
-      *tfoot = true;
-    }
-  else
-    fputs ("\n<br>", html->file);
-}
-
-static void
-html_put_footnote_markers (struct html_driver *html,
-                           struct footnote **footnotes,
-                           size_t n_footnotes)
-{
-  if (n_footnotes > 0)
-    {
-      fputs ("<sup>", html->file);
-      for (size_t i = 0; i < n_footnotes; i++)
-        {
-          const struct footnote *f = footnotes[i];
-
-          if (i > 0)
-            putc (',', html->file);
-          escape_string (html->file, f->marker, " ", "<br>");
-        }
-      fputs ("</sup>", html->file);
-    }
-}
-
-static void
-html_put_table_cell_text (struct html_driver *html,
-                          const struct table_cell *cell)
-{
-  const char *s = cell->text;
-  s += strspn (s, CC_SPACES);
-  escape_string (html->file, s, " ", "<br>");
-
-  if (cell->n_subscripts)
-    {
-      fputs ("<sub>", html->file);
-      for (size_t i = 0; i < cell->n_subscripts; i++)
-        {
-          if (i)
-            putc (',', html->file);
-          escape_string (html->file, cell->subscripts[i],
-                         "&nbsp;", "<br>");
-        }
-      fputs ("</sub>", html->file);
-    }
-  html_put_footnote_markers (html, cell->footnotes, cell->n_footnotes);
-}
-
-static void
-html_put_table_item_layers (struct html_driver *html,
-                            const struct table_item_layers *layers)
-{
-  for (size_t i = 0; i < layers->n_layers; i++)
-    {
-      if (i)
-        fputs ("<br>\n", html->file);
-
-      const struct table_item_layer *layer = &layers->layers[i];
-      escape_string (html->file, layer->content, " ", "<br>");
-      html_put_footnote_markers (html, layer->footnotes, layer->n_footnotes);
-    }
-}
-
-static void
-html_put_table_cell (struct html_driver *html, const struct table *t,
-                     const struct table_cell *cell, const char *tag,
-                     bool border)
+html_put_table_cell (struct html_driver *html, const struct pivot_table *pt,
+                     const struct table_cell *cell,
+                     const char *tag, const struct table *t)
 {
   fprintf (html->file, "<%s", tag);
 
   struct css_style style;
   style_start (&style, html->file);
-  enum table_halign halign = table_halign_interpret (
-    cell->style->cell_style.halign, cell->options & TAB_NUMERIC);
+
+  struct string body = DS_EMPTY_INITIALIZER;
+  bool numeric = pivot_value_format_body (cell->value, pt, &body);
+
+  enum table_halign halign = table_halign_interpret (cell->cell_style->halign,
+                                                     numeric);
 
   switch (halign)
     {
@@ -579,14 +501,14 @@ html_put_table_cell (struct html_driver *html, const struct table *t,
   if (cell->options & TAB_ROTATE)
     put_style (&style, "writing-mode", "sideways-lr");
 
-  if (cell->style->cell_style.valign != TABLE_VALIGN_TOP)
+  if (cell->cell_style->valign != TABLE_VALIGN_TOP)
     {
       put_style (&style, "vertical-align",
-                 (cell->style->cell_style.valign == TABLE_VALIGN_BOTTOM
+                 (cell->cell_style->valign == TABLE_VALIGN_BOTTOM
                   ? "bottom" : "middle"));
     }
 
-  const struct font_style *fs = &cell->style->font_style;
+  const struct font_style *fs = cell->font_style;
   char bgcolor[32];
   if (format_color (fs->bg[cell->d[V][0] % 2],
                     (struct cell_color) CELL_COLOR_WHITE,
@@ -618,7 +540,7 @@ html_put_table_cell (struct html_driver *html, const struct table *t,
       put_style (&style, "font-size", buf);
     }
 
-  if (border)
+  if (t && html->borders)
     {
       put_border (t, cell, &style, V, 0, 0, "top");
       put_border (t, cell, &style, H, 0, 0, "left");
@@ -640,90 +562,155 @@ html_put_table_cell (struct html_driver *html, const struct table *t,
 
   putc ('>', html->file);
 
-  html_put_table_cell_text (html, cell);
+  const char *s = ds_cstr (&body);
+  s += strspn (s, CC_SPACES);
+  escape_string (html->file, s, " ", "<br>");
+  ds_destroy (&body);
+
+  if (cell->value->n_subscripts)
+    {
+      fputs ("<sub>", html->file);
+      for (size_t i = 0; i < cell->value->n_subscripts; i++)
+        {
+          if (i)
+            putc (',', html->file);
+          escape_string (html->file, cell->value->subscripts[i],
+                         "&nbsp;", "<br>");
+        }
+      fputs ("</sub>", html->file);
+    }
+  if (cell->value->n_footnotes > 0)
+    {
+      fputs ("<sup>", html->file);
+      for (size_t i = 0; i < cell->value->n_footnotes; i++)
+        {
+          if (i > 0)
+            putc (',', html->file);
+
+          size_t idx = cell->value->footnote_indexes[i];
+          const struct pivot_footnote *f = pt->footnotes[idx];
+          char *marker = pivot_value_to_string (f->marker, pt);
+          escape_string (html->file, marker, " ", "<br>");
+          free (marker);
+        }
+      fputs ("</sup>", html->file);
+    }
 
   /* output </th> or </td>. */
   fprintf (html->file, "</%s>\n", tag);
 }
 
 static void
-html_output_table (struct html_driver *html, const struct table_item *item)
+html_output_table_layer (struct html_driver *html, const struct pivot_table *pt,
+                         const size_t *layer_indexes)
 {
-  const struct table *t = table_item_get_table (item);
-  bool tfoot = false;
+  struct table *title, *layers, *body, *caption, *footnotes;
+  pivot_output (pt, layer_indexes, true, &title, &layers, &body,
+                &caption, &footnotes, NULL, NULL);
 
   fputs ("<table", html->file);
-  if (item->notes)
+  if (pt->notes)
     {
       fputs (" title=\"", html->file);
-      escape_string (html->file, item->notes, " ", "\n");
+      escape_string (html->file, pt->notes, " ", "\n");
       putc ('"', html->file);
     }
   fputs (">\n", html->file);
 
-  const struct table_cell *caption = table_item_get_caption (item);
-  if (caption)
+  if (title)
     {
-      put_tfoot (html, t, &tfoot);
-      html_put_table_cell (html, t, caption, "span", false);
+      struct table_cell cell;
+      table_get_cell (title, 0, 0, &cell);
+      html_put_table_cell (html, pt, &cell, "caption", NULL);
     }
-  struct footnote **f;
-  size_t n_footnotes = table_collect_footnotes (item, &f);
 
-  for (size_t i = 0; i < n_footnotes; i++)
-    {
-      put_tfoot (html, t, &tfoot);
-      escape_tag (html->file, "sup", f[i]->marker, " ", "<br>");
-      escape_string (html->file, f[i]->content, " ", "<br>");
-    }
-  free (f);
-  if (tfoot)
+  if (layers)
     {
-      fputs ("</td>\n", html->file);
-      fputs ("</tr>\n", html->file);
-      fputs ("</tfoot>\n", html->file);
-    }
+      fputs ("<thead>\n", html->file);
+      for (size_t y = 0; y < layers->n[V]; y++)
+        {
+          fputs ("<tr>\n", html->file);
 
-  const struct table_cell *title = table_item_get_title (item);
-  const struct table_item_layers *layers = table_item_get_layers (item);
-  if (title || layers)
-    {
-      fputs ("<caption>", html->file);
-      if (title)
-        html_put_table_cell (html, t, title, "span", false);
-      if (title && layers)
-        fputs ("<br>\n", html->file);
-      if (layers)
-        html_put_table_item_layers (html, layers);
-      fputs ("</caption>\n", html->file);
+          struct table_cell cell;
+          table_get_cell (layers, 0, y, &cell);
+          cell.d[H][1] = body->n[H];
+          html_put_table_cell (html, pt, &cell, "td", NULL);
+
+          fputs ("</tr>\n", html->file);
+        }
+      fputs ("</thead>\n", html->file);
     }
 
   fputs ("<tbody>\n", html->file);
-
-  for (int y = 0; y < t->n[V]; y++)
+  for (int y = 0; y < body->n[V]; y++)
     {
       fputs ("<tr>\n", html->file);
-      for (int x = 0; x < t->n[H]; )
+      for (int x = 0; x < body->n[H]; )
         {
           struct table_cell cell;
-          table_get_cell (t, x, y, &cell);
+          table_get_cell (body, x, y, &cell);
           if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0])
             {
-              bool is_header = (y < t->h[V][0]
-                                || y >= t->n[V] - t->h[V][1]
-                                || x < t->h[H][0]
-                                || x >= t->n[H] - t->h[H][1]);
+              bool is_header = (y < body->h[V][0]
+                                || y >= body->n[V] - body->h[V][1]
+                                || x < body->h[H][0]
+                                || x >= body->n[H] - body->h[H][1]);
               const char *tag = is_header ? "th" : "td";
-              html_put_table_cell (html, t, &cell, tag, html->borders);
+              html_put_table_cell (html, pt, &cell, tag, body);
             }
 
           x = cell.d[TABLE_HORZ][1];
         }
       fputs ("</tr>\n", html->file);
     }
-
   fputs ("</tbody>\n", html->file);
+
+  if (caption || footnotes)
+    {
+      fprintf (html->file, "<tfoot>\n");
+
+      if (caption)
+        {
+          fputs ("<tr>\n", html->file);
+
+          struct table_cell cell;
+          table_get_cell (caption, 0, 0, &cell);
+          cell.d[H][1] = body->n[H];
+          html_put_table_cell (html, pt, &cell, "td", NULL);
+
+          fputs ("</tr>\n", html->file);
+        }
+
+      if (footnotes)
+        for (size_t y = 0; y < footnotes->n[V]; y++)
+          {
+            fputs ("<tr>\n", html->file);
+
+            struct table_cell cell;
+            table_get_cell (footnotes, 0, y, &cell);
+            cell.d[H][1] = body->n[H];
+            html_put_table_cell (html, pt, &cell, "td", NULL);
+
+            fputs ("</tr>\n", html->file);
+          }
+      fputs ("</tfoot>\n", html->file);
+    }
+
   fputs ("</table>\n\n", html->file);
+
+  table_unref (title);
+  table_unref (layers);
+  table_unref (body);
+  table_unref (caption);
+  table_unref (footnotes);
+}
+
+static void
+html_output_table (struct html_driver *html, const struct table_item *item)
+{
+  size_t *layer_indexes;
+  PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
+    html_output_table_layer (html, item->pt, layer_indexes);
 }
 
 struct output_driver_factory html_driver_factory =
index 9397bf142574359aca5cd7439214c693a8de2ebe..c21acea3757a1d7d820f9df3628c25e7a388cc0e 100644 (file)
@@ -40,6 +40,8 @@
 #include "output/driver-provider.h"
 #include "output/message-item.h"
 #include "output/options.h"
+#include "output/pivot-table.h"
+#include "output/pivot-output.h"
 #include "output/table-item.h"
 #include "output/table-provider.h"
 #include "output/text-item.h"
@@ -194,6 +196,18 @@ write_style_data (struct odt_driver *odt)
     xmlTextWriterEndElement (w); /* style:style */
   }
 
+  {
+    xmlTextWriterStartElement (w, _xml ("style:style"));
+    xmlTextWriterWriteAttribute (w, _xml ("style:name"), _xml ("superscript"));
+    xmlTextWriterWriteAttribute (w, _xml ("style:family"), _xml ("text"));
+
+    xmlTextWriterStartElement (w, _xml ("style:text-properties"));
+    xmlTextWriterWriteAttribute (w, _xml ("style:text-position"),
+                                 _xml ("super 58%"));
+    xmlTextWriterEndElement (w); /* style:text-properties */
+
+    xmlTextWriterEndElement (w); /* style:style */
+  }
 
   xmlTextWriterEndElement (w); /* office:styles */
   xmlTextWriterEndElement (w); /* office:document-styles */
@@ -402,75 +416,70 @@ write_xml_with_line_breaks (struct odt_driver *odt, const char *line_)
 }
 
 static void
-write_footnote (struct odt_driver *odt, const struct footnote *f)
+write_footnotes (struct odt_driver *odt,
+                 const struct pivot_table *pt,
+                 const size_t *footnote_indexes,
+                 size_t n_footnotes)
 {
-  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"));
-  if (strlen (f->marker) > 1)
-    xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("text:label"),
-                                       "(%s)", f->marker);
-  else
-    xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:label"),
-                                 _xml(f->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, f->content);
-  xmlTextWriterEndElement (odt->content_wtr);
-  xmlTextWriterEndElement (odt->content_wtr);
-
-  xmlTextWriterEndElement (odt->content_wtr);
+  for (size_t i = 0; i < n_footnotes; i++)
+    {
+      xmlTextWriterStartElement (odt->content_wtr, _xml("text:span"));
+      xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"),
+                                   _xml("superscript"));
+      const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
+      char *s = pivot_value_to_string (f->marker, pt);
+      write_xml_with_line_breaks (odt, s);
+      free (s);
+      xmlTextWriterEndElement (odt->content_wtr);
+    }
 }
 
 static void
 write_table_item_cell (struct odt_driver *odt,
+                       const struct pivot_table *pt,
                        const struct table_cell *cell)
 {
-  if (!cell)
-    return;
-
-  xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
-  xmlTextWriterWriteFormatAttribute (odt->content_wtr,
-                                     _xml("text:outline-level"), "%d", 2);
-  xmlTextWriterWriteString (odt->content_wtr, _xml (cell->text));
-  for (size_t i = 0; i < cell->n_footnotes; i++)
-    write_footnote (odt, cell->footnotes[i]);
-  xmlTextWriterEndElement (odt->content_wtr);
+  struct string body = DS_EMPTY_INITIALIZER;
+  pivot_value_format_body (cell->value, pt, &body);
+  xmlTextWriterWriteString (odt->content_wtr, _xml (ds_cstr (&body)));
+  ds_destroy (&body);
+
+  write_footnotes (odt, pt, cell->value->footnote_indexes,
+                   cell->value->n_footnotes);
 }
 
 static void
-write_table_item_layers (struct odt_driver *odt,
-                         const struct table_item_layers *layers)
+write_table__ (struct odt_driver *odt, const struct pivot_table *pt,
+               const struct table *t)
 {
-  if (!layers)
-    return;
-
-  for (size_t i = 0; i < layers->n_layers; i++)
+  if (t)
     {
-      const struct table_item_layer *layer = &layers->layers[i];
-      xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
-      xmlTextWriterWriteFormatAttribute (odt->content_wtr,
-                                         _xml("text:outline-level"), "%d", 2);
-      xmlTextWriterWriteString (odt->content_wtr, _xml (layer->content));
-      for (size_t i = 0; i < layer->n_footnotes; i++)
-        write_footnote (odt, layer->footnotes[i]);
-      xmlTextWriterEndElement (odt->content_wtr);
+      for (size_t y = 0; y < t->n[V]; y++)
+        {
+          xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
+          xmlTextWriterWriteFormatAttribute (odt->content_wtr,
+                                             _xml("text:outline-level"), "%d", 2);
+
+          struct table_cell cell;
+          table_get_cell (t, 0, y, &cell);
+          write_table_item_cell (odt, pt, &cell);
+
+          xmlTextWriterEndElement (odt->content_wtr);
+        }
     }
 }
 
 static void
-write_table (struct odt_driver *odt, const struct table_item *item)
+write_table_layer (struct odt_driver *odt, const struct pivot_table *pt,
+                   const size_t *layer_indexes)
 {
-  const struct table *tab = table_item_get_table (item);
-  int r, c;
+  struct table *title, *layers, *body, *caption, *footnotes;
+  pivot_output (pt, layer_indexes, true, &title, &layers, &body,
+                &caption, &footnotes, NULL, NULL);
 
   /* Write a heading for the table */
-  write_table_item_cell (odt, table_item_get_title (item));
-  write_table_item_layers (odt, table_item_get_layers (item));
+  write_table__ (odt, pt, title);
+  write_table__ (odt, pt, layers);
 
   /* Start table */
   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table"));
@@ -480,27 +489,27 @@ write_table (struct odt_driver *odt, const struct table_item *item)
 
   /* Start column definitions */
   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-column"));
-  xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:number-columns-repeated"), "%d", tab->n[H]);
+  xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:number-columns-repeated"), "%d", body->n[H]);
   xmlTextWriterEndElement (odt->content_wtr);
 
 
   /* Deal with row headers */
-  if (tab->h[V][0] > 0)
+  if (body->h[V][0] > 0)
     xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-header-rows"));
 
 
   /* Write all the rows */
-  for (r = 0 ; r < tab->n[V]; ++r)
+  for (int r = 0 ; r < body->n[V]; ++r)
     {
       /* Start row definition */
       xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-row"));
 
       /* Write all the columns */
-      for (c = 0 ; c < tab->n[H] ; ++c)
+      for (int c = 0 ; c < body->n[H] ; ++c)
        {
           struct table_cell cell;
 
-          table_get_cell (tab, c, r, &cell);
+          table_get_cell (body, c, r, &cell);
 
           if (c == cell.d[H][0] && r == cell.d[V][0])
             {
@@ -522,23 +531,12 @@ write_table (struct odt_driver *odt, const struct table_item *item)
 
               xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
 
-              if (r < tab->h[V][0] || c < tab->h[H][0])
+              if (r < body->h[V][0] || c < body->h[H][0])
                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading"));
               else
                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents"));
 
-              if (cell.options & TAB_MARKUP)
-                {
-                  /* XXX */
-                  char *s = output_get_text_from_markup (cell.text);
-                  write_xml_with_line_breaks (odt, s);
-                  free (s);
-                }
-              else
-                write_xml_with_line_breaks (odt, cell.text);
-
-              for (int i = 0; i < cell.n_footnotes; i++)
-                write_footnote (odt, cell.footnotes[i]);
+              write_table_item_cell (odt, pt, &cell);
 
               xmlTextWriterEndElement (odt->content_wtr); /* text:p */
               xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */
@@ -552,7 +550,7 @@ write_table (struct odt_driver *odt, const struct table_item *item)
 
       xmlTextWriterEndElement (odt->content_wtr); /* row */
 
-      int ht = tab->h[V][0];
+      int ht = body->h[V][0];
       if (ht > 0 && r == ht - 1)
        xmlTextWriterEndElement (odt->content_wtr); /* table-header-rows */
     }
@@ -560,7 +558,22 @@ write_table (struct odt_driver *odt, const struct table_item *item)
   xmlTextWriterEndElement (odt->content_wtr); /* table */
 
   /* Write a caption for the table */
-  write_table_item_cell (odt, table_item_get_caption (item));
+  write_table__ (odt, pt, caption);
+  write_table__ (odt, pt, footnotes);
+
+  table_unref (title);
+  table_unref (layers);
+  table_unref (body);
+  table_unref (caption);
+  table_unref (footnotes);
+}
+
+static void
+write_table (struct odt_driver *odt, const struct table_item *item)
+{
+  size_t *layer_indexes;
+  PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
+    write_table_layer (odt, item->pt, layer_indexes);
 }
 
 static void
index 665747246a15bbed7702b438df053bcdc21f32f0..cd21e2cb195b74c81208ee90fe9860c7bb74a0ea 100644 (file)
 
 #include <stdlib.h>
 
-#include "output/pivot-table.h"
+#include "output/pivot-output.h"
 
 #include "data/settings.h"
 #include "libpspp/assertion.h"
 #include "libpspp/pool.h"
-#include "output/table.h"
 #include "output/page-eject-item.h"
+#include "output/pivot-table.h"
 #include "output/table-item.h"
-#include "output/text-item.h"
 #include "output/table-provider.h"
+#include "output/table.h"
+#include "output/text-item.h"
 
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
 #define H TABLE_HORZ
 #define V TABLE_VERT
 
+size_t *
+pivot_output_next_layer (const struct pivot_table *pt, size_t *indexes,
+                         bool print)
+{
+  const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
+  if (print && pt->look->print_all_layers)
+    return pivot_axis_iterator_next (indexes, layer_axis);
+  else if (!indexes)
+    {
+      size_t size = layer_axis->n_dimensions * sizeof *pt->current_layer;
+      return size ? xmemdup (pt->current_layer, size) : xmalloc (1);
+    }
+  else
+    {
+      free (indexes);
+      return NULL;
+    }
+}
+
 static const struct pivot_category *
 find_category (const struct pivot_dimension *d, int dim_index,
                const size_t *indexes, int row_ofs)
@@ -93,106 +113,28 @@ table_area_style_override (struct pool *pool,
   return out;
 }
 
-static int
-format_cell (const struct pivot_value *value, int style_idx,
-             enum settings_value_show show_values,
-             enum settings_value_show show_variables,
-             bool rotate_label, struct string *s)
-{
-  int options = style_idx << TAB_STYLE_SHIFT;
-  if (value)
-    {
-      bool numeric = pivot_value_format_body (value, show_values,
-                                              show_variables, s);
-      if (numeric)
-        options |= TAB_NUMERIC;
-      if (value->font_style && value->font_style->markup)
-        options |= TAB_MARKUP;
-      if (rotate_label)
-        options |= TAB_ROTATE;
-    }
-  return options;
-}
-
 static void
 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
-           const struct table_area_style *style, int style_idx,
-           const struct pivot_value *value, struct footnote **footnotes,
-           enum settings_value_show show_values,
-           enum settings_value_show show_variables,
+           int style_idx, const struct pivot_value *value,
            bool rotate_label)
 {
-  struct string s = DS_EMPTY_INITIALIZER;
-  int options = format_cell (value, style_idx,
-                             show_values, show_variables, rotate_label, &s);
-  table_joint_text (t, x1, y1, x2, y2, options, ds_cstr (&s));
-  ds_destroy (&s);
-
-  if (value)
-    {
-      if (value->cell_style || value->font_style || rotate_label)
-        table_add_style (t, x1, y1,
-                         table_area_style_override (t->container, style,
-                                                    value->cell_style,
-                                                    value->font_style,
-                                                    rotate_label));
-
-      for (size_t i = 0; i < value->n_footnotes; i++)
-        {
-          struct footnote *f = footnotes[value->footnotes[i]->idx];
-          if (f)
-            table_add_footnote (t, x1, y1, f);
-        }
+  int options = style_idx << TAB_STYLE_SHIFT;
+  if (rotate_label)
+    options |= TAB_ROTATE;
 
-      if (value->n_subscripts)
-        table_add_subscripts (t, x1, y1,
-                              value->subscripts, value->n_subscripts);
-    }
+  table_put (t, x1, y1, x2, y2, options, value);
 }
 
-static struct table_cell *
-pivot_value_to_table_cell (const struct pivot_value *value,
-                           const struct table_area_style *style, int style_idx,
-                           struct footnote **footnotes,
-                           enum settings_value_show show_values,
-                           enum settings_value_show show_variables)
+static void
+fill_cell_owned (struct table *t, int x1, int y1, int x2, int y2,
+                 int style_idx, struct string *s, bool rotate_label)
 {
-  if (!value)
-    return NULL;
-
-  struct string s = DS_EMPTY_INITIALIZER;
-  int options = format_cell (value, style_idx,
-                             show_values, show_variables, false, &s);
-
-  struct table_cell *cell = xmalloc (sizeof *cell);
-  *cell = (struct table_cell) {
-    .options = options,
-    .text = ds_steal_cstr (&s),
-    .style = table_area_style_override (
-      NULL, style, value->cell_style, value->font_style, false),
-  };
-
-  if (value->n_subscripts)
-    {
-      cell->subscripts = xnmalloc (value->n_subscripts,
-                                   sizeof *cell->subscripts);
-      cell->n_subscripts = value->n_subscripts;
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        cell->subscripts[i] = xstrdup (value->subscripts[i]);
-    }
-
-  if (value->n_footnotes)
-    {
-      cell->footnotes = xnmalloc (value->n_footnotes, sizeof *cell->footnotes);
-      for (size_t i = 0; i < value->n_footnotes; i++)
-        {
-          struct footnote *f = footnotes[value->footnotes[i]->idx];
-          if (f)
-            cell->footnotes[cell->n_footnotes++] = f;
-        }
-    }
+  int options = style_idx << TAB_STYLE_SHIFT;
+  if (rotate_label)
+    options |= TAB_ROTATE;
 
-  return cell;
+  table_put_owned (t, x1, y1, x2, y2, options,
+                   pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
 }
 
 static int
@@ -223,30 +165,25 @@ draw_line (struct table *t, const struct table_border_style *styles,
    headings.  */
 static void
 compose_headings (struct table *t,
-                  const struct pivot_axis *a_axis, enum table_axis a,
-                  const struct pivot_axis *b_axis,
+                  const struct pivot_axis *h_axis, enum table_axis h,
+                  const struct pivot_axis *v_axis,
                   const struct table_border_style *borders,
                   enum pivot_border dim_col_horz,
                   enum pivot_border dim_col_vert,
                   enum pivot_border cat_col_horz,
                   enum pivot_border cat_col_vert,
                   const size_t *column_enumeration, size_t n_columns,
-                  const struct table_area_style *label_style,
                   int label_style_idx,
-                  const struct table_area_style *corner_style,
-                  struct footnote **footnotes,
-                  enum settings_value_show show_values,
-                  enum settings_value_show show_variables,
                   bool rotate_inner_labels, bool rotate_outer_labels)
 {
-  enum table_axis b = !a;
-  int b_size = a_axis->label_depth;
-  int a_ofs = b_axis->label_depth;
+  const enum table_axis v = !h;
+  const int v_size = h_axis->label_depth;
+  const int h_ofs = v_axis->label_depth;
 
-  if (!a_axis->n_dimensions || !n_columns || !b_size)
+  if (!h_axis->n_dimensions || !n_columns || !v_size)
     return;
 
-  const int stride = MAX (1, a_axis->n_dimensions);
+  const int stride = MAX (1, h_axis->n_dimensions);
 
   /* Below, we're going to iterate through the dimensions.  Each dimension
      occupies one or more rows in the heading.  'top_row' is the top row of
@@ -300,9 +237,9 @@ compose_headings (struct table *t,
   */
   bool *vrules = xzalloc (n_columns + 1);
   vrules[0] = vrules[n_columns] = true;
-  for (int dim_index = a_axis->n_dimensions; --dim_index >= 0; )
+  for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
     {
-      const struct pivot_dimension *d = a_axis->dimensions[dim_index];
+      const struct pivot_dimension *d = h_axis->dimensions[dim_index];
       if (d->hide_all_labels)
         continue;
 
@@ -334,19 +271,18 @@ compose_headings (struct table *t,
               int y1 = top_row + row_ofs;
               int y2 = top_row + row_ofs + c->extra_depth + 1;
               bool is_outer_row = y1 == 0;
-              bool is_inner_row = y2 == b_size;
+              bool is_inner_row = y2 == v_size;
               if (pivot_category_is_leaf (c) || c->show_label)
                 {
                   int bb[TABLE_N_AXES][2];
-                  bb[a][0] = x1 + a_ofs;
-                  bb[a][1] = x2 + a_ofs - 1;
-                  bb[b][0] = y1;
-                  bb[b][1] = y2 - 1;
+                  bb[h][0] = x1 + h_ofs;
+                  bb[h][1] = x2 + h_ofs - 1;
+                  bb[v][0] = y1;
+                  bb[v][1] = y2 - 1;
                   bool rotate = ((rotate_inner_labels && is_inner_row)
                                  || (rotate_outer_labels && is_outer_row));
                   fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
-                             label_style, label_style_idx, c->name, footnotes,
-                             show_values, show_variables, rotate);
+                             label_style_idx, c->name, rotate);
 
                   /* Draw all the vertical lines in our running example, other
                      than the far left and far right ones.  Only the ones that
@@ -365,17 +301,17 @@ compose_headings (struct table *t,
                      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
                   */
                   enum pivot_border style
-                    = (y1 == b_size - 1 ? cat_col_vert : dim_col_vert);
+                    = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
                   if (!vrules[x2])
                     {
-                      draw_line (t, borders, style, b, x2 + a_ofs, y1,
-                                 t->n[b] - 1);
+                      draw_line (t, borders, style, v, x2 + h_ofs, y1,
+                                 t->n[v] - 1);
                       vrules[x2] = true;
                     }
                   if (!vrules[x1])
                     {
-                      draw_line (t, borders, style, b, x1 + a_ofs, y1,
-                                 t->n[b] - 1);
+                      draw_line (t, borders, style, v, x1 + h_ofs, y1,
+                                 t->n[v] - 1);
                       vrules[x1] = true;
                     }
                 }
@@ -397,22 +333,21 @@ compose_headings (struct table *t,
                  +-----+-----+-----+-----+-----+-----+-----+-----+-----+
               */
               if (c->parent && c->parent->show_label)
-                draw_line (t, borders, cat_col_horz, a, y1,
-                           x1 + a_ofs, x2 + a_ofs - 1);
+                draw_line (t, borders, cat_col_horz, h, y1,
+                           x1 + h_ofs, x2 + h_ofs - 1);
               x1 = x2;
             }
         }
 
-      if (d->root->show_label_in_corner && a_ofs > 0)
+      if (d->root->show_label_in_corner && h_ofs > 0)
         {
           int bb[TABLE_N_AXES][2];
-          bb[a][0] = 0;
-          bb[a][1] = a_ofs - 1;
-          bb[b][0] = top_row;
-          bb[b][1] = top_row + d->label_depth - 1;
+          bb[h][0] = 0;
+          bb[h][1] = h_ofs - 1;
+          bb[v][0] = top_row;
+          bb[v][1] = top_row + d->label_depth - 1;
           fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
-                     corner_style, PIVOT_AREA_CORNER, d->root->name, footnotes,
-                     show_values, show_variables, false);
+                     PIVOT_AREA_CORNER, d->root->name, false);
         }
 
       /* Draw the horizontal line between dimensions, e.g. the ===== line here:
@@ -427,100 +362,157 @@ compose_headings (struct table *t,
          |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
          +-----+-----+-----+-----+-----+-----+-----+-----+-----+
       */
-      if (dim_index != a_axis->n_dimensions - 1)
-        draw_line (t, borders, dim_col_horz, a, top_row, a_ofs,
-                   t->n[a] - 1);
+      if (dim_index != h_axis->n_dimensions - 1)
+        draw_line (t, borders, dim_col_horz, h, top_row, h_ofs,
+                   t->n[h] - 1);
       top_row += d->label_depth;
     }
   free (vrules);
 }
 
+static struct table *
+create_aux_table (const struct pivot_table *pt, int nc, int nr,
+                  int style_idx)
+{
+  struct table *table = table_create (nc, nr, 0, 0, 0, 0);
+  table->styles[style_idx] = table_area_style_override (
+      table->container, &pt->look->areas[style_idx], NULL, NULL, false);
+  return table;
+}
+
+
 static void
-pivot_table_submit_layer (const struct pivot_table *pt,
-                          const size_t *layer_indexes)
+add_references (const struct pivot_table *pt, const struct table *table,
+                bool *refs, size_t *n_refs)
+{
+  if (!table)
+    return;
+
+  for (int y = 0; y < table->n[V]; y++)
+    for (int x = 0; x < table->n[H]; )
+      {
+        struct table_cell cell;
+        table_get_cell (table, x, y, &cell);
+
+        if (x == cell.d[H][0] && y == cell.d[V][0])
+          {
+            for (size_t i = 0; i < cell.value->n_footnotes; i++)
+              {
+                size_t idx = cell.value->footnote_indexes[i];
+                assert (idx < pt->n_footnotes);
+
+                if (!refs[idx])
+                  {
+                    refs[idx] = true;
+                    (*n_refs)++;
+                  }
+              }
+          }
+
+        x = cell.d[TABLE_HORZ][1];
+      }
+}
+
+static struct pivot_footnote **
+collect_footnotes (const struct pivot_table *pt,
+                   const struct table *title,
+                   const struct table *layers,
+                   const struct table *body,
+                   const struct table *caption,
+                   size_t *n_footnotesp)
+{
+  if (!pt->n_footnotes)
+    {
+      *n_footnotesp = 0;
+      return NULL;
+    }
+
+  bool *refs = xzalloc (pt->n_footnotes);
+  size_t n_refs = 0;
+  add_references (pt, title, refs, &n_refs);
+  add_references (pt, layers, refs, &n_refs);
+  add_references (pt, body, refs, &n_refs);
+  add_references (pt, caption, refs, &n_refs);
+
+  struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
+  size_t n_footnotes = 0;
+  for (size_t i = 0; i < pt->n_footnotes; i++)
+    if (refs[i])
+      footnotes[n_footnotes++] = pt->footnotes[i];
+  assert (n_footnotes == n_refs);
+
+  free (refs);
+
+  *n_footnotesp = n_footnotes;
+  return footnotes;
+}
+
+void
+pivot_output (const struct pivot_table *pt,
+              const size_t *layer_indexes,
+              bool printing UNUSED,
+              struct table **titlep,
+              struct table **layersp,
+              struct table **bodyp,
+              struct table **captionp,
+              struct table **footnotesp,
+              struct pivot_footnote ***fp, size_t *nfp)
 {
   const size_t *pindexes[PIVOT_N_AXES]
     = { [PIVOT_AXIS_LAYER] = layer_indexes };
 
-  size_t body[TABLE_N_AXES];
+  size_t data[TABLE_N_AXES];
   size_t *column_enumeration = pivot_table_enumerate_axis (
-    pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &body[H]);
+    pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
   size_t *row_enumeration = pivot_table_enumerate_axis (
-    pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &body[V]);
+    pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
 
   int stub[TABLE_N_AXES] = {
     [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
     [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
   };
-  struct table *table = table_create (body[H] + stub[H],
-                                      body[V] + stub[V],
-                                      stub[H], 0, stub[V], 0);
-
+  struct table *body = table_create (data[H] + stub[H],
+                                     data[V] + stub[V],
+                                     stub[H], 0, stub[V], 0);
   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
-    table->styles[i] = table_area_style_override (
-      table->container, &pt->look->areas[i], NULL, NULL, false);
+    body->styles[i] = table_area_style_override (
+      body->container, &pt->look->areas[i], NULL, NULL, false);
+
+  struct table_border_style borders[PIVOT_N_BORDERS];
+  memcpy (borders, pt->look->borders, sizeof borders);
+  if (!printing && pt->show_grid_lines)
+    for (int b = 0; b < PIVOT_N_BORDERS; b++)
+      if (borders[b].stroke == TABLE_STROKE_NONE)
+        borders[b].stroke = TABLE_STROKE_DASHED;
 
   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
     {
       const struct table_border_style *in = &pt->look->borders[i];
-      table->rule_colors[i] = pool_alloc (table->container,
-                                          sizeof *table->rule_colors[i]);
-      struct cell_color *out = table->rule_colors[i];
-      out->alpha = in->color.alpha;
-      out->r = in->color.r;
-      out->g = in->color.g;
-      out->b = in->color.b;
+      body->rule_colors[i] = pool_alloc (body->container,
+                                         sizeof *body->rule_colors[i]);
+      *body->rule_colors[i] = in->color;
     }
 
-  struct footnote **footnotes = XCALLOC (pt->n_footnotes,  struct footnote *);
-  for (size_t i = 0; i < pt->n_footnotes; i++)
-    {
-      const struct pivot_footnote *pf = pt->footnotes[i];
-
-      if (!pf->show)
-        continue;
-
-      char *content = pivot_value_to_string (pf->content, pt->show_values,
-                                             pt->show_variables);
-      char *marker = pivot_value_to_string (pf->marker, pt->show_values,
-                                            pt->show_variables);
-      footnotes[i] = table_create_footnote (
-        table, i, content, marker,
-        table_area_style_override (table->container,
-                                   &pt->look->areas[PIVOT_AREA_FOOTER],
-                                   pf->content->cell_style,
-                                   pf->content->font_style,
-                                   false));
-      free (marker);
-      free (content);
-    }
-
-  compose_headings (table,
+  compose_headings (body,
                     &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
-                    pt->look->borders,
+                    borders,
                     PIVOT_BORDER_DIM_COL_HORZ,
                     PIVOT_BORDER_DIM_COL_VERT,
                     PIVOT_BORDER_CAT_COL_HORZ,
                     PIVOT_BORDER_CAT_COL_VERT,
-                    column_enumeration, body[H],
-                    &pt->look->areas[PIVOT_AREA_COLUMN_LABELS],
+                    column_enumeration, data[H],
                     PIVOT_AREA_COLUMN_LABELS,
-                    &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
-                    pt->show_values, pt->show_variables,
                     pt->rotate_outer_row_labels, false);
 
-  compose_headings (table,
+  compose_headings (body,
                     &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
-                    pt->look->borders,
+                    borders,
                     PIVOT_BORDER_DIM_ROW_VERT,
                     PIVOT_BORDER_DIM_ROW_HORZ,
                     PIVOT_BORDER_CAT_ROW_VERT,
                     PIVOT_BORDER_CAT_ROW_HORZ,
-                    row_enumeration, body[V],
-                    &pt->look->areas[PIVOT_AREA_ROW_LABELS],
+                    row_enumeration, data[V],
                     PIVOT_AREA_ROW_LABELS,
-                    &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
-                    pt->show_values, pt->show_variables,
                     false, pt->rotate_inner_column_labels);
 
   size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
@@ -535,12 +527,8 @@ pivot_table_submit_layer (const struct pivot_table *pt,
         {
           pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
           const struct pivot_value *value = pivot_table_get (pt, dindexes);
-          fill_cell (table,
-                     x + stub[H], y + stub[V],
-                     x + stub[H], y + stub[V],
-                     &pt->look->areas[PIVOT_AREA_DATA], PIVOT_AREA_DATA,
-                     value, footnotes,
-                     pt->show_values, pt->show_variables, false);
+          fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
+                     PIVOT_AREA_DATA, value, false);
 
           x++;
         }
@@ -551,132 +539,131 @@ pivot_table_submit_layer (const struct pivot_table *pt,
 
   if ((pt->corner_text || !pt->look->row_labels_in_corner)
       && stub[H] && stub[V])
-    fill_cell (table, 0, 0, stub[H] - 1, stub[V] - 1,
-               &pt->look->areas[PIVOT_AREA_CORNER], PIVOT_AREA_CORNER,
-               pt->corner_text, footnotes,
-               pt->show_values, pt->show_variables, false);
+    fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
+               PIVOT_AREA_CORNER, pt->corner_text, false);
 
-  if (table->n[H] && table->n[V])
+  if (body->n[H] && body->n[V])
     {
       table_hline (
-        table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_TOP),
-        0, table->n[H] - 1, 0);
+        body, get_table_rule (borders, PIVOT_BORDER_INNER_TOP),
+        0, body->n[H] - 1, 0);
       table_hline (
-        table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_BOTTOM),
-        0, table->n[H] - 1, table->n[V]);
+        body, get_table_rule (borders, PIVOT_BORDER_INNER_BOTTOM),
+        0, body->n[H] - 1, body->n[V]);
       table_vline (
-        table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_LEFT),
-        0, 0, table->n[V] - 1);
+        body, get_table_rule (borders, PIVOT_BORDER_INNER_LEFT),
+        0, 0, body->n[V] - 1);
       table_vline (
-        table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_RIGHT),
-        table->n[H], 0, table->n[V] - 1);
+        body, get_table_rule (borders, PIVOT_BORDER_INNER_RIGHT),
+        body->n[H], 0, body->n[V] - 1);
 
       if (stub[V])
         table_hline (
-          table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_TOP),
-          0, table->n[H] - 1, stub[V]);
+          body, get_table_rule (borders, PIVOT_BORDER_DATA_TOP),
+          0, body->n[H] - 1, stub[V]);
       if (stub[H])
         table_vline (
-          table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_LEFT),
-          stub[H], 0, table->n[V] - 1);
+          body, get_table_rule (borders, PIVOT_BORDER_DATA_LEFT),
+          stub[H], 0, body->n[V] - 1);
 
     }
   free (column_enumeration);
   free (row_enumeration);
 
-  struct table_item *ti = table_item_create (table);
-
-  if (pt->notes)
-    table_item_set_notes (ti, pt->notes);
-
-  if (pt->title && pt->show_title)
+  /* Title. */
+  struct table *title;
+  if (pt->title && pt->show_title && titlep)
     {
-      struct table_cell *title = pivot_value_to_table_cell (
-        pt->title, &pt->look->areas[PIVOT_AREA_TITLE], PIVOT_AREA_TITLE,
-        footnotes, pt->show_values, pt->show_variables);
-      table_item_set_title (ti, title);
-      table_cell_destroy (title);
+      title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
+      fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
     }
+  else
+    title = NULL;
 
+  /* Layers. */
   const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
-  struct table_item_layers *layers = NULL;
-  for (size_t i = 0; i < layer_axis->n_dimensions; i++)
+  int n_layers = 0;
+  if (layersp)
+    for (size_t i = 0; i < layer_axis->n_dimensions; i++)
+      {
+        const struct pivot_dimension *d = layer_axis->dimensions[i];
+        if (d->n_leaves)
+          n_layers++;
+      }
+
+  struct table *layers;
+  if (n_layers > 0)
     {
-      const struct pivot_dimension *d = layer_axis->dimensions[i];
-      if (d->n_leaves)
+      layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
+      size_t y = 0;
+      for (size_t i = 0; i < layer_axis->n_dimensions; i++)
         {
-          if (!layers)
-            {
-              layers = xzalloc (sizeof *layers);
-              layers->style = table_area_style_override (
-                NULL, &pt->look->areas[PIVOT_AREA_LAYERS], NULL, NULL, false);
-              layers->layers = xnmalloc (layer_axis->n_dimensions,
-                                         sizeof *layers->layers);
-            }
+          const struct pivot_dimension *d = layer_axis->dimensions[i];
+          if (!d->n_leaves)
+            continue;
 
-          const struct pivot_value *name
-            = d->data_leaves[layer_indexes[i]]->name;
-          struct table_item_layer *layer = &layers->layers[layers->n_layers++];
           struct string s = DS_EMPTY_INITIALIZER;
-          pivot_value_format_body (name, pt->show_values, pt->show_variables,
-                                   &s);
-          layer->content = ds_steal_cstr (&s);
-          layer->n_footnotes = 0;
-          layer->footnotes = xnmalloc (name->n_footnotes,
-                                       sizeof *layer->footnotes);
-          for (size_t i = 0; i < name->n_footnotes; i++)
-            {
-              struct footnote *f = footnotes[name->footnotes[i]->idx];
-              if (f)
-                layer->footnotes[layer->n_footnotes++] = f;
-            }
+          pivot_value_format (d->root->name, pt, &s);
+          ds_put_cstr (&s, ": ");
+          pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
+          fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
+          y++;
         }
     }
-  if (layers)
-    {
-      table_item_set_layers (ti, layers);
-      table_item_layers_destroy (layers);
-    }
+  else
+    layers = NULL;
 
-  if (pt->caption && pt->show_caption)
+  /* Caption. */
+  struct table *caption;
+  if (pt->caption && pt->show_caption && captionp)
     {
-      struct table_cell *caption = pivot_value_to_table_cell (
-        pt->caption, &pt->look->areas[PIVOT_AREA_CAPTION], PIVOT_AREA_CAPTION,
-        footnotes, pt->show_values, pt->show_variables);
-      table_item_set_caption (ti, caption);
-      table_cell_destroy (caption);
+      caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
+      fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
     }
-
-  free (footnotes);
-  ti->pt = pivot_table_ref (pt);
-
-  table_item_submit (ti);
-}
-
-void
-pivot_table_submit (struct pivot_table *pt)
-{
-  pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, pt));
-
-  int old_decimal = settings_get_decimal_char (FMT_COMMA);
-  if (pt->decimal == '.' || pt->decimal == ',')
-    settings_set_decimal_char (pt->decimal);
-
-  if (pt->look->print_all_layers)
+  else
+    caption = NULL;
+
+  /* Footnotes. */
+  size_t nf;
+  struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
+                                                 caption, &nf);
+  struct table *footnotes;
+  if (nf && footnotesp)
     {
-      size_t *layer_indexes;
+      footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
 
-      PIVOT_AXIS_FOR_EACH (layer_indexes, &pt->axes[PIVOT_AXIS_LAYER])
+      for (size_t i = 0; i < nf; i++)
         {
-          if (pt->look->paginate_layers)
-            page_eject_item_submit (page_eject_item_create ());
-          pivot_table_submit_layer (pt, layer_indexes);
+          struct string s = DS_EMPTY_INITIALIZER;
+          pivot_value_format (f[i]->marker, pt, &s);
+          ds_put_cstr (&s, ". ");
+          pivot_value_format (f[i]->content, pt, &s);
+          fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
+                           false);
         }
     }
   else
-    pivot_table_submit_layer (pt, pt->current_layer);
-
-  settings_set_decimal_char (old_decimal);
+    footnotes = NULL;
+
+  *titlep = title;
+  if (layersp)
+    *layersp = layers;
+  *bodyp = body;
+  if (captionp)
+    *captionp = caption;
+  if (footnotesp)
+    *footnotesp = footnotes;
+  if (fp)
+    {
+      *fp = f;
+      *nfp = nf;
+    }
+  else
+    free (f);
+}
 
-  pivot_table_unref (pt);
+void
+pivot_table_submit (struct pivot_table *pt)
+{
+  table_item_submit (table_item_create (pt));
 }
diff --git a/src/output/pivot-output.h b/src/output/pivot-output.h
new file mode 100644 (file)
index 0000000..4013c1b
--- /dev/null
@@ -0,0 +1,43 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2020 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef OUTPUT_PIVOT_OUTPUT_H
+#define OUTPUT_PIVOT_OUTPUT_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct pivot_footnote;
+struct pivot_table;
+struct table;
+
+#define PIVOT_OUTPUT_FOR_EACH_LAYER(INDEXES, PT, PRINT)                 \
+  for ((INDEXES) = NULL;                                                \
+       ((INDEXES) = pivot_output_next_layer (PT, INDEXES, PRINT)); )
+size_t *pivot_output_next_layer (const struct pivot_table *,
+                                 size_t *indexes, bool print);
+
+void pivot_output (const struct pivot_table *,
+                   const size_t *layer_indexes,
+                   bool printing,
+                   struct table **titlep,
+                   struct table **layersp,
+                   struct table **bodyp,
+                   struct table **captionp,
+                   struct table **footnotesp,
+                   struct pivot_footnote ***fp, size_t *nfp);
+
+#endif /* output/pivot-output.h */
index 3bd0911b46e7adec6bbd8cda843f3c6d9a060360..9e45f244e061f6e162873e5c7a2f71e9ee304cf8 100644 (file)
@@ -25,6 +25,7 @@
 #include "data/value.h"
 #include "data/variable.h"
 #include "data/file-name.h"
+#include "libpspp/array.h"
 #include "libpspp/assertion.h"
 #include "libpspp/hash-functions.h"
 #include "libpspp/i18n.h"
@@ -806,6 +807,11 @@ is_pivot_result_class (const char *s)
 \f
 /* Pivot tables. */
 
+static struct pivot_cell *pivot_table_insert_cell (struct pivot_table *,
+                                                   const size_t *dindexes);
+static void pivot_table_delete_cell (struct pivot_table *,
+                                     struct pivot_cell *);
+
 /* Creates and returns a new pivot table with the given TITLE.  TITLE should be
    a text string marked for translation but not actually translated yet,
    e.g. N_("Descriptive Statistics").  The un-translated text string is used as
@@ -884,6 +890,233 @@ pivot_table_ref (const struct pivot_table *table_)
   return table;
 }
 
+static char *
+xstrdup_if_nonnull (const char *s)
+{
+  return s ? xstrdup (s) : NULL;
+}
+
+static struct pivot_table_sizing
+clone_sizing (const struct pivot_table_sizing *s)
+{
+  return (struct pivot_table_sizing) {
+    .widths = (s->n_widths
+               ? xmemdup (s->widths, s->n_widths * sizeof *s->widths)
+               : NULL),
+    .n_widths = s->n_widths,
+
+    .breaks = (s->n_breaks
+               ? xmemdup (s->breaks, s->n_breaks * sizeof *s->breaks)
+               : NULL),
+    .n_breaks = s->n_breaks,
+
+    .keeps = (s->n_keeps
+              ? xmemdup (s->keeps, s->n_keeps * sizeof *s->keeps)
+              : NULL),
+    .n_keeps = s->n_keeps,
+  };
+}
+
+static struct pivot_footnote **
+clone_footnotes (struct pivot_footnote **old, size_t n)
+{
+  if (!n)
+    return NULL;
+
+  struct pivot_footnote **new = xmalloc (n * sizeof *new);
+  for (size_t i = 0; i < n; i++)
+    {
+      new[i] = xmalloc (sizeof *new[i]);
+      *new[i] = (struct pivot_footnote) {
+        .idx = old[i]->idx,
+        .content = pivot_value_clone (old[i]->content),
+        .marker = pivot_value_clone (old[i]->marker),
+        .show = old[i]->show,
+      };
+    }
+  return new;
+}
+
+static struct pivot_category *
+clone_category (struct pivot_category *old,
+                struct pivot_dimension *new_dimension,
+                struct pivot_category *new_parent)
+{
+  struct pivot_category *new = xmalloc (sizeof *new);
+  *new = (struct pivot_category) {
+    .name = pivot_value_clone (old->name),
+    .parent = new_parent,
+    .dimension = new_dimension,
+    .label_depth = old->label_depth,
+    .extra_depth = old->extra_depth,
+
+    .subs = (old->n_subs
+             ? xzalloc (old->n_subs * sizeof *new->subs)
+             : NULL),
+    .n_subs = old->n_subs,
+    .allocated_subs = old->n_subs,
+
+    .show_label = old->show_label,
+    .show_label_in_corner = old->show_label_in_corner,
+
+    .format = old->format,
+    .group_index = old->group_index,
+    .data_index = old->data_index,
+    .presentation_index = old->presentation_index,
+  };
+
+  if (pivot_category_is_leaf (old))
+    {
+      new->dimension->data_leaves[new->data_index] = new;
+      new->dimension->presentation_leaves[new->presentation_index] = new;
+    }
+
+  for (size_t i = 0; i < new->n_subs; i++)
+    new->subs[i] = clone_category (old->subs[i], new_dimension, new);
+
+  return new;
+}
+
+static struct pivot_dimension *
+clone_dimension (struct pivot_dimension *old, struct pivot_table *new_pt)
+{
+  struct pivot_dimension *new = xmalloc (sizeof *new);
+  *new = (struct pivot_dimension) {
+    .table = new_pt,
+    .axis_type = old->axis_type,
+    .level = old->level,
+    .top_index = old->top_index,
+    .data_leaves = xzalloc (old->n_leaves * sizeof *new->data_leaves),
+    .presentation_leaves = xzalloc (old->n_leaves
+                                    * sizeof *new->presentation_leaves),
+    .n_leaves = old->n_leaves,
+    .allocated_leaves = old->n_leaves,
+    .hide_all_labels = old->hide_all_labels,
+    .label_depth = old->label_depth,
+  };
+
+  new->root = clone_category (old->root, new, NULL);
+
+  return new;
+}
+
+static struct pivot_dimension **
+clone_dimensions (struct pivot_dimension **old, size_t n,
+                  struct pivot_table *new_pt)
+{
+  if (!n)
+    return NULL;
+
+  struct pivot_dimension **new = xmalloc (n * sizeof *new);
+  for (size_t i = 0; i < n; i++)
+    new[i] = clone_dimension (old[i], new_pt);
+  return new;
+}
+
+struct pivot_table *
+pivot_table_unshare (struct pivot_table *old)
+{
+  assert (old->ref_cnt > 0);
+  if (old->ref_cnt == 1)
+    return old;
+
+  pivot_table_unref (old);
+
+  struct pivot_table *new = xmalloc (sizeof *new);
+  *new = (struct pivot_table) {
+    .ref_cnt = 1,
+
+    .look = pivot_table_look_ref (old->look),
+
+    .rotate_inner_column_labels = old->rotate_inner_column_labels,
+    .rotate_outer_row_labels = old->rotate_outer_row_labels,
+    .show_grid_lines = old->show_grid_lines,
+    .show_title = old->show_title,
+    .show_caption = old->show_caption,
+    .current_layer = (old->current_layer
+                      ? xmemdup (old->current_layer,
+                                 old->axes[PIVOT_AXIS_LAYER].n_dimensions
+                                 * sizeof *new->current_layer)
+                      : NULL),
+    .show_values = old->show_values,
+    .show_variables = old->show_variables,
+    .weight_format = old->weight_format,
+
+    .sizing = {
+      [TABLE_HORZ] = clone_sizing (&old->sizing[TABLE_HORZ]),
+      [TABLE_VERT] = clone_sizing (&old->sizing[TABLE_VERT]),
+    },
+
+    .epoch = old->epoch,
+    .decimal = old->decimal,
+    .grouping = old->grouping,
+    .ccs = {
+      [0] = xstrdup_if_nonnull (old->ccs[0]),
+      [1] = xstrdup_if_nonnull (old->ccs[1]),
+      [2] = xstrdup_if_nonnull (old->ccs[2]),
+      [3] = xstrdup_if_nonnull (old->ccs[3]),
+      [4] = xstrdup_if_nonnull (old->ccs[4]),
+    },
+    .small = old->small,
+
+    .command_local = xstrdup_if_nonnull (old->command_local),
+    .command_c = xstrdup_if_nonnull (old->command_c),
+    .language = xstrdup_if_nonnull (old->language),
+    .locale = xstrdup_if_nonnull (old->locale),
+
+    .dataset = xstrdup_if_nonnull (old->dataset),
+    .datafile = xstrdup_if_nonnull (old->datafile),
+    .date = old->date,
+
+    .footnotes = clone_footnotes (old->footnotes, old->n_footnotes),
+    .n_footnotes = old->n_footnotes,
+    .allocated_footnotes = old->n_footnotes,
+
+    .title = pivot_value_clone (old->title),
+    .subtype = pivot_value_clone (old->subtype),
+    .corner_text = pivot_value_clone (old->corner_text),
+    .caption = pivot_value_clone (old->caption),
+    .notes = xstrdup_if_nonnull (old->notes),
+
+    .dimensions = clone_dimensions (old->dimensions, old->n_dimensions, new),
+    .n_dimensions = old->n_dimensions,
+
+    .cells = HMAP_INITIALIZER (new->cells),
+  };
+
+  for (size_t i = 0; i < PIVOT_N_AXES; i++)
+    {
+      struct pivot_axis *new_axis = &new->axes[i];
+      const struct pivot_axis *old_axis = &old->axes[i];
+
+      *new_axis = (struct pivot_axis) {
+        .dimensions = xmalloc (old_axis->n_dimensions
+                               * sizeof *new_axis->dimensions),
+        .n_dimensions = old_axis->n_dimensions,
+        .extent = old_axis->extent,
+        .label_depth = old_axis->label_depth,
+      };
+
+      for (size_t i = 0; i < new_axis->n_dimensions; i++)
+        new_axis->dimensions[i] = new->dimensions[
+          old_axis->dimensions[i]->top_index];
+    }
+
+  const struct pivot_cell *old_cell;
+  size_t *dindexes = xmalloc (old->n_dimensions * sizeof *dindexes);
+  HMAP_FOR_EACH (old_cell, struct pivot_cell, hmap_node, &old->cells)
+    {
+      for (size_t i = 0; i < old->n_dimensions; i++)
+        dindexes[i] = old_cell->idx[i];
+      struct pivot_cell *new_cell
+        = pivot_table_insert_cell (new, dindexes);
+      new_cell->value = pivot_value_clone (old_cell->value);
+    }
+  free (dindexes);
+
+  return new;
+}
+
 /* Decreases TABLE's reference count, indicating that it has one fewer owner.
    If TABLE no longer has any owners, it is freed. */
 void
@@ -932,11 +1165,8 @@ pivot_table_unref (struct pivot_table *table)
   struct pivot_cell *cell, *next_cell;
   HMAP_FOR_EACH_SAFE (cell, next_cell, struct pivot_cell, hmap_node,
                       &table->cells)
-    {
-      hmap_delete (&table->cells, &cell->hmap_node);
-      pivot_value_destroy (cell->value);
-      free (cell);
-    }
+    pivot_table_delete_cell (table, cell);
+
   hmap_destroy (&table->cells);
 
   free (table);
@@ -950,6 +1180,124 @@ pivot_table_is_shared (const struct pivot_table *table)
   return table->ref_cnt > 1;
 }
 
+/* Swaps axes A and B in TABLE. */
+void
+pivot_table_swap_axes (struct pivot_table *table,
+                       enum pivot_axis_type a, enum pivot_axis_type b)
+{
+  if (a == b)
+    return;
+
+  struct pivot_axis tmp = table->axes[a];
+  table->axes[a] = table->axes[b];
+  table->axes[b] = tmp;
+
+  for (int a = 0; a < PIVOT_N_AXES; a++)
+    {
+      struct pivot_axis *axis = &table->axes[a];
+      for (size_t d = 0; d < axis->n_dimensions; d++)
+        axis->dimensions[d]->axis_type = a;
+    }
+
+  if (a == PIVOT_AXIS_LAYER || b == PIVOT_AXIS_LAYER)
+    {
+      free (table->current_layer);
+      table->current_layer = xzalloc (
+        table->axes[PIVOT_AXIS_LAYER].n_dimensions
+        * sizeof *table->current_layer);
+    }
+}
+
+/* Swaps the row and column axes in TABLE. */
+void
+pivot_table_transpose (struct pivot_table *table)
+{
+  pivot_table_swap_axes (table, PIVOT_AXIS_ROW, PIVOT_AXIS_COLUMN);
+}
+
+static void
+pivot_table_update_axes (struct pivot_table *table)
+{
+  for (int a = 0; a < PIVOT_N_AXES; a++)
+    {
+      struct pivot_axis *axis = &table->axes[a];
+
+      for (size_t d = 0; d < axis->n_dimensions; d++)
+        {
+          struct pivot_dimension *dim = axis->dimensions[d];
+          dim->axis_type = a;
+          dim->level = d;
+        }
+    }
+}
+
+/* Moves DIM from its current location in TABLE to POS within AXIS.  POS of 0
+   is the innermost dimension, 1 is the next one out, and so on. */
+void
+pivot_table_move_dimension (struct pivot_table *table,
+                            struct pivot_dimension *dim,
+                            enum pivot_axis_type axis, size_t pos)
+{
+  assert (dim->table == table);
+
+  struct pivot_axis *old_axis = &table->axes[dim->axis_type];
+  struct pivot_axis *new_axis = &table->axes[axis];
+  pos = MIN (pos, new_axis->n_dimensions);
+
+  if (old_axis == new_axis && pos == dim->level)
+    {
+      /* No change. */
+      return;
+    }
+
+
+  /* Update the current layer, if necessary.  If we're moving within the layer
+     axis, preserve the current layer. */
+  if (dim->axis_type == PIVOT_AXIS_LAYER)
+    {
+      if (axis == PIVOT_AXIS_LAYER)
+        {
+          /* Rearranging the layer axis. */
+          move_element (table->current_layer, old_axis->n_dimensions,
+                        sizeof *table->current_layer,
+                        dim->level, pos);
+        }
+      else
+        {
+          /* A layer is becoming a row or column. */
+          remove_element (table->current_layer, old_axis->n_dimensions,
+                          sizeof *table->current_layer, dim->level);
+        }
+    }
+  else if (axis == PIVOT_AXIS_LAYER)
+    {
+      /* A row or column is becoming a layer. */
+      table->current_layer = xrealloc (
+        table->current_layer,
+        (new_axis->n_dimensions + 1) * sizeof *table->current_layer);
+      insert_element (table->current_layer, new_axis->n_dimensions,
+                      sizeof *table->current_layer, pos);
+      table->current_layer[pos] = 0;
+    }
+
+  /* Remove DIM from its current axis. */
+  remove_element (old_axis->dimensions, old_axis->n_dimensions,
+                  sizeof *old_axis->dimensions, dim->level);
+  old_axis->n_dimensions--;
+
+  /* Insert DIM into its new axis. */
+  new_axis->dimensions = xrealloc (
+    new_axis->dimensions,
+    (new_axis->n_dimensions + 1) * sizeof *new_axis->dimensions);
+  insert_element (new_axis->dimensions, new_axis->n_dimensions,
+                  sizeof *new_axis->dimensions, pos);
+  new_axis->dimensions[pos] = dim;
+  new_axis->n_dimensions++;
+
+  pivot_table_update_axes (table);
+}
+
+
 const struct pivot_table_look *
 pivot_table_get_look (const struct pivot_table *table)
 {
@@ -1300,9 +1648,9 @@ pivot_table_enumerate_axis (const struct pivot_table *table,
   return enumeration;
 }
 
-static const struct pivot_cell *
+static struct pivot_cell *
 pivot_table_lookup_cell (const struct pivot_table *table,
-                          const size_t *dindexes)
+                         const size_t *dindexes)
 {
   unsigned int hash = pivot_cell_hash_indexes (dindexes, table->n_dimensions);
   return pivot_table_lookup_cell__ (table, dindexes, hash);
@@ -1324,6 +1672,27 @@ pivot_table_get_rw (struct pivot_table *table, const size_t *dindexes)
   return cell->value;
 }
 
+static void
+pivot_table_delete_cell (struct pivot_table *table, struct pivot_cell *cell)
+{
+  hmap_delete (&table->cells, &cell->hmap_node);
+  pivot_value_destroy (cell->value);
+  free (cell);
+}
+
+bool
+pivot_table_delete (struct pivot_table *table, const size_t *dindexes)
+{
+  struct pivot_cell *cell = pivot_table_lookup_cell (table, dindexes);
+  if (cell)
+    {
+      pivot_table_delete_cell (table, cell);
+      return true;
+    }
+  else
+    return false;
+}
+
 static void
 distribute_extra_depth (struct pivot_category *category, size_t extra_depth)
 {
@@ -1415,8 +1784,7 @@ indent (int indentation)
 static void
 pivot_value_dump (const struct pivot_value *value)
 {
-  char *s = pivot_value_to_string (value, SETTINGS_VALUE_SHOW_DEFAULT,
-                                   SETTINGS_VALUE_SHOW_DEFAULT);
+  char *s = pivot_value_to_string_defaults (value);
   fputs (s, stdout);
   free (s);
 }
@@ -1498,10 +1866,9 @@ table_border_style_dump (enum pivot_border border,
 }
 
 static char ***
-compose_headings (const struct pivot_axis *axis,
-                  const size_t *column_enumeration,
-                  enum settings_value_show show_values,
-                  enum settings_value_show show_variables)
+compose_headings (const struct pivot_table *pt,
+                  const struct pivot_axis *axis,
+                  const size_t *column_enumeration)
 {
   if (!axis->n_dimensions || !axis->extent || !axis->label_depth)
     return NULL;
@@ -1528,8 +1895,7 @@ compose_headings (const struct pivot_axis *axis,
               if (pivot_category_is_leaf (c) || (c->show_label
                                                  && !c->show_label_in_corner))
                 {
-                  headings[row][column] = pivot_value_to_string (
-                    c->name, show_values, show_variables);
+                  headings[row][column] = pivot_value_to_string (c->name, pt);
                   if (!*headings[row][column])
                     headings[row][column] = xstrdup ("<blank>");
                   row--;
@@ -1596,6 +1962,8 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
   if (!table)
     return;
 
+  pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, table));
+
   int old_decimal = settings_get_decimal_char (FMT_COMMA);
   if (table->decimal == '.' || table->decimal == ',')
     settings_set_decimal_char (table->decimal);
@@ -1650,12 +2018,9 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
       for (size_t i = 0; i < layer_axis->n_dimensions; i++)
         {
           const struct pivot_dimension *d = layer_axis->dimensions[i];
-          char *name = pivot_value_to_string (d->root->name,
-                                              table->show_values,
-                                              table->show_variables);
+          char *name = pivot_value_to_string (d->root->name, table);
           char *value = pivot_value_to_string (
-            d->data_leaves[table->current_layer[i]]->name,
-            table->show_values, table->show_variables);
+            d->data_leaves[table->current_layer[i]]->name, table);
           printf (" %s=%s", name, value);
           free (value);
           free (name);
@@ -1706,8 +2071,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
         table, PIVOT_AXIS_ROW, layer_indexes, table->look->omit_empty, NULL);
 
       char ***column_headings = compose_headings (
-        &table->axes[PIVOT_AXIS_COLUMN], column_enumeration,
-        table->show_values, table->show_variables);
+        table, &table->axes[PIVOT_AXIS_COLUMN], column_enumeration);
       for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++)
         {
           indent (indentation + 1);
@@ -1726,8 +2090,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
       printf ("-----------------------------------------------\n");
 
       char ***row_headings = compose_headings (
-        &table->axes[PIVOT_AXIS_ROW], row_enumeration,
-        table->show_values, table->show_variables);
+        table, &table->axes[PIVOT_AXIS_ROW], row_enumeration);
 
       size_t x = 0;
       const size_t *pindexes[PIVOT_N_AXES]
@@ -1805,8 +2168,7 @@ static size_t
 pivot_format_inner_template (struct string *out, const char *template,
                              char escape,
                              struct pivot_value **values, size_t n_values,
-                             enum settings_value_show show_values,
-                             enum settings_value_show show_variables)
+                             const struct pivot_table *pt)
 {
   size_t args_consumed = 0;
   while (*template && *template != ':')
@@ -1822,8 +2184,7 @@ pivot_format_inner_template (struct string *out, const char *template,
           template = consume_int (template + 1, &index);
           if (index >= 1 && index <= n_values)
             {
-              pivot_value_format (values[index - 1], show_values,
-                                  show_variables, out);
+              pivot_value_format (values[index - 1], pt, out);
               args_consumed = MAX (args_consumed, index);
             }
         }
@@ -1854,8 +2215,7 @@ pivot_extract_inner_template (const char *template, const char **p)
 static void
 pivot_format_template (struct string *out, const char *template,
                        const struct pivot_argument *args, size_t n_args,
-                       enum settings_value_show show_values,
-                       enum settings_value_show show_variables)
+                       const struct pivot_table *pt)
 {
   while (*template)
     {
@@ -1869,8 +2229,7 @@ pivot_format_template (struct string *out, const char *template,
           size_t index;
           template = consume_int (template + 1, &index);
           if (index >= 1 && index <= n_args && args[index - 1].n > 0)
-            pivot_value_format (args[index - 1].values[0],
-                                show_values, show_variables, out);
+            pivot_value_format (args[index - 1].values[0], pt, out);
         }
       else if (*template == '[')
         {
@@ -1892,8 +2251,7 @@ pivot_format_template (struct string *out, const char *template,
               int tmpl_idx = left == arg->n && *tmpl[0] != ':' ? 0 : 1;
               char escape = "%^"[tmpl_idx];
               size_t used = pivot_format_inner_template (
-                out, tmpl[tmpl_idx], escape, values, left,
-                show_values, show_variables);
+                out, tmpl[tmpl_idx], escape, values, left, pt);
               if (!used || used > left)
                 break;
               left -= used;
@@ -1916,8 +2274,8 @@ interpret_show (enum settings_value_show global_show,
           : global_show);
 }
 
-/* Appends a text representation of the body of VALUE to OUT.  SHOW_VALUES and
-   SHOW_VARIABLES control whether variable and value labels are included.
+/* Appends a text representation of the body of VALUE to OUT.  Settings on
+   PT control whether variable and value labels are included.
 
    The "body" omits subscripts and superscripts and footnotes.
 
@@ -1925,8 +2283,7 @@ interpret_show (enum settings_value_show global_show,
    otherwise.  */
 bool
 pivot_value_format_body (const struct pivot_value *value,
-                         enum settings_value_show show_values,
-                         enum settings_value_show show_variables,
+                         const struct pivot_table *pt,
                          struct string *out)
 {
   enum settings_value_show show;
@@ -1936,7 +2293,7 @@ pivot_value_format_body (const struct pivot_value *value,
     {
     case PIVOT_VALUE_NUMERIC:
       show = interpret_show (settings_get_show_values (),
-                             show_values,
+                             pt->show_values,
                              value->numeric.show,
                              value->numeric.value_label != NULL);
       if (show & SETTINGS_VALUE_SHOW_VALUE)
@@ -1957,7 +2314,7 @@ pivot_value_format_body (const struct pivot_value *value,
 
     case PIVOT_VALUE_STRING:
       show = interpret_show (settings_get_show_values (),
-                             show_values,
+                             pt->show_values,
                              value->string.show,
                              value->string.value_label != NULL);
       if (show & SETTINGS_VALUE_SHOW_VALUE)
@@ -1981,7 +2338,7 @@ pivot_value_format_body (const struct pivot_value *value,
 
     case PIVOT_VALUE_VARIABLE:
       show = interpret_show (settings_get_show_variables (),
-                             show_variables,
+                             pt->show_variables,
                              value->variable.show,
                              value->variable.var_label != NULL);
       if (show & SETTINGS_VALUE_SHOW_VALUE)
@@ -2000,25 +2357,23 @@ pivot_value_format_body (const struct pivot_value *value,
 
     case PIVOT_VALUE_TEMPLATE:
       pivot_format_template (out, value->template.local, value->template.args,
-                             value->template.n_args, show_values,
-                             show_variables);
+                             value->template.n_args, pt);
       break;
     }
 
   return numeric;
 }
 
-/* Appends a text representation of VALUE to OUT.  SHOW_VALUES and
-   SHOW_VARIABLES control whether variable and value labels are included.
+/* Appends a text representation of VALUE to OUT.  Settings on
+   PT control whether variable and value labels are included.
 
    Subscripts and footnotes are included. */
 void
 pivot_value_format (const struct pivot_value *value,
-                    enum settings_value_show show_values,
-                    enum settings_value_show show_variables,
+                    const struct pivot_table *pt,
                     struct string *out)
 {
-  pivot_value_format_body (value, show_values, show_variables, out);
+  pivot_value_format_body (value, pt, out);
 
   if (value->n_subscripts)
     {
@@ -2028,9 +2383,13 @@ pivot_value_format (const struct pivot_value *value,
 
   for (size_t i = 0; i < value->n_footnotes; i++)
     {
-      ds_put_byte (out, '^');
-      pivot_value_format (value->footnotes[i]->marker,
-                          show_values, show_variables, out);
+      ds_put_byte (out, '[');
+
+      size_t idx = value->footnote_indexes[i];
+      const struct pivot_footnote *f = pt->footnotes[idx];
+      pivot_value_format (f->marker, pt, out);
+
+      ds_put_byte (out, ']');
     }
 }
 
@@ -2038,23 +2397,29 @@ pivot_value_format (const struct pivot_value *value,
    with free(). */
 char *
 pivot_value_to_string (const struct pivot_value *value,
-                       enum settings_value_show show_values,
-                       enum settings_value_show show_variables)
+                       const struct pivot_table *pt)
 {
   struct string s = DS_EMPTY_INITIALIZER;
-  pivot_value_format (value, show_values, show_variables, &s);
+  pivot_value_format (value, pt, &s);
   return ds_steal_cstr (&s);
 }
 
-static char *
-xstrdup_if_nonnull (const char *s)
+char *
+pivot_value_to_string_defaults (const struct pivot_value *value)
 {
-  return s ? xstrdup (s) : NULL;
+  static const struct pivot_table pt = {
+    .show_values = SETTINGS_VALUE_SHOW_DEFAULT,
+    .show_variables = SETTINGS_VALUE_SHOW_DEFAULT,
+  };
+  return pivot_value_to_string (value, &pt);
 }
 
 struct pivot_value *
 pivot_value_clone (const struct pivot_value *old)
 {
+  if (!old)
+    return NULL;
+
   struct pivot_value *new = xmemdup (old, sizeof *new);
   if (old->font_style)
     {
@@ -2070,8 +2435,8 @@ pivot_value_clone (const struct pivot_value *old)
         new->subscripts[i] = xstrdup (old->subscripts[i]);
     }
   if (old->n_footnotes)
-    new->footnotes = xmemdup (old->footnotes,
-                              old->n_footnotes * sizeof *new->footnotes);
+    new->footnote_indexes = xmemdup (
+      old->footnote_indexes, old->n_footnotes * sizeof *new->footnote_indexes);
 
   switch (new->type)
     {
@@ -2127,9 +2492,7 @@ pivot_value_destroy (struct pivot_value *value)
       font_style_uninit (value->font_style);
       free (value->font_style);
       free (value->cell_style);
-      /* Do not free the elements of footnotes because VALUE does not own
-         them. */
-      free (value->footnotes);
+      free (value->footnote_indexes);
 
       for (size_t i = 0; i < value->n_subscripts; i++)
         free (value->subscripts[i]);
@@ -2449,12 +2812,12 @@ pivot_value_add_footnote (struct pivot_value *v,
   /* Some legacy tables include numerous duplicate footnotes.  Suppress
      them. */
   for (size_t i = 0; i < v->n_footnotes; i++)
-    if (v->footnotes[i] == footnote)
+    if (v->footnote_indexes[i] == footnote->idx)
       return;
 
-  v->footnotes = xrealloc (v->footnotes,
-                           (v->n_footnotes + 1) * sizeof *v->footnotes);
-  v->footnotes[v->n_footnotes++] = footnote;
+  v->footnote_indexes = xrealloc (
+    v->footnote_indexes, (v->n_footnotes + 1) * sizeof *v->footnote_indexes);
+  v->footnote_indexes[v->n_footnotes++] = footnote->idx;
 }
 
 /* If VALUE is a numeric value, and RC is a result class such as
index a70033bf3f29fd7e0b2cc82941b168ad09ebef07..e2bb886c30dc891d65c266d2aac7cdd3e6160fcf 100644 (file)
@@ -485,9 +485,18 @@ struct pivot_table *pivot_table_create_for_text (struct pivot_value *title,
                                                  struct pivot_value *content);
 
 struct pivot_table *pivot_table_ref (const struct pivot_table *);
+struct pivot_table *pivot_table_unshare (struct pivot_table *);
 void pivot_table_unref (struct pivot_table *);
 bool pivot_table_is_shared (const struct pivot_table *);
 
+/* Axes. */
+void pivot_table_swap_axes (struct pivot_table *,
+                            enum pivot_axis_type, enum pivot_axis_type);
+void pivot_table_transpose (struct pivot_table *);
+void pivot_table_move_dimension (struct pivot_table *,
+                                 struct pivot_dimension *,
+                                 enum pivot_axis_type, size_t ofs);
+
 /* Styling. */
 const struct pivot_table_look *pivot_table_get_look (
   const struct pivot_table *);
@@ -524,6 +533,8 @@ const struct pivot_value *pivot_table_get (const struct pivot_table *,
 struct pivot_value *pivot_table_get_rw (struct pivot_table *,
                                         const size_t *dindexes);
 
+bool pivot_table_delete (struct pivot_table *, const size_t *dindexes);
+
 /* Footnotes.
 
    Use pivot_table_create_footnote() to create a footnote.
@@ -636,7 +647,7 @@ struct pivot_value
     char **subscripts;
     size_t n_subscripts;
 
-    const struct pivot_footnote **footnotes;
+    size_t *footnote_indexes;
     size_t n_footnotes;
 
     enum pivot_value_type type;
@@ -734,15 +745,12 @@ void pivot_value_set_rc (const struct pivot_table *, struct pivot_value *,
 
 /* Converting a pivot_value to a string for display. */
 char *pivot_value_to_string (const struct pivot_value *,
-                             enum settings_value_show show_values,
-                             enum settings_value_show show_variables);
+                             const struct pivot_table *);
+char *pivot_value_to_string_defaults (const struct pivot_value *);
 void pivot_value_format (const struct pivot_value *,
-                         enum settings_value_show show_values,
-                         enum settings_value_show show_variables,
-                         struct string *);
+                         const struct pivot_table *, struct string *);
 bool pivot_value_format_body (const struct pivot_value *,
-                              enum settings_value_show show_values,
-                              enum settings_value_show show_variables,
+                              const struct pivot_table *,
                               struct string *);
 
 /* Styling. */
index ae67b6351e0cea70bd630a84049ede8714c8909a..b514f09e85e780e61352325f21e8e00306ec83c3 100644 (file)
@@ -26,6 +26,7 @@
 #include "libpspp/hash-functions.h"
 #include "libpspp/hmap.h"
 #include "libpspp/pool.h"
+#include "output/pivot-output.h"
 #include "output/pivot-table.h"
 #include "output/render.h"
 #include "output/table-item.h"
@@ -1088,7 +1089,7 @@ render_cell (const struct render_page *page, const int ofs[TABLE_N_AXES],
   bb[V][0] = clip[V][0] = ofs[V] + page->cp[V][cell->d[V][0] * 2 + 1];
   bb[V][1] = clip[V][1] = ofs[V] + page->cp[V][cell->d[V][1] * 2];
 
-  enum table_valign valign = cell->style->cell_style.valign;
+  enum table_valign valign = cell->cell_style->valign;
   int valign_offset = 0;
   if (valign != TABLE_VALIGN_TOP)
     {
@@ -1494,13 +1495,12 @@ struct render_pager
     struct render_break y_break;
   };
 
-static const struct render_page *
+static void
 render_pager_add_table (struct render_pager *p, struct table *table,
                         int min_width)
 {
-  struct render_page *page = render_page_create (p->params, table, min_width);
-  p->pages[p->n_pages++] = page;
-  return page;
+  if (table)
+    p->pages[p->n_pages++] = render_page_create (p->params, table, min_width);
 }
 
 static void
@@ -1511,74 +1511,25 @@ 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 table_item *item)
-{
-  struct footnote **f;
-  size_t n_footnotes = table_collect_footnotes (item, &f);
-  if (!n_footnotes)
-    return;
-
-  struct table *t = table_create (1, n_footnotes, 0, 0, 0, 0);
-
-  for (size_t i = 0; i < n_footnotes; i++)
-    {
-      table_text_format (t, 0, i, 0, "%s. %s", f[i]->marker, f[i]->content);
-      table_add_style (t, 0, i, f[i]->style);
-    }
-  render_pager_add_table (p, t, 0);
-
-  free (f);
-}
-
-static void
-add_table_cell_page (struct render_pager *p, const struct table_cell *cell,
-                     int min_width)
-{
-  if (!cell)
-    return;
-
-  struct table *tab = table_create (1, 1, 0, 0, 0, 0);
-  table_text (tab, 0, 0, 0, cell->text);
-  for (size_t i = 0; i < cell->n_footnotes; i++)
-    table_add_footnote (tab, 0, 0, cell->footnotes[i]);
-  if (cell->style)
-    tab->styles[0] = table_area_style_clone (tab->container, cell->style);
-  render_pager_add_table (p, tab, min_width);
-}
-
-static void
-add_layers_page (struct render_pager *p,
-                 const struct table_item_layers *layers, int min_width)
-{
-  if (!layers)
-    return;
-
-  struct table *tab = table_create (1, layers->n_layers, 0, 0, 0, 0);
-  for (size_t i = 0; i < layers->n_layers; i++)
-    {
-      const struct table_item_layer *layer = &layers->layers[i];
-      table_text (tab, 0, i, 0, layer->content);
-      for (size_t j = 0; j < layer->n_footnotes; j++)
-        table_add_footnote (tab, 0, i, layer->footnotes[j]);
-    }
-  if (layers->style)
-    tab->styles[0] = table_area_style_clone (tab->container, layers->style);
-  render_pager_add_table (p, tab, min_width);
-}
-
 /* Creates and returns a new render_pager for rendering TABLE_ITEM on the
    device with the given PARAMS. */
 struct render_pager *
 render_pager_create (const struct render_params *params,
-                     const struct table_item *table_item)
+                     const struct table_item *table_item,
+                     const size_t *layer_indexes)
 {
-  const struct table *table = table_item_get_table (table_item);
+  const struct pivot_table *pt = table_item->pt;
+  if (!layer_indexes)
+    layer_indexes = pt->current_layer;
+
+  struct table *title, *layers, *body, *caption, *footnotes;
+  pivot_output (pt, layer_indexes, params->printing,
+                &title, &layers, &body, &caption, &footnotes, NULL, NULL);
 
   /* Figure out the width of the body of the table.  Use this to determine the
      base scale. */
-  struct render_page *page = render_page_create (params, table_ref (table), 0);
-  int body_width = table_width (page, H);
+  struct render_page *body_page = render_page_create (params, body, 0);
+  int body_width = table_width (body_page, H);
   double scale = 1.0;
   if (body_width > params->size[H])
     {
@@ -1589,7 +1540,7 @@ render_pager_create (const struct render_params *params,
       else
         {
           struct render_break b;
-          render_break_init (&b, page, H);
+          render_break_init (&b, render_page_ref (body_page), H);
           struct render_page *subpage
             = render_break_next (&b, params->size[H]);
           body_width = subpage ? subpage->cp[H][2 * subpage->n[H] + 1] : 0;
@@ -1601,11 +1552,12 @@ render_pager_create (const struct render_params *params,
   /* Create the pager. */
   struct render_pager *p = xmalloc (sizeof *p);
   *p = (struct render_pager) { .params = params, .scale = scale };
-  add_table_cell_page (p, table_item_get_title (table_item), body_width);
-  add_layers_page (p, table_item_get_layers (table_item), body_width);
-  render_pager_add_table (p, table_ref (table_item_get_table (table_item)), 0);
-  add_table_cell_page (p, table_item_get_caption (table_item), 0);
-  add_footnote_page (p, table_item);
+  render_pager_add_table (p, title, body_width);
+  render_pager_add_table (p, layers, body_width);
+  p->pages[p->n_pages++] = body_page;
+  render_pager_add_table (p, caption, 0);
+  render_pager_add_table (p, footnotes, 0);
+  assert (p->n_pages <= sizeof p->pages / sizeof *p->pages);
 
   /* If we're shrinking tables to fit the page length, then adjust the scale
      factor.
index 5df97a1b6718847ad42a955860b88b77510ee0fc..bc8db241f88ce91b88db1808367c742e2686df8c 100644 (file)
@@ -79,6 +79,10 @@ struct render_params
     /* True if the local language has a right-to-left direction, otherwise
        false.  (Use render_direction_rtl() to find out.) */
     bool rtl;
+
+    /* True if the table is being rendered for printing (as opposed to
+       on-screen display). */
+    bool printing;
   };
 
 struct render_ops
@@ -148,7 +152,8 @@ struct render_ops
 
 /* An iterator for breaking render_pages into smaller chunks. */
 struct render_pager *render_pager_create (const struct render_params *,
-                                          const struct table_item *);
+                                          const struct table_item *,
+                                          const size_t *layer_indexes);
 void render_pager_destroy (struct render_pager *);
 
 bool render_pager_has_next (const struct render_pager *);
index 13ea6de57ce0c292d81b800eb40a4214478f1807..70d2a18791a9af1bfb8aeda8b351b8496ab824a2 100644 (file)
@@ -51,9 +51,7 @@ spv_item_dump (const struct spv_item *item, int indentation)
       break;
 
     case SPV_ITEM_TEXT:
-      printf ("text \"%s\"\n",
-              pivot_value_to_string (item->text, SETTINGS_VALUE_SHOW_DEFAULT,
-                                     SETTINGS_VALUE_SHOW_DEFAULT));
+      printf ("text \"%s\"\n", pivot_value_to_string_defaults (item->text));
       break;
 
     case SPV_ITEM_TABLE:
index 1894092a14bf2e97e5b2bf8f5b084c1f620e5654..b90d6b0ab39492a9fffd7e67d80088edfbfe0694 100644 (file)
@@ -1418,8 +1418,8 @@ apply_styles_to_value (struct pivot_table *table,
     {
       if (sf->reset > 0)
         {
-          free (value->footnotes);
-          value->footnotes = NULL;
+          free (value->footnote_indexes);
+          value->footnote_indexes = NULL;
           value->n_footnotes = 0;
         }
 
index 0d059c703616e86b38e0ac926a0004c873787d28..351f7caf80e3e5292322f092248a28ce14686547 100644 (file)
@@ -393,7 +393,8 @@ decode_spvlb_value (const struct pivot_table *table,
 
       if (vm->n_refs)
         {
-          out->footnotes = xnmalloc (vm->n_refs, sizeof *out->footnotes);
+          out->footnote_indexes = xnmalloc (vm->n_refs,
+                                            sizeof *out->footnote_indexes);
           for (size_t i = 0; i < vm->n_refs; i++)
             {
               uint16_t idx = vm->refs[i];
@@ -404,7 +405,7 @@ decode_spvlb_value (const struct pivot_table *table,
                                     idx, table->n_footnotes);
                 }
 
-              out->footnotes[out->n_footnotes++] = table->footnotes[idx];
+              out->footnote_indexes[out->n_footnotes++] = idx;
             }
         }
 
index 00b3dead4729416faa1ca19cb28a63314a850ced..c21ec22bcb29bc0c80dfd98cc41462e24aeea527 100644 (file)
@@ -33,8 +33,7 @@ spv_text_submit (const struct spv_item *in)
        : class == SPV_CLASS_PAGETITLE ? TEXT_ITEM_PAGE_TITLE
        : TEXT_ITEM_LOG);
   const struct pivot_value *value = spv_item_get_text (in);
-  char *text = pivot_value_to_string (value, SETTINGS_VALUE_SHOW_DEFAULT,
-                                      SETTINGS_VALUE_SHOW_DEFAULT);
+  char *text = pivot_value_to_string_defaults (value);
   char *label = in->label ? xstrdup (in->label) : NULL;
   struct text_item *item = text_item_create_nocopy (type, text, label);
 
index a1069f963a191a28279169ba68bc5254eac0ae1c..fe8b8bacbe87fdaeb9729bd228f155d1776ec316 100644 (file)
@@ -528,7 +528,7 @@ put_value_mod (struct buf *buf, const struct pivot_value *value,
       /* Footnotes. */
       put_u32 (buf, value->n_footnotes);
       for (size_t i = 0; i < value->n_footnotes; i++)
-        put_u16 (buf, value->footnotes[i]->idx);
+        put_u16 (buf, value->footnote_indexes[i]);
 
       /* Subscripts. */
       put_u32 (buf, value->n_subscripts);
@@ -987,14 +987,9 @@ spv_writer_put_table (struct spv_writer *w, const struct pivot_table *table)
 
   start_container (w);
 
-  char *title = pivot_value_to_string (table->title,
-                                       SETTINGS_VALUE_SHOW_DEFAULT,
-                                       SETTINGS_VALUE_SHOW_DEFAULT);
-
-  char *subtype = pivot_value_to_string (table->subtype,
-                                         SETTINGS_VALUE_SHOW_DEFAULT,
-                                         SETTINGS_VALUE_SHOW_DEFAULT);
-
+  char *title = pivot_value_to_string (table->title, table);
+  char *subtype = pivot_value_to_string (table->subtype, table);
+  
   start_elem (w, "label");
   write_text (w, title);
   end_elem (w);
index 7e8542c176b8ec778ad55a5f1ea75a00803cd66e..b52bb8ce56655c287ea3967c915f4e55fb1e0477 100644 (file)
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-void
-table_item_layer_copy (struct table_item_layer *dst,
-                       const struct table_item_layer *src)
-{
-  dst->content = xstrdup (src->content);
-  dst->footnotes = xmemdup (src->footnotes,
-                            src->n_footnotes * sizeof *src->footnotes);
-  dst->n_footnotes = src->n_footnotes;
-}
-void
-table_item_layer_uninit (struct table_item_layer *layer)
-{
-  if (layer)
-    {
-      free (layer->content);
-      free (layer->footnotes);
-    }
-}
-
-struct table_item_layers *
-table_item_layers_clone (const struct table_item_layers *old)
-{
-  if (!old)
-    return NULL;
-
-  struct table_item_layers *new = xmalloc (sizeof *new);
-  *new = (struct table_item_layers) {
-    .layers = xnmalloc (old->n_layers, sizeof *new->layers),
-    .n_layers = old->n_layers,
-    .style = table_area_style_clone (NULL, old->style),
-  };
-  for (size_t i = 0; i < new->n_layers; i++)
-    table_item_layer_copy (&new->layers[i], &old->layers[i]);
-  return new;
-}
-
-void
-table_item_layers_destroy (struct table_item_layers *layers)
-{
-  if (layers)
-    {
-      for (size_t i = 0; i < layers->n_layers; i++)
-        table_item_layer_uninit (&layers->layers[i]);
-      free (layers->layers);
-      table_area_style_free (layers->style);
-      free (layers);
-    }
-}
-
-/* Initializes ITEM as a table item for rendering TABLE.  Takes ownership of
-   TABLE. */
+/* Initializes ITEM as a table item for rendering PT.  Takes ownership of
+   PT. */
 struct table_item *
-table_item_create (struct table *table)
+table_item_create (struct pivot_table *pt)
 {
+  pivot_table_assign_label_depth (pt);
+
   struct table_item *item = xmalloc (sizeof *item);
   *item = (struct table_item) {
     .output_item = OUTPUT_ITEM_INITIALIZER (&table_item_class),
-    .table = table,
+    .pt = pivot_table_unshare (pt),
   };
   return item;
 }
 
-/* Returns the table contained by TABLE_ITEM.  The caller must not modify or
-   unref the returned table. */
-const struct table *
-table_item_get_table (const struct table_item *table_item)
-{
-  return table_item->table;
-}
-
-/* Returns ITEM's title, which is a null pointer if no title has been
-   set. */
-const struct table_cell *
-table_item_get_title (const struct table_item *item)
-{
-  return item->title;
-}
-
-/* Sets ITEM's title to TITLE, replacing any previous title.  Specify NULL for
-   TITLE to clear any title from ITEM.  The caller retains ownership of TITLE.
-
-   This function may only be used on a table_item that is unshared. */
-void
-table_item_set_title (struct table_item *item, const struct table_cell *title)
-{
-  assert (!table_item_is_shared (item));
-  table_cell_destroy (item->title);
-  item->title = table_cell_clone (title);
-}
-
-/* Returns ITEM's layers, which will be a null pointer if no layers have been
-   set. */
-const struct table_item_layers *
-table_item_get_layers (const struct table_item *item)
-{
-  return item->layers;
-}
-
-/* Sets ITEM's layers to LAYERS, replacing any previous layers.  Specify NULL
-   for LAYERS to clear any layers from ITEM.  The caller retains ownership of
-   LAYERS.
-
-   This function may only be used on a table_item that is unshared. */
-void
-table_item_set_layers (struct table_item *item,
-                       const struct table_item_layers *layers)
-{
-  assert (!table_item_is_shared (item));
-  table_item_layers_destroy (item->layers);
-  item->layers = table_item_layers_clone (layers);
-}
-
-/* Returns ITEM's caption, which is a null pointer if no caption has been
-   set. */
-const struct table_cell *
-table_item_get_caption (const struct table_item *item)
-{
-  return item->caption;
-}
-
-/* Sets ITEM's caption to CAPTION, replacing any previous caption.  Specify
-   NULL for CAPTION to clear any caption from ITEM.  The caller retains
-   ownership of CAPTION.
-
-   This function may only be used on a table_item that is unshared. */
-void
-table_item_set_caption (struct table_item *item,
-                        const struct table_cell *caption)
-{
-  assert (!table_item_is_shared (item));
-  table_cell_destroy (item->caption);
-  item->caption = table_cell_clone (caption);
-}
-
-/* Returns ITEM's notes, which is a null pointer if ITEM has no notes. */
-const char *
-table_item_get_notes (const struct table_item *item)
-{
-  return item->notes;
-}
-
-/* Sets ITEM's notes to NOTES, replacing any previous notes.  Specify NULL for
-   NOTES to clear any notes from ITEM.  The caller retains ownership of
-   NOTES.
-
-   This function may only be used on a table_item that is unshared.*/
-void
-table_item_set_notes (struct table_item *item, const char *notes)
-{
-  assert (!table_item_is_shared (item));
-  free (item->notes);
-  item->notes = notes ? xstrdup (notes) : NULL;
-}
-
 /* Submits TABLE_ITEM to the configured output drivers, and transfers ownership
    to the output subsystem. */
 void
@@ -199,22 +59,24 @@ table_item_submit (struct table_item *table_item)
 static const char *
 table_item_get_label (const struct output_item *output_item)
 {
-  const struct table_item *item = to_table_item (output_item);
-  return (item->title && item->title->text
-          ? item->title->text
-          : _("Table"));
+  struct table_item *item = to_table_item (output_item);
+
+  if (!item->cached_label)
+    {
+      if (!item->pt->title)
+        return _("Table");
+
+      item->cached_label = pivot_value_to_string (item->pt->title, item->pt);
+    }
+  return item->cached_label;
 }
 
 static void
 table_item_destroy (struct output_item *output_item)
 {
   struct table_item *item = to_table_item (output_item);
-  table_cell_destroy (item->title);
-  table_cell_destroy (item->caption);
-  table_item_layers_destroy (item->layers);
-  free (item->notes);
   pivot_table_unref (item->pt);
-  table_unref (item->table);
+  free (item->cached_label);
   free (item);
 }
 
index aa41edaa3afbbd948835a88122baa7a867ef9717..be0987659684ef68fdeba2f23e9d80d9e0506074 100644 (file)
 #include "output/output-item.h"
 #include "output/table.h"
 
-struct table_item_layer
-  {
-    char *content;
-    struct footnote **footnotes;
-    size_t n_footnotes;
-  };
-
-void table_item_layer_copy (struct table_item_layer *,
-                            const struct table_item_layer *);
-void table_item_layer_uninit (struct table_item_layer *);
-
-struct table_item_layers
-  {
-    struct table_item_layer *layers;
-    size_t n_layers;
-    struct table_area_style *style;
-  };
-
-struct table_item_layers *table_item_layers_clone (
-  const struct table_item_layers *);
-void table_item_layers_destroy (struct table_item_layers *);
-
 /* A table item.
 
    The members of struct table_item should not be accessed directly.  Use one
@@ -58,31 +36,12 @@ void table_item_layers_destroy (struct table_item_layers *);
 struct table_item
   {
     struct output_item output_item;   /* Superclass. */
-    struct table *table;              /* The table to be rendered. */
-    struct table_cell *title;         /* Null if there is no title. */
-    struct table_cell *caption;       /* Null if there is no caption. */
-    struct table_item_layers *layers; /* Null if there is no layer info. */
-    char *notes;                      /* Shown as tooltip. */
-    struct pivot_table *pt;
-  };
-
-struct table_item *table_item_create (struct table *);
-
-const struct table *table_item_get_table (const struct table_item *);
-
-const struct table_cell *table_item_get_title (const struct table_item *);
-void table_item_set_title (struct table_item *, const struct table_cell *);
+    struct pivot_table *pt;           /* The table to be rendered. */
 
-const struct table_item_layers *table_item_get_layers (
-  const struct table_item *);
-void table_item_set_layers (struct table_item *,
-                           const struct table_item_layers *);
-
-const struct table_cell *table_item_get_caption (const struct table_item *);
-void table_item_set_caption (struct table_item *, const struct table_cell *);
+    char *cached_label;
+  };
 
-const char *table_item_get_notes (const struct table_item *);
-void table_item_set_notes (struct table_item *, const char *notes);
+struct table_item *table_item_create (struct pivot_table *);
 \f
 /* This boilerplate for table_item, a subclass of output_item, was
    autogenerated by mk-class-boilerplate. */
@@ -139,5 +98,5 @@ table_item_is_shared (const struct table_item *instance)
 }
 
 void table_item_submit (struct table_item *);
-\f
+
 #endif /* output/table-item.h */
index 6aa59d86484d24bca72a6bfb5903a8bb2b393d26..f87293721f47846d0153d89f5ea1fa430894a164 100644 (file)
@@ -25,17 +25,6 @@ struct string;
 
 enum table_halign table_halign_interpret (enum table_halign, bool numeric);
 
-struct footnote
-  {
-    size_t idx;
-    char *content;
-    char *marker;
-    struct table_area_style *style;
-  };
-
-struct footnote *footnote_clone (const struct footnote *);
-void footnote_destroy (struct footnote *);
-
 /* A cell in a table. */
 struct table_cell
   {
@@ -57,20 +46,11 @@ struct table_cell
     int d[TABLE_N_AXES][2];
 
     unsigned int options;       /* TAB_*. */
-    char *text;                 /* A paragraph of text. */
-    char **subscripts;
-    size_t n_subscripts;
-    struct footnote **footnotes;
-    size_t n_footnotes;
-    struct table_area_style *style;
+    const struct pivot_value *value;
+    const struct font_style *font_style;
+    const struct cell_style *cell_style;
   };
 
-struct table_cell *table_cell_clone (const struct table_cell *);
-void table_cell_destroy (struct table_cell *);
-
-void table_cell_format_footnote_markers (const struct table_cell *,
-                                         struct string *);
-
 /* Returns the number of columns that CELL spans.  This is 1 for an ordinary
    cell and greater than one for a cell that joins multiple columns. */
 static inline int
@@ -100,6 +80,5 @@ table_cell_is_joined (const struct table_cell *cell)
 void table_get_cell (const struct table *, int x, int y, struct table_cell *);
 int table_get_rule (const struct table *, enum table_axis, int x, int y,
                     struct cell_color *);
-size_t table_collect_footnotes (const struct table_item *, struct footnote ***);
 
 #endif /* output/table-provider.h */
index 44c33b9d10b36ceabb6df4125d1038567f16076d..eabcc85b89f846bd26969a9e85533e924a6191a0 100644 (file)
@@ -29,6 +29,7 @@
 #include "libpspp/compiler.h"
 #include "libpspp/pool.h"
 #include "libpspp/str.h"
+#include "output/pivot-table.h"
 #include "output/table-item.h"
 #include "output/table.h"
 #include "output/text-item.h"
@@ -90,170 +91,6 @@ table_area_style_free (struct table_area_style *style)
       free (style);
     }
 }
-
-struct footnote *
-footnote_clone (const struct footnote *old)
-{
-  struct footnote *new = xmalloc (sizeof *new);
-  *new = (struct footnote) {
-    .idx = old->idx,
-    .content = old->content ? xstrdup (old->content) : NULL,
-    .marker = old->marker ? xstrdup (old->marker) : NULL,
-    .style = old->style ? table_area_style_clone (NULL, old->style) : NULL,
-  };
-  return new;
-}
-
-void
-footnote_destroy (struct footnote *f)
-{
-  if (f)
-    {
-      free (f->content);
-      free (f->marker);
-      if (f->style)
-        {
-          table_area_style_uninit (f->style);
-          free (f->style);
-        }
-      free (f);
-    }
-}
-
-struct table_cell *
-table_cell_clone (const struct table_cell *old)
-{
-  struct table_cell *new = xmalloc (sizeof *new);
-  *new = *old;
-  new->text = xstrdup (new->text);
-
-  if (old->n_subscripts)
-    {
-      new->subscripts = xnmalloc (old->n_subscripts, sizeof *new->subscripts);
-      for (size_t i = 0; i < old->n_subscripts; i++)
-        new->subscripts[i] = xstrdup (old->subscripts[i]);
-    }
-  else
-    new->subscripts = NULL;
-
-  if (old->n_footnotes)
-    {
-      new->footnotes = xnmalloc (old->n_footnotes, sizeof *new->footnotes);
-      for (size_t i = 0; i < old->n_footnotes; i++)
-        new->footnotes[i] = footnote_clone (old->footnotes[i]);
-    }
-  else
-    new->footnotes = NULL;
-
-  if (old->style)
-    new->style = table_area_style_clone (NULL, old->style);
-
-  return new;
-}
-
-void
-table_cell_destroy (struct table_cell *cell)
-{
-  if (!cell)
-    return;
-
-  free (cell->text);
-  for (size_t i = 0; i < cell->n_subscripts; i++)
-    free (cell->subscripts[i]);
-  free (cell->subscripts);
-  for (size_t i = 0; i < cell->n_footnotes; i++)
-    footnote_destroy (cell->footnotes[i]);
-  free (cell->footnotes);
-  if (cell->style)
-    {
-      table_area_style_uninit (cell->style);
-      free (cell->style);
-    }
-  free (cell);
-}
-
-void
-table_cell_format_footnote_markers (const struct table_cell *cell,
-                                    struct string *s)
-{
-  for (size_t i = 0; i < cell->n_footnotes; i++)
-    {
-      if (i)
-        ds_put_byte (s, ',');
-      ds_put_cstr (s, cell->footnotes[i]->marker);
-    }
-}
-
-static struct footnote **
-add_footnotes (struct footnote **refs, size_t n_refs,
-               struct footnote **footnotes, size_t *allocated, size_t *n)
-{
-  for (size_t i = 0; i < n_refs; i++)
-    {
-      struct footnote *f = refs[i];
-      if (f->idx >= *allocated)
-        {
-          size_t new_allocated = (f->idx + 1) * 2;
-          footnotes = xrealloc (footnotes, new_allocated * sizeof *footnotes);
-          while (*allocated < new_allocated)
-            footnotes[(*allocated)++] = NULL;
-        }
-      footnotes[f->idx] = f;
-      if (f->idx >= *n)
-        *n = f->idx + 1;
-    }
-  return footnotes;
-}
-
-size_t
-table_collect_footnotes (const struct table_item *item,
-                         struct footnote ***footnotesp)
-{
-  struct footnote **footnotes = NULL;
-  size_t allocated = 0;
-  size_t n = 0;
-
-  struct table *t = item->table;
-  for (int y = 0; y < t->n[V]; y++)
-    {
-      struct table_cell cell;
-      for (int x = 0; x < t->n[H]; 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])
-            footnotes = add_footnotes (cell.footnotes, cell.n_footnotes,
-                                       footnotes, &allocated, &n);
-        }
-    }
-
-  const struct table_cell *title = table_item_get_title (item);
-  if (title)
-    footnotes = add_footnotes (title->footnotes, title->n_footnotes,
-                               footnotes, &allocated, &n);
-
-  const struct table_item_layers *layers = table_item_get_layers (item);
-  if (layers)
-    {
-      for (size_t i = 0; i < layers->n_layers; i++)
-        footnotes = add_footnotes (layers->layers[i].footnotes,
-                                   layers->layers[i].n_footnotes,
-                                   footnotes, &allocated, &n);
-    }
-
-  const struct table_cell *caption = table_item_get_caption (item);
-  if (caption)
-    footnotes = add_footnotes (caption->footnotes, caption->n_footnotes,
-                               footnotes, &allocated, &n);
-
-  size_t n_nonnull = 0;
-  for (size_t i = 0; i < n; i++)
-    if (footnotes[i])
-      footnotes[n_nonnull++] = footnotes[i];
-
-  *footnotesp = footnotes;
-  return n_nonnull;
-}
 \f
 const char *
 table_halign_to_string (enum table_halign halign)
@@ -591,194 +428,58 @@ table_box (struct table *t, int f_h, int f_v, int i_h, int i_v,
 \f
 /* Cells. */
 
-static void
-do_table_text (struct table *table, int c, int r, unsigned opt, char *text)
-{
-  assert (c >= 0);
-  assert (r >= 0);
-  assert (c < table->n[H]);
-  assert (r < table->n[V]);
-
-  if (debugging)
-    {
-      if (c < 0 || r < 0 || c >= table->n[H] || r >= table->n[V])
-        {
-          printf ("table_text(): bad cell (%d,%d) in table size (%d,%d)\n",
-                  c, r, table->n[H], table->n[V]);
-          return;
-        }
-    }
-
-  table->cc[c + r * table->n[H]] = text;
-  table->ct[c + r * table->n[H]] = opt;
-}
-
-/* Sets cell (C,R) in TABLE, with options OPT, to have text value
-   TEXT. */
+/* Fill TABLE cells (X1,X2)-(Y1,Y2), inclusive, with VALUE and OPT. */
 void
-table_text (struct table *table, int c, int r, unsigned opt,
-          const char *text)
+table_put (struct table *table, int x1, int y1, int x2, int y2,
+           unsigned opt, const struct pivot_value *value)
 {
-  do_table_text (table, c, r, opt, pool_strdup (table->container, text));
-}
+  assert (0 <= x1 && x1 <= x2 && x2 < table->n[H]);
+  assert (0 <= y1 && y1 <= y2 && y2 < table->n[V]);
 
-/* Sets cell (C,R) in TABLE, with options OPT, to have text value
-   FORMAT, which is formatted as if passed to printf. */
-void
-table_text_format (struct table *table, int c, int r, unsigned opt,
-                   const char *format, ...)
-{
-  va_list args;
-
-  va_start (args, format);
-  do_table_text (table, c, r, opt,
-                 pool_vasprintf (table->container, format, args));
-  va_end (args);
-}
-
-static struct table_cell *
-add_joined_cell (struct table *table, int x1, int y1, int x2, int y2,
-                 unsigned opt)
-{
-  assert (x1 >= 0);
-  assert (y1 >= 0);
-  assert (y2 >= y1);
-  assert (x2 >= x1);
-  assert (y2 < table->n[V]);
-  assert (x2 < table->n[H]);
-
-  if (debugging)
+  if (x1 == x2 && y1 == y2)
     {
-      if (x1 < 0 || x1 >= table->n[H]
-          || y1 < 0 || y1 >= table->n[V]
-          || x2 < x1 || x2 >= table->n[H]
-          || y2 < y1 || y2 >= table->n[V])
-        {
-          printf ("table_joint_text(): bad cell "
-                  "(%d,%d)-(%d,%d) in table size (%d,%d)\n",
-                  x1, y1, x2, y2, table->n[H], table->n[V]);
-          return NULL;
-        }
+      table->cc[x1 + y1 * table->n[H]] = CONST_CAST (struct pivot_value *, value);
+      table->ct[x1 + y1 * table->n[H]] = opt;
     }
+  else
+    {
+      table_box (table, -1, -1, TABLE_STROKE_NONE, TABLE_STROKE_NONE,
+                 x1, y1, x2, y2);
 
-  table_box (table, -1, -1, TABLE_STROKE_NONE, TABLE_STROKE_NONE,
-             x1, y1, x2, y2);
-
-  struct table_cell *cell = pool_alloc (table->container, sizeof *cell);
-  *cell = (struct table_cell) {
-    .d = { [TABLE_HORZ] = { x1, ++x2 },
-           [TABLE_VERT] = { y1, ++y2 } },
-    .options = opt,
-  };
+      struct table_cell *cell = pool_alloc (table->container, sizeof *cell);
+      *cell = (struct table_cell) {
+        .d = { [H] = { x1, x2 + 1 }, [V] = { y1, y2 + 1 } },
+        .options = opt,
+        .value = value,
+      };
 
-  void **cc = &table->cc[x1 + y1 * table->n[H]];
-  unsigned short *ct = &table->ct[x1 + y1 * table->n[H]];
-  const int ofs = table->n[H] - (x2 - x1);
-  for (int y = y1; y < y2; y++)
-    {
-      for (int x = x1; x < x2; x++)
+      for (int y = y1; y <= y2; y++)
         {
-          *cc++ = cell;
-          *ct++ = opt | TAB_JOIN;
+          size_t ofs = x1 + y * table->n[H];
+          void **cc = &table->cc[ofs];
+          unsigned short *ct = &table->ct[ofs];
+          for (int x = x1; x <= x2; x++)
+            {
+              *cc++ = cell;
+              *ct++ = opt | TAB_JOIN;
+            }
         }
-
-      cc += ofs;
-      ct += ofs;
-    }
-
-  return cell;
-}
-
-/* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
-   options OPT to have text value TEXT. */
-void
-table_joint_text (struct table *table, int x1, int y1, int x2, int y2,
-                  unsigned opt, const char *text)
-{
-  char *s = pool_strdup (table->container, text);
-  if (x1 == x2 && y1 == y2)
-    do_table_text (table, x1, y1, opt, s);
-  else
-    add_joined_cell (table, x1, y1, x2, y2, opt)->text = s;
-}
-
-static struct table_cell *
-get_joined_cell (struct table *table, int x, int y)
-{
-  int index = x + y * table->n[H];
-  unsigned short opt = table->ct[index];
-  struct table_cell *cell;
-
-  if (opt & TAB_JOIN)
-    cell = table->cc[index];
-  else
-    {
-      char *text = table->cc[index];
-
-      cell = add_joined_cell (table, x, y, x, y, table->ct[index]);
-      cell->text = text ? text : pool_strdup (table->container, "");
     }
-  return cell;
 }
 
-/* Sets the subscripts for column X, row Y in TABLE. */
-void
-table_add_subscripts (struct table *table, int x, int y,
-                      char **subscripts, size_t n_subscripts)
-{
-  struct table_cell *cell = get_joined_cell (table, x, y);
-
-  cell->n_subscripts = n_subscripts;
-  cell->subscripts = pool_nalloc (table->container, n_subscripts,
-                                  sizeof *cell->subscripts);
-  for (size_t i = 0; i < n_subscripts; i++)
-    cell->subscripts[i] = pool_strdup (table->container, subscripts[i]);
-}
-
-/* Create a footnote in TABLE with MARKER (e.g. "a") as its marker and CONTENT
-   as its content.  The footnote will be styled as STYLE, which is mandatory.
-   IDX must uniquely identify the footnote within TABLE.
-
-   Returns the new footnote.  The return value is the only way to get to the
-   footnote later, so it is important for the caller to remember it. */
-struct footnote *
-table_create_footnote (struct table *table, size_t idx, const char *content,
-                       const char *marker, struct table_area_style *style)
-{
-  assert (style);
-
-  struct footnote *f = pool_alloc (table->container, sizeof *f);
-  f->idx = idx;
-  f->content = pool_strdup (table->container, content);
-  f->marker = pool_strdup (table->container, marker);
-  f->style = style;
-  return f;
-}
-
-/* Attaches a reference to footnote F to the cell at column X, row Y in
-   TABLE. */
-void
-table_add_footnote (struct table *table, int x, int y, struct footnote *f)
+static void
+free_value (void *value_)
 {
-  assert (f->style);
-
-  struct table_cell *cell = get_joined_cell (table, x, y);
-
-  cell->footnotes = pool_realloc (
-    table->container, cell->footnotes,
-    (cell->n_footnotes + 1) * sizeof *cell->footnotes);
-
-  cell->footnotes[cell->n_footnotes++] = f;
+  struct pivot_value *value = value_;
+  pivot_value_destroy (value);
 }
 
-/* Overrides the style for column X, row Y in TABLE with STYLE.
-   Does not make a copy of STYLE, so it should either be allocated from
-   TABLE->container or have a lifetime that will outlive TABLE. */
 void
-table_add_style (struct table *table, int x, int y,
-                 struct table_area_style *style)
+table_put_owned (struct table *table, int x1, int y1, int x2, int y2,
+                 unsigned opt, struct pivot_value *value)
 {
-  get_joined_cell (table, x, y)->style = style;
+  table_put (table, x1, y1, x2, y2, opt, value);
+  pool_register (table->container, free_value, value);
 }
 
 /* Returns true if column C, row R has no contents, otherwise false. */
@@ -802,23 +503,42 @@ table_get_cell (const struct table *t, int x, int y, struct table_cell *cell)
 
   struct table_area_style *style
     = t->styles[(opt & TAB_STYLE_MASK) >> TAB_STYLE_SHIFT];
+
+  static const struct pivot_value empty_value = {
+    .type = PIVOT_VALUE_TEXT,
+    .text = {
+      .local = (char *) "",
+      .c = (char *) "",
+      .id = (char *) "",
+      .user_provided = true,
+    },
+  };
+
   if (opt & TAB_JOIN)
     {
       const struct table_cell *jc = cc;
       *cell = *jc;
-      if (!cell->style)
-        cell->style = style;
+      if (!cell->value)
+        cell->value = &empty_value;
+      if (!cell->font_style)
+        cell->font_style = &style->font_style;
+      if (!cell->cell_style)
+        cell->cell_style = &style->cell_style;
     }
   else
-    *cell = (struct table_cell) {
-      .d = { [TABLE_HORZ] = { x, x + 1 },
-             [TABLE_VERT] = { y, y + 1 } },
-      .options = opt,
-      .text = CONST_CAST (char *, cc ? cc : ""),
-      .style = style,
-    };
-
-  assert (cell->style);
+    {
+      const struct pivot_value *v = cc ? cc : &empty_value;
+      *cell = (struct table_cell) {
+        .d = { [H] = { x, x + 1 }, [V] = { y, y + 1 } },
+        .options = opt,
+        .value = v,
+        .font_style = v->font_style ? v->font_style : &style->font_style,
+        .cell_style = v->cell_style ? v->cell_style : &style->cell_style,
+      };
+    }
+
+  assert (cell->font_style);
+  assert (cell->cell_style);
 }
 
 /* Returns one of the TAL_* enumeration constants (declared in output/table.h)
index e7a801b9e7fccb552f068c12938b208482cc6d98..51cb1f42429ec72c02d1527be31e7816ace0e7aa 100644 (file)
@@ -37,6 +37,8 @@
 
 struct casereader;
 struct fmt_spec;
+struct pivot_footnote;
+struct pivot_value;
 struct pool;
 struct table_item;
 struct variable;
@@ -180,8 +182,6 @@ void table_area_style_free (struct table_area_style *);
 enum
   {
     TAB_NONE = 0,
-    TAB_MARKUP     = 1 << 2,    /* Text contains Pango markup. */
-    TAB_NUMERIC    = 1 << 3,    /* Cell contents are numeric. */
     TAB_ROTATE     = 1 << 4,    /* Rotate cell contents 90 degrees. */
 
     TAB_STYLE_SHIFT = 5,
@@ -222,7 +222,7 @@ struct table
 
     /* Table contents.
 
-       Each array element in cc[] is ordinarily a "char *" pointer to a string.
+       Each array element in cc[] is ordinarily a "struct pivot_value *".
        If TAB_JOIN (defined in table.c) is set in ct[] for the element,
        however, it is a joined cell and the corresponding element of cc[]
        points to a struct table_cell. */
@@ -257,28 +257,10 @@ void table_box (struct table *, int f_h, int f_v, int i_h, int i_v,
                 int x1, int y1, int x2, int y2);
 
 /* Cells. */
-void table_text (struct table *, int c, int r, unsigned opt, const char *);
-void table_text_format (struct table *, int c, int r, unsigned opt,
-                        const char *, ...)
-  PRINTF_FORMAT (5, 6);
-void table_joint_text (struct table *, int x1, int y1, int x2, int y2,
-                       unsigned opt, const char *);
-
-void table_add_subscripts (struct table *, int x, int y,
-                           char **subscripts, size_t n_subscripts);
-
-/* Footnotes.
-
-   Use table_create_footnote() to create the footnotes themselves, then use
-   table_add_footnote() to create a reference from a table cell to a footnote.
-   There are two steps because a footnote may have multiple references. */
-struct footnote *table_create_footnote (struct table *, size_t idx,
-                                        const char *content,
-                                        const char *marker,
-                                        struct table_area_style *);
-void table_add_footnote (struct table *, int x, int y, struct footnote *);
-
-void table_add_style (struct table *, int x, int y, struct table_area_style *);
+void table_put (struct table *, int x1, int y1, int x2, int y2,
+                unsigned opt, const struct pivot_value *);
+void table_put_owned (struct table *, int x1, int y1, int x2, int y2,
+                      unsigned opt, struct pivot_value *);
 
 bool table_cell_is_empty (const struct table *, int c, int r);
 
index b6064097495b9006684f4e88764aeb4dc429bed0..2dcfcfb0c903cdcc7bf3d68fa54fcb6f38b78e8e 100644 (file)
@@ -44,6 +44,8 @@
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/output-item-provider.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/table-provider.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
@@ -378,71 +380,92 @@ tex_submit (struct output_driver *driver,
 
 static void
 tex_put_footnote_markers (struct tex_driver *tex,
-                          struct footnote **footnotes,
+                          const struct pivot_table *pt,
+                          const size_t *footnote_indexes,
                           size_t n_footnotes)
 {
   if (n_footnotes > 0)
     shipout (&tex->token_list, "$^{");
   for (size_t i = 0; i < n_footnotes; i++)
     {
-      const struct footnote *f = footnotes[i];
-
-      tex_escape_string (tex, f->marker, true);
+      const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
+      char *marker = pivot_value_to_string (f->marker, pt);
+      tex_escape_string (tex, marker, true);
+      free (marker);
     }
   if (n_footnotes > 0)
     shipout (&tex->token_list, "}$");
 }
 
 static void
-tex_put_table_cell (struct tex_driver *tex, const struct table_cell *cell)
+tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
+                    const struct table_cell *cell)
 {
-  tex_escape_string (tex, cell->text, false);
-  tex_put_footnote_markers (tex, cell->footnotes, cell->n_footnotes);
+  struct string s = DS_EMPTY_INITIALIZER;
+  pivot_value_format_body (cell->value, pt, &s);
+  tex_escape_string (tex, ds_cstr (&s), false);
+  ds_destroy (&s);
+
+  tex_put_footnote_markers (tex, pt,
+                            cell->value->footnote_indexes,
+                            cell->value->n_footnotes);
 }
 
 static void
-tex_output_table (struct tex_driver *tex, const struct table_item *item)
+tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
+                        const size_t *layer_indexes)
 {
   /* Tables are rendered in TeX with the \halign command.
      This is described in the TeXbook Ch. 22 */
-
-  const struct table *t = table_item_get_table (item);
+  struct table *title, *layers, *body, *caption;
+  struct pivot_footnote **footnotes;
+  size_t n_footnotes;
+  pivot_output (pt, layer_indexes, true, &title, &layers, &body,
+                &caption, NULL, &footnotes, &n_footnotes);
 
   shipout (&tex->token_list, "\n{\\parindent=0pt\n");
 
-  const struct table_cell *caption = table_item_get_caption (item);
   if (caption)
     {
       shipout (&tex->token_list, "{\\sl ");
-      tex_escape_string (tex, caption->text, false);
+      struct table_cell cell;
+      table_get_cell (caption, 0, 0, &cell);
+      tex_put_table_cell (tex, pt, &cell);
       shipout (&tex->token_list, "}\n\n");
     }
-  struct footnote **f;
-  size_t n_footnotes = table_collect_footnotes (item, &f);
 
-  const struct table_cell *title = table_item_get_title (item);
-  const struct table_item_layers *layers = table_item_get_layers (item);
   if (title || layers)
     {
       if (title)
         {
           shipout (&tex->token_list, "{\\bf ");
-          tex_put_table_cell (tex, title);
-          shipout (&tex->token_list, "}");
+          struct table_cell cell;
+          table_get_cell (title, 0, 0, &cell);
+          tex_put_table_cell (tex, pt, &cell);
+          shipout (&tex->token_list, "}\\par\n");
         }
+
       if (layers)
-        abort ();
-      shipout (&tex->token_list, "\\par\n");
+        {
+          for (size_t y = 0; y < layers->n[V]; y++)
+            {
+              shipout (&tex->token_list, "{");
+              struct table_cell cell;
+              table_get_cell (layers, 0, y, &cell);
+              tex_put_table_cell (tex, pt, &cell);
+              shipout (&tex->token_list, "}\\par\n");
+            }
+        }
     }
 
   shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
 
   /* Generate the preamble */
-  for (int x = 0; x < t->n[H]; ++x)
+  for (int x = 0; x < body->n[H]; ++x)
     {
-      shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", t->n[H]);
+      shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
 
-      if (x < t->n[H] - 1)
+      if (x < body->n[H] - 1)
         {
           shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
           shipout (&tex->token_list, "&\\vrule\n");
@@ -452,17 +475,17 @@ tex_output_table (struct tex_driver *tex, const struct table_item *item)
     }
 
   /* Emit the row data */
-  for (int y = 0; y < t->n[V]; y++)
+  for (int y = 0; y < body->n[V]; y++)
     {
       enum { H = TABLE_HORZ, V = TABLE_VERT };
-      bool is_column_header = y < t->h[V][0] || y >= t->n[V] - t->h[V][1];
+      bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
       int prev_x = -1;
       int skipped = 0;
-      for (int x = 0; x < t->n[H];)
+      for (int x = 0; x < body->n[H];)
         {
           struct table_cell cell;
 
-          table_get_cell (t, x, y, &cell);
+          table_get_cell (body, x, y, &cell);
 
           int colspan = table_cell_colspan (&cell);
           if (x > 0)
@@ -475,14 +498,16 @@ tex_output_table (struct tex_driver *tex, const struct table_item *item)
           if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
             goto next_1;
 
-          /* bool is_header = (y < t->h[V][0] */
-          /*                   || y >= t->n[V] - t->h[V][1] */
-          /*                   || x < t->h[H][0] */
-          /*                   || x >= t->n[H] - t->h[H][1]); */
+          /* bool is_header = (y < body->h[V][0] */
+          /*                   || y >= body->n[V] - body->h[V][1] */
+          /*                   || x < body->h[H][0] */
+          /*                   || x >= body->n[H] - body->h[H][1]); */
 
-          enum table_halign halign =
-            table_halign_interpret (cell.style->cell_style.halign,
-                                    cell.options & TAB_NUMERIC);
+          struct string s = DS_EMPTY_INITIALIZER;
+          bool numeric = pivot_value_format_body (cell.value, pt, &s);
+
+          enum table_halign halign = table_halign_interpret (
+            cell.cell_style->halign, numeric);
 
           /* int rowspan = table_cell_rowspan (&cell); */
 
@@ -504,8 +529,11 @@ tex_output_table (struct tex_driver *tex, const struct table_item *item)
             shipout (&tex->token_list, "\\right{");
 
           /* Output cell contents. */
-          tex_escape_string (tex, cell.text, true);
-          tex_put_footnote_markers (tex, cell.footnotes, cell.n_footnotes);
+          tex_escape_string (tex, ds_cstr (&s), true);
+          ds_destroy (&s);
+
+          tex_put_footnote_markers (tex, pt, cell.value->footnote_indexes,
+                                    cell.value->n_footnotes);
           if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
             {
               shipout (&tex->token_list, "}");
@@ -529,14 +557,33 @@ tex_output_table (struct tex_driver *tex, const struct table_item *item)
 
   for (int i = 0; i < n_footnotes; ++i)
     {
+      char *marker = pivot_value_to_string (footnotes[i]->marker, pt);
+      char *content = pivot_value_to_string (footnotes[i]->content, pt);
+
       shipout (&tex->token_list, "$^{");
-      tex_escape_string (tex, f[i]->marker, false);
+      tex_escape_string (tex, marker, false);
       shipout (&tex->token_list, "}$");
-      tex_escape_string (tex, f[i]->content, false);
+      tex_escape_string (tex, content, false);
+
+      free (content);
+      free (marker);
     }
-  free (f);
 
   shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
+
+  table_unref (title);
+  table_unref (layers);
+  table_unref (body);
+  table_unref (caption);
+  free (footnotes);
+}
+
+static void
+tex_output_table (struct tex_driver *tex, const struct table_item *item)
+{
+  size_t *layer_indexes;
+  PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
+    tex_output_table_layer (tex, item->pt, layer_indexes);
 }
 
 struct output_driver_factory tex_driver_factory =
index 5f7a91d243413c6f0ec2b1268937f45eb64e58bc..49c9f225c06a21a6b95557ddf7ce7bc2a0caff96 100644 (file)
@@ -25,6 +25,7 @@
 #include "libpspp/pool.h"
 #include "output/driver.h"
 #include "output/output-item-provider.h"
+#include "output/pivot-table.h"
 #include "output/table.h"
 #include "output/table-item.h"
 #include "output/table-provider.h"
@@ -158,26 +159,40 @@ text_item_append (struct text_item *dst, const struct text_item *src)
     }
 }
 
+static const struct pivot_table_look *
+text_item_table_look (void)
+{
+  static struct pivot_table_look *look;
+  if (!look)
+    {
+      look = pivot_table_look_new_builtin_default ();
+
+      for (int a = 0; a < PIVOT_N_AREAS; a++)
+        memset (look->areas[a].cell_style.margin, 0,
+                sizeof look->areas[a].cell_style.margin);
+      for (int b = 0; b < PIVOT_N_BORDERS; b++)
+        look->borders[b].stroke = TABLE_STROKE_NONE;
+    }
+  return look;
+}
+
 struct table_item *
 text_item_to_table_item (struct text_item *text_item)
 {
-  struct table *tab = table_create (1, 1, 0, 0, 0, 0);
+  struct pivot_table *table = pivot_table_create__ (NULL, "Text");
+  pivot_table_set_look (table, text_item_table_look ());
 
-  struct table_area_style *style = pool_alloc (tab->container, sizeof *style);
-  *style = (struct table_area_style) {
-    .cell_style = CELL_STYLE_INITIALIZER,
-    .cell_style.halign = TABLE_HALIGN_LEFT,
-  };
-  font_style_copy (tab->container, &style->font_style, &text_item->style);
-  tab->styles[0] = style;
-
-  int opts = 0;
-  if (text_item->style.markup)
-    opts |= TAB_MARKUP;
-  table_text (tab, 0, 0, opts, text_item_get_text (text_item));
-  struct table_item *table_item = table_item_create (tab);
-  text_item_unref (text_item);
-  return table_item;
+  struct pivot_dimension *d = pivot_dimension_create (
+    table, PIVOT_AXIS_ROW, N_("Text"));
+  d->hide_all_labels = true;
+  pivot_category_create_leaf (d->root, pivot_value_new_text ("null"));
+
+  struct pivot_value *content = pivot_value_new_user_text (
+    text_item->text, SIZE_MAX);
+  pivot_value_set_font_style (content, &text_item->style);
+  pivot_table_put1 (table, 0, content);
+
+  return table_item_create (table);
 }
 \f
 static const char *
index 76e0e1c1267493533174a79f19ae7c3952ccfdba..4a9a666cef6dd4c149425cdb6da69053fbb80285 100644 (file)
@@ -33,6 +33,7 @@
 #include "output/message-item.h"
 #include "output/output-item.h"
 #include "output/output-item-provider.h"
+#include "output/pivot-table.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
 
@@ -159,6 +160,7 @@ get_xr_fsm_style (struct psppire_output_view *view)
     .min_break = { [TABLE_HORZ] = xr_width / 2, [TABLE_VERT] = 0 },
     .font = pf,
     .use_system_colors = true,
+    .object_spacing = XR_POINT * 12,
     .font_resolution = 96.0,
   };
 
@@ -332,7 +334,7 @@ rerender (struct psppire_output_view *view)
       if (is_group_open_item (item->item))
         continue;
 
-      r = xr_fsm_create (item->item, view->style, cr);
+      r = xr_fsm_create_for_scrolling (item->item, view->style, cr);
       if (r == NULL)
         {
           g_warn_if_reached ();
@@ -359,7 +361,7 @@ rerender (struct psppire_output_view *view)
       if (is_table_item (item->item))
         {
           const struct table_item *ti = to_table_item (item->item);
-          gtk_widget_set_tooltip_text (item->drawing_area, ti->notes);
+          gtk_widget_set_tooltip_text (item->drawing_area, ti->pt->notes);
         }
 
       {
@@ -446,7 +448,7 @@ psppire_output_view_put (struct psppire_output_view *view,
       if (view->y > 0)
         view->y += view->object_spacing;
 
-      struct xr_fsm *r = xr_fsm_create (item, view->style, cr);
+      struct xr_fsm *r = xr_fsm_create_for_scrolling (item, view->style, cr);
       if (r == NULL)
        {
          gdk_window_end_draw_frame (win, ctx);
@@ -962,7 +964,6 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
       [V] = { margins[V][0], margins[V][1] },
     },
     .initial_page_number = 1,
-    .object_spacing = 12 * XR_POINT,
   };
 
   view->fsm_style = xmalloc (sizeof *view->fsm_style);
@@ -974,6 +975,7 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
     .font = pango_font_description_from_string ("Sans Serif 10"),
     .fg = CELL_COLOR_BLACK,
     .use_system_colors = false,
+    .object_spacing = 12 * XR_POINT,
     .font_resolution = 72.0
   };
 
index 523e5708805bae1f6a7228148e84fbb8197e1e2c..77a14c8dd95fce3d1e7305447f5dfc776249965d 100644 (file)
@@ -236,12 +236,14 @@ tests_math_chart_get_ticks_format_test_LDADD = \
        src/libpspp-core.la \
        gl/libgl.la
 
-check_PROGRAMS += tests/output/render-test
-tests_output_render_test_SOURCES = tests/output/render-test.c
-tests_output_render_test_LDADD = \
+check_PROGRAMS += tests/output/pivot-table-test
+tests_output_pivot_table_test_SOURCES = tests/output/pivot-table-test.c
+tests_output_pivot_table_test_LDADD = \
        src/libpspp.la \
        src/libpspp-core.la \
+       gl/libgl.la \
        $(CAIRO_LIBS)
+EXTRA_DIST += tests/output/look.stt
 
 check_PROGRAMS += tests/output/ascii-test
 tests_output_ascii_test_SOURCES = tests/output/ascii-test.c
@@ -450,6 +452,7 @@ TESTSUITE_AT = \
        tests/output/html.at \
        tests/output/output.at \
        tests/output/paper-size.at \
+       tests/output/pivot-table.at \
        tests/output/render.at \
        tests/output/tables.at \
        tests/output/tex.at \
diff --git a/tests/output/look.stt b/tests/output/look.stt
new file mode 100644 (file)
index 0000000..7e16140
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tableProperties xmlns="http://www.ibm.com/software/analytics/spss/xml/table-looks" xmlns:vizml="http://www.ibm.com/software/analytics/spss/xml/visualization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ibm.com/software/analytics/spss/xml/table-looks http://www.ibm.com/software/analytics/spss/xml/table-looks/table-looks-1.4.xsd">
+    <generalProperties hideEmptyRows="true" maximumColumnWidth="72" maximumRowWidth="120" minimumColumnWidth="36" minimumRowWidth="36" rowDimensionLabels="inCorner"/>
+    <footnoteProperties markerPosition="subscript" numberFormat="alphabetic"/>
+    <cellFormatProperties>
+        <title>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="bold" font-underline="none" labelLocationVertical="center" margin-bottom="6pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="left"/>
+        </title>
+        <caption>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="positive" margin-bottom="0pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="left"/>
+        </caption>
+        <footnotes>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="positive" margin-bottom="2pt" margin-left="8pt" margin-right="6pt" margin-top="1pt" textAlignment="left"/>
+        </footnotes>
+        <cornerLabels>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="negative" margin-bottom="0pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="left"/>
+        </cornerLabels>
+        <columnLabels>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="negative" margin-bottom="2pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="center"/>
+        </columnLabels>
+        <rowLabels>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="positive" margin-bottom="2pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="left"/>
+        </rowLabels>
+        <data>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="positive" margin-bottom="0pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="mixed"/>
+        </data>
+        <layers>
+            <vizml:style color="#000000" color2="#ffffff" font-family="Sans Serif" font-size="9pt" font-weight="regular" font-underline="none" labelLocationVertical="negative" margin-bottom="2pt" margin-left="6pt" margin-right="8pt" margin-top="0pt" textAlignment="left"/>
+        </layers>
+    </cellFormatProperties>
+    <borderProperties>
+        <titleLayerSeparator borderStyleType="none" color="#000000"/>
+        <leftOuterFrame borderStyleType="none" color="#000000"/>
+        <topOuterFrame borderStyleType="none" color="#000000"/>
+        <rightOuterFrame borderStyleType="none" color="#000000"/>
+        <bottomOuterFrame borderStyleType="none" color="#000000"/>
+        <leftInnerFrame borderStyleType="thick" color="#000000"/>
+        <topInnerFrame borderStyleType="thick" color="#000000"/>
+        <rightInnerFrame borderStyleType="thick" color="#000000"/>
+        <bottomInnerFrame borderStyleType="thick" color="#000000"/>
+        <dataAreaLeft borderStyleType="thick" color="#000000"/>
+        <dataAreaTop borderStyleType="thick" color="#000000"/>
+        <horizontalDimensionBorderRows borderStyleType="solid" color="#000000"/>
+        <verticalDimensionBorderRows borderStyleType="none" color="#000000"/>
+        <horizontalDimensionBorderColumns borderStyleType="solid" color="#000000"/>
+        <verticalDimensionBorderColumns borderStyleType="solid" color="#000000"/>
+        <horizontalCategoryBorderRows borderStyleType="none" color="#000000"/>
+        <verticalCategoryBorderRows borderStyleType="none" color="#000000"/>
+        <horizontalCategoryBorderColumns borderStyleType="solid" color="#000000"/>
+        <verticalCategoryBorderColumns borderStyleType="solid" color="#000000"/>
+    </borderProperties>
+    <printingProperties printAllLayers="false" rescaleLongTableToFitPage="false" rescaleWideTableToFitPage="false" windowOrphanLines="0"/>
+</tableProperties>
diff --git a/tests/output/pivot-table-test.c b/tests/output/pivot-table-test.c
new file mode 100644 (file)
index 0000000..41ff524
--- /dev/null
@@ -0,0 +1,1245 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2009, 2010, 2011, 2012, 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <errno.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "data/file-handle-def.h"
+#include "language/lexer/lexer.h"
+#include "language/lexer/format-parser.h"
+#include "libpspp/assertion.h"
+#include "libpspp/compiler.h"
+#include "libpspp/i18n.h"
+#include "libpspp/string-map.h"
+#include "output/driver.h"
+#include "output/message-item.h"
+#include "output/options.h"
+#include "output/pivot-table.h"
+#include "output/table-item.h"
+
+#include "gl/error.h"
+#include "gl/progname.h"
+#include "gl/xalloc.h"
+#include "gl/xvasprintf.h"
+
+/* --emphasis: Enable emphasis in ASCII driver? */
+static bool emphasis;
+
+/* --box: ASCII driver box option. */
+static char *box;
+
+/* -o, --output: Base name for output files. */
+static const char *output_base = "render";
+
+/* --dump: Print table dump to stdout? */
+static bool dump;
+
+static const char *parse_options (int argc, char **argv);
+static void usage (void) NO_RETURN;
+static struct pivot_table *read_table (struct lexer *);
+static void output_msg (const struct msg *, void *);
+
+int
+main (int argc, char **argv)
+{
+  const char *input_file_name;
+
+  set_program_name (argv[0]);
+  i18n_init ();
+  output_engine_push ();
+  input_file_name = parse_options (argc, argv);
+
+  settings_init ();
+
+  struct lex_reader *reader = lex_reader_for_file (input_file_name, NULL,
+                                                   LEX_SYNTAX_AUTO,
+                                                   LEX_ERROR_CONTINUE);
+  if (!reader)
+    exit (1);
+
+  struct lexer *lexer = lex_create ();
+  msg_set_handler (output_msg, lexer);
+  lex_include (lexer, reader);
+  lex_get (lexer);
+
+  for (;;)
+    {
+      while (lex_match (lexer, T_ENDCMD))
+        continue;
+      if (lex_match (lexer, T_STOP))
+        break;
+
+      struct pivot_table *pt = read_table (lexer);
+      if (dump)
+        pivot_table_dump (pt, 0);
+      pivot_table_submit (pt);
+    }
+
+  lex_destroy (lexer);
+  output_engine_pop ();
+  fh_done ();
+
+  return 0;
+}
+
+static void PRINTF_FORMAT (2, 3)
+register_driver (struct string_map *options,
+                 const char *output_file, ...)
+{
+  va_list args;
+  va_start (args, output_file);
+  string_map_insert_nocopy (options, xstrdup ("output-file"),
+                            xvasprintf (output_file, args));
+  va_end (args);
+
+  struct output_driver *driver = output_driver_create (options);
+  if (driver == NULL)
+    exit (EXIT_FAILURE);
+  output_driver_register (driver);
+}
+
+static void
+configure_drivers (int width, int length UNUSED, int min_break)
+{
+  /* Render to stdout. */
+  struct string_map options = STRING_MAP_INITIALIZER (options);
+  string_map_insert (&options, "format", "txt");
+  string_map_insert_nocopy (&options, xstrdup ("width"),
+                            xasprintf ("%d", width));
+  if (min_break >= 0)
+    string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
+                              xasprintf ("%d", min_break));
+  string_map_insert (&options, "emphasis", emphasis ? "true" : "false");
+  if (box != NULL)
+    string_map_insert (&options, "box", box);
+  register_driver (&options, "-");
+
+
+#ifdef HAVE_CAIRO
+  /* Render to <base>.pdf. */
+  string_map_insert (&options, "top-margin", "0");
+  string_map_insert (&options, "bottom-margin", "0");
+  string_map_insert (&options, "left-margin", "0");
+  string_map_insert (&options, "right-margin", "0");
+  string_map_insert (&options, "paper-size", "99x99in");
+  string_map_insert (&options, "trim", "true");
+  register_driver (&options, "%s.pdf", output_base);
+#endif
+
+  register_driver (&options, "%s.txt", output_base);
+  register_driver (&options, "%s.csv", output_base);
+  register_driver (&options, "%s.odt", output_base);
+  register_driver (&options, "%s.spv", output_base);
+  register_driver (&options, "%s.html", output_base);
+  register_driver (&options, "%s.tex", output_base);
+
+  string_map_destroy (&options);
+}
+
+static const char *
+parse_options (int argc, char **argv)
+{
+  int width = 79;
+  int length = 66;
+  int min_break = -1;
+
+  for (;;)
+    {
+      enum {
+        OPT_WIDTH = UCHAR_MAX + 1,
+        OPT_LENGTH,
+        OPT_MIN_BREAK,
+        OPT_EMPHASIS,
+        OPT_BOX,
+        OPT_TABLE_LOOK,
+        OPT_DUMP,
+        OPT_HELP
+      };
+      static const struct option options[] =
+        {
+          {"width", required_argument, NULL, OPT_WIDTH},
+          {"length", required_argument, NULL, OPT_LENGTH},
+          {"min-break", required_argument, NULL, OPT_MIN_BREAK},
+          {"emphasis", no_argument, NULL, OPT_EMPHASIS},
+          {"box", required_argument, NULL, OPT_BOX},
+          {"output", required_argument, NULL, 'o'},
+          {"table-look", required_argument, NULL, OPT_TABLE_LOOK},
+          {"dump", no_argument, NULL, OPT_DUMP},
+          {"help", no_argument, NULL, OPT_HELP},
+          {NULL, 0, NULL, 0},
+        };
+
+      int c = getopt_long (argc, argv, "o:", options, NULL);
+      if (c == -1)
+        break;
+
+      switch (c)
+        {
+        case OPT_WIDTH:
+          width = atoi (optarg);
+          break;
+
+        case OPT_LENGTH:
+          length = atoi (optarg);
+          break;
+
+        case OPT_MIN_BREAK:
+          min_break = atoi (optarg);
+          break;
+
+        case OPT_EMPHASIS:
+          emphasis = true;
+          break;
+
+        case OPT_BOX:
+          box = optarg;
+          break;
+
+        case 'o':
+          output_base = optarg;
+          break;
+
+        case OPT_TABLE_LOOK:
+          {
+            struct pivot_table_look *look;
+            char *err = pivot_table_look_read (optarg, &look);
+            if (err)
+              error (1, 0, "%s", err);
+            pivot_table_look_set_default (look);
+            pivot_table_look_unref (look);
+          }
+          break;
+
+        case OPT_DUMP:
+          dump = true;
+          break;
+
+        case OPT_HELP:
+          usage ();
+
+        case 0:
+          break;
+
+        case '?':
+          exit (EXIT_FAILURE);
+          break;
+
+        default:
+          NOT_REACHED ();
+        }
+
+    }
+
+  configure_drivers (width, length, min_break);
+
+  if (optind + 1 != argc)
+    error (1, 0, "exactly one non-option argument required; "
+           "use --help for help");
+  return argv[optind];
+}
+
+static void
+usage (void)
+{
+  printf ("%s, to test rendering of PSPP tables\n"
+          "usage: %s [OPTIONS] INPUT\n"
+          "\nOptions:\n"
+          "  --width=WIDTH   set page width in characters\n"
+          "  --length=LINE   set page length in lines\n",
+          program_name, program_name);
+  exit (EXIT_SUCCESS);
+}
+
+static void
+force_match (struct lexer *lexer, enum token_type type)
+{
+  if (!lex_force_match (lexer, type))
+    exit (1);
+}
+
+static void
+force_string (struct lexer *lexer)
+{
+  if (!lex_force_string (lexer))
+    exit (1);
+}
+
+static void
+force_int (struct lexer *lexer)
+{
+  if (!lex_force_int (lexer))
+    exit (1);
+}
+
+static void
+force_num (struct lexer *lexer)
+{
+  if (!lex_force_num (lexer))
+    exit (1);
+}
+
+static bool
+parse_settings_value_show (struct lexer *lexer, const char *name,
+                           enum settings_value_show *show)
+{
+  if (lex_match_id (lexer, name))
+    {
+      lex_match (lexer, T_EQUALS);
+
+      if (lex_match_id (lexer, "DEFAULT"))
+        *show = SETTINGS_VALUE_SHOW_DEFAULT;
+      else if (lex_match_id (lexer, "VALUE"))
+        *show = SETTINGS_VALUE_SHOW_VALUE;
+      else if (lex_match_id (lexer, "LABEL"))
+        *show = SETTINGS_VALUE_SHOW_LABEL;
+      else if (lex_match_id (lexer, "BOTH"))
+        *show = SETTINGS_VALUE_SHOW_BOTH;
+      else
+        {
+          lex_error_expecting (lexer, "DEFAULT", "VALUE", "LABEL", "BOTH");
+          exit (1);
+        }
+
+      return true;
+    }
+  else
+    return false;
+}
+
+static bool
+parse_string_setting (struct lexer *lexer, const char *name, char **stringp)
+{
+  if (lex_match_id (lexer, name))
+    {
+      lex_match (lexer, T_EQUALS);
+      force_string (lexer);
+
+      free (*stringp);
+      *stringp = xstrdup (lex_tokcstr (lexer));
+
+      lex_get (lexer);
+      return true;
+    }
+  else
+    return false;
+}
+
+static bool
+match_kw (struct lexer *lexer, const char *kw)
+{
+  return (!strcmp (kw, "ALL")
+          ? lex_match (lexer, T_ALL)
+          : lex_match_id (lexer, kw));
+}
+
+static bool
+parse_bool_setting_with_default (struct lexer *lexer, const char *name,
+                                 const char *true_kw, const char *false_kw,
+                                 int default_value, bool *out)
+{
+  if (lex_match_id (lexer, name))
+    {
+      if (default_value >= 0)
+        {
+          if (!lex_match (lexer, T_EQUALS))
+            *out = default_value;
+          return true;
+        }
+      else
+        force_match (lexer, T_EQUALS);
+
+      if (match_kw (lexer, true_kw))
+        *out = true;
+      else if (match_kw (lexer, false_kw))
+        *out = false;
+      else
+        {
+          lex_error_expecting (lexer, true_kw, false_kw);
+          exit (1);
+        }
+
+      return true;
+    }
+  else
+    return false;
+}
+
+static bool
+parse_bool_setting (struct lexer *lexer, const char *name,
+                    const char *true_kw, const char *false_kw,
+                    bool *out)
+{
+  return parse_bool_setting_with_default (lexer, name, true_kw, false_kw, -1,
+                                          out);
+}
+
+static bool
+parse_yesno_setting (struct lexer *lexer, const char *name, bool *out)
+{
+  return parse_bool_setting_with_default (lexer, name, "YES", "NO", true, out);
+}
+
+static struct cell_color
+read_color (struct lexer *lexer)
+{
+  struct cell_color color;
+  if (!parse_color__ (lex_tokcstr (lexer), &color))
+    {
+      msg (SE, "%s: unknown color", lex_tokcstr (lexer));
+      exit (1);
+    }
+  lex_get (lexer);
+  return color;
+}
+
+static bool
+parse_color_pair_setting (struct lexer *lexer, const char *name,
+                          struct cell_color out[2])
+{
+  if (lex_match_id (lexer, name))
+    {
+      lex_match (lexer, T_EQUALS);
+      out[0] = read_color (lexer);
+      out[1] = lex_is_string (lexer) ? read_color (lexer) : out[0];
+      return true;
+    }
+  else
+    return false;
+}
+
+static bool
+parse_int_setting (struct lexer *lexer, const char *name, int *out)
+{
+  if (lex_match_id (lexer, name))
+    {
+      lex_match (lexer, T_EQUALS);
+      force_int (lexer);
+      *out = lex_integer (lexer);
+      lex_get (lexer);
+      return true;
+    }
+  else
+    return false;
+}
+
+static void
+read_font_style (struct lexer *lexer, struct font_style *fs)
+{
+  while (parse_yesno_setting (lexer, "BOLD", &fs->bold)
+         || parse_yesno_setting (lexer, "ITALIC", &fs->italic)
+         || parse_yesno_setting (lexer, "UNDERLINE", &fs->underline)
+         || parse_yesno_setting (lexer, "MARKUP", &fs->markup)
+         || parse_color_pair_setting (lexer, "FG", fs->fg)
+         || parse_color_pair_setting (lexer, "BG", fs->bg)
+         || parse_string_setting (lexer, "FACE", &fs->typeface)
+         || parse_int_setting (lexer, "SIZE", &fs->size))
+    continue;
+}
+
+static bool
+parse_halign_setting (struct lexer *lexer, enum table_halign *halign,
+                      double *decimal_offset)
+{
+  if (lex_match_id (lexer, "RIGHT"))
+    *halign = TABLE_HALIGN_RIGHT;
+  else if (lex_match_id (lexer, "LEFT"))
+    *halign = TABLE_HALIGN_LEFT;
+  else if (lex_match_id (lexer, "CELL"))
+    *halign = TABLE_HALIGN_CENTER;
+  else if (lex_match_id (lexer, "MIXED"))
+    *halign = TABLE_HALIGN_MIXED;
+  else if (lex_match_id (lexer, "DECIMAL"))
+    {
+      if (lex_is_number (lexer))
+        {
+          *decimal_offset = lex_number (lexer);
+          lex_get (lexer);
+        }
+    }
+  else
+    return false;
+
+  return true;
+}
+
+static bool
+parse_valign_setting (struct lexer *lexer, enum table_valign *valign)
+{
+  if (lex_match_id (lexer, "TOP"))
+    *valign = TABLE_VALIGN_TOP;
+  else if (lex_match_id (lexer, "MIDDLE"))
+    *valign = TABLE_VALIGN_CENTER;
+  else if (lex_match_id (lexer, "BOTTOM"))
+    *valign = TABLE_VALIGN_BOTTOM;
+  else
+    return false;
+
+  return true;
+}
+
+static bool
+parse_margin_setting (struct lexer *lexer, int margin[TABLE_N_AXES][2])
+{
+  if (lex_match_id (lexer, "MARGINS"))
+    {
+      int values[4];
+      int n = 0;
+
+      lex_match (lexer, T_EQUALS);
+      force_num (lexer);
+      while (lex_is_number (lexer) && n < 4)
+        {
+          values[n++] = lex_number (lexer);
+          lex_get (lexer);
+        }
+
+      if (n == 1)
+        {
+          margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[0];
+          margin[TABLE_VERT][0] = margin[TABLE_VERT][1] = values[0];
+        }
+      else if (n == 2)
+        {
+          margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[1];
+          margin[TABLE_VERT][0] = margin[TABLE_VERT][1] = values[0];
+        }
+      else if (n == 3)
+        {
+          margin[TABLE_VERT][0] = values[0];
+          margin[TABLE_HORZ][0] = margin[TABLE_HORZ][1] = values[1];
+          margin[TABLE_VERT][1] = values[2];
+        }
+      else
+        {
+          assert (n == 4);
+          margin[TABLE_VERT][0] = values[0];
+          margin[TABLE_HORZ][1] = values[1];
+          margin[TABLE_VERT][1] = values[2];
+          margin[TABLE_HORZ][0] = values[3];
+        }
+
+      return true;
+    }
+  else
+    return false;
+}
+
+static void
+read_cell_style (struct lexer *lexer, struct cell_style *cs)
+{
+  while (parse_halign_setting (lexer, &cs->halign, &cs->decimal_offset)
+         || parse_valign_setting (lexer, &cs->valign)
+         || parse_margin_setting (lexer, cs->margin))
+    continue;
+}
+
+static void
+read_value_option (struct lexer *lexer, const struct pivot_table *pt,
+                   struct pivot_value *value,
+                   const struct table_area_style *base_style)
+{
+  enum settings_value_show *show
+    = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.show
+       : value->type == PIVOT_VALUE_STRING ? &value->string.show
+       : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.show
+       : NULL);
+  if (show && parse_settings_value_show (lexer, "SHOW", show))
+    return;
+
+  char **var_name
+    = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.var_name
+       : value->type == PIVOT_VALUE_STRING ? &value->string.var_name
+       : NULL);
+  if (var_name && parse_string_setting (lexer, "VAR", var_name))
+    return;
+
+  char **label
+    = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.value_label
+       : value->type == PIVOT_VALUE_STRING ? &value->string.value_label
+       : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.var_label
+       : NULL);
+  if (label && parse_string_setting (lexer, "LABEL", label))
+    return;
+
+  if (value->type == PIVOT_VALUE_STRING && lex_match_id (lexer, "HEX"))
+    {
+      value->string.hex = true;
+      return;
+    }
+
+  if (value->type == PIVOT_VALUE_NUMERIC)
+    {
+      msg_disable ();
+      struct fmt_spec fmt;
+      bool ok = parse_format_specifier (lexer, &fmt);
+      msg_enable ();
+
+      if (ok)
+        {
+          if (!fmt_check_output (&fmt)
+              || !fmt_check_type_compat (&fmt, VAL_NUMERIC))
+            exit (1);
+
+          value->numeric.format = fmt;
+          return;
+        }
+    }
+
+  if (lex_match_id (lexer, "SUBSCRIPTS"))
+    {
+      lex_match (lexer, T_EQUALS);
+      size_t allocated_subscripts = value->n_subscripts;
+      while (lex_token (lexer) == T_STRING)
+        {
+          if (value->n_subscripts >= allocated_subscripts)
+            value->subscripts = x2nrealloc (value->subscripts,
+                                            &allocated_subscripts,
+                                            sizeof *value->subscripts);
+
+          value->subscripts[value->n_subscripts++] = xstrdup (
+            lex_tokcstr (lexer));
+          lex_get (lexer);
+        }
+      return;
+    }
+
+  if (lex_match_id (lexer, "FONT") && base_style)
+    {
+      lex_match (lexer, T_EQUALS);
+
+      if (!value->font_style)
+        {
+          value->font_style = xmalloc (sizeof *value->font_style);
+          font_style_copy (NULL, value->font_style, &base_style->font_style);
+        }
+      read_font_style (lexer, value->font_style);
+      return;
+    }
+
+  if (lex_match_id (lexer, "CELL") && base_style)
+    {
+      lex_match (lexer, T_EQUALS);
+
+      if (!value->cell_style)
+        {
+          value->cell_style = xmalloc (sizeof *value->cell_style);
+          *value->cell_style = base_style->cell_style;
+        }
+      read_cell_style (lexer, value->cell_style);
+      return;
+    }
+
+  if (lex_match_id (lexer, "FOOTNOTE"))
+    {
+      lex_match (lexer, T_EQUALS);
+
+      while (lex_is_integer (lexer))
+        {
+          size_t idx = lex_integer (lexer);
+          lex_get (lexer);
+
+          if (idx >= pt->n_footnotes)
+            {
+              msg (SE, "Footnote %zu not available "
+                   "(only %zu footnotes defined)", idx, pt->n_footnotes);
+              exit (1);
+            }
+          pivot_value_add_footnote (value, pt->footnotes[idx]);
+        }
+      return;
+    }
+
+  lex_error (lexer, "Expecting valid value option");
+  exit (1);
+}
+
+static struct pivot_value *
+read_value (struct lexer *lexer, const struct pivot_table *pt,
+            const struct table_area_style *base_style)
+{
+  struct pivot_value *value;
+  if (lex_is_number (lexer))
+    {
+      value = pivot_value_new_number (lex_number (lexer));
+      lex_get (lexer);
+    }
+  else if (lex_is_string (lexer))
+    {
+      value = xmalloc (sizeof *value);
+      *value = (struct pivot_value) {
+        .type = PIVOT_VALUE_STRING,
+        .string = { .s = xstrdup (lex_tokcstr (lexer)) },
+      };
+      lex_get (lexer);
+    }
+  else if (lex_token (lexer) == T_ID)
+    {
+      value = xmalloc (sizeof *value);
+      *value = (struct pivot_value) {
+        .type = PIVOT_VALUE_VARIABLE,
+        .variable = { .var_name = xstrdup (lex_tokcstr (lexer)) },
+      };
+      lex_get (lexer);
+    }
+  else
+    {
+      msg (SE, "Expecting pivot_value");
+      exit (1);
+    }
+
+  while (lex_match (lexer, T_LBRACK))
+    {
+      read_value_option (lexer, pt, value, base_style);
+      force_match (lexer, T_RBRACK);
+    }
+
+  return value;
+}
+
+static void
+read_group (struct lexer *lexer, struct pivot_table *pt,
+            struct pivot_category *group,
+            const struct table_area_style *label_style)
+{
+  if (lex_match (lexer, T_ASTERISK))
+    group->show_label = true;
+
+  force_match (lexer, T_LPAREN);
+  if (lex_match (lexer, T_RPAREN))
+    return;
+
+  do
+    {
+      struct pivot_value *name = read_value (lexer, pt, label_style);
+      if (lex_token (lexer) == T_ASTERISK
+          || lex_token (lexer) == T_LPAREN)
+        read_group (lexer, pt, pivot_category_create_group__ (group, name),
+                    label_style);
+      else
+        {
+          char *rc;
+          if (lex_token (lexer) == T_ID
+              && is_pivot_result_class (lex_tokcstr (lexer)))
+            {
+              rc = xstrdup (lex_tokcstr (lexer));
+              lex_get (lexer);
+            }
+          else
+            rc = NULL;
+
+          pivot_category_create_leaf_rc (group, name, rc);
+
+          free (rc);
+        }
+    }
+  while (lex_match (lexer, T_COMMA));
+  force_match (lexer, T_RPAREN);
+}
+
+static void
+read_dimension (struct lexer *lexer, struct pivot_table *pt,
+                enum pivot_axis_type a,
+                const struct table_area_style *label_style)
+{
+  if (!pivot_table_is_empty (pt))
+    error (1, 0, "can't add dimensions after adding data");
+
+  lex_match (lexer, T_EQUALS);
+
+  struct pivot_value *name = read_value (lexer, pt, label_style);
+  struct pivot_dimension *dim = pivot_dimension_create__ (pt, a, name);
+  read_group (lexer, pt, dim->root, label_style);
+}
+
+static void
+read_look (struct lexer *lexer, struct pivot_table *pt)
+{
+  lex_match (lexer, T_EQUALS);
+
+  if (lex_is_string (lexer))
+    {
+      struct pivot_table_look *look;
+      char *error = pivot_table_look_read (lex_tokcstr (lexer), &look);
+      if (error)
+        {
+          msg (SE, "%s", error);
+          exit (1);
+        }
+      lex_get (lexer);
+
+      pivot_table_set_look (pt, look);
+      pivot_table_look_unref (look);
+    }
+
+  struct pivot_table_look *look = pivot_table_look_unshare (
+    pivot_table_look_ref (pt->look));
+  for (;;)
+    {
+      if (!parse_bool_setting (lexer, "EMPTY", "HIDE", "SHOW",
+                               &look->omit_empty)
+          && !parse_bool_setting (lexer, "ROWLABELS", "CORNER", "NESTED",
+                                  &look->row_labels_in_corner)
+          && !parse_bool_setting (lexer, "MARKERS", "NUMERIC", "ALPHA",
+                                  &look->show_numeric_markers)
+          && !parse_bool_setting (lexer, "LEVEL", "SUPER", "SUB",
+                                  &look->footnote_marker_superscripts)
+          && !parse_bool_setting (lexer, "LAYERS", "ALL", "CURRENT",
+                                  &look->print_all_layers)
+          && !parse_bool_setting (lexer, "PAGINATELAYERS", "YES", "NO",
+                                  &look->paginate_layers)
+          && !parse_bool_setting (lexer, "HSHRINK", "YES", "NO",
+                                  &look->shrink_to_fit[TABLE_HORZ])
+          && !parse_bool_setting (lexer, "VSHRINK", "YES", "NO",
+                                  &look->shrink_to_fit[TABLE_VERT])
+          && !parse_bool_setting (lexer, "TOPCONTINUATION", "YES", "NO",
+                                  &look->top_continuation)
+          && !parse_bool_setting (lexer, "BOTTOMCONTINUATION", "YES", "NO",
+                                  &look->bottom_continuation)
+          && !parse_string_setting (lexer, "CONTINUATION",
+                                    &look->continuation))
+        break;
+    }
+  pivot_table_set_look (pt, look);
+  pivot_table_look_unref (look);
+}
+
+static enum table_stroke
+read_stroke (struct lexer *lexer)
+{
+  for (int stroke = 0; stroke < TABLE_N_STROKES; stroke++)
+    if (lex_match_id (lexer, table_stroke_to_string (stroke)))
+      return stroke;
+
+  lex_error (lexer, "expecting stroke");
+  exit (1);
+}
+
+static bool
+parse_value_setting (struct lexer *lexer, const struct pivot_table *pt,
+                     const char *name,
+                     struct pivot_value **valuep,
+                     struct table_area_style *base_style)
+{
+  if (lex_match_id (lexer, name))
+    {
+      lex_match (lexer, T_EQUALS);
+
+      pivot_value_destroy (*valuep);
+      *valuep = read_value (lexer, pt, base_style);
+
+      return true;
+    }
+  else
+    return false;
+}
+
+static void
+read_border (struct lexer *lexer, struct pivot_table *pt)
+{
+  static const char *const pivot_border_ids[PIVOT_N_BORDERS] = {
+    [PIVOT_BORDER_TITLE] = "title",
+    [PIVOT_BORDER_OUTER_LEFT] = "outer-left",
+    [PIVOT_BORDER_OUTER_TOP] = "outer-top",
+    [PIVOT_BORDER_OUTER_RIGHT] = "outer-right",
+    [PIVOT_BORDER_OUTER_BOTTOM] = "outer-bottom",
+    [PIVOT_BORDER_INNER_LEFT] = "inner-left",
+    [PIVOT_BORDER_INNER_TOP] = "inner-top",
+    [PIVOT_BORDER_INNER_RIGHT] = "inner-right",
+    [PIVOT_BORDER_INNER_BOTTOM] = "inner-bottom",
+    [PIVOT_BORDER_DATA_LEFT] = "data-left",
+    [PIVOT_BORDER_DATA_TOP] = "data-top",
+    [PIVOT_BORDER_DIM_ROW_HORZ] = "dim-row-horz",
+    [PIVOT_BORDER_DIM_ROW_VERT] = "dim-row-vert",
+    [PIVOT_BORDER_DIM_COL_HORZ] = "dim-col-horz",
+    [PIVOT_BORDER_DIM_COL_VERT] = "dim-col-vert",
+    [PIVOT_BORDER_CAT_ROW_HORZ] = "cat-row-horz",
+    [PIVOT_BORDER_CAT_ROW_VERT] = "cat-row-vert",
+    [PIVOT_BORDER_CAT_COL_HORZ] = "cat-col-horz",
+    [PIVOT_BORDER_CAT_COL_VERT] = "cat-col-vert",
+  };
+
+  lex_match (lexer, T_EQUALS);
+
+  struct pivot_table_look *look = pivot_table_look_unshare (
+    pivot_table_look_ref (pt->look));
+  while (lex_token (lexer) == T_STRING)
+    {
+      char *s = xstrdup (lex_tokcstr (lexer));
+      lex_get (lexer);
+      force_match (lexer, T_LPAREN);
+
+      struct table_border_style style = TABLE_BORDER_STYLE_INITIALIZER;
+      style.stroke = read_stroke (lexer);
+      if (lex_is_string (lexer))
+        style.color = read_color (lexer);
+      force_match (lexer, T_RPAREN);
+
+      int n = 0;
+      for (int b = 0; b < PIVOT_N_BORDERS; b++)
+        {
+          if (!fnmatch (s, pivot_border_ids[b], 0))
+            {
+              look->borders[b] = style;
+              n++;
+            }
+        }
+      if (!n)
+        {
+          msg (SE, "%s: no matching borders", s);
+          exit (1);
+        }
+      free (s);
+    }
+  pivot_table_set_look (pt, look);
+  pivot_table_look_unref (look);
+}
+
+static void
+read_footnote (struct lexer *lexer, struct pivot_table *pt)
+{
+  size_t idx;
+  if (lex_match (lexer, T_LBRACK))
+    {
+      force_int (lexer);
+
+      idx = lex_integer (lexer);
+      lex_get (lexer);
+
+      force_match (lexer, T_RBRACK);
+    }
+  else
+    idx = pt->n_footnotes;
+  lex_match (lexer, T_EQUALS);
+
+  struct pivot_value *content
+    = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_FOOTER]);
+
+  struct pivot_value *marker;
+  if (lex_match_id (lexer, "MARKER"))
+    {
+      lex_match (lexer, T_EQUALS);
+      marker = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_FOOTER]);
+    }
+  else
+    marker = NULL;
+
+  pivot_table_create_footnote__ (pt, idx, marker, content);
+}
+
+static void
+read_cell (struct lexer *lexer, struct pivot_table *pt)
+{
+  force_match (lexer, T_LBRACK);
+
+  size_t *lo = xnmalloc (pt->n_dimensions, sizeof *lo);
+  size_t *hi = xnmalloc (pt->n_dimensions, sizeof *hi);
+  for (size_t i = 0; i < pt->n_dimensions; i++)
+    {
+      const struct pivot_dimension *d = pt->dimensions[i];
+
+      if (i)
+        force_match (lexer, T_COMMA);
+
+      if (!d->n_leaves)
+        {
+          msg (SE, "can't define data because dimension %zu has no categories",
+               i);
+          exit (1);
+        }
+
+      if (lex_match (lexer, T_ALL))
+        {
+          lo[i] = 0;
+          hi[i] = d->n_leaves - 1;
+        }
+      else
+        {
+          force_int (lexer);
+          lo[i] = hi[i] = lex_integer (lexer);
+          lex_get (lexer);
+
+          if (lex_match_id (lexer, "THRU"))
+            {
+              force_int (lexer);
+              hi[i] = lex_integer (lexer);
+              lex_get (lexer);
+            }
+
+          if (hi[i] < lo[i])
+            {
+              msg (SE, "%zu THRU %zu is not a valid range", lo[i], hi[i]);
+              exit (1);
+            }
+          if (hi[i] >= d->n_leaves)
+            {
+              msg (SE, "dimension %zu (%s) has only %zu categories",
+                   i, pivot_value_to_string (d->root->name, pt),
+                   d->n_leaves);
+              exit (1);
+            }
+        }
+    }
+  force_match (lexer, T_RBRACK);
+
+  struct pivot_value *value = NULL;
+  bool delete = false;
+  if (lex_match (lexer, T_EQUALS))
+    {
+      if (lex_match_id (lexer, "DELETE"))
+        delete = true;
+      else
+        value = read_value (lexer, pt, &pt->look->areas[PIVOT_AREA_DATA]);
+    }
+
+  size_t *dindexes = xmemdup (lo, pt->n_dimensions * sizeof *lo);
+  for (size_t i = 0; ; i++)
+    {
+      if (delete)
+        pivot_table_delete (pt, dindexes);
+      else
+        pivot_table_put (pt, dindexes, pt->n_dimensions,
+                         (value
+                          ? pivot_value_clone (value)
+                          : pivot_value_new_integer (i)));
+
+      for (size_t j = 0; j < pt->n_dimensions; j++)
+        {
+          if (++dindexes[j] <= hi[j])
+            goto next;
+          dindexes[j] = lo[j];
+        }
+        break;
+    next:;
+    }
+  free (dindexes);
+
+  pivot_value_destroy (value);
+
+  free (lo);
+  free (hi);
+}
+
+static struct pivot_dimension *
+parse_dim_name (struct lexer *lexer, struct pivot_table *table)
+{
+  force_string (lexer);
+  for (size_t i = 0; i < table->n_dimensions; i++)
+    {
+      struct pivot_dimension *dim = table->dimensions[i];
+
+      struct string s = DS_EMPTY_INITIALIZER;
+      pivot_value_format_body (dim->root->name, table, &s);
+      bool match = !strcmp (ds_cstr (&s), lex_tokcstr (lexer));
+      ds_destroy (&s);
+
+      if (match)
+        {
+          lex_get (lexer);
+          return dim;
+        }
+    }
+
+  lex_error (lexer, "unknown dimension");
+  exit (1);
+}
+
+static enum pivot_axis_type
+parse_axis_type (struct lexer *lexer)
+{
+  if (lex_match_id (lexer, "ROW"))
+    return PIVOT_AXIS_ROW;
+  else if (lex_match_id (lexer, "COLUMN"))
+    return PIVOT_AXIS_COLUMN;
+  else if (lex_match_id (lexer, "LAYER"))
+    return PIVOT_AXIS_LAYER;
+  else
+    {
+      lex_error_expecting (lexer, "ROW", "COLUMN", "LAYER");
+      exit (1);
+    }
+}
+
+static void
+move_dimension (struct lexer *lexer, struct pivot_table *table)
+{
+  struct pivot_dimension *dim = parse_dim_name (lexer, table);
+
+  enum pivot_axis_type axis = parse_axis_type (lexer);
+
+  size_t position;
+  if (lex_is_integer (lexer))
+    {
+      position = lex_integer (lexer);
+      lex_get (lexer);
+    }
+  else
+    position = 0;
+
+  pivot_table_move_dimension (table, dim, axis, position);
+}
+
+static void
+swap_axes (struct lexer *lexer, struct pivot_table *table)
+{
+  enum pivot_axis_type a = parse_axis_type (lexer);
+  enum pivot_axis_type b = parse_axis_type (lexer);
+  pivot_table_swap_axes (table, a, b);
+}
+
+static void
+read_current_layer (struct lexer *lexer, struct pivot_table *table)
+{
+  lex_match (lexer, T_EQUALS);
+
+  const struct pivot_axis *layer_axis = &table->axes[PIVOT_AXIS_LAYER];
+  for (size_t i = 0; i < layer_axis->n_dimensions; i++)
+    {
+      const struct pivot_dimension *dim = layer_axis->dimensions[i];
+
+      force_int (lexer);
+      size_t index = lex_integer (lexer);
+      if (index >= dim->n_leaves)
+        {
+          lex_error (lexer, "only %zu dimensions", dim->n_leaves);
+          exit (1);
+        }
+      lex_get (lexer);
+
+      table->current_layer[i] = index;
+    }
+}
+
+static struct pivot_table *
+read_table (struct lexer *lexer)
+{
+  struct pivot_table *pt = pivot_table_create ("Default Title");
+  while (lex_match (lexer, T_SLASH))
+    {
+      assert (!pivot_table_is_shared (pt));
+
+      if (lex_match_id (lexer, "ROW"))
+        read_dimension (lexer, pt, PIVOT_AXIS_ROW,
+                        &pt->look->areas[PIVOT_AREA_ROW_LABELS]);
+      else if (lex_match_id (lexer, "COLUMN"))
+        read_dimension (lexer, pt, PIVOT_AXIS_COLUMN,
+                        &pt->look->areas[PIVOT_AREA_COLUMN_LABELS]);
+      else if (lex_match_id (lexer, "LAYER"))
+        read_dimension (lexer, pt, PIVOT_AXIS_LAYER,
+                        &pt->look->areas[PIVOT_AREA_LAYERS]);
+      else if (lex_match_id (lexer, "LOOK"))
+        read_look (lexer, pt);
+      else if (lex_match_id (lexer, "ROTATE"))
+        {
+          lex_match (lexer, T_EQUALS);
+          while (lex_token (lexer) == T_ID)
+            if (!parse_bool_setting (lexer, "INNERCOLUMNS", "YES", "NO",
+                                     &pt->rotate_inner_column_labels)
+                && !parse_bool_setting (lexer, "OUTERROWS", "YES", "NO",
+                                        &pt->rotate_outer_row_labels))
+              break;
+        }
+      else if (lex_match_id (lexer, "SHOW"))
+        {
+          lex_match (lexer, T_EQUALS);
+          while (lex_token (lexer) == T_ID)
+            {
+              if (parse_bool_setting (lexer, "GRID", "YES", "NO",
+                                      &pt->show_grid_lines)
+                  || parse_bool_setting (lexer, "CAPTION", "YES", "NO",
+                                         &pt->show_caption)
+                  || parse_bool_setting (lexer, "TITLE", "YES", "NO",
+                                         &pt->show_title))
+                continue;
+
+              if (parse_settings_value_show (lexer, "VALUES", &pt->show_values)
+                  || parse_settings_value_show (lexer, "VARIABLES",
+                                                &pt->show_variables))
+                continue;
+
+              if (lex_match_id (lexer, "LAYER"))
+                read_current_layer (lexer, pt);
+
+              break;
+            }
+        }
+      else if (parse_value_setting (lexer, pt, "TITLE", &pt->title,
+                                    &pt->look->areas[PIVOT_AREA_TITLE])
+               || parse_value_setting (lexer, pt, "SUBTYPE", &pt->subtype,
+                                       NULL)
+               || parse_value_setting (lexer, pt, "CORNER", &pt->corner_text,
+                                       &pt->look->areas[PIVOT_AREA_CORNER])
+               || parse_value_setting (lexer, pt, "CAPTION", &pt->caption,
+                                       &pt->look->areas[PIVOT_AREA_CAPTION])
+               || parse_string_setting (lexer, "NOTES", &pt->notes))
+        {
+          /* Nothing. */
+        }
+      else if (lex_match_id (lexer, "BORDER"))
+        read_border (lexer, pt);
+      else if (lex_match_id (lexer, "TRANSPOSE"))
+        pivot_table_transpose (pt);
+      else if (lex_match_id (lexer, "SWAP"))
+        swap_axes (lexer, pt);
+      else if (lex_match_id (lexer, "MOVE"))
+        move_dimension (lexer, pt);
+      else if (lex_match_id (lexer, "CELLS"))
+        read_cell (lexer, pt);
+      else if (lex_match_id (lexer, "FOOTNOTE"))
+        read_footnote (lexer, pt);
+      else if (lex_match_id (lexer, "DUMP"))
+        pivot_table_dump (pt, 0);
+      else if (lex_match_id (lexer, "DISPLAY"))
+        {
+          pivot_table_submit (pivot_table_ref (pt));
+          pt = pivot_table_unshare (pt);
+        }
+      else
+        {
+          msg (SE, "Expecting keyword");
+          exit (1);
+        }
+    }
+
+  force_match (lexer, T_ENDCMD);
+  return pt;
+}
+
+static void
+output_msg (const struct msg *m_, void *lexer_)
+{
+  struct lexer *lexer = lexer_;
+  struct msg m = *m_;
+
+  if (m.file_name == NULL)
+    {
+      m.file_name = CONST_CAST (char *, lex_get_file_name (lexer));
+      m.first_line = lex_get_first_line_number (lexer, 0);
+      m.last_line = lex_get_last_line_number (lexer, 0);
+    }
+
+  m.command_name = output_get_command_name ();
+
+  message_item_submit (message_item_create (&m));
+
+  free (m.command_name);
+}
diff --git a/tests/output/pivot-table.at b/tests/output/pivot-table.at
new file mode 100644 (file)
index 0000000..4540274
--- /dev/null
@@ -0,0 +1,402 @@
+AT_BANNER([pivot table rendering])
+
+AT_SETUP([1-d pivot table])
+AT_DATA([pivot.txt], [[
+/col "a"*("a1", "a2", "a3")
+/cell[all]
+/title "Columns" /display
+/title "Rows" /transpose.
+]])
+AT_CHECK([pivot-table-test --table-look $srcdir/output/look.stt pivot.txt --box unicode], [0], [dnl
+Columns
+╭────────╮
+│    a   â”‚
+├──┬──┬──┤
+│a1│a2│a3│
+├──┼──┼──┤
+│ 0│ 1│ 2│
+╰──┴──┴──╯
+
+Rows
+╭──┬─╮
+│a â”‚ â”‚
+├──┼─┤
+│a1│0│
+│a2│1│
+│a3│2│
+╰──┴─╯
+])
+AT_CLEANUP
+
+AT_SETUP([2-d pivot table])
+AT_DATA([pivot.txt], [[
+/col "a"("a1", "a2", "a3")
+/col "b"("b1", "b2", "b3")
+/cell[all, all]
+/title "Columns" /display
+/title "Rows" /transpose /display
+/title "Column x Row" /move "a" column /display
+/title "Row x Column" /transpose
+]])
+AT_CHECK([pivot-table-test --table-look $srcdir/output/look.stt pivot.txt --box unicode], [0], [dnl
+Columns
+╭────────┬────────┬────────╮
+│   b1   â”‚   b2   â”‚   b3   â”‚
+├──┬──┬──┼──┬──┬──┼──┬──┬──┤
+│a1│a2│a3│a1│a2│a3│a1│a2│a3│
+├──┼──┼──┼──┼──┼──┼──┼──┼──┤
+│ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│
+╰──┴──┴──┴──┴──┴──┴──┴──┴──╯
+
+Rows
+╭─────┬─╮
+│b1 a1│0│
+│   a2│1│
+│   a3│2│
+├─────┼─┤
+│b2 a1│3│
+│   a2│4│
+│   a3│5│
+├─────┼─┤
+│b3 a1│6│
+│   a2│7│
+│   a3│8│
+╰─────┴─╯
+
+Column x Row
+╭──┬──┬──┬──╮
+│  â”‚a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+
+Row x Column
+╭──┬──┬──┬──╮
+│  â”‚b1│b2│b3│
+├──┼──┼──┼──┤
+│a1│ 0│ 3│ 6│
+│a2│ 1│ 4│ 7│
+│a3│ 2│ 5│ 8│
+╰──┴──┴──┴──╯
+])
+AT_CLEANUP
+
+AT_SETUP([2-d pivot table - dimension labels])
+AT_DATA([pivot.txt], [[
+/col "a"*("a1", "a2", "a3")
+/col "b"*("b1", "b2", "b3")
+/cell[all, all]
+/title "Columns" /display
+/title "Rows - Corner" /transpose /display
+/title "Rows - Nested" /look rowlabels=nested /display
+/title "Column x Row - Corner" /move "a" column /look rowlabels=corner /display
+/title "Column x Row - Nested" /look rowlabels=nested /display
+/title "Row x Column - Corner" /transpose /look rowlabels=corner /display
+/title "Row x Column - Nested" /look rowlabels=nested
+]])
+AT_CHECK([pivot-table-test --table-look $srcdir/output/look.stt pivot.txt --box unicode], [0], [dnl
+Columns
+╭──────────────────────────╮
+│             b            â”‚
+├────────┬────────┬────────┤
+│   b1   â”‚   b2   â”‚   b3   â”‚
+├────────┼────────┼────────┤
+│    a   â”‚    a   â”‚    a   â”‚
+├──┬──┬──┼──┬──┬──┼──┬──┬──┤
+│a1│a2│a3│a1│a2│a3│a1│a2│a3│
+├──┼──┼──┼──┼──┼──┼──┼──┼──┤
+│ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│
+╰──┴──┴──┴──┴──┴──┴──┴──┴──╯
+
+Rows - Corner
+╭─────┬─╮
+│b  a â”‚ â”‚
+├─────┼─┤
+│b1 a1│0│
+│   a2│1│
+│   a3│2│
+├─────┼─┤
+│b2 a1│3│
+│   a2│4│
+│   a3│5│
+├─────┼─┤
+│b3 a1│6│
+│   a2│7│
+│   a3│8│
+╰─────┴─╯
+
+Rows - Nested
+╭─────────┬─╮
+│b b1 a a1│0│
+│       a2│1│
+│       a3│2│
+│ â•¶â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│  b2 a a1│3│
+│       a2│4│
+│       a3│5│
+│ â•¶â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│  b3 a a1│6│
+│       a2│7│
+│       a3│8│
+╰─────────┴─╯
+
+Column x Row - Corner
+╭──┬────────╮
+│  â”‚    a   â”‚
+│  â”œâ”€â”€â”¬â”€â”€â”¬â”€â”€â”¤
+│b â”‚a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+
+Column x Row - Nested
+╭────┬────────╮
+│    â”‚    a   â”‚
+│    â”œâ”€â”€â”¬â”€â”€â”¬â”€â”€â”¤
+│    â”‚a1│a2│a3│
+├────┼──┼──┼──┤
+│b b1│ 0│ 1│ 2│
+│  b2│ 3│ 4│ 5│
+│  b3│ 6│ 7│ 8│
+╰────┴──┴──┴──╯
+
+Row x Column - Corner
+╭──┬────────╮
+│  â”‚    b   â”‚
+│  â”œâ”€â”€â”¬â”€â”€â”¬â”€â”€â”¤
+│a â”‚b1│b2│b3│
+├──┼──┼──┼──┤
+│a1│ 0│ 3│ 6│
+│a2│ 1│ 4│ 7│
+│a3│ 2│ 5│ 8│
+╰──┴──┴──┴──╯
+
+Row x Column - Nested
+╭────┬────────╮
+│    â”‚    b   â”‚
+│    â”œâ”€â”€â”¬â”€â”€â”¬â”€â”€â”¤
+│    â”‚b1│b2│b3│
+├────┼──┼──┼──┤
+│a a1│ 0│ 3│ 6│
+│  a2│ 1│ 4│ 7│
+│  a3│ 2│ 5│ 8│
+╰────┴──┴──┴──╯
+])
+AT_CLEANUP
+
+AT_SETUP([2-d pivot table - groups])
+AT_DATA([pivot.txt], [[
+/col "a"("a1", "ag1"("a2", "a3"))
+/col "b"("bg1"("b1", "b2"), "b3")
+/cell[all, all]
+/title "Columns" /display
+/title "Rows" /transpose /display
+/title "Column x Row" /move "a" column /display
+/title "Row x Column" /transpose /display
+/title "Row x Column - delete b2" /cells[all,1]=delete /display
+/title "Row x Column - delete b2 - show empty" /look empty=show /display
+/title "Row x Column - delete b1" /cells[all,0]=delete /look empty=hide /display
+/title "Row x Column - delete b1 - show empty" /look empty=show.
+]])
+AT_CHECK([pivot-table-test --table-look $srcdir/output/look.stt pivot.txt --box unicode], [0], [dnl
+Columns
+╭─────────────────┬────────╮
+│       bg1       â”‚        â”‚
+├────────┬────────┤        â”‚
+│   b1   â”‚   b2   â”‚   b3   â”‚
+├──┬─────┼──┬─────┼──┬─────┤
+│  â”‚ ag1 â”‚  â”‚ ag1 â”‚  â”‚ ag1 â”‚
+│  â”œâ”€â”€â”¬â”€â”€â”¤  â”œâ”€â”€â”¬â”€â”€â”¤  â”œâ”€â”€â”¬â”€â”€â”¤
+│a1│a2│a3│a1│a2│a3│a1│a2│a3│
+├──┼──┼──┼──┼──┼──┼──┼──┼──┤
+│ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│
+╰──┴──┴──┴──┴──┴──┴──┴──┴──╯
+
+Rows
+╭─────────────┬─╮
+│bg1 b1 a1    â”‚0│
+│      â•¶â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│       ag1 a2│1│
+│           a3│2│
+│   â•¶â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│    b2 a1    â”‚3│
+│      â•¶â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│       ag1 a2│4│
+│           a3│5│
+├─────────────┼─┤
+│b3     a1    â”‚6│
+│      â•¶â”€â”€â”€â”€â”€â”€â”¼â”€â”¤
+│       ag1 a2│7│
+│           a3│8│
+╰─────────────┴─╯
+
+Column x Row
+╭──────┬──┬─────╮
+│      â”‚  â”‚ ag1 â”‚
+│      â”‚  â”œâ”€â”€â”¬â”€â”€â”¤
+│      â”‚a1│a2│a3│
+├──────┼──┼──┼──┤
+│bg1 b1│ 0│ 1│ 2│
+│    b2│ 3│ 4│ 5│
+├──────┼──┼──┼──┤
+│b3    â”‚ 6│ 7│ 8│
+╰──────┴──┴──┴──╯
+
+Row x Column
+╭──────┬─────┬──╮
+│      â”‚ bg1 â”‚  â”‚
+│      â”œâ”€â”€â”¬â”€â”€â”¤  â”‚
+│      â”‚b1│b2│b3│
+├──────┼──┼──┼──┤
+│a1    â”‚ 0│ 3│ 6│
+├──────┼──┼──┼──┤
+│ag1 a2│ 1│ 4│ 7│
+│    a3│ 2│ 5│ 8│
+╰──────┴──┴──┴──╯
+
+Row x Column - delete b2
+╭──────┬───┬──╮
+│      â”‚bg1│  â”‚
+│      â”œâ”€â”€â”€â”¤  â”‚
+│      â”‚ b1│b3│
+├──────┼───┼──┤
+│a1    â”‚  0│ 6│
+├──────┼───┼──┤
+│ag1 a2│  1│ 7│
+│    a3│  2│ 8│
+╰──────┴───┴──╯
+
+Row x Column - delete b2 - show empty
+╭──────┬─────┬──╮
+│      â”‚ bg1 â”‚  â”‚
+│      â”œâ”€â”€â”¬â”€â”€â”¤  â”‚
+│      â”‚b1│b2│b3│
+├──────┼──┼──┼──┤
+│a1    â”‚ 0│  â”‚ 6│
+├──────┼──┼──┼──┤
+│ag1 a2│ 1│  â”‚ 7│
+│    a3│ 2│  â”‚ 8│
+╰──────┴──┴──┴──╯
+
+Row x Column - delete b1
+╭──────┬──╮
+│      â”‚b3│
+├──────┼──┤
+│a1    â”‚ 6│
+├──────┼──┤
+│ag1 a2│ 7│
+│    a3│ 8│
+╰──────┴──╯
+
+Row x Column - delete b1 - show empty
+╭──────┬─────┬──╮
+│      â”‚ bg1 â”‚  â”‚
+│      â”œâ”€â”€â”¬â”€â”€â”¤  â”‚
+│      â”‚b1│b2│b3│
+├──────┼──┼──┼──┤
+│a1    â”‚  â”‚  â”‚ 6│
+├──────┼──┼──┼──┤
+│ag1 a2│  â”‚  â”‚ 7│
+│    a3│  â”‚  â”‚ 8│
+╰──────┴──┴──┴──╯
+])
+AT_CLEANUP
+
+AT_SETUP([2-d pivot table - layers])
+AT_DATA([pivot.txt], [[
+/col "a"("a1", "a2", "a3")
+/layer "b"("b1", "b2", "b3")
+/cell[all, all]
+/title "Column x b1" /display
+/title "Row x b1" /transpose /display
+/title "Column x b2" /show layer 1 /transpose /display
+/title "Row x b2" /transpose /display
+/title "Column (All Layers)" /look layers=all /display
+/title "Row (All Layers)" /transpose /look layers=all
+]])
+AT_CHECK([pivot-table-test --table-look $srcdir/output/look.stt pivot.txt --box unicode], [0], [dnl
+Column x b1
+b: b1
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 0│ 1│ 2│
+╰──┴──┴──╯
+
+Row x b1
+b: b1
+╭──┬─╮
+│a1│0│
+│a2│1│
+│a3│2│
+╰──┴─╯
+
+Column x b2
+b: b2
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 3│ 4│ 5│
+╰──┴──┴──╯
+
+Row x b2
+b: b2
+╭──┬─╮
+│a1│3│
+│a2│4│
+│a3│5│
+╰──┴─╯
+
+Column (All Layers)
+b: b1
+╭──┬─╮
+│a1│0│
+│a2│1│
+│a3│2│
+╰──┴─╯
+
+Column (All Layers)
+b: b2
+╭──┬─╮
+│a1│3│
+│a2│4│
+│a3│5│
+╰──┴─╯
+
+Column (All Layers)
+b: b3
+╭──┬─╮
+│a1│6│
+│a2│7│
+│a3│8│
+╰──┴─╯
+
+Row (All Layers)
+b: b1
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 0│ 1│ 2│
+╰──┴──┴──╯
+
+Row (All Layers)
+b: b2
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 3│ 4│ 5│
+╰──┴──┴──╯
+
+Row (All Layers)
+b: b3
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 6│ 7│ 8│
+╰──┴──┴──╯
+])
+AT_CLEANUP
diff --git a/tests/output/render-test.c b/tests/output/render-test.c
deleted file mode 100644 (file)
index c382ee1..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2009, 2010, 2011, 2012, 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
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <config.h>
-
-#include <errno.h>
-#include <getopt.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "data/file-handle-def.h"
-#include "libpspp/assertion.h"
-#include "libpspp/compiler.h"
-#include "libpspp/i18n.h"
-#include "libpspp/string-map.h"
-#include "output/ascii.h"
-#include "output/driver.h"
-#include "output/table.h"
-#include "output/table-item.h"
-
-#include "gl/error.h"
-#include "gl/progname.h"
-#include "gl/xalloc.h"
-#include "gl/xvasprintf.h"
-
-/* --emphasis: ASCII driver emphasis option. */
-static bool bold;
-static bool underline;
-
-/* --box: ASCII driver box option. */
-static char *box;
-
-/* --draw-mode: special ASCII driver test mode. */
-static int draw_mode;
-
-/* --no-txt: Whether to render to <base>.txt. */
-static int render_txt = true;
-
-/* --no-stdout: Whether to render to stdout. */
-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;
-
-/* -o, --output: Base name for output files. */
-static const char *output_base = "render";
-
-static const char *parse_options (int argc, char **argv);
-static void usage (void) NO_RETURN;
-static struct table *read_table (FILE *);
-static void draw (FILE *);
-
-int
-main (int argc, char **argv)
-{
-  const char *input_file_name;
-  FILE *input;
-
-  set_program_name (argv[0]);
-  i18n_init ();
-  output_engine_push ();
-  input_file_name = parse_options (argc, argv);
-
-  if (!strcmp (input_file_name, "-"))
-    input = stdin;
-  else
-    {
-      input = fopen (input_file_name, "r");
-      if (input == NULL)
-        error (1, errno, "%s: open failed", input_file_name);
-    }
-
-  if (!draw_mode)
-    {
-      struct table **tables = NULL;
-      size_t allocated_tables = 0;
-      size_t n_tables = 0;
-      struct table *table;
-
-      for (;;)
-        {
-          int ch;
-
-          if (n_tables >= allocated_tables)
-            tables = x2nrealloc (tables, &allocated_tables, sizeof *tables);
-
-          tables[n_tables] = read_table (input);
-          n_tables++;
-
-          ch = getc (input);
-          if (ch == EOF)
-            break;
-          ungetc (ch, input);
-        }
-
-      table = tables[n_tables - 1];
-      table_item_submit (table_item_create (table));
-      free (tables);
-    }
-  else
-    draw (input);
-
-  if (input != stdin)
-    fclose (input);
-
-  output_engine_pop ();
-  fh_done ();
-
-  return 0;
-}
-
-static void
-configure_drivers (int width, int length, int min_break)
-{
-  struct string_map options, tmp;
-  struct output_driver *driver;
-
-  string_map_init (&options);
-  string_map_insert (&options, "format", "txt");
-  string_map_insert (&options, "output-file", "-");
-  string_map_insert_nocopy (&options, xstrdup ("width"),
-                            xasprintf ("%d", width));
-  if (min_break >= 0)
-    string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
-                              xasprintf ("%d", min_break));
-  if (bold || underline)
-    string_map_insert (&options, "emphasis", "true");
-  if (box != NULL)
-    string_map_insert (&options, "box", box);
-
-  /* Render to stdout. */
-  if (render_stdout)
-    {
-      string_map_clone (&tmp, &options);
-      ascii_driver = driver = output_driver_create (&tmp);
-      if (driver == NULL)
-        exit (EXIT_FAILURE);
-      output_driver_register (driver);
-      string_map_destroy (&tmp);
-    }
-
-  if (draw_mode)
-   {
-     string_map_destroy (&options);
-     return;
-   }
-
-  /* Render to <base>.txt. */
-  if (render_txt)
-    {
-      string_map_clear (&options);
-      string_map_insert_nocopy (&options, xstrdup ("output-file"),
-                                xasprintf ("%s.txt", output_base));
-      driver = output_driver_create (&options);
-      if (driver == NULL)
-        exit (EXIT_FAILURE);
-      output_driver_register (driver);
-    }
-
-#ifdef HAVE_CAIRO
-  /* Render to <base>.pdf. */
-  if (render_pdf)
-    {
-      string_map_clear (&options);
-      string_map_insert_nocopy (&options, xstrdup ("output-file"),
-                                 xasprintf ("%s.pdf", output_base));
-      string_map_insert (&options, "top-margin", "0");
-      string_map_insert (&options, "bottom-margin", "0");
-      string_map_insert (&options, "left-margin", "0");
-      string_map_insert (&options, "right-margin", "0");
-      string_map_insert_nocopy (&options, xstrdup ("paper-size"),
-                                xasprintf ("%dx%dpt", width * 5, length * 8));
-      if (min_break >= 0)
-        {
-          string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
-                                    xasprintf ("%d", min_break * 5));
-          string_map_insert_nocopy (&options, xstrdup ("min-vbreak"),
-                                    xasprintf ("%d", min_break * 8));
-        }
-      driver = output_driver_create (&options);
-      if (driver == NULL)
-        exit (EXIT_FAILURE);
-      output_driver_register (driver);
-    }
-#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));
-  driver = output_driver_create (&options);
-  if (driver == NULL)
-    exit (EXIT_FAILURE);
-  output_driver_register (driver);
-
-  string_map_destroy (&options);
-}
-
-static const char *
-parse_options (int argc, char **argv)
-{
-  int width = 79;
-  int length = 66;
-  int min_break = -1;
-
-  for (;;)
-    {
-      enum {
-        OPT_WIDTH = UCHAR_MAX + 1,
-        OPT_LENGTH,
-        OPT_MIN_BREAK,
-        OPT_EMPHASIS,
-        OPT_BOX,
-        OPT_HELP
-      };
-      static const struct option options[] =
-        {
-          {"width", required_argument, NULL, OPT_WIDTH},
-          {"length", required_argument, NULL, OPT_LENGTH},
-          {"min-break", required_argument, NULL, OPT_MIN_BREAK},
-          {"emphasis", required_argument, NULL, OPT_EMPHASIS},
-          {"box", required_argument, NULL, OPT_BOX},
-          {"draw-mode", no_argument, &draw_mode, 1},
-          {"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},
-        };
-
-      int c = getopt_long (argc, argv, "o:", options, NULL);
-      if (c == -1)
-        break;
-
-      switch (c)
-        {
-        case OPT_WIDTH:
-          width = atoi (optarg);
-          break;
-
-        case OPT_LENGTH:
-          length = atoi (optarg);
-          break;
-
-        case OPT_MIN_BREAK:
-          min_break = atoi (optarg);
-          break;
-
-        case OPT_EMPHASIS:
-          if (!strcmp (optarg, "bold"))
-            {
-              bold = true;
-              underline = false;
-            }
-          else if (!strcmp (optarg, "underline"))
-            {
-              bold = false;
-              underline = true;
-            }
-          else if (!strcmp (optarg, "none"))
-            {
-              bold = underline = false;
-            }
-          else
-            error (1, 0, "argument to --emphasis must be \"bold\" or "
-                   "\"underline\" or \"none\"");
-          break;
-
-        case OPT_BOX:
-          box = optarg;
-          break;
-
-        case 'o':
-          output_base = optarg;
-          break;
-
-        case OPT_HELP:
-          usage ();
-
-        case 0:
-          break;
-
-        case '?':
-          exit(EXIT_FAILURE);
-          break;
-
-        default:
-          NOT_REACHED ();
-        }
-
-    }
-
-  configure_drivers (width, length, min_break);
-
-  if (optind + 1 != argc)
-    error (1, 0, "exactly one non-option argument required; "
-           "use --help for help");
-  return argv[optind];
-}
-
-static void
-usage (void)
-{
-  printf ("%s, to test rendering of PSPP tables\n"
-          "usage: %s [OPTIONS] INPUT\n"
-          "\nOptions:\n"
-          "  --width=WIDTH   set page width in characters\n"
-          "  --length=LINE   set page length in lines\n",
-          program_name, program_name);
-  exit (EXIT_SUCCESS);
-}
-
-static void
-replace_newlines (char *p)
-{
-  char *q;
-
-  for (q = p; *p != '\0';)
-    if (*p == '\\' && p[1] == 'n')
-      {
-        *q++ = '\n';
-        p += 2;
-      }
-    else
-      *q++ = *p++;
-  *q = '\0';
-}
-
-static struct table *
-read_table (FILE *stream)
-{
-  struct table *tab;
-  char buffer[1024];
-  int input[6];
-  int n_input = 0;
-  int nr, nc, hl, hr, ht, hb;
-  int r, c;
-  size_t n_footnotes = 0;
-
-  if (fgets (buffer, sizeof buffer, stream) == NULL
-      || (n_input = sscanf (buffer, "%d %d %d %d %d %d",
-                            &input[0], &input[1], &input[2],
-                            &input[3], &input[4], &input[5])) < 2)
-    error (1, 0, "syntax error reading row and column count");
-
-  nr = input[0];
-  nc = input[1];
-  hl = n_input >= 3 ? input[2] : 0;
-  hr = n_input >= 4 ? input[3] : 0;
-  ht = n_input >= 5 ? input[4] : 0;
-  hb = n_input >= 6 ? input[5] : 0;
-
-  tab = table_create (nc, nr, hl, hr, ht, hb);
-  for (r = 0; r < nr; r++)
-    for (c = 0; c < nc; c++)
-      if (table_cell_is_empty (tab, c, r))
-        {
-          char *new_line;
-          char *text;
-          int rs, cs;
-
-          if (fgets (buffer, sizeof buffer, stream) == NULL)
-            error (1, 0, "unexpected end of input reading row %d, column %d",
-                   r, c);
-          new_line = strchr (buffer, '\n');
-          if (new_line != NULL)
-            *new_line = '\0';
-
-          text = buffer;
-          if (sscanf (text, "%d*%d", &rs, &cs) == 2)
-            {
-              while (*text != ' ' && *text != '\0')
-                text++;
-              if (*text == ' ')
-                text++;
-            }
-          else
-            {
-              rs = 1;
-              cs = 1;
-            }
-
-#define S(H) { TABLE_AREA_STYLE_INITIALIZER__, .cell_style.halign = H }
-          static const struct table_area_style left_style
-            = S (TABLE_HALIGN_LEFT);
-          static const struct table_area_style right_style
-            = S (TABLE_HALIGN_RIGHT);
-          static const struct table_area_style center_style
-            = S (TABLE_HALIGN_CENTER);
-
-          const struct table_area_style *style = &right_style;
-          while (*text && strchr ("<>^,@()|", *text))
-            switch (*text++)
-              {
-              case '<':
-                table_vline (tab, TABLE_STROKE_SOLID, c, r, r + rs - 1);
-                break;
-
-              case '>':
-                table_vline (tab, TABLE_STROKE_SOLID, c + cs, r, r + rs - 1);
-                break;
-
-              case '^':
-                table_hline (tab, TABLE_STROKE_SOLID, c, c + cs - 1, r);
-                break;
-
-              case ',':
-                table_hline (tab, TABLE_STROKE_SOLID, c, c + cs - 1, r + rs);
-                break;
-
-              case '@':
-                table_box (tab, TABLE_STROKE_SOLID, TABLE_STROKE_SOLID,
-                           -1, -1, c, r, c + cs - 1, r + rs - 1);
-                break;
-
-              case '(':
-                style = &left_style;
-                break;
-
-              case ')':
-                style = &right_style;
-                break;
-
-              case '|':
-                style = &center_style;
-                break;
-
-              default:
-                NOT_REACHED ();
-              }
-
-          replace_newlines (text);
-
-          char *pos = text;
-          char *content;
-          int i;
-
-          for (i = 0; (content = strsep (&pos, "#")) != NULL; i++)
-            if (!i)
-              {
-                table_joint_text (tab, c, r, c + cs - 1, r + rs - 1, 0,
-                                  content);
-                table_add_style (tab, c, r,
-                                 CONST_CAST (struct table_area_style *,
-                                             style));
-              }
-            else
-              {
-                char marker[2] = { 'a' + n_footnotes, '\0' };
-                struct footnote *f = table_create_footnote (
-                  tab, n_footnotes, content, marker,
-                  table_area_style_clone (tab->container, &left_style));
-                table_add_footnote (tab, c, r, f);
-                n_footnotes++;
-              }
-        }
-
-  return tab;
-}
-
-static void
-draw (FILE *stream)
-{
-  char buffer[1024];
-  int line = 0;
-
-  while (fgets (buffer, sizeof buffer, stream))
-    {
-      char text[sizeof buffer];
-      int length;
-      int emph;
-      int x, y;
-
-      line++;
-      if (strchr ("#\r\n", buffer[0]))
-        continue;
-
-      if (sscanf (buffer, "%d %d %d %[^\n]", &x, &y, &emph, text) == 4)
-        ascii_test_write (ascii_driver, text, x, y, emph ? bold : false,
-                          emph ? underline : false);
-      else if (sscanf (buffer, "set-length %d %d", &y, &length) == 2)
-        ascii_test_set_length (ascii_driver, y, length);
-      else
-        error (1, 0, "line %d has invalid format", line);
-    }
-  ascii_test_flush (ascii_driver);
-}
index 2bdac10f4602ddf03914137ecf23569bd7b91da5..d9cb9a621e309ff3a34d591f9e9559cbbe967e72 100644 (file)
@@ -13,1417 +13,7 @@ dnl GNU General Public License for more details.
 dnl
 dnl You should have received a copy of the GNU General Public License
 dnl along with this program.  If not, see <http://www.gnu.org/licenses/>.
-# +-+---+-+-+-+
-# |a|bcd|e|f|i|
-# +-+-+-+-+g+-+
-# |j|m|nop|h|q|
-# |k+-+-+-+-+r|
-# |l|t|w|xyz|s|
-# +-+u+-+-+-+-+
-# |A|v|B|E|FGH|
-# +-+-+C+-+-+-+
-# |IJK|D|L|O|P|
-# +-+-+-+M+-+-+
-# |Q|RST|N|U|V|
-# +-+---+-+-+-+
-m4_define([WEAVE_6X6],
-  [6 6 $1
-@a
-1*2 @bcd
-@e
-2*1 @f\ng\nh
-@i
-2*1 @j\nk\nl
-@m
-1*2 @nop
-2*1 @q\nr\ns
-2*1 @t\nu\nv
-@w
-1*2 @xyz
-@A
-2*1 @B\nC\nD
-@E
-1*2 @FGH
-1*2 @IJK
-2*1 @L\nM\nN
-@O
-@P
-@Q
-1*2 @RST
-@U
-@V
-])
-
-# +-+-+-+-+-+-+-+-+
-# |a|b|c|d|e|f|g|h|
-# +-+-+-+-+-+-+-+-+
-# |i|jkl|m|nop|q|t|
-# +-+-+-+-+-+-+r+-+
-# |u|v|wxy|z|A|s|D|
-# +-+-+-+-+-+B+-+-+
-# |E|F|I|JKL|C|M|P|
-# +-+G+-+---+-+N+-+
-# |Q|H|R|UVW|X|O|Y|
-# +-+-+S+-+-+-+-+-+
-# |Z|0|T|3|456|7|8|
-# +-+1+-+-+-+-+-+-+
-# |9|2|abc|d|efg|h|
-# +-+-+-+-+-+-+-+-+
-# |i|j|k|l|m|n|o|p|
-# +-+-+-+-+-+-+-+-+
-m4_define([WEAVE_8X8],
-  [8 8 $1
-@a
-@b
-@c
-@d
-@e
-@f
-@g
-@h
-@i
-1*2 @jkl
-@m
-1*2 @nop
-2*1 @q\nr\ns
-@t
-@u
-@v
-1*2 @wxy
-@z
-2*1 @A\nB\nC
-@D
-@E
-2*1 @F\nG\nH
-@I
-1*2 @JKL
-2*1 @M\nN\nO
-@P
-@Q
-2*1 @R\nS\nT
-1*2 @UVW
-@X
-@Y
-@Z
-2*1 @0\n1\n2
-@3
-1*2 @456
-@7
-@8
-@9
-1*2 @abc
-@d
-1*2 @efg
-@h
-@i
-@j
-@k
-@l
-@m
-@n
-@o
-@p
-])
-
-# This input is something of a counterexample, in that it could render
-# compactly as this if the algorithm for choosing cell widths and
-# heights were smarter:
-#
-# +---+---+---+-+-+
-# |abc|jkl|mno|v|x|
-# |def+---+pqr+-+-+
-# |ghi|yzA|stu|HIJ|
-# +-+-+BCD+-+-+KLM|
-# |Q|V|EFG|W|Z|NOP|
-# |R+-+-+-+X+-+-+-+
-# |S|012|9|Y|abc|j|
-# |T|345+-+-+def|k|
-# |U|678|opq|ghi|l|
-# +-+-+-+rst+---+m|
-# |xyz|G|uvw|JKL|n|
-# |ABC|H+---+-+-+-+
-# |DEF|I|MNOPQ|123|
-# +---+-+RSTUV|456|
-# |abcde|WXYZ0|789|
-# +-----+-----+---+
-m4_define([WEAVE_8X8_2],
-  [8 8 $1
-2*2 @abc\ndef\nghi
-1*2 @jkl
-2*2 @mno\npqr\nstu
-1*2 @vwx
-2*2 @yzA\nBCD\nEFG
-2*2 @HIJ\nKLM\nNOP
-3*1 @Q\nR\nS\nT\nU
-@V
-2*1 @W\nX\nY
-@Z
-2*2 @012\n345\n678
-@9
-2*2 @abc\ndef\nghi
-3*1 @j\nk\nl\nm\nn
-2*2 @opq\nrst\nuvw
-2*2 @xyz\nABC\nDEF
-2*1 @G\nH\nI
-1*2 @JKL
-2*3 @MNOPQ\nRSTUV\nWXYZ0
-2*2 @123\n456\n789
-1*3 @abcde
-])
-\f
-AT_BANNER([output rendering -- no page breaking])
-
-AT_SETUP([single cell])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [1 1
-abc
-])
-AT_CHECK([render-test input], [0], [abc
-])
-AT_CLEANUP
-
-AT_SETUP([single cell with border])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [1 1
-@abc
-])
-AT_CHECK([render-test input], [0], [dnl
-+---+
-|abc|
-+---+
-])
-AT_CLEANUP
-
-AT_SETUP([joined columns])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [2 2
-1*2 @abcdefg
-@hij
-@klm
-])
-AT_CHECK([render-test input], [0], [dnl
-+-------+
-|abcdefg|
-+---+---+
-|hij|klm|
-+---+---+
-])
-AT_CLEANUP
-
-AT_SETUP([3x3, joined rows and columns])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [3 3
-1*2 @abc
-2*1 @d\ne\nf
-2*1 @g\nh\ni
-@j
-1*2 @klm
-])
-AT_CHECK([render-test input], [0], [dnl
-+---+-+
-|abc|d|
-+-+-+e|
-|g|j|f|
-|h+-+-+
-|i|klm|
-+-+---+
-])
-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,
-Footnote: a. Approximation.
-Footnote: 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.
-Footnote: c. One
-Footnote: d. Two
-Footnote: e. Three
-]])
-AT_CLEANUP
-
-AT_SETUP([6x6, joined rows and columns])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_6X6])
-AT_CHECK([render-test input], [0], [dnl
-+-+---+-+-+-+
-|a|bcd|e|f|i|
-+-+-+-+-+g+-+
-|j|m|nop|h|q|
-|k+-+-+-+-+r|
-|l|t|w|xyz|s|
-+-+u+-+-+-+-+
-|A|v|B|E|FGH|
-+-+-+C+-+-+-+
-|IJK|D|L|O|P|
-+-+-+-+M+-+-+
-|Q|RST|N|U|V|
-+-+---+-+-+-+
-])
-AT_CLEANUP
-
-AT_SETUP([3 rows with many joined cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [3 19
-m4_foreach([x], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s], [x
-])@1
-m4_for([x], [2], [19], [1], [1*2 @x
-])@20
-])
-AT_CHECK([render-test input], [0], [dnl
- a b c d e f g h i j k l m n o p q r  s
-+-+---+---+---+---+---+---+---+---+----+
-|1|  2|  3|  4|  5|  6|  7|  8|  9|  10|
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+
-| 11| 12| 13| 14| 15| 16| 17| 18| 19|20|
-+---+---+---+---+---+---+---+---+---+--+
-])
-AT_CLEANUP
-
-AT_SETUP([3 columns with many joined cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [19 3
-a
-@1
-2*1 @11\nab\ncd
-b
-2*1 @2\nab\ncd
-c
-2*1 @12\nab\ncd
-d
-2*1 @3\nab\ncd
-e
-2*1 @13\nab\ncd
-f
-2*1 @4\nab\ncd
-g
-2*1 @14\nab\ncd
-h
-2*1 @5\nab\ncd
-i
-2*1 @15\nab\ncd
-j
-2*1 @6\nab\ncd
-k
-2*1 @16\nab\ncd
-l
-2*1 @7\nab\ncd
-m
-2*1 @17\nab\ncd
-n
-2*1 @8\nab\ncd
-o
-2*1 @18\nab\ncd
-p
-2*1 @9\nab\ncd
-q
-2*1 @19\nab\ncd
-r
-2*1 @10\nab\ncd
-s
-@20
-])
-AT_CHECK([render-test input], [0], [dnl
- +--+--+
-a| 1|11|
- +--+ab|
-b| 2|cd|
- |ab+--+
-c|cd|12|
- +--+ab|
-d| 3|cd|
- |ab+--+
-e|cd|13|
- +--+ab|
-f| 4|cd|
- |ab+--+
-g|cd|14|
- +--+ab|
-h| 5|cd|
- |ab+--+
-i|cd|15|
- +--+ab|
-j| 6|cd|
- |ab+--+
-k|cd|16|
- +--+ab|
-l| 7|cd|
- |ab+--+
-m|cd|17|
- +--+ab|
-n| 8|cd|
- |ab+--+
-o|cd|18|
- +--+ab|
-p| 9|cd|
- |ab+--+
-q|cd|19|
- +--+ab|
-r|10|cd|
- |ab+--+
-s|cd|20|
- +--+--+
-])
-AT_CLEANUP
-
-AT_SETUP([joined rows])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [2 2
-2*1 @ab\ncd\nef
-@hij
-@klm
-])
-AT_CHECK([render-test input], [0], [dnl
-+--+---+
-|ab|hij|
-|cd+---+
-|ef|klm|
-+--+---+
-])
-AT_CLEANUP
-
-dnl This checks for bug #31346, a segmentation fault that surfaced
-dnl when two or more rows  had no unspanned cells and no rules.
-AT_SETUP([joined rows only, no rules])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [2 2
-2*1 ab\ncd\nef
-2*1 hij\nklm\nnop
-])
-AT_CHECK([render-test input], [0], [dnl
-ab hij
-cd klm
-ef nop
-])
-AT_CLEANUP
-
-AT_SETUP([joined columns only, no rules])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [2 2
-1*2 abc\ndef
-1*2 hij\nklm\nnop
-])
-AT_CHECK([render-test input], [0], [dnl
- abc
- def
- hij
- klm
- nop
-])
-AT_CLEANUP
-
-AT_SETUP([5 big narrow cells])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [1 5
-@This cell has a lot of text but its minimum width is pretty narrow.
-@This cell also has a lot of text but its minimum width is pretty narrow.
-@A third cell with a lot of text but a pretty narrow minimum width.
-@A fourth cell with a lot of text but a pretty narrow minimum width.
-@A fifth cell with a lot of text but a pretty narrow minimum width.
-])
-AT_CHECK([render-test input], [0], [dnl
-+---------------+---------------+--------------+---------------+--------------+
-|This cell has a| This cell also|  A third cell|  A fourth cell|  A fifth cell|
-|lot of text but|   has a lot of| with a lot of|  with a lot of| with a lot of|
-|    its minimum|   text but its|    text but a|     text but a|    text but a|
-|width is pretty|  minimum width| pretty narrow|  pretty narrow| pretty narrow|
-|        narrow.|      is pretty|minimum width.| minimum width.|minimum width.|
-|               |        narrow.|              |               |              |
-+---------------+---------------+--------------+---------------+--------------+
-])
-AT_CLEANUP
-
-AT_SETUP([9 big narrow cells])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [1 9
-@This cell has a lot of text but its minimum width is pretty narrow.
-@This cell also has a lot of text but its minimum width is pretty narrow.
-@A third cell with a lot of text but a pretty narrow minimum width.
-@A fourth cell with a lot of text but a pretty narrow minimum width.
-@A fifth cell with a lot of text but a pretty narrow minimum width.
-@A sixth cell with a lot of text but a pretty narrow minimum width.
-@A seventh cell with a lot of text but a pretty narrow minimum width.
-@A eighth cell with a lot of text but a pretty narrow minimum width.
-@A ninth cell with a lot of text but a pretty narrow minimum width.
-])
-AT_CHECK([render-test input], [0], [dnl
-+--------+-------+--------+--------+-------+--------+--------+-------+--------+
-|    This|   This| A third|A fourth|A fifth| A sixth|       A|      A| A ninth|
-|cell has|   cell|    cell|    cell|   cell|    cell| seventh| eighth|    cell|
-|a lot of|   also|  with a|  with a| with a|  with a|    cell|   cell|  with a|
-|text but|  has a|  lot of|  lot of| lot of|  lot of|  with a| with a|  lot of|
-|     its| lot of|text but|text but|   text|text but|  lot of| lot of|text but|
-| minimum|   text|a pretty|a pretty|  but a|a pretty|text but|   text|a pretty|
-|width is|but its|  narrow|  narrow| pretty|  narrow|a pretty|  but a|  narrow|
-|  pretty|minimum| minimum| minimum| narrow| minimum|  narrow| pretty| minimum|
-| narrow.|  width|  width.|  width.|minimum|  width.| minimum| narrow|  width.|
-|        |     is|        |        | width.|        |  width.|minimum|        |
-|        | pretty|        |        |       |        |        | width.|        |
-|        |narrow.|        |        |       |        |        |       |        |
-+--------+-------+--------+--------+-------+--------+--------+-------+--------+
-])
-AT_CLEANUP
-
-AT_SETUP([2 big cells with new-lines])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [1 2
-@PSPP does not place many restrictions on ordering of commands. The main restriction is that variables must be defined before they are otherwise referenced.  This section describes the details of command ordering, but most users will have no need to refer to them. PSPP possesses five internal states, called initial, INPUT PROGRAM, FILE TYPE, transformation, and procedure states.
-@PSPP includes special support\nfor unknown numeric data values.\nMissing observations are assigned\na special value, called the\n``system‑missing value''.  This\n``value'' actually indicates the\nabsence of a value; it\nmeans that the actual\nvalue is unknown.
-])
-AT_CHECK([render-test input], [0], [dnl
-+----------------------------------------------------------+------------------+
-|      PSPP does not place many restrictions on ordering of|     PSPP includes|
-|  commands. The main restriction is that variables must be|   special support|
-|       defined before they are otherwise referenced.  This|       for unknown|
-|    section describes the details of command ordering, but|      numeric data|
-|       most users will have no need to refer to them. PSPP|           values.|
-|     possesses five internal states, called initial, INPUT|           Missing|
-| PROGRAM, FILE TYPE, transformation, and procedure states.|  observations are|
-|                                                          |          assigned|
-|                                                          |  a special value,|
-|                                                          |        called the|
-|                                                          |  ``system‑missing|
-|                                                          |    value''.  This|
-|                                                          |``value'' actually|
-|                                                          |     indicates the|
-|                                                          |      absence of a|
-|                                                          |         value; it|
-|                                                          |    means that the|
-|                                                          |            actual|
-|                                                          | value is unknown.|
-+----------------------------------------------------------+------------------+
-])
-AT_CLEANUP
-
-AT_SETUP([8x8 with many 2x2 joins])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_8X8_2])
-AT_CHECK([render-test input], [0],[dnl
-+---+---+----+----+
-|abc|jkl| mno| vwx|
-|def|   | pqr|    |
-|ghi+---+ stu+----+
-|   |yzA|    | HIJ|
-+-+-+BCD+-+--+ KLM|
-|Q|V|EFG|W| Z| NOP|
-|R| |   |X|  |    |
-|S+-+-+-+Y+--+-+--+
-|T|012|9| | abc| j|
-|U|345| | | def| k|
-| |678+-+-+ ghi| l|
-| |   |opq|    | m|
-+-+-+-+rst+----+ n|
-|xyz|G|uvw| JKL|  |
-|ABC|H|   |    |  |
-|DEF|I+---+--+-+--+
-|   | | MNOPQ| 123|
-+---+-+ RSTUV| 456|
-|abcde| WXYZ0| 789|
-|     |      |    |
-+-----+------+----+
-])
-AT_CLEANUP
-
-
-AT_SETUP([ASCII driver Unicode box characters])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [3 3
-1*2 @abc
-2*1 @d\ne\nf
-2*1 @g\nh\ni
-@j
-1*2 @klm
-])
-AT_CHECK([render-test --box=unicode input], [0], [dnl
-╭───┬─╮
-│abc│d│
-├─┬─┤e│
-│g│j│f│
-│h├─┴─┤
-│i│klm│
-╰─┴───╯
-])
-AT_CLEANUP
-\f
-AT_BANNER([output rendering -- horizontal page breaks])
-
-AT_SETUP([breaking row of many small cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 50
-m4_for([x], [1], [50], [1], [@x
-])])
-AT_CHECK([render-test input], [0], [dnl
-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|
-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-])
-AT_CHECK([render-test input -o mb0 --min-break=0], [0], [dnl
-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|
-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking row of many small cells, with headers])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 54 2 2
-@ha
-@hb
-m4_for([x], [1], [50], [1], [@x
-])dnl
-@hc
-@hd
-])
-AT_CHECK([render-test input], [0], [dnl
-+--+--+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|ha|hb|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|hc|hd|
-+--+--+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|ha|hb|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|hc|hd|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+
-|ha|hb|48|49|50|hc|hd|
-+--+--+--+--+--+--+--+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
-+--+--+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|ha|hb|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|hc|hd|
-+--+--+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|ha|hb|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|hc|hd|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+
-|ha|hb|48|49|50|hc|hd|
-+--+--+--+--+--+--+--+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking row of many medium-size cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 50
-m4_for([x], [1], [50], [1], [@cell x
-])])
-AT_CHECK([render-test input], [0], [dnl
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|
-|   1|   2|   3|   4|   5|   6|   7|   8|   9|  10|  11|  12|  13|  14|  15|
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|
-|  16|  17|  18|  19|  20|  21|  22|  23|  24|  25|  26|  27|  28|  29|  30|
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|
-|  31|  32|  33|  34|  35|  36|  37|  38|  39|  40|  41|  42|  43|  44|  45|
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
-
-+----+----+----+----+----+
-|cell|cell|cell|cell|cell|
-|  46|  47|  48|  49|  50|
-+----+----+----+----+----+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+---
-|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cel
-|   1|   2|   3|   4|   5|   6|   7|   8|   9|  10|  11|  12|  13|  14|  15|  1
-+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+---
-
--+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+--
-l|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|ce
-6|  17|  18|  19|  20|  21|  22|  23|  24|  25|  26|  27|  28|  29|  30|  31|
--+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+--
-
---+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+-
-ll|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|c
-32|  33|  34|  35|  36|  37|  38|  39|  40|  41|  42|  43|  44|  45|  46|  47|
---+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+-
-
----+----+----+
-ell|cell|cell|
- 48|  49|  50|
----+----+----+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking row of many medium-size cells, with headers])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 52 1 1
-header1
-m4_for([x], [1], [50], [1], [@cell x
-])dnl
-header2
-])
-AT_CHECK([render-test input], [0], [dnl
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |   1|   2|   3|   4|   5|   6|   7|   8|   9|  10|  11|  12|
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |  13|  14|  15|  16|  17|  18|  19|  20|  21|  22|  23|  24|
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |  25|  26|  27|  28|  29|  30|  31|  32|  33|  34|  35|  36|
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |  37|  38|  39|  40|  41|  42|  43|  44|  45|  46|  47|  48|
-       +----+----+----+----+----+----+----+----+----+----+----+----+
-
-       +----+----+
-header1|cell|cell|header2
-       |  49|  50|
-       +----+----+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
-       +----+----+----+----+----+----+----+----+----+----+----+----+--+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|ce|header2
-       |   1|   2|   3|   4|   5|   6|   7|   8|   9|  10|  11|  12|  |
-       +----+----+----+----+----+----+----+----+----+----+----+----+--+
-
-       +--+----+----+----+----+----+----+----+----+----+----+----+----+
-header1|ll|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |13|  14|  15|  16|  17|  18|  19|  20|  21|  22|  23|  24|  25|
-       +--+----+----+----+----+----+----+----+----+----+----+----+----+
-
-       +----+----+----+----+----+----+----+----+----+----+----+----+--+
-header1|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|ce|header2
-       |  26|  27|  28|  29|  30|  31|  32|  33|  34|  35|  36|  37|  |
-       +----+----+----+----+----+----+----+----+----+----+----+----+--+
 
-       +--+----+----+----+----+----+----+----+----+----+----+----+----+
-header1|ll|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|cell|header2
-       |38|  39|  40|  41|  42|  43|  44|  45|  46|  47|  48|  49|  50|
-       +--+----+----+----+----+----+----+----+----+----+----+----+----+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking row of many big narrow cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 50
-m4_for([x], [1], [50], [1], [@This is cell x in a series of 50.
-])])
-AT_CHECK([render-test input], [0], [dnl
-+------+------+------+------+------+------+------+------+------+------+------+
-|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-|cell 1|cell 2|cell 3|cell 4|cell 5|cell 6|cell 7|cell 8|cell 9|  cell|  cell|
-|  in a|  in a|  in a|  in a|  in a|  in a|  in a|  in a|  in a| 10 in| 11 in|
-|series|series|series|series|series|series|series|series|series|     a|     a|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|series|series|
-|      |      |      |      |      |      |      |      |      |of 50.|of 50.|
-+------+------+------+------+------+------+------+------+------+------+------+
-
-+------+------+------+------+------+------+------+------+------+------+------+
-|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|
-| 12 in| 13 in| 14 in| 15 in| 16 in| 17 in| 18 in| 19 in| 20 in| 21 in| 22 in|
-|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-|series|series|series|series|series|series|series|series|series|series|series|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|
-+------+------+------+------+------+------+------+------+------+------+------+
-
-+------+------+------+------+------+------+------+------+------+------+------+
-|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|
-| 23 in| 24 in| 25 in| 26 in| 27 in| 28 in| 29 in| 30 in| 31 in| 32 in| 33 in|
-|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-|series|series|series|series|series|series|series|series|series|series|series|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|
-+------+------+------+------+------+------+------+------+------+------+------+
-
-+------+------+------+------+------+------+------+------+------+------+------+
-|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|
-| 34 in| 35 in| 36 in| 37 in| 38 in| 39 in| 40 in| 41 in| 42 in| 43 in| 44 in|
-|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-|series|series|series|series|series|series|series|series|series|series|series|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|
-+------+------+------+------+------+------+------+------+------+------+------+
-
-+------+------+------+------+------+------+
-|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|
-|  cell|  cell|  cell|  cell|  cell|  cell|
-| 45 in| 46 in| 47 in| 48 in| 49 in| 50 in|
-|     a|     a|     a|     a|     a|     a|
-|series|series|series|series|series|series|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|
-+------+------+------+------+------+------+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
-+------+------+------+------+------+------+------+------+------+------+------+-
-|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-|cell 1|cell 2|cell 3|cell 4|cell 5|cell 6|cell 7|cell 8|cell 9|  cell|  cell|
-|  in a|  in a|  in a|  in a|  in a|  in a|  in a|  in a|  in a| 10 in| 11 in|
-|series|series|series|series|series|series|series|series|series|     a|     a|
-|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|series|series|s
-|      |      |      |      |      |      |      |      |      |of 50.|of 50.|o
-+------+------+------+------+------+------+------+------+------+------+------+-
-
------+------+------+------+------+------+------+------+------+------+------+---
- This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  T
-   is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
- cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  c
-12 in| 13 in| 14 in| 15 in| 16 in| 17 in| 18 in| 19 in| 20 in| 21 in| 22 in| 23
-    a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-eries|series|series|series|series|series|series|series|series|series|series|ser
-f 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of
------+------+------+------+------+------+------+------+------+------+------+---
-
----+------+------+------+------+------+------+------+------+------+------+-----
-his|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  Thi
- is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    i
-ell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cel
- in| 24 in| 25 in| 26 in| 27 in| 28 in| 29 in| 30 in| 31 in| 32 in| 33 in| 34 i
-  a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-ies|series|series|series|series|series|series|series|series|series|series|serie
-50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50
----+------+------+------+------+------+------+------+------+------+------+-----
-
--+------+------+------+------+------+------+------+------+------+------+------+
-s|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|  This|
-s|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|    is|
-l|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|  cell|
-n| 35 in| 36 in| 37 in| 38 in| 39 in| 40 in| 41 in| 42 in| 43 in| 44 in| 45 in|
-a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|     a|
-s|series|series|series|series|series|series|series|series|series|series|series|
-.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|of 50.|
--+------+------+------+------+------+------+------+------+------+------+------+
-
-+------+------+------+------+------+
-|  This|  This|  This|  This|  This|
-|    is|    is|    is|    is|    is|
-|  cell|  cell|  cell|  cell|  cell|
-| 46 in| 47 in| 48 in| 49 in| 50 in|
-|     a|     a|     a|     a|     a|
-|series|series|series|series|series|
-|of 50.|of 50.|of 50.|of 50.|of 50.|
-+------+------+------+------+------+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking 2 rows of many small cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [2 50
-m4_for([x], [1], [100], [1], [@x
-])])
-AT_CHECK([render-test input], [0], [dnl
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49| 50|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49| 50|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking 3 rows with many joined cells])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [3 49
-m4_foreach([var], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,dnl
-A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W], [var
-])@1
-m4_for([x], [2], [49], [1], [1*2 @x
-])@50
-])
-AT_CHECK([render-test input], [0], [dnl
- a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M
-+-+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-|1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20|
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-| 26| 27| 28| 29| 30| 31| 32| 33| 34| 35| 36| 37| 38| 39| 40| 41| 42| 43| 44| 4
-+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--
-
- N O P Q R S T U V  W
-+---+---+---+---+----+
-| 21| 22| 23| 24|  25|
-+-+-+-+-+-+-+-+-+-+--+
-45| 46| 47| 48| 49|50|
---+---+---+---+---+--+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 input], [0], [dnl
- a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M
-+-+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-|1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20|
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-| 26| 27| 28| 29| 30| 31| 32| 33| 34| 35| 36| 37| 38| 39| 40| 41| 42| 43| 44| 4
-+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--
-
- N O P Q R S T U V  W
-+---+---+---+---+----+
-| 21| 22| 23| 24|  25|
-+-+-+-+-+-+-+-+-+-+--+
-45| 46| 47| 48| 49|50|
---+---+---+---+---+--+
-])
-AT_CLEANUP
-
-AT_SETUP([horz break 6x6, joined rows and columns])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_6X6])
-AT_DATA([expout], [dnl
-+-+--
-|a|bc
-+-+-+
-|j|m|
-|k+-+
-|l|t|
-+-+u|
-|A|v|
-+-+-+
-|IJK|
-+-+-+
-|Q|RS
-+-+--
-
---+-+
-cd|e|
-+-+-+
-|nop|
-+-+-+
-|w|xy
-+-+-+
-|B|E|
-|C+-+
-|D|L|
-+-+M|
-ST|N|
---+-+
-
-+-+-+
-|f|i|
-|g+-+
-|h|q|
-+-+r|
-yz|s|
-+-+-+
-|FGH|
-+-+-+
-|O|P|
-+-+-+
-|U|V|
-+-+-+
-])
-AT_CHECK([render-test --width=6 input], [0], [expout])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=6 input], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([horz break 6x6, joined rows and columns, left header])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_6X6([1 0 0 0])])
-AT_DATA([expout], [dnl
-+-+---+-+
-|a|bcd|e|
-+-+-+-+-+
-|j|m|nop|
-|k+-+-+-+
-|l|t|w|xy
-+-+u+-+-+
-|A|v|B|E|
-+-+-+C+-+
-|K K|D|L|
-+-+-+-+M|
-|Q|RST|N|
-+-+---+-+
-
-+-+-+-+
-|a|f|i|
-+-+g+-+
-|j|h|q|
-|k+-+r|
-|l|z|s|
-+-+-+-+
-|A|FGH|
-+-+-+-+
-|K|O|P|
-+-+-+-+
-|Q|U|V|
-+-+-+-+
-])
-AT_CHECK([render-test --width=10 input], [0], [expout])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=10 input], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([horz break 6x6, joined rows and columns, right header])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_6X6([0 1 0 0])])
-AT_DATA([expout], [dnl
-+-+---+-+
-|a|bcd|i|
-+-+-+-+-+
-|j|m|n|q|
-|k+-+-+r|
-|l|t|w|s|
-+-+u+-+-+
-|A|v|B|H|
-+-+-+C+-+
-|IJK|D|P|
-+-+-+-+-+
-|Q|RST|V|
-+-+---+-+
-
-+-+-+-+
-|e|f|i|
-+-+g+-+
-op|h|q|
-+-+-+r|
-|xyz|s|
-+-+-+-+
-|E|H H|
-+-+-+-+
-|L|O|P|
-|M+-+-+
-|N|U|V|
-+-+-+-+
-])
-AT_CHECK([render-test --width=10 input], [0], [expout])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=10 input], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([breaking joined cells too wide for page])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [4 6
-1*6 @abc def ghi jkl
-1*3 @mno pqr
-1*3 @stu vwx
-1*2 @yzA
-1*2 @BCD
-1*2 @EFG
-@H
-@I
-@J
-@K
-@L
-@M
-])
-AT_CHECK([render-test --width=10 input], [0], [dnl
-+--------
-|abc def
-|
-+-----+--
-|  mno|
-|  pqr|
-+---+-+-+
-|yzA|BCD|
-+-+-+-+-+
-|H|I|J|K|
-+-+-+-+-+
-
-----+
- ghi|
- jkl|
-----+
- stu|
- vwx|
-+---+
-|EFG|
-+-+-+
-|L|M|
-+-+-+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=10 input], [0], [dnl
-+--------
-|abc def
-|
-+-----+--
-|  mno|
-|  pqr|
-+---+-+-+
-|yzA|BCD|
-+-+-+-+-+
-|H|I|J|K|
-+-+-+-+-+
-
-----+
- ghi|
- jkl|
-----+
- stu|
- vwx|
-+---+
-|EFG|
-+-+-+
-|L|M|
-+-+-+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking joined cells much too wide for page])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [4 6
-1*6 @abc def ghi jkl
-1*3 @mno pqr
-1*3 @stu vwx
-1*2 @yzA
-1*2 @BCD
-1*2 @EFG
-@H
-@I
-@J
-@K
-@L
-@M
-])
-AT_CHECK([render-test --width=6 input], [0], [dnl
-+----
-|abc
-|
-+----
-|  mn
-|  pq
-+---+
-|yzA|
-+-+-+
-|H|I|
-+-+-+
-
------
- def
-
---+--
-no|
-qr|
-+-+-+
-|BCD|
-+-+-+
-|J|K|
-+-+-+
-
-----+
- ghi|
- jkl|
-----+
- stu|
- vwx|
-+---+
-|EFG|
-+-+-+
-|L|M|
-+-+-+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=6 input], [0], [dnl
-+----
-|abc
-|
-+----
-|  mn
-|  pq
-+---+
-|yzA|
-+-+-+
-|H|I|
-+-+-+
-
------
- def
-
---+--
-no|
-qr|
-+-+-+
-|BCD|
-+-+-+
-|J|K|
-+-+-+
-
-----+
- ghi|
- jkl|
-----+
- stu|
- vwx|
-+---+
-|EFG|
-+-+-+
-|L|M|
-+-+-+
-])
-AT_CLEANUP
-
-AT_SETUP([breaking cell too wide for page, no border])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 1
-abcdefghijklmnopqrstuvwxyz
-])
-AT_CHECK([render-test --width=6 input], [0], [dnl
-abcdef
-
-ghijkl
-
-mnopqr
-
-stuvwx
-
-yz
-])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=6 input], [0], [dnl
-abcdef
-
-ghijkl
-
-mnopqr
-
-stuvwx
-
-yz
-])
-AT_CLEANUP
-
-AT_SETUP([breaking cell too wide for page, with border])
-AT_KEYWORDS([render rendering])
-AT_CAPTURE_FILE([input])
-AT_DATA([input], [1 1
-@abcdefghijklmnopqrstuvwxyz
-])
-AT_DATA([expout], [dnl
-+-----
-|abcde
-+-----
-
-------
-fghijk
-------
-
-------
-lmnopq
-------
-
-------
-rstuvw
-------
-
----+
-xyz|
----+
-])
-AT_CHECK([render-test --width=6 input], [0], [expout])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=6 input], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([horz break 8x8 with many 2x2 joins])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [WEAVE_8X8_2])
-AT_CHECK([render-test --width=8 input], [0],[dnl
-+---+--
-|abc|jk
-|def|
-|ghi+--
-|   |yz
-+-+-+BC
-|Q|V|EF
-|R| |
-|S+-+-+
-|T|012|
-|U|345|
-| |678|
-| |   |
-+-+-+-+
-|xyz|G|
-|ABC|H|
-|DEF|I|
-|   | |
-+---+-+
-|abcde|
-|     |
-+-----+
-
---+----+
-kl| mno|
-  | pqr|
---+ stu|
-zA|    |
-CD+-+--+
-FG|W| Z|
-  |X|  |
-+-+Y+--+
-|9| | ab
-| | | de
-+-+-+ gh
-|opq|
-|rst+---
-|uvw| JK
-|   |
-+---+--+
-| MNOPQ|
-| RSTUV|
-| WXYZ0|
-|      |
-+------+
-
-+----+
-| vwx|
-|    |
-+----+
-| HIJ|
-| KLM|
-| NOP|
-|    |
-+-+--+
-bc| j|
-ef| k|
-hi| l|
-  | m|
---+ n|
-KL|  |
-  |  |
-+-+--+
-| 123|
-| 456|
-| 789|
-|    |
-+----+
-])
-AT_CHECK([render-test -o mb0 --min-break=0 --width=8 input], [0],[dnl
-+---+--
-|abc|jk
-|def|
-|ghi+--
-|   |yz
-+-+-+BC
-|Q|V|EF
-|R| |
-|S+-+-+
-|T|012|
-|U|345|
-| |678|
-| |   |
-+-+-+-+
-|xyz|G|
-|ABC|H|
-|DEF|I|
-|   | |
-+---+-+
-|abcde|
-|     |
-+-----+
-
---+----+
-kl| mno|
-  | pqr|
---+ stu|
-zA|    |
-CD+-+--+
-FG|W| Z|
-  |X|  |
-+-+Y+--+
-|9| | ab
-| | | de
-+-+-+ gh
-|opq|
-|rst+---
-|uvw| JK
-|   |
-+---+--+
-| MNOPQ|
-| RSTUV|
-| WXYZ0|
-|      |
-+------+
-
-+----+
-| vwx|
-|    |
-+----+
-| HIJ|
-| KLM|
-| NOP|
-|    |
-+-+--+
-bc| j|
-ef| k|
-hi| l|
-  | m|
---+ n|
-KL|  |
-  |  |
-+-+--+
-| 123|
-| 456|
-| 789|
-|    |
-+----+
-])
-AT_CLEANUP
-\f
 AT_BANNER([output rendering -- problematic procedures])
 
 dnl LIST used to put columns right up next to each other without any
@@ -1450,61 +40,3 @@ Data List
 +-+-+-+
 ])
 AT_CLEANUP
-
-# Long string variables tend to end in lots of spaces.  The ASCII
-# driver didn't handle this very well: it would essentially produce
-# one blank line in a cell for each trailing space.  This test
-# checks for regression.  See bug #38672.
-AT_SETUP([ASCII driver renders end of line spaces reasonably])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [dnl
-3 3
-@a
-@b
-@xyzzy                                          @&t@
-@d
-@e
-@f
-@g
-@h
-@i
-])
-AT_CHECK([render-test --width=15 --length=15 input], [0], [dnl
-+-+-+-----+
-|a|b|xyzzy|
-+-+-+-----+
-|d|e|    f|
-+-+-+-----+
-|g|h|    i|
-+-+-+-----+
-])
-AT_CLEANUP
-
-# There was a bug that, when multiple cells spanned a single column
-# (or row), only the dimensions of the cell nearest the bottom of the
-# table were actually considered.  This checks for regression.  (This
-# problem was most easily observed with SYSFILE INFO, which uses lots
-# of spanned cells).
-#
-# Without the fix, the output looks like this:
-# +-------+
-# | A long|
-# |   text|
-# |string.|
-# +-------+
-# |shorter|
-AT_SETUP([multiple spanned cells all contribute to dimensions])
-AT_KEYWORDS([render rendering])
-AT_DATA([input], [dnl
-2 2
-1*2 @A long text string.
-1*2 @shorter
-])
-AT_CHECK([render-test --width=30 --length=15 input], [0], [dnl
-+--------------------+
-| A long text string.|
-+--------------------+
-|             shorter|
-+--------------------+
-])
-AT_CLEANUP
index deb2000432e74e6e3a1c605fa3083866dacd4a14..6e8c3323a868172b2df204580cae90a110cc66cc 100644 (file)
@@ -148,9 +148,7 @@ print_item_directory (const struct spv_item *item)
   if (type == SPV_ITEM_TABLE)
     {
       const struct pivot_table *table = spv_item_get_table (item);
-      char *title = pivot_value_to_string (table->title,
-                                           SETTINGS_VALUE_SHOW_DEFAULT,
-                                           SETTINGS_VALUE_SHOW_DEFAULT);
+      char *title = pivot_value_to_string (table->title, table);
       if (!label || strcmp (title, label))
         printf (" title \"%s\"", title);
       free (title);