Add support for reading and writing SPV files.
[pspp] / src / output / spv / spv-light-decoder.c
diff --git a/src/output/spv/spv-light-decoder.c b/src/output/spv/spv-light-decoder.c
new file mode 100644 (file)
index 0000000..f8b07d0
--- /dev/null
@@ -0,0 +1,856 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2017, 2018 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 "output/spv/spv-light-decoder.h"
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "output/pivot-table.h"
+#include "output/spv/light-binary-parser.h"
+#include "output/spv/spv.h"
+
+#include "gl/xalloc.h"
+#include "gl/xsize.h"
+
+static char *
+to_utf8 (const char *s, const char *encoding)
+{
+  return recode_string ("UTF-8", encoding, s, strlen (s));
+}
+
+static char *
+to_utf8_if_nonempty (const char *s, const char *encoding)
+{
+  return s && s[0] ? to_utf8 (s, encoding) : NULL;
+}
+
+static void
+convert_widths (const uint32_t *in, uint32_t n, int **out, size_t *n_out)
+{
+  if (n)
+    {
+      *n_out = n;
+      *out = xnmalloc (n, sizeof **out);
+      for (size_t i = 0; i < n; i++)
+        (*out)[i] = in[i];
+    }
+}
+
+static void
+convert_breakpoints (const struct spvlb_breakpoints *in,
+                     size_t **out, size_t *n_out)
+{
+  if (in && in->n_breaks)
+    {
+      *n_out = in->n_breaks;
+      *out = xnmalloc (in->n_breaks, sizeof *out);
+      for (size_t i = 0; i < in->n_breaks; i++)
+        (*out)[i] = in->breaks[i];
+    }
+}
+
+static void
+convert_keeps (const struct spvlb_keeps *in,
+               struct pivot_keep **out, size_t *n_out)
+{
+  if (in && in->n_keeps)
+    {
+      *n_out = in->n_keeps;
+      *out = xnmalloc (*n_out, sizeof **out);
+      for (size_t i = 0; i < *n_out; i++)
+        {
+          (*out)[i].ofs = in->keeps[i]->offset;
+          (*out)[i].n = in->keeps[i]->n;
+        }
+    }
+}
+
+static struct cell_color
+decode_spvlb_color_string (const char *s, uint8_t def)
+{
+  int r, g, b;
+  if (sscanf (s, "#%2x%2x%2x", &r, &g, &b) != 3)
+    {
+      if (*s)
+        {
+          fprintf (stderr, "bad color %s\n", s);
+          exit (1);
+        }
+      r = g = b = def;
+    }
+  return (struct cell_color) CELL_COLOR (r, g, b);
+}
+
+static struct cell_color
+decode_spvlb_color_u32 (uint32_t x)
+{
+  return (struct cell_color) { x >> 24, x >> 16, x >> 8, x };
+}
+
+static struct font_style *
+decode_spvlb_font_style (const struct spvlb_font_style *in,
+                         const char *encoding)
+{
+  if (!in)
+    return NULL;
+
+  struct font_style *out = xmalloc (sizeof *out);
+  *out = (struct font_style) {
+    .bold = in->bold,
+    .italic = in->italic,
+    .underline = in->underline,
+    .fg[0] = decode_spvlb_color_string (in->fg_color, 0x00),
+    .bg[0] = decode_spvlb_color_string (in->bg_color, 0xff),
+    .typeface = to_utf8 (in->typeface, encoding),
+    .size = in->size / 1.33,
+  };
+  out->fg[1] = out->fg[0];
+  out->bg[1] = out->bg[0];
+  return out;
+}
+
+static enum table_halign
+decode_spvlb_halign (uint32_t in)
+{
+  switch (in)
+    {
+    case 0:
+      return TABLE_HALIGN_CENTER;
+
+    case 2:
+      return TABLE_HALIGN_LEFT;
+
+    case 4:
+      return TABLE_HALIGN_RIGHT;
+
+    case 6:
+    case 61453:
+      return TABLE_HALIGN_DECIMAL;
+
+    case 0xffffffad:
+    case 64173:
+      return TABLE_HALIGN_MIXED;
+
+    default:
+      fprintf (stderr, "bad cell style halign %"PRIu32"\n", in);
+      exit (1);
+    }
+}
+
+static enum table_valign
+decode_spvlb_valign (uint32_t in)
+{
+  switch (in)
+    {
+    case 0:
+      return TABLE_VALIGN_CENTER;
+
+    case 1:
+      return TABLE_VALIGN_TOP;
+
+    case 3:
+      return TABLE_VALIGN_BOTTOM;
+
+    default:
+      fprintf (stderr, "bad cell style valign %"PRIu32"\n", in);
+      exit (1);
+    }
+}
+
+static struct cell_style *
+decode_spvlb_cell_style (const struct spvlb_cell_style *in)
+{
+  if (!in)
+    return NULL;
+
+  struct cell_style *out = xzalloc (sizeof *out);
+
+  out->halign = decode_spvlb_halign (in->halign);
+  out->valign = decode_spvlb_valign (in->valign);
+  out->decimal_offset = in->decimal_offset;
+  out->margin[TABLE_HORZ][0] = in->left_margin;
+  out->margin[TABLE_HORZ][1] = in->right_margin;
+  out->margin[TABLE_VERT][0] = in->top_margin;
+  out->margin[TABLE_VERT][1] = in->bottom_margin;
+
+  return out;
+}
+
+static struct pivot_value *decode_spvlb_value (const struct pivot_table *,
+                                             const struct spvlb_value *,
+                                             const char *encoding);
+
+static void
+decode_spvlb_argument (const struct pivot_table *table,
+                       const struct spvlb_argument *in,
+                       struct pivot_argument *out,
+                       const char *encoding)
+{
+  if (in->value)
+    {
+      out->n = 1;
+      out->values = xmalloc (sizeof *out->values);
+      out->values[0] = decode_spvlb_value (table, in->value, encoding);
+    }
+  else
+    {
+      out->n = in->n_values;
+      out->values = xnmalloc (out->n, sizeof *out->values);
+      for (size_t i = 0; i < out->n; i++)
+        out->values[i] = decode_spvlb_value (table, in->values[i], encoding);
+    }
+}
+
+static enum settings_value_show
+decode_spvlb_value_show (uint8_t in)
+{
+  switch (in)
+    {
+    case 0: return SETTINGS_VALUE_SHOW_DEFAULT;
+    case 1: return SETTINGS_VALUE_SHOW_VALUE;
+    case 2: return SETTINGS_VALUE_SHOW_LABEL;
+    case 3: return SETTINGS_VALUE_SHOW_BOTH;
+    default:
+      fprintf (stderr, "bad value show %"PRIu8"\n", in);
+      exit (1);
+    }
+}
+
+static struct pivot_value *
+decode_spvlb_value (const struct pivot_table *table,
+                    const struct spvlb_value *in,
+                    const char *encoding)
+{
+  struct pivot_value *out = xzalloc (sizeof *out);
+  const struct spvlb_value_mod *vm;
+
+  switch (in->type)
+    {
+    case 1:
+      vm = in->type_01.value_mod;
+      out->type = PIVOT_VALUE_NUMERIC;
+      out->numeric.x = in->type_01.x;
+      out->numeric.format = spv_decode_fmt_spec (in->type_01.format);
+      break;
+
+    case 2:
+      vm = in->type_02.value_mod;
+      out->type = PIVOT_VALUE_NUMERIC;
+      out->numeric.x = in->type_02.x;
+      out->numeric.format = spv_decode_fmt_spec (in->type_02.format);
+      out->numeric.var_name = to_utf8_if_nonempty (in->type_02.var_name,
+                                                   encoding);
+      out->numeric.value_label = to_utf8_if_nonempty (in->type_02.value_label,
+                                                      encoding);
+      out->numeric.show = decode_spvlb_value_show (in->type_02.show);
+      break;
+
+    case 3:
+      vm = in->type_03.value_mod;
+      out->type = PIVOT_VALUE_TEXT;
+      out->text.local = to_utf8 (in->type_03.local, encoding);
+      out->text.c = to_utf8 (in->type_03.c, encoding);
+      out->text.id = to_utf8 (in->type_03.id, encoding);
+      out->text.user_provided = !in->type_03.fixed;
+      break;
+
+    case 4:
+      vm = in->type_04.value_mod;
+      out->type = PIVOT_VALUE_STRING;
+      out->string.s = to_utf8 (in->type_04.s, encoding);
+      out->string.var_name = to_utf8 (in->type_04.var_name, encoding);
+      out->string.value_label = to_utf8_if_nonempty (in->type_04.value_label,
+                                                     encoding);
+      out->string.show = decode_spvlb_value_show (in->type_04.show);
+      break;
+
+    case 5:
+      vm = in->type_05.value_mod;
+      out->type = PIVOT_VALUE_VARIABLE;
+      out->variable.var_name = to_utf8 (in->type_05.var_name, encoding);
+      out->variable.var_label = to_utf8_if_nonempty (in->type_05.var_label,
+                                                     encoding);
+      out->variable.show = decode_spvlb_value_show (in->type_05.show);
+      break;
+
+    case 6:
+      vm = in->type_06.value_mod;
+      out->type = PIVOT_VALUE_TEXT;
+      out->text.local = to_utf8 (in->type_06.local, encoding);
+      out->text.c = to_utf8 (in->type_06.c, encoding);
+      out->text.id = to_utf8 (in->type_06.id, encoding);
+      out->text.user_provided = false;
+      break;
+
+    case -1:
+      vm = in->type_else.value_mod;
+      out->type = PIVOT_VALUE_TEMPLATE;
+      out->template.local = to_utf8 (in->type_else.template, encoding);
+      out->template.id = out->template.local;
+      out->template.n_args = in->type_else.n_args;
+      out->template.args = xnmalloc (in->type_else.n_args,
+                                     sizeof *out->template.args);
+      for (size_t i = 0; i < out->template.n_args; i++)
+        decode_spvlb_argument (table, in->type_else.args[i],
+                               &out->template.args[i], encoding);
+      break;
+
+    default:
+      assert (0);
+    }
+
+  if (vm)
+    {
+      if (vm->subscript)
+        out->subscript = to_utf8 (vm->subscript, encoding);
+
+      if (vm->n_refs)
+        {
+          out->footnotes = xnmalloc (vm->n_refs, sizeof *out->footnotes);
+          for (size_t i = 0; i < vm->n_refs; i++)
+            {
+              uint16_t idx = vm->refs[i];
+              if (idx < table->n_footnotes)
+                out->footnotes[out->n_footnotes++] = table->footnotes[idx];
+              else
+                {
+                  fprintf (stderr, "bad footnote index: %"PRIu16" >= %zu\n",
+                           idx, table->n_footnotes);
+                  exit (1);
+                }
+            }
+        }
+
+      if (vm->style_pair)
+        {
+          out->font_style = decode_spvlb_font_style (
+            vm->style_pair->font_style, encoding);
+          out->cell_style = decode_spvlb_cell_style (
+            vm->style_pair->cell_style);
+        }
+
+      if (vm->template_string
+          && vm->template_string->id
+          && vm->template_string->id[0]
+          && out->type == PIVOT_VALUE_TEMPLATE)
+        out->template.id = to_utf8 (vm->template_string->id, encoding);
+    }
+
+  return out;
+}
+
+static void
+decode_spvlb_area (const struct spvlb_area *in, struct area_style *out,
+                   const char *encoding)
+{
+  out->font_style.bold = (in->style & 1) != 0;
+  out->font_style.italic = (in->style & 2) != 0;
+  out->font_style.underline = in->underline;
+  out->font_style.fg[0] = decode_spvlb_color_string (in->fg_color, 0x00);
+  out->font_style.bg[0] = decode_spvlb_color_string (in->bg_color, 0xff);
+  out->font_style.typeface = to_utf8 (in->typeface, encoding);
+  out->font_style.size = in->size / 1.33;
+  out->font_style.fg[1] = (in->alternate
+                           ? decode_spvlb_color_string (in->alt_fg_color, 0x00)
+                           : out->font_style.fg[0]);
+  out->font_style.bg[1] = (in->alternate
+                           ? decode_spvlb_color_string (in->alt_bg_color, 0xff)
+                           : out->font_style.bg[0]);
+  assert (in->halign != 61453);
+  out->cell_style.halign = decode_spvlb_halign (in->halign);
+  out->cell_style.valign = decode_spvlb_valign (in->valign);
+
+  /* TABLE_HALIGN_DECIMAL doesn't seem to be a real halign for areas, which is
+     good because there's no way to indicate the decimal offset.  Just in
+     case: */
+  if (out->cell_style.halign == TABLE_HALIGN_DECIMAL)
+    out->cell_style.halign = TABLE_HALIGN_MIXED;
+
+  out->cell_style.margin[TABLE_HORZ][0] = in->left_margin;
+  out->cell_style.margin[TABLE_HORZ][1] = in->right_margin;
+  out->cell_style.margin[TABLE_VERT][0] = in->top_margin;
+  out->cell_style.margin[TABLE_VERT][1] = in->bottom_margin;
+}
+
+static void decode_spvlb_group (const struct pivot_table *,
+                                struct spvlb_category **,
+                                size_t n_categories,
+                                bool show_label,
+                                struct pivot_category *parent,
+                                struct pivot_dimension *,
+                                const char *encoding);
+
+static void
+decode_spvlb_categories (const struct pivot_table *table,
+                         struct spvlb_category **categories,
+                         size_t n_categories,
+                         struct pivot_category *parent,
+                         struct pivot_dimension *dimension,
+                         const char *encoding)
+{
+  for (size_t i = 0; i < n_categories; i++)
+    {
+      const struct spvlb_category *in = categories[i];
+      if (in->group && in->group->merge)
+        {
+          decode_spvlb_categories (table, in->group->subcategories,
+                                   in->group->n_subcategories,
+                                   parent, dimension, encoding);
+          continue;
+        }
+
+      struct pivot_category *out = xzalloc (sizeof *out);
+      out->name = decode_spvlb_value (table, in->name, encoding);
+      out->parent = parent;
+      out->dimension = dimension;
+      if (in->group)
+        {
+          decode_spvlb_group (table, in->group->subcategories,
+                              in->group->n_subcategories,
+                              true, out, dimension, encoding);
+          out->data_index = SIZE_MAX;
+          out->presentation_index = SIZE_MAX;
+        }
+      else
+        {
+          out->data_index = in->leaf->leaf_index;
+          out->presentation_index = dimension->n_leaves;
+          dimension->n_leaves++;
+        }
+
+      if (parent->n_subs >= parent->allocated_subs)
+        parent->subs = x2nrealloc (parent->subs, &parent->allocated_subs,
+                                   sizeof *parent->subs);
+      parent->subs[parent->n_subs++] = out;
+    }
+}
+
+static void
+decode_spvlb_group (const struct pivot_table *table,
+                    struct spvlb_category **categories,
+                    size_t n_categories, bool show_label,
+                    struct pivot_category *category,
+                    struct pivot_dimension *dimension,
+                    const char *encoding)
+{
+  category->subs = xcalloc (n_categories, sizeof *category->subs);
+  category->n_subs = 0;
+  category->allocated_subs = 0;
+  category->show_label = show_label;
+
+  decode_spvlb_categories (table, categories, n_categories, category,
+                           dimension, encoding);
+}
+
+static void
+fill_leaves (struct pivot_category *category,
+             struct pivot_dimension *dimension)
+{
+  if (pivot_category_is_group (category))
+    {
+      for (size_t i = 0; i < category->n_subs; i++)
+        fill_leaves (category->subs[i], dimension);
+    }
+  else
+    {
+      if (category->data_index >= dimension->n_leaves)
+        {
+          fprintf (stderr, "leaf_index %zu >= n_leaves %zu\n",
+                   category->data_index, dimension->n_leaves);
+          exit (1);
+        }
+      if (dimension->data_leaves[category->data_index])
+        {
+          fprintf (stderr, "two leaves with data_index %zu\n",
+                   category->data_index);
+          exit (1);
+        }
+      dimension->data_leaves[category->data_index] = category;
+      dimension->presentation_leaves[category->presentation_index] = category;
+    }
+}
+
+static struct pivot_dimension *
+decode_spvlb_dimension (const struct pivot_table *table,
+                        const struct spvlb_dimension *in,
+                        size_t idx, const char *encoding)
+{
+  /* Convert most of the dimension. */
+  struct pivot_dimension *out = xzalloc (sizeof *out);
+  out->level = UINT_MAX;
+  out->top_index = idx;
+  out->hide_all_labels = in->props->hide_all_labels;
+
+  out->root = xzalloc (sizeof *out->root);
+  *out->root = (struct pivot_category) {
+    .name = decode_spvlb_value (table, in->name, encoding),
+    .dimension = out,
+    .data_index = SIZE_MAX,
+    .presentation_index = SIZE_MAX,
+  };
+  decode_spvlb_group (table, in->categories, in->n_categories,
+                      !in->props->hide_dim_label, out->root, out, encoding);
+
+  /* Allocate and fill the array of leaves now that we know how many there
+     are. */
+  out->data_leaves = xcalloc (out->n_leaves, sizeof *out->data_leaves);
+  out->presentation_leaves = xcalloc (out->n_leaves,
+                                      sizeof *out->presentation_leaves);
+  out->allocated_leaves = out->n_leaves;
+  fill_leaves (out->root, out);
+  for (size_t i = 0; i < out->n_leaves; i++)
+    {
+      assert (out->data_leaves[i] != NULL);
+      assert (out->presentation_leaves[i] != NULL);
+    }
+
+  return out;
+}
+
+static enum table_stroke
+decode_spvlb_stroke (uint32_t stroke_type)
+{
+  switch (stroke_type)
+    {
+    case 0: return TABLE_STROKE_NONE;
+    case 1: return TABLE_STROKE_SOLID;
+    case 2: return TABLE_STROKE_DASHED;
+    case 3: return TABLE_STROKE_THICK;
+    case 4: return TABLE_STROKE_THIN;
+    case 5: return TABLE_STROKE_DOUBLE;
+
+    default:
+      fprintf (stderr, "bad stroke %"PRIu32"\n", stroke_type);
+      exit (1);
+    }
+}
+
+static void
+decode_spvlb_border (const struct spvlb_border *in, struct pivot_table *table)
+
+{
+  if (in->border_type >= PIVOT_N_BORDERS)
+    {
+      fprintf (stderr, "bad border type %"PRIu32"\n", in->border_type);
+      exit (1);
+    }
+
+  struct table_border_style *out = &table->borders[in->border_type];
+  out->stroke = decode_spvlb_stroke (in->stroke_type);
+  out->color = decode_spvlb_color_u32 (in->color);
+}
+
+static void
+decode_spvlb_axis (const uint32_t *dimension_indexes, size_t n_dimensions,
+                   enum pivot_axis_type axis_type, struct pivot_table *table)
+{
+  struct pivot_axis *axis = &table->axes[axis_type];
+  axis->dimensions = xnmalloc (n_dimensions, sizeof *axis->dimensions);
+  axis->n_dimensions = n_dimensions;
+  axis->extent = 1;
+  for (size_t i = 0; i < n_dimensions; i++)
+    {
+      uint32_t idx = dimension_indexes[i];
+      if (idx >= table->n_dimensions)
+        {
+          fprintf (stderr, "bad dimension index %"PRIu32" >= %zu",
+                   idx, table->n_dimensions);
+          exit (1);
+        }
+
+      struct pivot_dimension *d = table->dimensions[idx];
+      if (d->level != UINT_MAX)
+        {
+          fprintf (stderr, "duplicate dimension %"PRIu32, idx);
+          exit (1);
+        }
+
+      axis->dimensions[i] = d;
+      d->axis_type = axis_type;
+      d->level = i;
+
+      axis->extent *= d->n_leaves;
+    }
+}
+
+static void
+decode_data_index (uint64_t in, const struct pivot_table *table,
+                   size_t *out)
+{
+  uint64_t remainder = in;
+  for (size_t i = table->n_dimensions - 1; i > 0; i--)
+    {
+      const struct pivot_dimension *d = table->dimensions[i];
+      if (d->n_leaves)
+        {
+          out[i] = remainder % d->n_leaves;
+          remainder /= d->n_leaves;
+        }
+      else
+        out[i] = 0;
+    }
+  if (remainder >= table->dimensions[0]->n_leaves)
+    {
+      fprintf (stderr, "out of range cell data index %"PRIu64, in);
+      exit (1);
+    }
+  out[0] = remainder;
+}
+
+static void
+decode_spvlb_cells (struct spvlb_cell **in, size_t n_in,
+                    struct pivot_table *table, const char *encoding)
+{
+  if (!table->n_dimensions)
+    return;
+
+  size_t *dindexes = xnmalloc (table->n_dimensions, sizeof *dindexes);
+  for (size_t i = 0; i < n_in; i++)
+    {
+      decode_data_index (in[i]->index, table, dindexes);
+      struct pivot_value *value = decode_spvlb_value (table, in[i]->value,
+                                                      encoding);
+      pivot_table_put (table, dindexes, table->n_dimensions, value);
+    }
+  free (dindexes);
+}
+
+static void
+decode_spvlb_footnote (const struct spvlb_footnote *in, const char *encoding,
+                       size_t idx, struct pivot_table *table)
+{
+  struct pivot_value *content = decode_spvlb_value (table, in->text, encoding);
+  struct pivot_value *marker = NULL;
+  if (in->marker)
+    {
+      marker = decode_spvlb_value (table, in->marker, encoding);
+      if (marker->type == PIVOT_VALUE_TEXT)
+        marker->text.user_provided = false;
+    }
+  pivot_table_create_footnote__ (table, idx, marker, content);
+}
+
+static void
+decode_current_layer (uint64_t current_layer, struct pivot_table *table)
+{
+  const struct pivot_axis *axis = &table->axes[PIVOT_AXIS_LAYER];
+  table->current_layer = xnmalloc (axis->n_dimensions,
+                                   sizeof *table->current_layer);
+
+  for (size_t i = 0; i < axis->n_dimensions; i++)
+    {
+      const struct pivot_dimension *d = axis->dimensions[i];
+      if (d->n_leaves)
+        {
+          table->current_layer[i] = current_layer % d->n_leaves;
+          current_layer /= d->n_leaves;
+        }
+      else
+        table->current_layer[i] = 0;
+    }
+  if (current_layer > 0)
+    {
+      fprintf (stderr, "out of range layer data index %"PRIu64, current_layer);
+      exit (1);
+    }
+}
+
+char *
+decode_spvlb_table (const struct spvlb_table *in, struct pivot_table **outp)
+{
+  if (in->header->version != 1 && in->header->version != 3)
+    return xasprintf ("unknown version %"PRIu32" (expected 1 or 3)",
+                      in->header->version);
+
+  struct pivot_table *out = xzalloc (sizeof *out);
+  out->ref_cnt = 1;
+  hmap_init (&out->cells);
+
+  const struct spvlb_y1 *y1 = (in->formats->x0 ? in->formats->x0->y1
+                               : in->formats->x3 ? in->formats->x3->y1
+                               : NULL);
+  const char *encoding;
+  if (y1)
+    encoding = y1->charset;
+  else
+    {
+      const char *dot = strchr (in->formats->locale, '.');
+      encoding = dot ? dot + 1 : "windows-1252";
+    }
+
+  /* Display settings. */
+  out->show_numeric_markers = !in->ts->show_alphabetic_markers;
+  out->rotate_inner_column_labels = in->header->rotate_inner_column_labels;
+  out->rotate_outer_row_labels = in->header->rotate_outer_row_labels;
+  out->row_labels_in_corner = in->ts->show_row_labels_in_corner;
+  out->show_grid_lines = in->borders->show_grid_lines;
+  out->footnote_marker_superscripts = in->ts->footnote_marker_superscripts;
+  out->omit_empty = in->ts->omit_empty;
+
+  const struct spvlb_x1 *x1 = in->formats->x1;
+  if (x1)
+    {
+      out->show_values = decode_spvlb_value_show (x1->show_values);
+      out->show_variables = decode_spvlb_value_show (x1->show_variables);
+    }
+
+  /* Column and row display settings. */
+  out->sizing[TABLE_VERT].range[0] = in->header->min_row_height;
+  out->sizing[TABLE_VERT].range[1] = in->header->max_row_height;
+  out->sizing[TABLE_HORZ].range[0] = in->header->min_col_width;
+  out->sizing[TABLE_HORZ].range[1] = in->header->max_col_width;
+
+  convert_widths (in->formats->widths, in->formats->n_widths,
+                  &out->sizing[TABLE_HORZ].widths,
+                  &out->sizing[TABLE_HORZ].n_widths);
+
+  const struct spvlb_x2 *x2 = in->formats->x2;
+  if (x2)
+    convert_widths (x2->row_heights, x2->n_row_heights,
+                    &out->sizing[TABLE_VERT].widths,
+                    &out->sizing[TABLE_VERT].n_widths);
+
+  convert_breakpoints (in->ts->row_breaks,
+                       &out->sizing[TABLE_VERT].breaks,
+                       &out->sizing[TABLE_VERT].n_breaks);
+  convert_breakpoints (in->ts->col_breaks,
+                       &out->sizing[TABLE_HORZ].breaks,
+                       &out->sizing[TABLE_HORZ].n_breaks);
+
+  convert_keeps (in->ts->row_keeps,
+                 &out->sizing[TABLE_VERT].keeps,
+                 &out->sizing[TABLE_VERT].n_keeps);
+  convert_keeps (in->ts->col_keeps,
+                 &out->sizing[TABLE_HORZ].keeps,
+                 &out->sizing[TABLE_HORZ].n_keeps);
+
+  out->notes = to_utf8_if_nonempty (in->ts->notes, encoding);
+  out->table_look = to_utf8_if_nonempty (in->ts->table_look, encoding);
+
+  /* Print settings. */
+  out->print_all_layers = in->ps->all_layers;
+  out->paginate_layers = in->ps->paginate_layers;
+  out->shrink_to_fit[TABLE_HORZ] = in->ps->fit_width;
+  out->shrink_to_fit[TABLE_VERT] = in->ps->fit_length;
+  out->top_continuation = in->ps->top_continuation;
+  out->bottom_continuation = in->ps->bottom_continuation;
+  out->continuation = xstrdup (in->ps->continuation_string);
+  out->n_orphan_lines = in->ps->n_orphan_lines;
+
+  /* Format settings. */
+  out->epoch = in->formats->y0->epoch;
+  out->decimal = in->formats->y0->decimal;
+  out->grouping = in->formats->y0->grouping;
+  const struct spvlb_custom_currency *cc = in->formats->custom_currency;
+  for (int i = 0; i < 5; i++)
+    if (cc && i < cc->n_ccs)
+      out->ccs[i] = xstrdup (cc->ccs[i]);
+  out->small = in->formats->x3 ? in->formats->x3->small : 0;
+
+  /* Command information. */
+  if (y1)
+    {
+      out->command_local = to_utf8 (y1->command_local, encoding);
+      out->command_c = to_utf8 (y1->command, encoding);
+      out->language = xstrdup (y1->language);
+      /* charset? */
+      out->locale = xstrdup (y1->locale);
+    }
+
+  /* Source information. */
+  const struct spvlb_x3 *x3 = in->formats->x3;
+  if (x3)
+    {
+      if (x3->dataset && x3->dataset[0] && x3->dataset[0] != 4)
+        out->dataset = to_utf8 (x3->dataset, encoding);
+      out->datafile = to_utf8_if_nonempty (x3->datafile, encoding);
+      out->date = x3->date;
+    }
+
+  /* Footnotes.
+
+     Any pivot_value might refer to footnotes, so it's important to process the
+     footnotes early to ensure that those references can be resolved.  There is
+     a possible problem that a footnote might itself reference an
+     as-yet-unprocessed footnote, but that's OK because footnote references
+     don't actually look at the footnote contents but only resolve a pointer to
+     where the footnote will go later.
+
+     Before we really start, create all the footnotes we'll fill in.  This is
+     because sometimes footnotes refer to themselves or to each other and we
+     don't want to reject those references. */
+  const struct spvlb_footnotes *fn = in->footnotes;
+  if (fn->n_footnotes > 0)
+    {
+      pivot_table_create_footnote__ (out, fn->n_footnotes - 1, NULL, NULL);
+      for (size_t i = 0; i < fn->n_footnotes; i++)
+        decode_spvlb_footnote (in->footnotes->footnotes[i], encoding, i, out);
+    }
+
+  /* Title and caption. */
+  out->title = decode_spvlb_value (out, in->titles->user_title, encoding);
+  out->subtype = decode_spvlb_value (out, in->titles->subtype, encoding);
+  if (in->titles->corner_text)
+    out->corner_text = decode_spvlb_value (out, in->titles->corner_text,
+                                           encoding);
+  if (in->titles->caption)
+    out->caption = decode_spvlb_value (out, in->titles->caption, encoding);
+
+  /* Styles. */
+  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+    decode_spvlb_area (in->areas->areas[i], &out->areas[i], encoding);
+  for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
+    decode_spvlb_border (in->borders->borders[i], out);
+
+  /* Dimensions. */
+  out->n_dimensions = in->dimensions->n_dims;
+  out->dimensions = xcalloc (out->n_dimensions, sizeof *out->dimensions);
+  for (size_t i = 0; i < out->n_dimensions; i++)
+    out->dimensions[i] = decode_spvlb_dimension (out, in->dimensions->dims[i],
+                                                 i, encoding);
+
+  /* Axes. */
+  size_t a = in->axes->n_layers;
+  size_t b = in->axes->n_rows;
+  size_t c = in->axes->n_columns;
+  if (size_overflow_p (xsum3 (a, b, c)) || a + b + c != out->n_dimensions)
+    {
+      fprintf (stderr, "wrong number of dimensions\n");
+      exit (1);
+    }
+  decode_spvlb_axis (in->axes->layers, in->axes->n_layers,
+                     PIVOT_AXIS_LAYER, out);
+  decode_spvlb_axis (in->axes->rows, in->axes->n_rows, PIVOT_AXIS_ROW, out);
+  decode_spvlb_axis (in->axes->columns, in->axes->n_columns,
+                     PIVOT_AXIS_COLUMN, out);
+
+  pivot_table_assign_label_depth (out);
+
+  decode_current_layer (in->ts->current_layer, out);
+
+  /* Data. */
+  decode_spvlb_cells (in->cells->cells, in->cells->n_cells, out, encoding);
+
+  *outp = out;
+  return NULL;
+}