output: Render pivot tables monolithically. pivot-outer-borders
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 17 Jan 2023 04:06:16 +0000 (20:06 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 16 Feb 2023 22:59:20 +0000 (14:59 -0800)
This is a step toward supporting outer borders on tables,
which is something that PSPP doesn't do at all now, and
which SPSS does very badly.

doc/dev/spv-file-format.texi
src/output/pivot-output.c
src/output/pivot-output.h
src/output/render.c
src/output/table.h
tests/language/commands/ctables.at
tests/output/pivot-table-test.c

index 523eb556e9e61538120f9cd33d7fb4974e6b7642..dab0cfd36aff8f7c29cb71814d71c5bace07f1ef 100644 (file)
@@ -126,7 +126,10 @@ example, the @code{viewer-tree} schema is associated with namespace
 additional @file{viewer/}).  Under either name, the schema URIs are
 not resolvable to obtain the schemas themselves.
 
-One may ignore all of the above in interpreting a structure member.
+@c XXX The namespaces are important for writing SPV files, so we
+@c should note them.
+
+One may ignore all of the above in interpreting a structure member.p
 The actual XML has a simple and straightforward form that does not
 require a reader to take schemas or namespaces into account.  A
 structure member's root is @code{heading} element, which contains
index 6d5d8b015cdd1b2fcce5c47ac4a166491992fe74..ca01968e8c985b4b1ef4508fcdd9f927602ac1e1 100644 (file)
@@ -112,7 +112,7 @@ table_area_style_override (struct pool *pool,
 }
 
 static void
-fill_cell (struct table *t, int x1, int y1, int x2, int y2,
+fill_cell (struct table *t, int x, int y,
            int style_idx, const struct pivot_value *value,
            bool rotate_label)
 {
@@ -120,14 +120,27 @@ fill_cell (struct table *t, int x1, int y1, int x2, int y2,
   if (rotate_label)
     options |= TABLE_CELL_ROTATE;
 
+  table_put (t, x, y, x, y, options, value);
+}
+
+static void
+fill_cell_spanned (struct table *t, int x1, int y1, int x2, int y2,
+                   int style_idx, const struct pivot_value *value,
+                   bool rotate_label, int options)
+{
+  options |= style_idx << TABLE_CELL_STYLE_SHIFT;
+  if (rotate_label)
+    options |= TABLE_CELL_ROTATE;
+
   table_put (t, x1, y1, x2, y2, options, value);
 }
 
 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)
+                 int style_idx, struct string *s, bool rotate_label,
+                 int options)
 {
-  int options = style_idx << TABLE_CELL_STYLE_SHIFT;
+  options |= style_idx << TABLE_CELL_STYLE_SHIFT;
   if (rotate_label)
     options |= TABLE_CELL_ROTATE;
 
@@ -162,11 +175,12 @@ compose_headings (struct table *t,
                   enum pivot_border cat_col_vert,
                   const size_t *column_enumeration, size_t n_columns,
                   int label_style_idx,
-                  bool rotate_inner_labels, bool rotate_outer_labels)
+                  bool rotate_inner_labels, bool rotate_outer_labels,
+                  int area[TABLE_N_AXES][2])
 {
   const enum table_axis v = !h;
   const int v_size = h_axis->label_depth;
-  const int h_ofs = v_axis->label_depth;
+  const int h_ofs = v_axis->label_depth + area[h][0];
 
   if (!h_axis->n_dimensions || !n_columns || !v_size)
     return;
@@ -176,7 +190,10 @@ compose_headings (struct table *t,
   /* 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
      these (and 'top_row + d->label_depth - 1' is the bottom row). */
-  int top_row = 0;
+  int top_row = area[v][0];
+
+  const int h_max = area[h][1];
+  const int v_max = area[v][1];
 
   /* We're going to iterate through dimensions and the rows that label them
      from top to bottom (from outer to inner dimensions).  As we move downward,
@@ -258,8 +275,6 @@ 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 == v_size;
               if (pivot_category_is_leaf (c) || c->show_label)
                 {
                   int bb[TABLE_N_AXES][2];
@@ -267,10 +282,12 @@ compose_headings (struct table *t,
                   bb[h][1] = x2 + h_ofs - 1;
                   bb[v][0] = y1;
                   bb[v][1] = y2 - 1;
+                  bool is_outer_row = y1 == area[v][0];
+                  bool is_inner_row = y2 == v_size + area[v][0];
                   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_idx, c->name, rotate);
+                  fill_cell_spanned (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
+                                     label_style_idx, c->name, rotate, 0);
 
                   /* Draw all the vertical lines in our running example, other
                      than the far left and far right ones.  Only the ones that
@@ -289,15 +306,15 @@ compose_headings (struct table *t,
                      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
                   */
                   enum pivot_border style
-                    = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
+                    = (y1 == area[v][0] + v_size - 1 ? cat_col_vert : dim_col_vert);
                   if (!vrules[x2])
                     {
-                      draw_line (t, style, v, x2 + h_ofs, y1, t->n[v] - 1);
+                      draw_line (t, style, v, x2 + h_ofs, y1, v_max);
                       vrules[x2] = true;
                     }
                   if (!vrules[x1])
                     {
-                      draw_line (t, style, v, x1 + h_ofs, y1, t->n[v] - 1);
+                      draw_line (t, style, v, x1 + h_ofs, y1, v_max);
                       vrules[x1] = true;
                     }
                 }
@@ -324,15 +341,15 @@ compose_headings (struct table *t,
             }
         }
 
