+
+ void **cc = &table->cc[x1 + y1 * table->n[H]];
+ unsigned short *ct = &table->ct[x1 + y1 * table->n[H]];
+ const int ofs = table->n[H] - (x2 - x1);
+ for (int y = y1; y < y2; y++)
+ {
+ for (int x = x1; x < x2; x++)
+ {
+ *cc++ = cell;
+ *ct++ = opt | TAB_JOIN;
+ }
+
+ cc += ofs;
+ ct += ofs;
+ }
+
+ return cell;
+}
+
+/* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
+ options OPT to have text value TEXT. */
+void
+table_joint_text (struct table *table, int x1, int y1, int x2, int y2,
+ unsigned opt, const char *text)
+{
+ char *s = pool_strdup (table->container, text);
+ if (x1 == x2 && y1 == y2)
+ do_table_text (table, x1, y1, opt, s);
+ else
+ add_joined_cell (table, x1, y1, x2, y2, opt)->text = s;
+}
+
+static struct table_cell *
+get_joined_cell (struct table *table, int x, int y)
+{
+ int index = x + y * table->n[H];
+ unsigned short opt = table->ct[index];
+ struct table_cell *cell;
+
+ if (opt & TAB_JOIN)
+ cell = table->cc[index];
+ else
+ {
+ char *text = table->cc[index];
+
+ cell = add_joined_cell (table, x, y, x, y, table->ct[index]);
+ cell->text = text ? text : pool_strdup (table->container, "");
+ }
+ return cell;
+}
+
+/* Sets the subscripts for column X, row Y in TABLE. */
+void
+table_add_subscripts (struct table *table, int x, int y,
+ char **subscripts, size_t n_subscripts)
+{
+ struct table_cell *cell = get_joined_cell (table, x, y);
+
+ cell->n_subscripts = n_subscripts;
+ cell->subscripts = pool_nalloc (table->container, n_subscripts,
+ sizeof *cell->subscripts);
+ for (size_t i = 0; i < n_subscripts; i++)
+ cell->subscripts[i] = pool_strdup (table->container, subscripts[i]);
+}
+
+/* Create a footnote in TABLE with MARKER (e.g. "a") as its marker and CONTENT
+ as its content. The footnote will be styled as STYLE, which is mandatory.
+ IDX must uniquely identify the footnote within TABLE.
+
+ Returns the new footnote. The return value is the only way to get to the
+ footnote later, so it is important for the caller to remember it. */
+struct footnote *
+table_create_footnote (struct table *table, size_t idx, const char *content,
+ const char *marker, struct table_area_style *style)
+{
+ assert (style);
+
+ struct footnote *f = pool_alloc (table->container, sizeof *f);
+ f->idx = idx;
+ f->content = pool_strdup (table->container, content);
+ f->marker = pool_strdup (table->container, marker);
+ f->style = style;
+ return f;
+}
+
+/* Attaches a reference to footnote F to the cell at column X, row Y in
+ TABLE. */
+void
+table_add_footnote (struct table *table, int x, int y, struct footnote *f)
+{
+ assert (f->style);
+
+ struct table_cell *cell = get_joined_cell (table, x, y);
+
+ cell->footnotes = pool_realloc (
+ table->container, cell->footnotes,
+ (cell->n_footnotes + 1) * sizeof *cell->footnotes);
+
+ cell->footnotes[cell->n_footnotes++] = f;
+}
+
+/* Overrides the style for column X, row Y in TABLE with STYLE.
+ Does not make a copy of STYLE, so it should either be allocated from
+ TABLE->container or have a lifetime that will outlive TABLE. */
+void
+table_add_style (struct table *table, int x, int y,
+ struct table_area_style *style)
+{
+ get_joined_cell (table, x, y)->style = style;
+}
+
+/* Returns true if column C, row R has no contents, otherwise false. */
+bool
+table_cell_is_empty (const struct table *table, int c, int r)
+{
+ return table->cc[c + r * table->n[H]] == NULL;
+}
+\f
+/* Initializes CELL with the contents of the table cell at column X and row Y
+ within TABLE. */
+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 * t->n[H];
+ unsigned short opt = t->ct[index];
+ const void *cc = t->cc[index];
+
+ struct table_area_style *style
+ = t->styles[(opt & TAB_STYLE_MASK) >> TAB_STYLE_SHIFT];
+ if (opt & TAB_JOIN)
+ {
+ const struct table_cell *jc = cc;
+ *cell = *jc;
+ if (!cell->style)
+ cell->style = style;
+ }
+ else
+ *cell = (struct table_cell) {
+ .d = { [TABLE_HORZ] = { x, x + 1 },
+ [TABLE_VERT] = { y, y + 1 } },
+ .options = opt,
+ .text = CONST_CAST (char *, cc ? cc : ""),
+ .style = style,
+ };
+
+ assert (cell->style);
+}
+
+/* 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->n[H] * y]
+ : table->rv[x + (table->n[H] + 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;
+}