table: Merge tab.c into table.c.
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 25 Dec 2019 23:13:35 +0000 (23:13 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 29 Dec 2019 05:28:10 +0000 (05:28 +0000)
src/output/automake.mk
src/output/tab.c [deleted file]
src/output/tab.h
src/output/table.c

index 668a1c5fb33b3649208e451cb6f9adc08955c312..cbdaa4fc7be843c237a9e63575d887dbe5619430 100644 (file)
@@ -72,7 +72,6 @@ src_output_liboutput_la_SOURCES = \
        src/output/pivot-table.h \
        src/output/render.c \
        src/output/render.h \
-       src/output/tab.c \
        src/output/tab.h \
        src/output/table-item.c \
        src/output/table-item.h \
diff --git a/src/output/tab.c b/src/output/tab.c
deleted file mode 100644 (file)
index c7df771..0000000
+++ /dev/null
@@ -1,544 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011, 2013, 2014, 2016 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/tab.h"
-
-#include <ctype.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <stdlib.h>
-
-#include "data/data-out.h"
-#include "data/format.h"
-#include "data/settings.h"
-#include "data/value.h"
-#include "data/variable.h"
-#include "libpspp/assertion.h"
-#include "libpspp/compiler.h"
-#include "libpspp/i18n.h"
-#include "libpspp/misc.h"
-#include "libpspp/pool.h"
-#include "output/driver.h"
-#include "output/table-item.h"
-#include "output/table-provider.h"
-#include "output/text-item.h"
-
-#include "gl/minmax.h"
-#include "gl/xalloc.h"
-
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-\f
-
-static const bool debugging = true;
-
-/* Joined cell. */
-struct tab_joined_cell
-{
-  int d[TABLE_N_AXES][2];       /* Table region, same as struct table_cell. */
-  char *text;
-
-  size_t n_footnotes;
-  const struct footnote **footnotes;
-
-  const struct area_style *style;
-};
-
-/* Creates and returns a new table with NC columns and NR rows and initially no
-   header rows or columns.
-
-   Sets the number of header rows on each side of TABLE to HL on the
-   left, HR on the right, HT on the top, HB on the bottom.  Header rows
-   are repeated when a table is broken across multiple columns or
-   multiple pages.
-
-   The table's cells are initially empty. */
-struct table *
-tab_create (int nc, int nr, int hl, int hr, int ht, int hb)
-{
-  struct table *t;
-
-  t = pool_create_container (struct table, container);
-  t->n[TABLE_HORZ] = nc;
-  t->n[TABLE_VERT] = nr;
-  t->h[TABLE_HORZ][0] = hl;
-  t->h[TABLE_HORZ][1] = hr;
-  t->h[TABLE_VERT][0] = ht;
-  t->h[TABLE_VERT][1] = hb;
-  t->ref_cnt = 1;
-
-  t->cc = pool_calloc (t->container, nr * nc, sizeof *t->cc);
-  t->ct = pool_calloc (t->container, nr * nc, sizeof *t->ct);
-
-  t->rh = pool_nmalloc (t->container, nc, nr + 1);
-  memset (t->rh, TAL_0, nc * (nr + 1));
-
-  t->rv = pool_nmalloc (t->container, nr, nc + 1);
-  memset (t->rv, TAL_0, nr * (nc + 1));
-
-  memset (t->styles, 0, sizeof t->styles);
-  memset (t->rule_colors, 0, sizeof t->rule_colors);
-
-  return t;
-}
-\f
-/* Rules. */
-
-/* Draws a vertical line to the left of cells at horizontal position X
-   from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
-void
-tab_vline (struct table *t, int style, int x, int y1, int y2)
-{
-  if (debugging)
-    {
-      if (x < 0 || x > table_nc (t)
-          || y1 < 0 || y1 >= table_nr (t)
-          || y2 < 0 || y2 >= table_nr (t))
-        {
-          printf (_("bad vline: x=%d y=(%d,%d) in table size (%d,%d)\n"),
-                  x, y1, y2, table_nc (t), table_nr (t));
-          return;
-        }
-    }
-
-  assert (x >= 0);
-  assert (x <= table_nc (t));
-  assert (y1 >= 0);
-  assert (y2 >= y1);
-  assert (y2 <= table_nr (t));
-
-  if (style != -1)
-    {
-      int y;
-      for (y = y1; y <= y2; y++)
-        t->rv[x + (table_nc (t) + 1) * y] = style;
-    }
-}
-
-/* Draws a horizontal line above cells at vertical position Y from X1
-   to X2 inclusive in style STYLE, if style is not -1. */
-void
-tab_hline (struct table *t, int style, int x1, int x2, int y)
-{
-  if (debugging)
-    {
-      if (y < 0 || y > table_nr (t)
-          || x1 < 0 || x1 >= table_nc (t)
-          || x2 < 0 || x2 >= table_nc (t))
-        {
-          printf (_("bad hline: x=(%d,%d) y=%d in table size (%d,%d)\n"),
-                  x1, x2, y, table_nc (t), table_nr (t));
-          return;
-        }
-    }
-
-  assert (y >= 0);
-  assert (y <= table_nr (t));
-  assert (x2 >= x1);
-  assert (x1 >= 0);
-  assert (x2 < table_nc (t));
-
-  if (style != -1)
-    {
-      int x;
-      for (x = x1; x <= x2; x++)
-        t->rh[x + table_nc (t) * y] = style;
-    }
-}
-
-/* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
-   lines of style F_H and vertical lines of style F_V.  Fills the
-   interior of the box with horizontal lines of style I_H and vertical
-   lines of style I_V.  Any of the line styles may be -1 to avoid
-   drawing those lines.  This is distinct from 0, which draws a null
-   line. */
-void
-tab_box (struct table *t, int f_h, int f_v, int i_h, int i_v,
-         int x1, int y1, int x2, int y2)
-{
-  if (debugging)
-    {
-      if (x1 < 0 || x1 >= table_nc (t)
-          || x2 < 0 || x2 >= table_nc (t)
-          || y1 < 0 || y1 >= table_nr (t)
-          || y2 < 0 || y2 >= table_nr (t))
-        {
-          printf (_("bad box: (%d,%d)-(%d,%d) in table size (%d,%d)\n"),
-                  x1, y1, x2, y2, table_nc (t), table_nr (t));
-          NOT_REACHED ();
-        }
-    }
-
-  assert (x2 >= x1);
-  assert (y2 >= y1);
-  assert (x1 >= 0);
-  assert (y1 >= 0);
-  assert (x2 < table_nc (t));
-  assert (y2 < table_nr (t));
-
-  if (f_h != -1)
-    {
-      int x;
-      for (x = x1; x <= x2; x++)
-        {
-          t->rh[x + table_nc (t) * y1] = f_h;
-          t->rh[x + table_nc (t) * (y2 + 1)] = f_h;
-        }
-    }
-  if (f_v != -1)
-    {
-      int y;
-      for (y = y1; y <= y2; y++)
-        {
-          t->rv[x1 + (table_nc (t) + 1) * y] = f_v;
-          t->rv[(x2 + 1) + (table_nc (t) + 1) * y] = f_v;
-        }
-    }
-
-  if (i_h != -1)
-    {
-      int y;
-
-      for (y = y1 + 1; y <= y2; y++)
-        {
-          int x;
-
-          for (x = x1; x <= x2; x++)
-            t->rh[x + table_nc (t) * y] = i_h;
-        }
-    }
-  if (i_v != -1)
-    {
-      int x;
-
-      for (x = x1 + 1; x <= x2; x++)
-        {
-          int y;
-
-          for (y = y1; y <= y2; y++)
-            t->rv[x + (table_nc (t) + 1) * y] = i_v;
-        }
-    }
-}
-\f
-/* Cells. */
-
-static void
-do_tab_text (struct table *table, int c, int r, unsigned opt, char *text)
-{
-  assert (c >= 0);
-  assert (r >= 0);
-  assert (c < table_nc (table));
-  assert (r < table_nr (table));
-
-  if (debugging)
-    {
-      if (c < 0 || r < 0 || c >= table_nc (table) || r >= table_nr (table))
-        {
-          printf ("tab_text(): bad cell (%d,%d) in table size (%d,%d)\n",
-                  c, r, table_nc (table), table_nr (table));
-          return;
-        }
-    }
-
-  table->cc[c + r * table_nc (table)] = text;
-  table->ct[c + r * table_nc (table)] = opt;
-}
-
-/* Sets cell (C,R) in TABLE, with options OPT, to have text value
-   TEXT. */
-void
-tab_text (struct table *table, int c, int r, unsigned opt,
-          const char *text)
-{
-  do_tab_text (table, c, r, opt, pool_strdup (table->container, text));
-}
-
-/* Sets cell (C,R) in TABLE, with options OPT, to have text value
-   FORMAT, which is formatted as if passed to printf. */
-void
-tab_text_format (struct table *table, int c, int r, unsigned opt,
-                 const char *format, ...)
-{
-  va_list args;
-
-  va_start (args, format);
-  do_tab_text (table, c, r, opt,
-               pool_vasprintf (table->container, format, args));
-  va_end (args);
-}
-
-static struct tab_joined_cell *
-add_joined_cell (struct table *table, int x1, int y1, int x2, int y2,
-                 unsigned opt)
-{
-  struct tab_joined_cell *j;
-
-  assert (x1 >= 0);
-  assert (y1 >= 0);
-  assert (y2 >= y1);
-  assert (x2 >= x1);
-  assert (y2 < table_nr (table));
-  assert (x2 < table_nc (table));
-
-  if (debugging)
-    {
-      if (x1 < 0 || x1 >= table_nc (table)
-          || y1 < 0 || y1 >= table_nr (table)
-          || x2 < x1 || x2 >= table_nc (table)
-          || y2 < y1 || y2 >= table_nr (table))
-        {
-          printf ("tab_joint_text(): bad cell "
-                  "(%d,%d)-(%d,%d) in table size (%d,%d)\n",
-                  x1, y1, x2, y2, table_nc (table), table_nr (table));
-          return NULL;
-        }
-    }
-
-  tab_box (table, -1, -1, TAL_0, TAL_0, x1, y1, x2, y2);
-
-  j = pool_alloc (table->container, sizeof *j);
-  j->d[TABLE_HORZ][0] = x1;
-  j->d[TABLE_VERT][0] = y1;
-  j->d[TABLE_HORZ][1] = ++x2;
-  j->d[TABLE_VERT][1] = ++y2;
-  j->n_footnotes = 0;
-  j->footnotes = NULL;
-  j->style = NULL;
-
-  {
-    void **cc = &table->cc[x1 + y1 * table_nc (table)];
-    unsigned short *ct = &table->ct[x1 + y1 * table_nc (table)];
-    const int ofs = table_nc (table) - (x2 - x1);
-
-    int y;
-
-    for (y = y1; y < y2; y++)
-      {
-        int x;
-
-        for (x = x1; x < x2; x++)
-          {
-            *cc++ = j;
-            *ct++ = opt | TAB_JOIN;
-          }
-
-        cc += ofs;
-        ct += ofs;
-      }
-  }
-
-  return j;
-}
-
-/* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
-   options OPT to have text value TEXT. */
-void
-tab_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_tab_text (table, x1, y1, opt, s);
-  else
-    add_joined_cell (table, x1, y1, x2, y2, opt)->text = s;
-}
-
-struct footnote *
-tab_create_footnote (struct table *table, size_t idx, const char *content,
-                     const char *marker, struct area_style *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;
-}
-
-void
-tab_add_footnote (struct table *table, int x, int y,
-                  const struct footnote *f)
-{
-  int index = x + y * table_nc (table);
-  unsigned short opt = table->ct[index];
-  struct tab_joined_cell *j;
-
-  if (opt & TAB_JOIN)
-    j = table->cc[index];
-  else
-    {
-      char *text = table->cc[index];
-
-      j = add_joined_cell (table, x, y, x, y, table->ct[index]);
-      j->text = text ? text : xstrdup ("");
-    }
-
-  j->footnotes = pool_realloc (table->container, j->footnotes,
-                               (j->n_footnotes + 1) * sizeof *j->footnotes);
-
-  j->footnotes[j->n_footnotes++] = f;
-}
-
-void
-tab_add_style (struct table *table, int x, int y,
-               const struct area_style *style)
-{
-  int index = x + y * table_nc (table);
-  unsigned short opt = table->ct[index];
-  struct tab_joined_cell *j;
-
-  if (opt & TAB_JOIN)
-    j = table->cc[index];
-  else
-    {
-      char *text = table->cc[index];
-
-      j = add_joined_cell (table, x, y, x, y, table->ct[index]);
-      j->text = text ? text : xstrdup ("");
-    }
-
-  j->style = style;
-}
-
-bool
-tab_cell_is_empty (const struct table *table, int c, int r)
-{
-  return table->cc[c + r * table_nc (table)] == NULL;
-}
-\f
-/* Editing. */
-
-/* Writes STRING to the output.  OPTIONS may be any valid combination of TAB_*
-   bits.
-
-   This function is obsolete.  Please do not add new uses of it.  Instead, use
-   a text_item (see output/text-item.h). */
-void
-tab_output_text (int options UNUSED, const char *string)
-{
-  text_item_submit (text_item_create (TEXT_ITEM_LOG, string));
-}
-
-/* Same as tab_output_text(), but FORMAT is passed through printf-like
-   formatting before output. */
-void
-tab_output_text_format (int options, const char *format, ...)
-{
-  va_list args;
-  char *text;
-
-  va_start (args, format);
-  text = xvasprintf (format, args);
-  va_end (args);
-
-  tab_output_text (options, text);
-
-  free (text);
-}
-\f
-/* Table class implementation. */
-
-void
-tab_destroy (struct table *table)
-{
-  pool_destroy (table->container);
-}
-
-void
-tab_get_cell (const struct table *t, int x, int y, struct table_cell *cell)
-{
-  int index = x + y * table_nc (t);
-  unsigned short opt = t->ct[index];
-  const void *cc = t->cc[index];
-
-  cell->options = opt;
-  cell->n_footnotes = 0;
-
-  int style_idx = (opt & TAB_STYLE_MASK) >> TAB_STYLE_SHIFT;
-  const struct area_style *style = t->styles[style_idx];
-  if (style)
-    cell->style = style;
-  else
-    {
-      static const struct area_style styles[3][3] = {
-#define S(H,V) [H][V] = { AREA_STYLE_INITIALIZER__,     \
-                          .cell_style.halign = H,       \
-                          .cell_style.valign = V }
-        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_TOP),
-        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_CENTER),
-        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_BOTTOM),
-        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_TOP),
-        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_CENTER),
-        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_BOTTOM),
-        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_TOP),
-        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_CENTER),
-        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_BOTTOM),
-      };
-
-      enum table_halign halign
-        = ((opt & TAB_HALIGN) == TAB_LEFT ? TABLE_HALIGN_LEFT
-           : (opt & TAB_HALIGN) == TAB_CENTER ? TABLE_HALIGN_CENTER
-           : TABLE_HALIGN_RIGHT);
-      enum table_valign valign
-        = ((opt & TAB_VALIGN) == TAB_TOP ? TABLE_VALIGN_TOP
-           : (opt & TAB_VALIGN) == TAB_MIDDLE ? TABLE_VALIGN_CENTER
-           : TABLE_VALIGN_BOTTOM);
-
-      cell->style = &styles[halign][valign];
-    }
-
-  if (opt & TAB_JOIN)
-    {
-      const struct tab_joined_cell *jc = cc;
-      cell->text = jc->text;
-
-      cell->footnotes = jc->footnotes;
-      cell->n_footnotes = jc->n_footnotes;
-
-      cell->d[TABLE_HORZ][0] = jc->d[TABLE_HORZ][0];
-      cell->d[TABLE_HORZ][1] = jc->d[TABLE_HORZ][1];
-      cell->d[TABLE_VERT][0] = jc->d[TABLE_VERT][0];
-      cell->d[TABLE_VERT][1] = jc->d[TABLE_VERT][1];
-
-      if (jc->style)
-        cell->style = jc->style;
-    }
-  else
-    {
-      cell->d[TABLE_HORZ][0] = x;
-      cell->d[TABLE_HORZ][1] = x + 1;
-      cell->d[TABLE_VERT][0] = y;
-      cell->d[TABLE_VERT][1] = y + 1;
-      cell->text = CONST_CAST (char *, cc ? cc : "");
-    }
-}
-
-int
-tab_get_rule (const struct table *t, enum table_axis axis, int x, int y,
-              struct cell_color *color)
-{
-  uint8_t raw = (axis == TABLE_VERT
-                 ? t->rh[x + table_nc (t) * y]
-                 : t->rv[x + (table_nc (t) + 1) * y]);
-  struct cell_color *p = t->rule_colors[(raw & TAB_RULE_STYLE_MASK)
-                                        >> TAB_RULE_STYLE_SHIFT];
-  if (p)
-    *color = *p;
-  return (raw & TAB_RULE_TYPE_MASK) >> TAB_RULE_TYPE_SHIFT;
-}
index 9beefc73d412bab89017d1e2c4f565b410e0d83e..fd8ccf4b97b6c70aa3316ad0e1b596b89c82589a 100644 (file)
@@ -67,12 +67,5 @@ void tab_output_text (int options, const char *string);
 void tab_output_text_format (int options, const char *, ...)
      PRINTF_FORMAT (2, 3);
 
