+add_references (const struct pivot_table *pt, const struct table *table,
+ bool *refs, size_t *n_refs)
+{
+ if (!table)
+ return;
+
+ for (int y = 0; y < table->n[V]; y++)
+ for (int x = 0; x < table->n[H]; )
+ {
+ struct table_cell cell;
+ table_get_cell (table, x, y, &cell);
+
+ if (x == cell.d[H][0] && y == cell.d[V][0])
+ {
+ const struct pivot_value_ex *ex = pivot_value_ex (cell.value);
+ for (size_t i = 0; i < ex->n_footnotes; i++)
+ {
+ size_t idx = ex->footnote_indexes[i];
+ assert (idx < pt->n_footnotes);
+
+ if (!refs[idx] && pt->footnotes[idx]->show)
+ {
+ refs[idx] = true;
+ (*n_refs)++;
+ }
+ }
+ }
+
+ x = cell.d[TABLE_HORZ][1];
+ }
+}
+
+static struct pivot_footnote **
+collect_footnotes (const struct pivot_table *pt,
+ const struct table *title,
+ const struct table *layers,
+ const struct table *body,
+ const struct table *caption,
+ size_t *n_footnotesp)
+{
+ if (!pt->n_footnotes)
+ {
+ *n_footnotesp = 0;
+ return NULL;
+ }
+
+ bool *refs = XCALLOC (pt->n_footnotes, bool);
+ size_t n_refs = 0;
+ add_references (pt, title, refs, &n_refs);
+ add_references (pt, layers, refs, &n_refs);
+ add_references (pt, body, refs, &n_refs);
+ add_references (pt, caption, refs, &n_refs);
+
+ struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
+ size_t n_footnotes = 0;
+ for (size_t i = 0; i < pt->n_footnotes; i++)
+ if (refs[i])
+ footnotes[n_footnotes++] = pt->footnotes[i];
+ assert (n_footnotes == n_refs);
+
+ free (refs);
+
+ *n_footnotesp = n_footnotes;
+ return footnotes;
+}
+
+static enum pivot_border
+pivot_border_fallback (enum pivot_border border)
+{
+ switch (border)
+ {
+ case PIVOT_BORDER_TITLE:
+ case PIVOT_BORDER_OUTER_LEFT:
+ case PIVOT_BORDER_OUTER_TOP:
+ case PIVOT_BORDER_OUTER_RIGHT:
+ case PIVOT_BORDER_OUTER_BOTTOM:
+ case PIVOT_BORDER_INNER_LEFT:
+ case PIVOT_BORDER_INNER_TOP:
+ case PIVOT_BORDER_INNER_RIGHT:
+ case PIVOT_BORDER_INNER_BOTTOM:
+ case PIVOT_BORDER_DATA_LEFT:
+ case PIVOT_BORDER_DATA_TOP:
+ return border;
+
+ /* Dimensions. */
+ case PIVOT_BORDER_DIM_ROW_HORZ:
+ return PIVOT_BORDER_CAT_ROW_HORZ;
+ case PIVOT_BORDER_DIM_ROW_VERT:
+ return PIVOT_BORDER_CAT_ROW_VERT;
+ case PIVOT_BORDER_DIM_COL_HORZ:
+ return PIVOT_BORDER_CAT_COL_HORZ;
+ case PIVOT_BORDER_DIM_COL_VERT:
+ return PIVOT_BORDER_CAT_COL_VERT;
+
+ /* Categories. */
+ case PIVOT_BORDER_CAT_ROW_HORZ:
+ case PIVOT_BORDER_CAT_ROW_VERT:
+ case PIVOT_BORDER_CAT_COL_HORZ:
+ case PIVOT_BORDER_CAT_COL_VERT:
+ return border;
+
+ case PIVOT_N_BORDERS:
+ default:
+ NOT_REACHED ();
+ }
+}
+
+static struct table_border_style
+resolve_border_style (const struct pivot_table_look *look, enum pivot_border b,
+ bool show_grid_lines)
+{
+ struct table_border_style style = look->borders[b];
+ if (style.stroke != TABLE_STROKE_NONE)
+ return style;
+
+ style = look->borders[pivot_border_fallback (b)];
+ if (style.stroke != TABLE_STROKE_NONE)
+ return style;
+
+ if (show_grid_lines)
+ return (struct table_border_style) { .stroke = TABLE_STROKE_DASHED,
+ .color = CELL_COLOR_BLACK };
+
+ return style;
+}
+
+void
+pivot_output (const struct pivot_table *pt,
+ const size_t *layer_indexes,
+ bool printing,
+ struct table **titlep,
+ struct table **layersp,
+ struct table **bodyp,
+ struct table **captionp,
+ struct table **footnotesp,
+ struct pivot_footnote ***fp, size_t *nfp)