-      if (d->root->show_label_in_corner && h_ofs > 0)
+      if (d->root->show_label_in_corner && h_ofs > area[h][0])
         {
           int bb[TABLE_N_AXES][2];
-          bb[h][0] = 0;
+          bb[h][0] = area[h][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],
-                     PIVOT_AREA_CORNER, d->root->name, false);
+          fill_cell_spanned (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
+                             PIVOT_AREA_CORNER, d->root->name, false, 0);
         }
 
       /* Draw the horizontal line between dimensions, e.g. the ===== line here:
@@ -348,7 +365,7 @@ compose_headings (struct table *t,
          +-----+-----+-----+-----+-----+-----+-----+-----+-----+
       */
       if (dim_index != h_axis->n_dimensions - 1)
-        draw_line (t, dim_col_horz, h, top_row, h_ofs, t->n[h] - 1);
+        draw_line (t, dim_col_horz, h, top_row, h_ofs, h_max);
       top_row += d->label_depth;
     }
   free (vrules);
@@ -492,6 +509,102 @@ resolve_border_style (const struct pivot_table_look *look, enum pivot_border b,
   return style;
 }
 
+static struct table *
+pivot_output_title (const struct pivot_table *pt)
+{
+  if (!pt->title || !pt->show_title)
+    return NULL;
+  struct table *title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
+  fill_cell (title, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
+  return title;
+}
+
+static int
+pivot_count_layers (const struct pivot_table *pt)
+{
+  const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
+  int n_layers = 0;
+  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++;
+    }
+  return n_layers;
+}
+
+static void
+put_layers (struct table *t, const struct pivot_table *pt,
+            const size_t *layer_indexes, int x1, int y1 UNUSED, int x2, int y2)
+{
+  assert (y1 + pivot_count_layers (pt) - 1 == y2);
+  const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
+  for (size_t i = 0; i < layer_axis->n_dimensions; i++)
+    {
+      const struct pivot_dimension *d = layer_axis->dimensions[i];
+      if (!d->n_leaves)
+        continue;
+
+      struct string s = DS_EMPTY_INITIALIZER;
+      pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
+      int y = y2 - i;
+      fill_cell_owned (t, x1, y, x2, y, PIVOT_AREA_LAYERS, &s, false,
+                       TABLE_CELL_FULL_WIDTH);
+    }
+}
+
+static struct table *
+pivot_output_layers (const struct pivot_table *pt, const size_t *layer_indexes)
+{
+  int n_layers = pivot_count_layers (pt);
+  if (!n_layers)
+    return NULL;
+
+  struct table *layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
+  put_layers (layers, pt, layer_indexes, 0, 0, 0, n_layers - 1);
+  return layers;
+}
+
+static struct table *
+pivot_output_caption (const struct pivot_table *pt)
+{
+  if (!pt->caption || !pt->show_caption)
+    return NULL;
+
+  struct table *caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
+  fill_cell (caption, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
+  return caption;
+}
+
+static void
+put_footnotes (struct table *t, const struct pivot_table *pt,
+               struct pivot_footnote **f, size_t nf,
+               int x1, int x2, int y1)
+{
+  for (size_t i = 0; i < nf; i++)
+    {
+      struct string s = DS_EMPTY_INITIALIZER;
+      pivot_footnote_format_marker (f[i], pt, &s);
+      ds_put_cstr (&s, ". ");
+      pivot_value_format (f[i]->content, pt, &s);
+      int y = y1 + i;
+      fill_cell_owned (t, x1, y, x2, y, PIVOT_AREA_FOOTER, &s, false,
+                       TABLE_CELL_FULL_WIDTH);
+    }
+}
+
+static struct table *
+pivot_output_footnotes (const struct pivot_table *pt,
+                        struct pivot_footnote **f, size_t nf)
+{
+  if (!nf)
+    return NULL;
+
+  struct table *footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
+  put_footnotes (footnotes, pt, f, nf, 0, 0, 0);
+  return footnotes;
+}
+
 void
 pivot_output (const struct pivot_table *pt,
               const size_t *layer_indexes,
@@ -516,8 +629,8 @@ pivot_output (const struct pivot_table *pt,
     [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
     [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
   };
-  struct table *body = table_create (data[H] + stub[H],
-                                     data[V] + stub[V],
+
+  struct table *body = table_create (stub[H] + data[H], stub[V] + data[V],
                                      stub[H], stub[V]);
   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
     body->styles[i] = table_area_style_override (
@@ -530,6 +643,10 @@ pivot_output (const struct pivot_table *pt,
     body->borders[i] = resolve_border_style (pt->look, i,
                                              printing && pt->show_grid_lines);
 
+  int body_area[TABLE_N_AXES][2] = {
+    [H] = { 0, body->n[H] - 1 },
+    [V] = { 0, body->n[V] - 1 },
+  };
   compose_headings (body,
                     &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
                     PIVOT_BORDER_DIM_COL_HORZ,
@@ -538,7 +655,7 @@ pivot_output (const struct pivot_table *pt,
                     PIVOT_BORDER_CAT_COL_VERT,
                     column_enumeration, data[H],
                     PIVOT_AREA_COLUMN_LABELS,
-                    pt->rotate_outer_row_labels, false);
+                    pt->rotate_outer_row_labels, false, body_area);
 
   compose_headings (body,
                     &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
@@ -548,7 +665,7 @@ pivot_output (const struct pivot_table *pt,
                     PIVOT_BORDER_CAT_ROW_HORZ,
                     row_enumeration, data[V],
                     PIVOT_AREA_ROW_LABELS,
-                    false, pt->rotate_inner_column_labels);
+                    false, pt->rotate_inner_column_labels, body_area);
 
   size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
   size_t y = 0;
@@ -562,7 +679,7 @@ pivot_output (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 (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
+          fill_cell (body, x + stub[H], y + stub[V],
                      PIVOT_AREA_DATA, value, false);
 
           x++;
@@ -574,115 +691,278 @@ pivot_output (const struct pivot_table *pt,
 
   if ((pt->corner_text || !pt->look->row_labels_in_corner)
       && stub[H] && stub[V])
-    fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
-               PIVOT_AREA_CORNER, pt->corner_text, false);
+    fill_cell_spanned (body, 0, 0, stub[H] - 1, stub[V] - 1,
+                       PIVOT_AREA_CORNER, pt->corner_text, false,
+                       TABLE_CELL_FULL_WIDTH);
 
   if (body->n[H] && body->n[V])
     {
-      table_hline (body, PIVOT_BORDER_INNER_TOP, 0, body->n[H] - 1, 0);
-      table_hline (body, PIVOT_BORDER_INNER_BOTTOM, 0, body->n[H] - 1,
-                   body->n[V]);
-      table_vline (body, PIVOT_BORDER_INNER_LEFT, 0, 0, body->n[V] - 1);
-      table_vline (body, PIVOT_BORDER_INNER_RIGHT, body->n[H], 0,
-                   body->n[V] - 1);
+      int inner[TABLE_N_AXES][2] = {
+        [H] = { 0, body->n[H] },
+        [V] = { 0, body->n[V] },
+      };
+      table_hline (body, PIVOT_BORDER_INNER_TOP,
+                   inner[H][0], inner[H][1] - 1, inner[V][0]);
+      table_hline (body, PIVOT_BORDER_INNER_BOTTOM,
+                   inner[H][0], inner[H][1] - 1, inner[V][1]);
+      table_vline (body, PIVOT_BORDER_INNER_LEFT,
+                   inner[H][0], inner[V][0], inner[V][1] - 1);
+      table_vline (body, PIVOT_BORDER_INNER_LEFT,
+                   inner[H][1], inner[V][0], inner[V][1] - 1);
 
       if (stub[V])
-        table_hline (body, PIVOT_BORDER_DATA_TOP, 0, body->n[H] - 1, stub[V]);
+        table_hline (body, PIVOT_BORDER_DATA_TOP,
+                     inner[H][0], inner[H][1] - 1, stub[V]);
       if (stub[H])
-        table_vline (body, PIVOT_BORDER_DATA_LEFT, stub[H], 0, body->n[V] - 1);
-
+        table_vline (body, PIVOT_BORDER_DATA_LEFT,
+                     stub[H], inner[V][0], inner[V][1] - 1);
     }
+
   free (column_enumeration);
   free (row_enumeration);
 
-  /* Title. */
-  struct table *title;
-  if (pt->title && pt->show_title && titlep)
-    {
-      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];
-  int n_layers = 0;
+  *bodyp = body;
+  if (titlep)
+    *titlep = pivot_output_title (pt);
   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++;
-      }
+    *layersp = pivot_output_layers (pt, layer_indexes);
+  if (captionp)
+    *captionp = pivot_output_caption (pt);
 
-  struct table *layers;
-  if (n_layers > 0)
+  if (fp || footnotesp)
     {
-      layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
-      size_t y = n_layers - 1;
-      for (size_t i = 0; i < layer_axis->n_dimensions; i++)
+      size_t nf;
+      struct pivot_footnote **f = collect_footnotes (
+        pt, titlep ? *titlep : NULL, layersp ? *layersp : NULL, body,
+        captionp ? *captionp : NULL, &nf);
+
+      if (footnotesp)
+        *footnotesp = pivot_output_footnotes (pt, f, nf);
+
+      if (fp)
         {
-          const struct pivot_dimension *d = layer_axis->dimensions[i];
-          if (!d->n_leaves)
-            continue;
-
-          struct string s = DS_EMPTY_INITIALIZER;
-          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--;
+          *fp = f;
+          *nfp = nf;
         }
+      else
+        free (f);
     }
-  else
-    layers = NULL;
+}
+
+struct table *
+pivot_output_monolithic (const struct pivot_table *pt,
+                         const size_t *layer_indexes,
+                         bool printing)
+{
+  /* Table structure:
+
+     #============ Frame ============#
+     # Title                         #
+     # Layers                        #
+     # +--- Body ------------------+ #
+     # | Stub                      | #
+     # |      +--------------------+ #
+     # |      | Data               | #
+     # |      |                    | #
+     # |      |                    | #
+     # |      |                    | #
+     # +------+--------------------+ #
+     # Caption                       #
+     # Footnotes                     #
+     #===============================#
+
+     The regions are the following size:
+
+     - Frame: Either 0 or 1 row or column wide, depending on whether the table
+       style has an outer border on each side.  The frame cells are always
+       empty and marked as TABLE_CELL_PADDING.
+
+     - Title: Either 0 or 1 row high.
+
+     - Layers: Usually, one row for each layer dimension, but less if a layer
+       dimension is empty (has no value).
+
+     - Body and data: Variable (and can be empty).
+
+     - Caption: Either 0 or 1 row high.
+
+     - Footnotes: From zero rows up to the number of footnotes in PT (only
+       footnotes referenced in the other regions are shown).
 
-  /* Caption. */
-  struct table *caption;
-  if (pt->caption && pt->show_caption && captionp)
+     The title, layers, caption, and footnote Region join all the body's
+     columns into single cells.
+ */
+
+  /* Size of data region. */
+  size_t data[TABLE_N_AXES];
+  size_t *column_enumeration = pivot_table_enumerate_axis (
+    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, &data[V]);
+
+  /* Size of stub region. */
+  int stub[TABLE_N_AXES] = {
+    [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
+    [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
+  };
+
+  int n_title = pt->title && pt->show_title;
+  int n_layers = pivot_count_layers (pt);
+  int n_caption = pt->caption && pt->show_caption;
+  int max_footnotes = pt->n_footnotes;
+
+  int min_body_width = n_title || n_layers || n_caption;
+  int n[TABLE_N_AXES] = {
+    [H] = MAX (min_body_width, stub[H] + data[H]),
+    [V] = n_title + n_layers + stub[V] + data[V] + n_caption + max_footnotes,
+  };
+
+  struct table *t = table_create (n[H], n[V],
+                                  stub[H], n_title + n_layers + stub[V]);
+  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+    t->styles[i] = table_area_style_override (
+      t->container, &pt->look->areas[i], NULL, NULL, false);
+
+  t->n_borders = PIVOT_N_BORDERS;
+  t->borders = pool_nmalloc (t->container, PIVOT_N_BORDERS, sizeof *t->borders);
+  for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
+    t->borders[i] = resolve_border_style (pt->look, i,
+                                          printing && pt->show_grid_lines);
+
+  int body[TABLE_N_AXES][2] = {
+    [H] = { 0, n[H] - 1 },
+    [V] = {
+      n_title + n_layers,
+      n_title + n_layers + stub[V] + data[V] - 1
+    },
+  };
+
+  if (n_layers)
+    put_layers (t, pt, layer_indexes,
+                body[H][0], n_title,
+                body[H][1], n_title + n_layers - 1);
+
+  compose_headings (t,
+                    &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
+                    PIVOT_BORDER_DIM_COL_HORZ,
+                    PIVOT_BORDER_DIM_COL_VERT,
+                    PIVOT_BORDER_CAT_COL_HORZ,
+                    PIVOT_BORDER_CAT_COL_VERT,
+                    column_enumeration, data[H],
+                    PIVOT_AREA_COLUMN_LABELS,
+                    pt->rotate_outer_row_labels, false, body);
+
+  compose_headings (t,
+                    &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
+                    PIVOT_BORDER_DIM_ROW_VERT,
+                    PIVOT_BORDER_DIM_ROW_HORZ,
+                    PIVOT_BORDER_CAT_ROW_VERT,
+                    PIVOT_BORDER_CAT_ROW_HORZ,
+                    row_enumeration, data[V],
+                    PIVOT_AREA_ROW_LABELS,
+                    false, pt->rotate_inner_column_labels, body);
+
+  int data_ofs[TABLE_N_AXES] = {
+    [H] = body[H][0] + stub[H],
+    [V] = body[V][0] + stub[V],
+  };
+
+  const size_t *pindexes[PIVOT_N_AXES] = { [PIVOT_AXIS_LAYER] = layer_indexes };
+  size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
+  int y = data_ofs[V];
+  PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
+                              &pt->axes[PIVOT_AXIS_ROW])
     {
-      caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
-      fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
+      int x = data_ofs[H];
+      PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
+                                  column_enumeration,
+                                  &pt->axes[PIVOT_AXIS_COLUMN])
+        {
+          pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
+          const struct pivot_value *value = pivot_table_get (pt, dindexes);
+          fill_cell (t, x, y, PIVOT_AREA_DATA, value, false);
+          x++;
+        }
+
+      y++;
     }
-  else
-    caption = NULL;
+  free (dindexes);
 
-  /* Footnotes. */
-  size_t nf;
-  struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
-                                                 caption, &nf);
-  struct table *footnotes;
-  if (nf && footnotesp)
+  if ((pt->corner_text || !pt->look->row_labels_in_corner)
+      && stub[H] && stub[V])
+    fill_cell_spanned (t, body[H][0], body[V][0],
+                       body[H][0] + stub[H] - 1, body[V][0] + stub[V] - 1,
+                       PIVOT_AREA_CORNER, pt->corner_text, false, 0);
+
+  if (body[H][1] >= body[H][0] && body[V][1] >= body[V][0])
     {
-      footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
+      table_hline (t, PIVOT_BORDER_INNER_TOP,
+                   body[H][0], body[H][1], body[V][0]);
+      table_hline (t, PIVOT_BORDER_INNER_BOTTOM,
+                   body[H][0], body[H][1], body[V][1] + 1);
+      table_vline (t, PIVOT_BORDER_INNER_LEFT,
+                   body[H][0], body[V][0], body[V][1]);
+      table_vline (t, PIVOT_BORDER_INNER_RIGHT,
+                   body[H][1] + 1, body[V][0], body[V][1]);
 
-      for (size_t i = 0; i < nf; i++)
-        {
-          struct string s = DS_EMPTY_INITIALIZER;
-          pivot_footnote_format_marker (f[i], 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);
-        }
+      if (stub[V])
+        table_hline (t, PIVOT_BORDER_DATA_TOP,
+                     body[H][0], body[H][1], data_ofs[V]);
+      if (stub[H])
+        table_vline (t, PIVOT_BORDER_DATA_LEFT,
+                     data_ofs[H], body[V][0], body[V][1]);
     }
-  else
-    footnotes = NULL;
 
-  *titlep = title;
-  if (layersp)
-    *layersp = layers;
-  *bodyp = body;
-  if (captionp)
-    *captionp = caption;
-  if (footnotesp)
-    *footnotesp = footnotes;
-  if (fp)
+  if (n_caption)
+    fill_cell_spanned (t,
+                       body[H][0], body[V][1] + 1,
+                       body[H][1], body[V][1] + 1,
+                       PIVOT_AREA_CAPTION, pt->caption, false,
+                       TABLE_CELL_FULL_WIDTH);
+
+  size_t nf;
+  struct pivot_footnote **f = collect_footnotes (pt, t, NULL, NULL, NULL, &nf);
+  assert (nf <= max_footnotes);
+  put_footnotes (t, pt, f, nf, body[H][0], body[H][1],
+                 body[V][1] + 1 + n_caption);
+  t->n[V] = body[V][1] + 1 + n_caption + nf;
+  free (f);
+
+  if (n_title)
     {
-      *fp = f;
-      *nfp = nf;
+#if 0
+      printf ("%d,%d - %d,%d\n", 
+              body[H][0], 0,
+              body[H][1], 0);
+#endif
+    fill_cell_spanned (t,
+                       body[H][0], 0,
+                       body[H][1], 0,
+                       PIVOT_AREA_TITLE, pt->title, false,
+                       TABLE_CELL_FULL_WIDTH);
     }
-  else
-    free (f);
+
+  free (column_enumeration);
+  free (row_enumeration);
+
+#if 0
+  printf ("output page:\n");
+  for (int y = 0; y < t->n[V]; y++)
+    for (int x = 0; x < t->n[H]; x++)
+      {
+        struct table_cell cell;
+
+        table_get_cell (t, x, y, &cell);
+        char *s = pivot_value_to_string (cell.value, NULL);
+        printf ("%d,%d - %d,%d: %s\n",
+                cell.d[H][0], cell.d[V][0],
+                cell.d[H][1], cell.d[V][1],
+                s);
+        free (s);
+      }
+#endif
+
+  return t;
 }
 
 void
index 4013c1bc4543283eecba6532e9d78a8699e2ed4f..6620cf7618fea616e86214c11e608d0fb0d9c46b 100644 (file)
@@ -40,4 +40,8 @@ void pivot_output (const struct pivot_table *,
                    struct table **footnotesp,
                    struct pivot_footnote ***fp, size_t *nfp);
 
+struct table *pivot_output_monolithic (const struct pivot_table *,
+                                       const size_t *layer_indexes,
+                                       bool printing);
+
 #endif /* output/pivot-output.h */
index fbf055029a6f2e24686c9c6b96aca52b0c0f5448..a7dcfa6856c739ca3f04f5467bb80a838feabb90 100644 (file)
@@ -160,7 +160,7 @@ struct render_page
   };
 
 static struct render_page *render_page_create (const struct render_params *,
-                                               struct table *, int min_width,
+                                               struct table *,
                                                const struct pivot_table_look *);
 
 struct render_page *render_page_ref (const struct render_page *page_);
@@ -510,25 +510,27 @@ measure_rule (const struct render_params *params, const struct table *table,
 }
 
 /* Allocates and returns a new render_page using PARAMS and TABLE.  Allocates
-   space for rendering a table with dimensions given in N.  The caller must
-   initialize most of the members itself. */
+   space for rendering a table with dimensions given in N, headers in H, and
+   content in R.  The caller must initialize most of the members itself. */
 static struct render_page *
 render_page_allocate__ (const struct render_params *params,
-                        struct table *table, int n[TABLE_N_AXES])
+                        struct table *table,
+                        const int n[TABLE_N_AXES],
+                        const int h[TABLE_N_AXES],
+                        const int r[TABLE_N_AXES][2])
 {
   struct render_page *page = xmalloc (sizeof *page);
-  page->params = params;
-  page->table = table;
-  page->ref_cnt = 1;
-  page->n[H] = n[H];
-  page->n[V] = n[V];
-
-  for (int i = 0; i < TABLE_N_AXES; i++)
-    page->cp[i] = xcalloc ((2 * n[i] + 2) , sizeof *page->cp[i]);
-
-  hmap_init (&page->overflows);
-  memset (page->is_edge_cutoff, 0, sizeof page->is_edge_cutoff);
-
+  *page = (struct render_page) {
+    .params = params,
+    .table = table,
+    .ref_cnt = 1,
+    .n = { [H] = n[H], [V] = n[V] },
+    .h = { [H] = h[H], [V] = h[V] },
+    .r = { [H] = { r[H][0], r[H][1] }, [V] = { r[V][0], r[V][1] } },
+    .cp = { [H] = xcalloc (2 * n[H] + 2, sizeof *page->cp[H]),
+            [V] = xcalloc (2 * n[V] + 2, sizeof *page->cp[V]) },
+    .overflows = HMAP_INITIALIZER (page->overflows),
+  };
   return page;
 }
 
@@ -538,14 +540,15 @@ render_page_allocate__ (const struct render_params *params,
 static struct render_page *
 render_page_allocate (const struct render_params *params, struct table *table)
 {
-  struct render_page *page = render_page_allocate__ (params, table, table->n);
+  int h[TABLE_N_AXES];
+  int r[TABLE_N_AXES][2];
   for (enum table_axis a = 0; a < TABLE_N_AXES; a++)
     {
-      page->h[a] = table->h[a];
-      page->r[a][0] = table->h[a];
-      page->r[a][1] = table->n[a];
+      h[a] = table->h[a];
+      r[a][0] = table->h[a];
+      r[a][1] = table->n[a];
     }
-  return page;
+  return render_page_allocate__ (params, table, table->n, h, r);
 }
 
 /* Allocates and returns a new render_page for PARAMS and TABLE, initializing
@@ -668,6 +671,12 @@ render_get_cell (const struct render_page *page, int x, int y,
       cell->d[a][0] = MAX (cell->d[a][0], m->p0);
       cell->d[a][1] = MIN (cell->d[a][1], m->p0 + m->n);
     }
+
+  if (cell->options & TABLE_CELL_FULL_WIDTH)
+    {
+      cell->d[H][0] = 0;
+      cell->d[H][1] = page->n[H];
+    }
 }
 
 /* Creates and returns a new render_page for rendering TABLE with the given
@@ -678,7 +687,7 @@ render_get_cell (const struct render_page *page, int x, int y,
    up to fit on such a device, using the render_break abstraction.  */
 static struct render_page *
 render_page_create (const struct render_params *params, struct table *table,
-                    int min_width, const struct pivot_table_look *look)
+                    const struct pivot_table_look *look)
 {
   enum { MIN, MAX };
 
@@ -714,40 +723,44 @@ render_page_create (const struct render_params *params, struct table *table,
         struct table_cell cell;
 
         table_get_cell (table, x, y, &cell);
-        if (y == cell.d[V][0])
+        if (y == cell.d[V][0]
+            && table_cell_colspan (&cell) == 1)
           {
-            if (table_cell_colspan (&cell) == 1)
+            int w[2];
+            params->ops->measure_cell_width (params->aux, &cell,
+                                             &w[MIN], &w[MAX]);
+
+            if (cell.options & TABLE_CELL_FULL_WIDTH)
               {
-                int w[2];
-                params->ops->measure_cell_width (params->aux, &cell,
-                                                 &w[MIN], &w[MAX]);
+                int rule_width = rules[H][0] + rules[H][table->n[H]];
+                w[MIN] = w[MAX] = MAX (0, w[MIN] - rule_width);
+              }
 
-                if (params->px_size)
+            if (params->px_size)
+              {
+                const int *wr = (x < table->h[H] ? row_heading_width_range
+                                 : y < table->h[V] ? col_heading_width_range
+                                 : NULL);
+                if (wr)
                   {
-                    const int *wr = (x < table->h[H] ? row_heading_width_range
-                                     : y < table->h[V] ? col_heading_width_range
-                                     : NULL);
-                    if (wr)
+                    if (w[0] < wr[0])
                       {
-                        if (w[0] < wr[0])
-                          {
-                            w[0] = wr[0];
-                            if (w[0] > w[1])
-                              w[1] = w[0];
-                          }
-                        else if (w[1] > wr[1])
-                          {
-                            w[1] = wr[1];
-                            if (w[1] < w[0])
-                              w[0] = w[1];
-                          }
+                        w[0] = wr[0];
+                        if (w[0] > w[1])
+                          w[1] = w[0];
+                      }
+                    else if (w[1] > wr[1])
+                      {
+                        w[1] = wr[1];
+                        if (w[1] < w[0])
+                          w[0] = w[1];
                       }
                   }
-
-                for (int i = 0; i < 2; i++)
-                  if (columns[i][x].unspanned < w[i])
-                    columns[i][x].unspanned = w[i];
               }
+
+            for (int i = 0; i < 2; i++)
+              if (columns[i][x].unspanned < w[i])
+                columns[i][x].unspanned = w[i];
           }
         x = cell.d[H][1];
       }
@@ -762,12 +775,19 @@ render_page_create (const struct render_params *params, struct table *table,
         struct table_cell cell;
 
         table_get_cell (table, x, y, &cell);
-        if (y == cell.d[V][0] && table_cell_colspan (&cell) > 1)
+        if (y == cell.d[V][0]
+            && table_cell_colspan (&cell) > 1
+            && !(cell.options & TABLE_CELL_FULL_WIDTH))
           {
             int w[2];
 
             params->ops->measure_cell_width (params->aux, &cell,
                                              &w[MIN], &w[MAX]);
+            if (cell.options & TABLE_CELL_FULL_WIDTH)
+              {
+                int rule_width = rules[H][0] + rules[H][table->n[H]];
+                w[MIN] = w[MAX] = MAX (0, w[MIN] - rule_width);
+              }
             for (int i = 0; i < 2; i++)
               distribute_spanned_width (w[i],
                                         &columns[i][cell.d[H][0]],
@@ -776,9 +796,6 @@ render_page_create (const struct render_params *params, struct table *table,
           }
         x = cell.d[H][1];
       }
-  if (min_width > 0)
-    for (int i = 0; i < 2; i++)
-      distribute_spanned_width (min_width, &columns[i][0], rules[H], nc);
 
   /* In pathological cases, spans can cause the minimum width of a column to
      exceed the maximum width.  This bollixes our interpolation algorithm
@@ -827,6 +844,8 @@ render_page_create (const struct render_params *params, struct table *table,
         if (y == cell.d[V][0] && table_cell_rowspan (&cell) == 1)
           {
             int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
+            if (cell.options & TABLE_CELL_FULL_WIDTH)
+              w += rules[H][0] + rules[H][table->n[H]];
             int h = params->ops->measure_cell_height (params->aux,
                                                       &cell, w);
             if (h > r->unspanned)
@@ -847,6 +866,8 @@ render_page_create (const struct render_params *params, struct table *table,
         if (y == cell.d[V][0] && table_cell_rowspan (&cell) > 1)
           {
             int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
+            if (cell.options & TABLE_CELL_FULL_WIDTH)
+              w += rules[H][0] + rules[H][table->n[H]];
             int h = params->ops->measure_cell_height (params->aux, &cell, w);
             distribute_spanned_width (h,
                                       &rows[cell.d[V][0]],
@@ -1078,18 +1099,30 @@ render_cell (const struct render_page *page, const int ofs[TABLE_N_AXES],
     }
 
   int bb[TABLE_N_AXES][2];
-  int clip[TABLE_N_AXES][2];
 
-  bb[H][0] = clip[H][0] = ofs[H] + page->cp[H][cell->d[H][0] * 2 + 1];
-  bb[H][1] = clip[H][1] = ofs[H] + page->cp[H][cell->d[H][1] * 2];
-  if (page->params->rtl)
+  if (!(cell->options & TABLE_CELL_FULL_WIDTH))
     {
-      int temp = bb[H][0];
-      bb[H][0] = clip[H][0] = render_page_get_size (page, H) - bb[H][1];
-      bb[H][1] = clip[H][1] = render_page_get_size (page, H) - temp;
+      bb[H][0] = ofs[H] + page->cp[H][cell->d[H][0] * 2 + 1];
+      bb[H][1] = ofs[H] + page->cp[H][cell->d[H][1] * 2];
+      if (page->params->rtl)
+        {
+          int temp = bb[H][0];
+          bb[H][0] = render_page_get_size (page, H) - bb[H][1];
+          bb[H][1] = render_page_get_size (page, H) - temp;
+        }
     }
-  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];
+  else
+    {
+      bb[H][0] = ofs[H];
+      bb[H][1] = ofs[H] + page->cp[H][cell->d[H][1] * 2 + 1];
+    }
+  bb[V][0] = ofs[V] + page->cp[V][cell->d[V][0] * 2 + 1];
+  bb[V][1] = ofs[V] + page->cp[V][cell->d[V][1] * 2];
+
+  int clip[TABLE_N_AXES][2] = {
+    [H] = { bb[H][0], bb[H][1] },
+    [V] = { bb[V][0], bb[V][1] },
+  };
 
   enum table_valign valign = cell->cell_style->valign;
   int valign_offset = 0;
@@ -1474,31 +1507,16 @@ struct render_pager
     const struct render_params *params;
     double scale;
 
-    /* An array of "render_page"s to be rendered, in order, vertically.  There
-       may be up to 5 pages, for the pivot table's title, layers, body,
-       captions, and footnotes. */
-    struct render_page *pages[5];
-    size_t n_pages;
+    struct render_page *page;
 
-    size_t cur_page;
     struct render_break x_break;
     struct render_break y_break;
   };
 
-static void
-render_pager_add_table (struct render_pager *p, struct table *table,
-                        int min_width, const struct pivot_table_look *look)
-{
-  if (table)
-    p->pages[p->n_pages++] = render_page_create (p->params, table, min_width,
-                                                 look);
-}
-
 static void
 render_pager_start_page (struct render_pager *p)
 {
-  render_break_init (&p->x_break, render_page_ref (p->pages[p->cur_page++]),
-                     H);
+  render_break_init (&p->x_break, render_page_ref (p->page), H);
   render_break_init_empty (&p->y_break);
 }
 
@@ -1512,26 +1530,24 @@ render_pager_create (const struct render_params *params,
   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);
+  struct table *table = pivot_output_monolithic (pt, layer_indexes,
+                                                 params->printing);
 
-  /* Figure out the width of the body of the table.  Use this to determine the
-     base scale. */
-  struct render_page *body_page = render_page_create (params, body, 0, pt->look);
-  int body_width = table_width (body_page, H);
+  /* Measure the table width and use it to determine the base scale. */
+  struct render_page *page = render_page_create (params, table, pt->look);
+  int width = table_width (page, H);
   double scale = 1.0;
-  if (body_width > params->size[H])
+  if (width > params->size[H])
     {
       if (pt->look->shrink_to_fit[H] && params->ops->scale)
-        scale = params->size[H] / (double) body_width;
+        scale = params->size[H] / (double) width;
       else
         {
           struct render_break b;
-          render_break_init (&b, render_page_ref (body_page), H);
+          render_break_init (&b, render_page_ref (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;
+          width = subpage ? subpage->cp[H][2 * subpage->n[H] + 1] : 0;
           render_page_unref (subpage);
           render_break_destroy (&b);
         }
@@ -1539,13 +1555,7 @@ 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 };
-  render_pager_add_table (p, title, body_width, pt->look);
-  render_pager_add_table (p, layers, body_width, pt->look);
-  p->pages[p->n_pages++] = body_page;
-  render_pager_add_table (p, caption, 0, pt->look);
-  render_pager_add_table (p, footnotes, 0, pt->look);
-  assert (p->n_pages <= sizeof p->pages / sizeof *p->pages);
+  *p = (struct render_pager) { .params = params, .scale = scale, .page = page };
 
   /* If we're shrinking tables to fit the page length, then adjust the scale
      factor.
@@ -1557,11 +1567,9 @@ render_pager_create (const struct render_params *params,
      necessary would require an iterative search. */
   if (pt->look->shrink_to_fit[V] && params->ops->scale)
     {
-      int total_height = 0;
-      for (size_t i = 0; i < p->n_pages; i++)
-        total_height += table_width (p->pages[i], V);
-      if (total_height * p->scale >= params->size[V])
-        p->scale *= params->size[V] / (double) total_height;
+      double height = table_width (p->page, V);
+      if (height * p->scale >= params->size[V])
+        p->scale *= params->size[V] / height;
     }
 
   render_pager_start_page (p);
@@ -1577,8 +1585,7 @@ render_pager_destroy (struct render_pager *p)
     {
       render_break_destroy (&p->x_break);
       render_break_destroy (&p->y_break);
-      for (size_t i = 0; i < p->n_pages; i++)
-        render_page_unref (p->pages[i]);
+      render_page_unref (p->page);
       free (p);
     }
 }
@@ -1596,13 +1603,9 @@ render_pager_has_next (const struct render_pager *p_)
       if (!render_break_has_next (&p->x_break))
         {
           render_break_destroy (&p->x_break);
-          if (p->cur_page >= p->n_pages)
-            {
-              render_break_init_empty (&p->x_break);
-              render_break_init_empty (&p->y_break);
-              return false;
-            }
-          render_pager_start_page (p);
+          render_break_init_empty (&p->x_break);
+          render_break_init_empty (&p->y_break);
+          return false;
         }
       else
         render_break_init (
@@ -1628,22 +1631,17 @@ render_pager_draw_next (struct render_pager *p, int space)
     }
 
   int ofs[TABLE_N_AXES] = { 0, 0 };
-  size_t start_page = SIZE_MAX;
 
-  while (render_pager_has_next (p))
+  if (render_pager_has_next (p))
     {
-      if (start_page == p->cur_page)
-        break;
-      start_page = p->cur_page;
-
       struct render_page *page
         = render_break_next (&p->y_break, space - ofs[V]);
-      if (!page)
-        break;
-
-      render_page_draw (page, ofs);
-      ofs[V] += render_page_get_size (page, V);
-      render_page_unref (page);
+      if (page)
+        {
+          render_page_draw (page, ofs);
+          ofs[V] += render_page_get_size (page, V);
+          render_page_unref (page);
+        }
     }
 
   if (p->scale != 1.0)
@@ -1671,18 +1669,14 @@ render_pager_draw_region (const struct render_pager *p,
 
   clip[H][0] = x;
   clip[H][1] = x + w;
-  for (size_t i = 0; i < p->n_pages; i++)
-    {
-      const struct render_page *page = p->pages[i];
-      int size = render_page_get_size (page, V);
+  int size = render_page_get_size (p->page, V);
 
-      clip[V][0] = MAX (y, ofs[V]) - ofs[V];
-      clip[V][1] = MIN (y + h, ofs[V] + size) - ofs[V];
-      if (clip[V][1] > clip[V][0])
-        render_page_draw_region (page, ofs, clip);
+  clip[V][0] = MAX (y, ofs[V]) - ofs[V];
+  clip[V][1] = MIN (y + h, ofs[V] + size) - ofs[V];
+  if (clip[V][1] > clip[V][0])
+    render_page_draw_region (p->page, ofs, clip);
 
-      ofs[V] += size;
-    }
+  ofs[V] += size;
 }
 
 /* Returns the size of P's content along AXIS; i.e. the content's width if AXIS
@@ -1690,32 +1684,16 @@ render_pager_draw_region (const struct render_pager *p,
 int
 render_pager_get_size (const struct render_pager *p, enum table_axis axis)
 {
-  int size = 0;
-
-  for (size_t i = 0; i < p->n_pages; i++)
-    {
-      int subsize = render_page_get_size (p->pages[i], axis);
-      size = axis == H ? MAX (size, subsize) : size + subsize;
-    }
-
-  return size;
+  return render_page_get_size (p->page, axis);
 }
 
 int
 render_pager_get_best_breakpoint (const struct render_pager *p, int height)
 {
-  int y = 0;
-  size_t i;
-
-  for (i = 0; i < p->n_pages; i++)
-    {
-      int size = render_page_get_size (p->pages[i], V);
-      if (y + size >= height)
-        return render_page_get_best_breakpoint (p->pages[i], height - y) + y;
-      y += size;
-    }
-
-  return height;
+  int size = render_page_get_size (p->page, V);
+  return (size < height
+          ? height
+          : render_page_get_best_breakpoint (p->page, height));
 }
 \f
 /* render_page_select() and helpers. */
@@ -1772,18 +1750,21 @@ render_page_select (const struct render_page *page, enum table_axis axis,
 
   /* Allocate subpage. */
   int trim[2] = { z0 - page->h[a], page->n[a] - z1 };
+
   int n[TABLE_N_AXES] = { [H] = page->n[H], [V] = page->n[V] };
   n[a] -= trim[0] + trim[1];
-  struct render_page *subpage = render_page_allocate__ (
-    page->params, table_ref (page->table), n);
+
+  int r[TABLE_N_AXES][2];
   for (enum table_axis k = 0; k < TABLE_N_AXES; k++)
     {
-      subpage->h[k] = page->h[k];
-      subpage->r[k][0] = page->r[k][0];
-      subpage->r[k][1] = page->r[k][1];
+      r[k][0] = page->r[k][0];
+      r[k][1] = page->r[k][1];
     }
-  subpage->r[a][0] += trim[0];
-  subpage->r[a][1] -= trim[1];
+  r[a][0] += trim[0];
+  r[a][1] -= trim[1];
+
+  struct render_page *subpage = render_page_allocate__ (
+    page->params, table_ref (page->table), n, page->h, r);
 
   /* An edge is cut off if it was cut off in PAGE or if we're trimming pixels
      off that side of the page and there are no headers. */
index 42bdf299e73605efe37d4f8a253ee4839db8ebb4..032ed5ba08051612672d8ab2ea3ca4021b967fb5 100644 (file)
@@ -183,7 +183,8 @@ enum
   {
     TABLE_CELL_ROTATE     = 1 << 0,    /* Rotate cell contents 90 degrees. */
     TABLE_CELL_JOIN       = 1 << 1,    /* Joined cell (internal use only). */
-    TABLE_CELL_STYLE_SHIFT = 2,
+    TABLE_CELL_FULL_WIDTH = 1 << 2,    /* Spans full width, ignoring headers. */
+    TABLE_CELL_STYLE_SHIFT = 3,
     TABLE_CELL_STYLE_MASK = 7 << TABLE_CELL_STYLE_SHIFT,
   };
 
index 22e1380a58582c74d286e1a09dffcfb659ef9ab0..27656e26e8f7506bcfe5a08eabd01d8c29cf9fa7 100644 (file)
@@ -2006,7 +2006,7 @@ CTABLES
     /TABLE=qn105ba [COUNT, ROWPCT] BY qns1
     /CATEGORIES VARIABLES=qns1 [1, 2, SUBTOTAL, 3, 4, 5, &x, &y, SUBTOTAL] TOTAL=YES
 ]])
-AT_CHECK([pspp ctables.sps -O box=unicode -O width=140], [0], [dnl
+AT_CHECK([pspp ctables.sps -o - -O box=unicode -O width=140 -o ctables.pdf -o ctables.spv], [0], [dnl
                                                                 Custom Tables
 ╭───────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │                   │                    S1. Including yourself, how many members of this household are age 16 or older?                   │
index 8bed7550ebb0cc3d7143d04b8b2ac6e7a1d1875f..2d02ed71acb6f87303fbbd1a9a2871984cb82b8d 100644 (file)
@@ -127,7 +127,6 @@ configure_drivers (int width, int length UNUSED, int min_break)
     string_map_insert (&options, "box", box);
   register_driver (&options, "-");
 
-
   /* Render to <base>.pdf. */
   string_map_insert (&options, "top-margin", "0");
   string_map_insert (&options, "bottom-margin", "0");
@@ -154,7 +153,6 @@ configure_drivers (int width, int length UNUSED, int min_break)
   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);
 }