X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Ftable.c;h=44c33b9d10b36ceabb6df4125d1038567f16076d;hb=11ce7bf9a452f1358cdff5f7880663378fe3abeb;hp=696931ae50980147be4212dd1af477a73c2448d1;hpb=dfd1972f7bcb550a4fc3b05dbe7e71d12334b0a7;p=pspp diff --git a/src/output/table.c b/src/output/table.c index 696931ae50..44c33b9d10 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009 Free Software Foundation, Inc. + Copyright (C) 2009, 2011, 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 @@ -16,17 +16,29 @@ #include -#include -#include +#include "output/table.h" +#include "output/table-provider.h" #include +#include #include -#include -#include +#include "data/format.h" +#include "libpspp/assertion.h" +#include "libpspp/cast.h" +#include "libpspp/compiler.h" +#include "libpspp/pool.h" +#include "libpspp/str.h" +#include "output/table-item.h" +#include "output/table.h" +#include "output/text-item.h" #include "gl/xalloc.h" +/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */ +#define H TABLE_HORZ +#define V TABLE_VERT + /* Increases TABLE's reference count, indicating that it has an additional owner. An table that is shared among multiple owners must not be modified. */ @@ -47,7 +59,7 @@ table_unref (struct table *table) { assert (table->ref_cnt > 0); if (--table->ref_cnt == 0) - table->class->destroy (table); + pool_destroy (table->container); } } @@ -58,282 +70,806 @@ table_is_shared (const struct table *table) { return table->ref_cnt > 1; } + +struct table_area_style * +table_area_style_clone (struct pool *pool, const struct table_area_style *old) +{ + struct table_area_style *new = pool_malloc (pool, sizeof *new); + *new = *old; + if (new->font_style.typeface) + new->font_style.typeface = pool_strdup (pool, new->font_style.typeface); + return new; +} -/* Sets the number of left header columns in TABLE to HL. */ void -table_set_hl (struct table *table, int hl) +table_area_style_free (struct table_area_style *style) { - assert (!table_is_shared (table)); - table->h[TABLE_HORZ][0] = hl; + if (style) + { + free (style->font_style.typeface); + free (style); + } +} + +struct footnote * +footnote_clone (const struct footnote *old) +{ + struct footnote *new = xmalloc (sizeof *new); + *new = (struct footnote) { + .idx = old->idx, + .content = old->content ? xstrdup (old->content) : NULL, + .marker = old->marker ? xstrdup (old->marker) : NULL, + .style = old->style ? table_area_style_clone (NULL, old->style) : NULL, + }; + return new; } -/* Sets the number of right header columns in TABLE to HR. */ void -table_set_hr (struct table *table, int hr) +footnote_destroy (struct footnote *f) { - assert (!table_is_shared (table)); - table->h[TABLE_HORZ][1] = hr; + if (f) + { + free (f->content); + free (f->marker); + if (f->style) + { + table_area_style_uninit (f->style); + free (f->style); + } + free (f); + } +} + +struct table_cell * +table_cell_clone (const struct table_cell *old) +{ + struct table_cell *new = xmalloc (sizeof *new); + *new = *old; + new->text = xstrdup (new->text); + + if (old->n_subscripts) + { + new->subscripts = xnmalloc (old->n_subscripts, sizeof *new->subscripts); + for (size_t i = 0; i < old->n_subscripts; i++) + new->subscripts[i] = xstrdup (old->subscripts[i]); + } + else + new->subscripts = NULL; + + if (old->n_footnotes) + { + new->footnotes = xnmalloc (old->n_footnotes, sizeof *new->footnotes); + for (size_t i = 0; i < old->n_footnotes; i++) + new->footnotes[i] = footnote_clone (old->footnotes[i]); + } + else + new->footnotes = NULL; + + if (old->style) + new->style = table_area_style_clone (NULL, old->style); + + return new; } -/* Sets the number of top header rows in TABLE to HT. */ void -table_set_ht (struct table *table, int ht) +table_cell_destroy (struct table_cell *cell) { - assert (!table_is_shared (table)); - table->h[TABLE_VERT][0] = ht; + if (!cell) + return; + + free (cell->text); + for (size_t i = 0; i < cell->n_subscripts; i++) + free (cell->subscripts[i]); + free (cell->subscripts); + for (size_t i = 0; i < cell->n_footnotes; i++) + footnote_destroy (cell->footnotes[i]); + free (cell->footnotes); + if (cell->style) + { + table_area_style_uninit (cell->style); + free (cell->style); + } + free (cell); } -/* Sets the number of top header rows in TABLE to HB. */ void -table_set_hb (struct table *table, int hb) +table_cell_format_footnote_markers (const struct table_cell *cell, + struct string *s) { - assert (!table_is_shared (table)); - table->h[TABLE_VERT][1] = hb; + for (size_t i = 0; i < cell->n_footnotes; i++) + { + if (i) + ds_put_byte (s, ','); + ds_put_cstr (s, cell->footnotes[i]->marker); + } +} + +static struct footnote ** +add_footnotes (struct footnote **refs, size_t n_refs, + struct footnote **footnotes, size_t *allocated, size_t *n) +{ + for (size_t i = 0; i < n_refs; i++) + { + struct footnote *f = refs[i]; + if (f->idx >= *allocated) + { + size_t new_allocated = (f->idx + 1) * 2; + footnotes = xrealloc (footnotes, new_allocated * sizeof *footnotes); + while (*allocated < new_allocated) + footnotes[(*allocated)++] = NULL; + } + footnotes[f->idx] = f; + if (f->idx >= *n) + *n = f->idx + 1; + } + return footnotes; +} + +size_t +table_collect_footnotes (const struct table_item *item, + struct footnote ***footnotesp) +{ + struct footnote **footnotes = NULL; + size_t allocated = 0; + size_t n = 0; + + struct table *t = item->table; + for (int y = 0; y < t->n[V]; y++) + { + struct table_cell cell; + for (int x = 0; x < t->n[H]; x = cell.d[TABLE_HORZ][1]) + { + table_get_cell (t, x, y, &cell); + + if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0]) + footnotes = add_footnotes (cell.footnotes, cell.n_footnotes, + footnotes, &allocated, &n); + } + } + + const struct table_cell *title = table_item_get_title (item); + if (title) + footnotes = add_footnotes (title->footnotes, title->n_footnotes, + footnotes, &allocated, &n); + + const struct table_item_layers *layers = table_item_get_layers (item); + if (layers) + { + for (size_t i = 0; i < layers->n_layers; i++) + footnotes = add_footnotes (layers->layers[i].footnotes, + layers->layers[i].n_footnotes, + footnotes, &allocated, &n); + } + + const struct table_cell *caption = table_item_get_caption (item); + if (caption) + footnotes = add_footnotes (caption->footnotes, caption->n_footnotes, + footnotes, &allocated, &n); + + size_t n_nonnull = 0; + for (size_t i = 0; i < n; i++) + if (footnotes[i]) + footnotes[n_nonnull++] = footnotes[i]; + + *footnotesp = footnotes; + return n_nonnull; } -/* Initializes TABLE as a table of the specified CLASS, initially with a - reference count of 1. +const char * +table_halign_to_string (enum table_halign halign) +{ + switch (halign) + { + case TABLE_HALIGN_LEFT: return "left"; + case TABLE_HALIGN_CENTER: return "center"; + case TABLE_HALIGN_RIGHT: return "right"; + case TABLE_HALIGN_DECIMAL: return "decimal"; + case TABLE_HALIGN_MIXED: return "mixed"; + default: return "**error**"; + } +} - TABLE initially has 0 rows and columns and no headers. The table - implementation should update the numbers of rows and columns. The table - implementation (or its client) may update the header rows and columns. +const char * +table_valign_to_string (enum table_valign valign) +{ + switch (valign) + { + case TABLE_VALIGN_TOP: return "top"; + case TABLE_VALIGN_CENTER: return "center"; + case TABLE_VALIGN_BOTTOM: return "bottom"; + default: return "**error**"; + } +} - A table is an abstract class, that is, a plain struct table is not useful on - its own. Thus, this function is normally called from the initialization - function of some subclass of table. */ -void -table_init (struct table *table, const struct table_class *class) +enum table_halign +table_halign_interpret (enum table_halign halign, bool numeric) { - table->class = class; - table->n[TABLE_HORZ] = table->n[TABLE_VERT] = 0; - table->h[TABLE_HORZ][0] = table->h[TABLE_HORZ][1] = 0; - table->h[TABLE_VERT][0] = table->h[TABLE_VERT][1] = 0; - table->ref_cnt = 1; + switch (halign) + { + case TABLE_HALIGN_LEFT: + case TABLE_HALIGN_CENTER: + case TABLE_HALIGN_RIGHT: + return halign; + + case TABLE_HALIGN_MIXED: + return numeric ? TABLE_HALIGN_RIGHT : TABLE_HALIGN_LEFT; + + case TABLE_HALIGN_DECIMAL: + return TABLE_HALIGN_DECIMAL; + + default: + NOT_REACHED (); + } } -/* Sets the number of columns in TABLE to NC. */ void -table_set_nc (struct table *table, int nc) +font_style_copy (struct pool *container, + struct font_style *dst, const struct font_style *src) { - assert (!table_is_shared (table)); - table->n[TABLE_HORZ] = nc; + *dst = *src; + if (dst->typeface) + dst->typeface = pool_strdup (container, dst->typeface); } -/* Sets the number of rows in TABLE to NR. */ void -table_set_nr (struct table *table, int nr) +font_style_uninit (struct font_style *font) { - assert (!table_is_shared (table)); - table->n[TABLE_VERT] = nr; + if (font) + free (font->typeface); } - -/* 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) +table_area_style_copy (struct pool *container, struct table_area_style *dst, + const struct table_area_style *src) { - assert (x >= 0 && x < table->n[TABLE_HORZ]); - assert (y >= 0 && y < table->n[TABLE_VERT]); - table->class->get_cell (table, x, y, cell); + font_style_copy (container, &dst->font_style, &src->font_style); + dst->cell_style = src->cell_style; } -/* Frees CELL, which should have been initialized by calling - table_get_cell(). */ void -table_cell_free (struct table_cell *cell) +table_area_style_uninit (struct table_area_style *area) { - if (cell->destructor != NULL) - cell->destructor (cell->destructor_aux); + if (area) + font_style_uninit (&area->font_style); } -/* Returns one of the TAL_* enumeration constants (declared in output/table.h) - representing a rule running alongside one of the cells in TABLE. +const char * +table_stroke_to_string (enum table_stroke stroke) +{ + switch (stroke) + { + case TABLE_STROKE_NONE: return "none"; + case TABLE_STROKE_SOLID: return "solid"; + case TABLE_STROKE_DASHED: return "dashed"; + case TABLE_STROKE_THICK: return "thick"; + case TABLE_STROKE_THIN: return "thin"; + case TABLE_STROKE_DOUBLE: return "double"; + default: + return "**error**"; + } +} - 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). +void +cell_color_dump (const struct cell_color *c) +{ + if (c->alpha != 255) + printf ("rgba(%d, %d, %d, %d)", c->r, c->g, c->b, c->alpha); + else + printf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8, c->r, c->g, c->b); +} - 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: +void +font_style_dump (const struct font_style *f) +{ + printf ("%s %dpx ", f->typeface, f->size); + cell_color_dump (&f->fg[0]); + putchar ('/'); + cell_color_dump (&f->bg[0]); + if (!cell_color_equal (&f->fg[0], &f->fg[1]) + || !cell_color_equal (&f->bg[0], &f->bg[1])) + { + printf (" alt="); + cell_color_dump (&f->fg[1]); + putchar ('/'); + cell_color_dump (&f->bg[1]); + } + if (f->bold) + fputs (" bold", stdout); + if (f->italic) + fputs (" italic", stdout); + if (f->underline) + fputs (" underline", stdout); +} - 0 1 2 3 4 5 6 7 - +--+--+--+--+--+--+--+ - 0 | | | | | | | | - +--+--+--+--+--+--+--+ - 1 | | | | | | | | - +--+--+--+--+--+--+--+ - 2 | | | | | | | | - +--+--+--+--+--+--+--+ - 3 | | | | | | | | - +--+--+--+--+--+--+--+ - 4 | | | | | | | | - +--+--+--+--+--+--+--+ - 5 | | | | | | | | - +--+--+--+--+--+--+--+ - 6 | | | | | | | | - +--+--+--+--+--+--+--+ +bool +font_style_equal (const struct font_style *a, const struct font_style *b) +{ + return (a->bold == b->bold + && a->italic == b->italic + && a->underline == b->underline + && a->markup == b->markup + && cell_color_equal (&a->fg[0], &b->fg[0]) + && cell_color_equal (&a->fg[1], &b->fg[1]) + && cell_color_equal (&a->bg[0], &b->bg[0]) + && cell_color_equal (&a->bg[1], &b->bg[1]) + && !strcmp (a->typeface ? a->typeface : "", + b->typeface ? b->typeface : "") + && a->size == b->size); +} - 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) +void +cell_style_dump (const struct cell_style *c) { - assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ)); - assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT)); - return table->class->get_rule (table, axis, x, y); + fputs (table_halign_to_string (c->halign), stdout); + if (c->halign == TABLE_HALIGN_DECIMAL) + printf ("(%.2gpx)", c->decimal_offset); + printf (" %s", table_valign_to_string (c->valign)); + printf (" %d,%d,%d,%dpx", + c->margin[TABLE_HORZ][0], c->margin[TABLE_HORZ][1], + c->margin[TABLE_VERT][0], c->margin[TABLE_VERT][1]); } -struct table_unshared - { - struct table table; - struct table *subtable; - }; -static const struct table_class table_unshared_class; +static const bool debugging = true; -/* Takes ownership of TABLE and returns a table with the same contents but - which is guaranteed not to be shared (as returned by table_is_shared()). +/* Creates and returns a new table with NC columns and NR rows and initially no + header rows or columns. - If TABLE is unshared, just returns TABLE. + 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 only real use for this function is to create a copy of TABLE in which - the headers can be adjusted, which is a pretty specialized use case. */ + The table's cells are initially empty. */ struct table * -table_unshare (struct table *table) +table_create (int nc, int nr, int hl, int hr, int ht, int hb) { - if (!table_is_shared (table)) - return table; - else + 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, TABLE_STROKE_NONE, nc * (nr + 1)); + + t->rv = pool_nmalloc (t->container, nr, nc + 1); + memset (t->rv, TABLE_STROKE_NONE, nr * (nc + 1)); + + memset (t->styles, 0, sizeof t->styles); + memset (t->rule_colors, 0, sizeof t->rule_colors); + + return t; +} + +/* 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 +table_vline (struct table *t, int style, int x, int y1, int y2) +{ + if (debugging) { - struct table_unshared *tiu = xmalloc (sizeof *tiu); - table_init (&tiu->table, &table_unshared_class); - table_set_nc (&tiu->table, table_nc (table)); - table_set_nr (&tiu->table, table_nr (table)); - table_set_hl (&tiu->table, table_hl (table)); - table_set_hr (&tiu->table, table_hr (table)); - table_set_ht (&tiu->table, table_ht (table)); - table_set_hb (&tiu->table, table_hb (table)); - tiu->subtable = table; - return &tiu->table; + if (x < 0 || x > t->n[H] + || y1 < 0 || y1 >= t->n[V] + || y2 < 0 || y2 >= t->n[V]) + { + printf ("bad vline: x=%d y=(%d,%d) in table size (%d,%d)\n", + x, y1, y2, t->n[H], t->n[V]); + return; + } + } + + assert (x >= 0); + assert (x <= t->n[H]); + assert (y1 >= 0); + assert (y2 >= y1); + assert (y2 <= t->n[V]); + + if (style != -1) + { + int y; + for (y = y1; y <= y2; y++) + t->rv[x + (t->n[H] + 1) * y] = style; } } -static struct table_unshared * -table_unshared_cast (const struct table *table) +/* Draws a horizontal line above cells at vertical position Y from X1 + to X2 inclusive in style STYLE, if style is not -1. */ +void +table_hline (struct table *t, int style, int x1, int x2, int y) { - assert (table->class == &table_unshared_class); - return UP_CAST (table, struct table_unshared, table); + if (debugging) + { + if (y < 0 || y > t->n[V] + || x1 < 0 || x1 >= t->n[H] + || x2 < 0 || x2 >= t->n[H]) + { + printf ("bad hline: x=(%d,%d) y=%d in table size (%d,%d)\n", + x1, x2, y, t->n[H], t->n[V]); + return; + } + } + + assert (y >= 0); + assert (y <= t->n[V]); + assert (x2 >= x1); + assert (x1 >= 0); + assert (x2 < t->n[H]); + + if (style != -1) + { + int x; + for (x = x1; x <= x2; x++) + t->rh[x + t->n[H] * y] = style; + } } -static void -table_unshared_destroy (struct table *tiu_) +/* 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 +table_box (struct table *t, int f_h, int f_v, int i_h, int i_v, + int x1, int y1, int x2, int y2) { - struct table_unshared *tiu = table_unshared_cast (tiu_); - table_unref (tiu->subtable); - free (tiu); + if (debugging) + { + if (x1 < 0 || x1 >= t->n[H] + || x2 < 0 || x2 >= t->n[H] + || y1 < 0 || y1 >= t->n[V] + || y2 < 0 || y2 >= t->n[V]) + { + printf ("bad box: (%d,%d)-(%d,%d) in table size (%d,%d)\n", + x1, y1, x2, y2, t->n[H], t->n[V]); + NOT_REACHED (); + } + } + + assert (x2 >= x1); + assert (y2 >= y1); + assert (x1 >= 0); + assert (y1 >= 0); + assert (x2 < t->n[H]); + assert (y2 < t->n[V]); + + if (f_h != -1) + { + int x; + for (x = x1; x <= x2; x++) + { + t->rh[x + t->n[H] * y1] = f_h; + t->rh[x + t->n[H] * (y2 + 1)] = f_h; + } + } + if (f_v != -1) + { + int y; + for (y = y1; y <= y2; y++) + { + t->rv[x1 + (t->n[H] + 1) * y] = f_v; + t->rv[(x2 + 1) + (t->n[H] + 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 + t->n[H] * 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 + (t->n[H] + 1) * y] = i_v; + } + } } + +/* Cells. */ static void -table_unshared_get_cell (const struct table *tiu_, int x, int y, - struct table_cell *cell) +do_table_text (struct table *table, int c, int r, unsigned opt, char *text) { - struct table_unshared *tiu = table_unshared_cast (tiu_); - table_get_cell (tiu->subtable, x, y, cell); + assert (c >= 0); + assert (r >= 0); + assert (c < table->n[H]); + assert (r < table->n[V]); + + if (debugging) + { + if (c < 0 || r < 0 || c >= table->n[H] || r >= table->n[V]) + { + printf ("table_text(): bad cell (%d,%d) in table size (%d,%d)\n", + c, r, table->n[H], table->n[V]); + return; + } + } + + table->cc[c + r * table->n[H]] = text; + table->ct[c + r * table->n[H]] = opt; } -static int -table_unshared_get_rule (const struct table *tiu_, - enum table_axis axis, int x, int y) +/* Sets cell (C,R) in TABLE, with options OPT, to have text value + TEXT. */ +void +table_text (struct table *table, int c, int r, unsigned opt, + const char *text) { - struct table_unshared *tiu = table_unshared_cast (tiu_); - return table_get_rule (tiu->subtable, axis, x, y); + do_table_text (table, c, r, opt, pool_strdup (table->container, text)); } -static const struct table_class table_unshared_class = - { - table_unshared_destroy, - table_unshared_get_cell, - table_unshared_get_rule, - NULL, /* paste */ - NULL, /* select */ - }; - -struct table_string - { - struct table table; - char *string; - unsigned int options; +/* Sets cell (C,R) in TABLE, with options OPT, to have text value + FORMAT, which is formatted as if passed to printf. */ +void +table_text_format (struct table *table, int c, int r, unsigned opt, + const char *format, ...) +{ + va_list args; + + va_start (args, format); + do_table_text (table, c, r, opt, + pool_vasprintf (table->container, format, args)); + va_end (args); +} + +static struct table_cell * +add_joined_cell (struct table *table, int x1, int y1, int x2, int y2, + unsigned opt) +{ + assert (x1 >= 0); + assert (y1 >= 0); + assert (y2 >= y1); + assert (x2 >= x1); + assert (y2 < table->n[V]); + assert (x2 < table->n[H]); + + if (debugging) + { + if (x1 < 0 || x1 >= table->n[H] + || y1 < 0 || y1 >= table->n[V] + || x2 < x1 || x2 >= table->n[H] + || y2 < y1 || y2 >= table->n[V]) + { + printf ("table_joint_text(): bad cell " + "(%d,%d)-(%d,%d) in table size (%d,%d)\n", + x1, y1, x2, y2, table->n[H], table->n[V]); + return NULL; + } + } + + table_box (table, -1, -1, TABLE_STROKE_NONE, TABLE_STROKE_NONE, + x1, y1, x2, y2); + + struct table_cell *cell = pool_alloc (table->container, sizeof *cell); + *cell = (struct table_cell) { + .d = { [TABLE_HORZ] = { x1, ++x2 }, + [TABLE_VERT] = { y1, ++y2 } }, + .options = opt, }; -static const struct table_class table_string_class; + 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; +} -/* Returns a table that contains a single cell, whose contents are S with - options OPTIONS (a combination of TAB_* values). */ -struct table * -table_from_string (unsigned int options, const char *s) +/* 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) { - struct table_string *ts = xmalloc (sizeof *ts); - table_init (&ts->table, &table_string_class); - ts->table.n[TABLE_HORZ] = ts->table.n[TABLE_VERT] = 1; - ts->string = xstrdup (s); - ts->options = options; - return &ts->table; + 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_string * -table_string_cast (const struct table *table) +static struct table_cell * +get_joined_cell (struct table *table, int x, int y) { - assert (table->class == &table_string_class); - return UP_CAST (table, struct table_string, table); + 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; } -static void -table_string_destroy (struct table *ts_) +/* 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_string *ts = table_string_cast (ts_); - free (ts->string); - free (ts); + 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]); } -static void -table_string_get_cell (const struct table *ts_, int x UNUSED, int y UNUSED, - struct table_cell *cell) +/* 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) { - struct table_string *ts = table_string_cast (ts_); - cell->d[TABLE_HORZ][0] = 0; - cell->d[TABLE_HORZ][1] = 1; - cell->d[TABLE_VERT][0] = 0; - cell->d[TABLE_VERT][1] = 1; - cell->contents = ts->string; - cell->options = ts->options; - cell->destructor = NULL; + 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; +} -static int -table_string_get_rule (const struct table *ts UNUSED, - enum table_axis axis UNUSED, int x UNUSED, int y UNUSED) +/* 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) { - return TAL_0; + get_joined_cell (table, x, y)->style = style; } -static const struct table_class table_string_class = - { - table_string_destroy, - table_string_get_cell, - table_string_get_rule, - NULL, /* paste */ - NULL, /* select */ - }; +/* 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; +} + +/* 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; +}