-/* For use by table-provider only. */
-struct table_cell;
-void tab_destroy (struct table *);
-void tab_get_cell (const struct table *, int x, int y, struct table_cell *);
-int tab_get_rule (const struct table *, enum table_axis, int x, int y,
-                  struct cell_color *);
-
 #endif /* output/tab.h */
 
index 9c0286e6f5dd3aa5b17f77ccc8d726f9b83a418d..1e3c53d256d29b5c0a9c9900ad6a70fc131f958d 100644 (file)
@@ -31,6 +31,7 @@
 #include "libpspp/str.h"
 #include "output/table-item.h"
 #include "output/tab.h"
+#include "output/text-item.h"
 
 #include "gl/xalloc.h"
 
@@ -54,7 +55,7 @@ table_unref (struct table *table)
     {
       assert (table->ref_cnt > 0);
       if (--table->ref_cnt == 0)
-        tab_destroy (table);
+        pool_destroy (table->container);
     }
 }
 
@@ -86,71 +87,6 @@ area_style_free (struct area_style *style)
     }
 }
 
-/* Initializes CELL with the contents of the table cell at column X and row Y
-   within TABLE.  When CELL is no longer needed, the caller is responsible for
-   freeing it by calling table_cell_free(CELL).
-
-   The caller must ensure that CELL is destroyed before TABLE is unref'ed. */
-void
-table_get_cell (const struct table *table, int x, int y,
-                struct table_cell *cell)
-{
-  assert (x >= 0 && x < table->n[TABLE_HORZ]);
-  assert (y >= 0 && y < table->n[TABLE_VERT]);
-
-  static const struct area_style default_style = AREA_STYLE_INITIALIZER;
-  cell->style = &default_style;
-
-  tab_get_cell (table, x, y, cell);
-}
-
-/* Returns one of the TAL_* enumeration constants (declared in output/table.h)
-   representing a rule running alongside one of the cells in TABLE.
-
-   Suppose NC is the number of columns in TABLE and NR is the number of rows.
-   Then, if AXIS is TABLE_HORZ, then 0 <= X <= NC and 0 <= Y < NR.  If (X,Y) =
-   (0,0), the return value is the rule that runs vertically on the left side of
-   cell (0,0); if (X,Y) = (1,0), it is the vertical rule between that cell and
-   cell (1,0); and so on, up to (NC,0), which runs vertically on the right of
-   cell (NC-1,0).
-
-   The following diagram illustrates the meaning of (X,Y) for AXIS = TABLE_HORZ
-   within a 7x7 table.  The '|' characters at the intersection of the X labels
-   and Y labels show the rule whose style would be returned by calling
-   table_get_rule with those X and Y values:
-
-                           0  1  2  3  4  5  6  7
-                           +--+--+--+--+--+--+--+
-                         0 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         1 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         2 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         3 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         4 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         5 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-                         6 |  |  |  |  |  |  |  |
-                           +--+--+--+--+--+--+--+
-
-   Similarly, if AXIS is TABLE_VERT, then 0 <= X < NC and 0 <= Y <= NR.  If
-   (X,Y) = (0,0), the return value is the rule that runs horizontally above
-   the top of cell (0,0); if (X,Y) = (0,1), it is the horizontal rule
-   between that cell and cell (0,1); and so on, up to (0,NR), which runs
-   horizontally below cell (0,NR-1). */
-int
-table_get_rule (const struct table *table, enum table_axis axis, int x, int y,
-                struct cell_color *color)
-{
-  assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ));
-  assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT));
-  *color = (struct cell_color) CELL_COLOR_BLACK;
-  return tab_get_rule (table, axis, x, y, color);
-}
-
 void
 table_cell_format_footnote_markers (const struct table_cell *cell,
                                     struct string *s)
@@ -379,3 +315,541 @@ cell_style_dump (const struct cell_style *c)
           c->margin[TABLE_HORZ][0], c->margin[TABLE_HORZ][1],
           c->margin[TABLE_VERT][0], c->margin[TABLE_VERT][1]);
 }
+\f
+
+static const bool debugging = true;
+
+/* Joined cell. */
+struct tab_joined_cell
+{
+  int d[TABLE_N_AXES][2];       /* Table region, same as struct table_cell. */
+  char *text;
+
+  size_t n_footnotes;
+  const struct footnote **footnotes;
+
+  const struct area_style *style;
+};
+
+/* Creates and returns a new table with NC columns and NR rows and initially no
+   header rows or columns.
+
+   Sets the number of header rows on each side of TABLE to HL on the
+   left, HR on the right, HT on the top, HB on the bottom.  Header rows
+   are repeated when a table is broken across multiple columns or
+   multiple pages.
+
+   The table's cells are initially empty. */
+struct table *
+tab_create (int nc, int nr, int hl, int hr, int ht, int hb)
+{
+  struct table *t;
+
+  t = pool_create_container (struct table, container);
+  t->n[TABLE_HORZ] = nc;
+  t->n[TABLE_VERT] = nr;
+  t->h[TABLE_HORZ][0] = hl;
+  t->h[TABLE_HORZ][1] = hr;
+  t->h[TABLE_VERT][0] = ht;
+  t->h[TABLE_VERT][1] = hb;
+  t->ref_cnt = 1;
+
+  t->cc = pool_calloc (t->container, nr * nc, sizeof *t->cc);
+  t->ct = pool_calloc (t->container, nr * nc, sizeof *t->ct);
+
+  t->rh = pool_nmalloc (t->container, nc, nr + 1);
+  memset (t->rh, TAL_0, nc * (nr + 1));
+
+  t->rv = pool_nmalloc (t->container, nr, nc + 1);
+  memset (t->rv, TAL_0, nr * (nc + 1));
+
+  memset (t->styles, 0, sizeof t->styles);
+  memset (t->rule_colors, 0, sizeof t->rule_colors);
+
+  return t;
+}
+\f
+/* Rules. */
+
+/* Draws a vertical line to the left of cells at horizontal position X
+   from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
+void
+tab_vline (struct table *t, int style, int x, int y1, int y2)
+{
+  if (debugging)
+    {
+      if (x < 0 || x > table_nc (t)
+          || y1 < 0 || y1 >= table_nr (t)
+          || y2 < 0 || y2 >= table_nr (t))
+        {
+          printf ("bad vline: x=%d y=(%d,%d) in table size (%d,%d)\n",
+                  x, y1, y2, table_nc (t), table_nr (t));
+          return;
+        }
+    }
+
+  assert (x >= 0);
+  assert (x <= table_nc (t));
+  assert (y1 >= 0);
+  assert (y2 >= y1);
+  assert (y2 <= table_nr (t));
+
+  if (style != -1)
+    {
+      int y;
+      for (y = y1; y <= y2; y++)
+        t->rv[x + (table_nc (t) + 1) * y] = style;
+    }
+}
+
+/* Draws a horizontal line above cells at vertical position Y from X1
+   to X2 inclusive in style STYLE, if style is not -1. */
+void
+tab_hline (struct table *t, int style, int x1, int x2, int y)
+{
+  if (debugging)
+    {
+      if (y < 0 || y > table_nr (t)
+          || x1 < 0 || x1 >= table_nc (t)
+          || x2 < 0 || x2 >= table_nc (t))
+        {
+          printf ("bad hline: x=(%d,%d) y=%d in table size (%d,%d)\n",
+                  x1, x2, y, table_nc (t), table_nr (t));
+          return;
+        }
+    }
+
+  assert (y >= 0);
+  assert (y <= table_nr (t));
+  assert (x2 >= x1);
+  assert (x1 >= 0);
+  assert (x2 < table_nc (t));
+
+  if (style != -1)
+    {
+      int x;
+      for (x = x1; x <= x2; x++)
+        t->rh[x + table_nc (t) * y] = style;
+    }
+}
+
+/* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
+   lines of style F_H and vertical lines of style F_V.  Fills the
+   interior of the box with horizontal lines of style I_H and vertical
+   lines of style I_V.  Any of the line styles may be -1 to avoid
+   drawing those lines.  This is distinct from 0, which draws a null
+   line. */
+void
+tab_box (struct table *t, int f_h, int f_v, int i_h, int i_v,
+         int x1, int y1, int x2, int y2)
+{
+  if (debugging)
+    {
+      if (x1 < 0 || x1 >= table_nc (t)
+          || x2 < 0 || x2 >= table_nc (t)
+          || y1 < 0 || y1 >= table_nr (t)
+          || y2 < 0 || y2 >= table_nr (t))
+        {
+          printf ("bad box: (%d,%d)-(%d,%d) in table size (%d,%d)\n",
+                  x1, y1, x2, y2, table_nc (t), table_nr (t));
+          NOT_REACHED ();
+        }
+    }
+
+  assert (x2 >= x1);
+  assert (y2 >= y1);
+  assert (x1 >= 0);
+  assert (y1 >= 0);
+  assert (x2 < table_nc (t));
+  assert (y2 < table_nr (t));
+
+  if (f_h != -1)
+    {
+      int x;
+      for (x = x1; x <= x2; x++)
+        {
+          t->rh[x + table_nc (t) * y1] = f_h;
+          t->rh[x + table_nc (t) * (y2 + 1)] = f_h;
+        }
+    }
+  if (f_v != -1)
+    {
+      int y;
+      for (y = y1; y <= y2; y++)
+        {
+          t->rv[x1 + (table_nc (t) + 1) * y] = f_v;
+          t->rv[(x2 + 1) + (table_nc (t) + 1) * y] = f_v;
+        }
+    }
+
+  if (i_h != -1)
+    {
+      int y;
+
+      for (y = y1 + 1; y <= y2; y++)
+        {
+          int x;
+
+          for (x = x1; x <= x2; x++)
+            t->rh[x + table_nc (t) * y] = i_h;
+        }
+    }
+  if (i_v != -1)
+    {
+      int x;
+
+      for (x = x1 + 1; x <= x2; x++)
+        {
+          int y;
+
+          for (y = y1; y <= y2; y++)
+            t->rv[x + (table_nc (t) + 1) * y] = i_v;
+        }
+    }
+}
+\f
+/* Cells. */
+
+static void
+do_tab_text (struct table *table, int c, int r, unsigned opt, char *text)
+{
+  assert (c >= 0);
+  assert (r >= 0);
+  assert (c < table_nc (table));
+  assert (r < table_nr (table));
+
+  if (debugging)
+    {
+      if (c < 0 || r < 0 || c >= table_nc (table) || r >= table_nr (table))
+        {
+          printf ("tab_text(): bad cell (%d,%d) in table size (%d,%d)\n",
+                  c, r, table_nc (table), table_nr (table));
+          return;
+        }
+    }
+
+  table->cc[c + r * table_nc (table)] = text;
+  table->ct[c + r * table_nc (table)] = opt;
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have text value
+   TEXT. */
+void
+tab_text (struct table *table, int c, int r, unsigned opt,
+          const char *text)
+{
+  do_tab_text (table, c, r, opt, pool_strdup (table->container, text));
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have text value
+   FORMAT, which is formatted as if passed to printf. */
+void
+tab_text_format (struct table *table, int c, int r, unsigned opt,
+                 const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  do_tab_text (table, c, r, opt,
+               pool_vasprintf (table->container, format, args));
+  va_end (args);
+}
+
+static struct tab_joined_cell *
+add_joined_cell (struct table *table, int x1, int y1, int x2, int y2,
+                 unsigned opt)
+{
+  struct tab_joined_cell *j;
+
+  assert (x1 >= 0);
+  assert (y1 >= 0);
+  assert (y2 >= y1);
+  assert (x2 >= x1);
+  assert (y2 < table_nr (table));
+  assert (x2 < table_nc (table));
+
+  if (debugging)
+    {
+      if (x1 < 0 || x1 >= table_nc (table)
+          || y1 < 0 || y1 >= table_nr (table)
+          || x2 < x1 || x2 >= table_nc (table)
+          || y2 < y1 || y2 >= table_nr (table))
+        {
+          printf ("tab_joint_text(): bad cell "
+                  "(%d,%d)-(%d,%d) in table size (%d,%d)\n",
+                  x1, y1, x2, y2, table_nc (table), table_nr (table));
+          return NULL;
+        }
+    }
+
+  tab_box (table, -1, -1, TAL_0, TAL_0, x1, y1, x2, y2);
+
+  j = pool_alloc (table->container, sizeof *j);
+  j->d[TABLE_HORZ][0] = x1;
+  j->d[TABLE_VERT][0] = y1;
+  j->d[TABLE_HORZ][1] = ++x2;
+  j->d[TABLE_VERT][1] = ++y2;
+  j->n_footnotes = 0;
+  j->footnotes = NULL;
+  j->style = NULL;
+
+  {
+    void **cc = &table->cc[x1 + y1 * table_nc (table)];
+    unsigned short *ct = &table->ct[x1 + y1 * table_nc (table)];
+    const int ofs = table_nc (table) - (x2 - x1);
+
+    int y;
+
+    for (y = y1; y < y2; y++)
+      {
+        int x;
+
+        for (x = x1; x < x2; x++)
+          {
+            *cc++ = j;
+            *ct++ = opt | TAB_JOIN;
+          }
+
+        cc += ofs;
+        ct += ofs;
+      }
+  }
+
+  return j;
+}
+
+/* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
+   options OPT to have text value TEXT. */
+void
+tab_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_tab_text (table, x1, y1, opt, s);
+  else
+    add_joined_cell (table, x1, y1, x2, y2, opt)->text = s;
+}
+
+struct footnote *
+tab_create_footnote (struct table *table, size_t idx, const char *content,
+                     const char *marker, struct area_style *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;
+}
+
+void
+tab_add_footnote (struct table *table, int x, int y,
+                  const struct footnote *f)
+{
+  int index = x + y * table_nc (table);
+  unsigned short opt = table->ct[index];
+  struct tab_joined_cell *j;
+
+  if (opt & TAB_JOIN)
+    j = table->cc[index];
+  else
+    {
+      char *text = table->cc[index];
+
+      j = add_joined_cell (table, x, y, x, y, table->ct[index]);
+      j->text = text ? text : xstrdup ("");
+    }
+
+  j->footnotes = pool_realloc (table->container, j->footnotes,
+                               (j->n_footnotes + 1) * sizeof *j->footnotes);
+
+  j->footnotes[j->n_footnotes++] = f;
+}
+
+void
+tab_add_style (struct table *table, int x, int y,
+               const struct area_style *style)
+{
+  int index = x + y * table_nc (table);
+  unsigned short opt = table->ct[index];
+  struct tab_joined_cell *j;
+
+  if (opt & TAB_JOIN)
+    j = table->cc[index];
+  else
+    {
+      char *text = table->cc[index];
+
+      j = add_joined_cell (table, x, y, x, y, table->ct[index]);
+      j->text = text ? text : xstrdup ("");
+    }
+
+  j->style = style;
+}
+
+bool
+tab_cell_is_empty (const struct table *table, int c, int r)
+{
+  return table->cc[c + r * table_nc (table)] == NULL;
+}
+\f
+/* Editing. */
+
+/* Writes STRING to the output.  OPTIONS may be any valid combination of TAB_*
+   bits.
+
+   This function is obsolete.  Please do not add new uses of it.  Instead, use
+   a text_item (see output/text-item.h). */
+void
+tab_output_text (int options UNUSED, const char *string)
+{
+  text_item_submit (text_item_create (TEXT_ITEM_LOG, string));
+}
+
+/* Same as tab_output_text(), but FORMAT is passed through printf-like
+   formatting before output. */
+void
+tab_output_text_format (int options, const char *format, ...)
+{
+  va_list args;
+  char *text;
+
+  va_start (args, format);
+  text = xvasprintf (format, args);
+  va_end (args);
+
+  tab_output_text (options, text);
+
+  free (text);
+}
+\f
+/* Initializes CELL with the contents of the table cell at column X and row Y
+   within TABLE.  When CELL is no longer needed, the caller is responsible for
+   freeing it by calling table_cell_free(CELL).
+
+   The caller must ensure that CELL is destroyed before TABLE is unref'ed. */
+void
+table_get_cell (const struct table *t, int x, int y, struct table_cell *cell)
+{
+  assert (x >= 0 && x < t->n[TABLE_HORZ]);
+  assert (y >= 0 && y < t->n[TABLE_VERT]);
+
+  int index = x + y * table_nc (t);
+  unsigned short opt = t->ct[index];
+  const void *cc = t->cc[index];
+
+  cell->options = opt;
+  cell->n_footnotes = 0;
+
+  int style_idx = (opt & TAB_STYLE_MASK) >> TAB_STYLE_SHIFT;
+  const struct area_style *style = t->styles[style_idx];
+  if (style)
+    cell->style = style;
+  else
+    {
+      static const struct area_style styles[3][3] = {
+#define S(H,V) [H][V] = { AREA_STYLE_INITIALIZER__,     \
+                          .cell_style.halign = H,       \
+                          .cell_style.valign = V }
+        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_TOP),
+        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_CENTER),
+        S(TABLE_HALIGN_LEFT, TABLE_VALIGN_BOTTOM),
+        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_TOP),
+        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_CENTER),
+        S(TABLE_HALIGN_CENTER, TABLE_VALIGN_BOTTOM),
+        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_TOP),
+        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_CENTER),
+        S(TABLE_HALIGN_RIGHT, TABLE_VALIGN_BOTTOM),
+      };
+
+      enum table_halign halign
+        = ((opt & TAB_HALIGN) == TAB_LEFT ? TABLE_HALIGN_LEFT
+           : (opt & TAB_HALIGN) == TAB_CENTER ? TABLE_HALIGN_CENTER
+           : TABLE_HALIGN_RIGHT);
+      enum table_valign valign
+        = ((opt & TAB_VALIGN) == TAB_TOP ? TABLE_VALIGN_TOP
+           : (opt & TAB_VALIGN) == TAB_MIDDLE ? TABLE_VALIGN_CENTER
+           : TABLE_VALIGN_BOTTOM);
+
+      cell->style = &styles[halign][valign];
+    }
+
+  if (opt & TAB_JOIN)
+    {
+      const struct tab_joined_cell *jc = cc;
+      cell->text = jc->text;
+
+      cell->footnotes = jc->footnotes;
+      cell->n_footnotes = jc->n_footnotes;
+
+      cell->d[TABLE_HORZ][0] = jc->d[TABLE_HORZ][0];
+      cell->d[TABLE_HORZ][1] = jc->d[TABLE_HORZ][1];
+      cell->d[TABLE_VERT][0] = jc->d[TABLE_VERT][0];
+      cell->d[TABLE_VERT][1] = jc->d[TABLE_VERT][1];
+
+      if (jc->style)
+        cell->style = jc->style;
+    }
+  else
+    {
+      cell->d[TABLE_HORZ][0] = x;
+      cell->d[TABLE_HORZ][1] = x + 1;
+      cell->d[TABLE_VERT][0] = y;
+      cell->d[TABLE_VERT][1] = y + 1;
+      cell->text = CONST_CAST (char *, cc ? cc : "");
+    }
+}
+
+/* Returns one of the TAL_* enumeration constants (declared in output/table.h)
+   representing a rule running alongside one of the cells in TABLE.
+
+   Suppose NC is the number of columns in TABLE and NR is the number of rows.
+   Then, if AXIS is TABLE_HORZ, then 0 <= X <= NC and 0 <= Y < NR.  If (X,Y) =
+   (0,0), the return value is the rule that runs vertically on the left side of
+   cell (0,0); if (X,Y) = (1,0), it is the vertical rule between that cell and
+   cell (1,0); and so on, up to (NC,0), which runs vertically on the right of
+   cell (NC-1,0).
+
+   The following diagram illustrates the meaning of (X,Y) for AXIS = TABLE_HORZ
+   within a 7x7 table.  The '|' characters at the intersection of the X labels
+   and Y labels show the rule whose style would be returned by calling
+   table_get_rule with those X and Y values:
+
+                           0  1  2  3  4  5  6  7
+                           +--+--+--+--+--+--+--+
+                         0 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         1 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         2 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         3 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         4 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         5 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+                         6 |  |  |  |  |  |  |  |
+                           +--+--+--+--+--+--+--+
+
+   Similarly, if AXIS is TABLE_VERT, then 0 <= X < NC and 0 <= Y <= NR.  If
+   (X,Y) = (0,0), the return value is the rule that runs horizontally above
+   the top of cell (0,0); if (X,Y) = (0,1), it is the horizontal rule
+   between that cell and cell (0,1); and so on, up to (0,NR), which runs
+   horizontally below cell (0,NR-1). */
+int
+table_get_rule (const struct table *table, enum table_axis axis, int x, int y,
+                struct cell_color *color)
+{
+  assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ));
+  assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT));
+
+  uint8_t raw = (axis == TABLE_VERT
+                 ? table->rh[x + table_nc (table) * y]
+                 : table->rv[x + (table_nc (table) + 1) * y]);
+  struct cell_color *p = table->rule_colors[(raw & TAB_RULE_STYLE_MASK)
+                                            >> TAB_RULE_STYLE_SHIFT];
+  *color = p ? *p : (struct cell_color) CELL_COLOR_BLACK;
+  return (raw & TAB_RULE_TYPE_MASK) >> TAB_RULE_TYPE_SHIFT;
+}