table: Simplify interface for number of rows and columns.
[pspp] / src / output / table.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2011, 2014, 2016 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/table.h"
20 #include "output/table-provider.h"
21
22 #include <assert.h>
23 #include <inttypes.h>
24 #include <stdlib.h>
25
26 #include "data/format.h"
27 #include "libpspp/assertion.h"
28 #include "libpspp/cast.h"
29 #include "libpspp/compiler.h"
30 #include "libpspp/pool.h"
31 #include "libpspp/str.h"
32 #include "output/table-item.h"
33 #include "output/tab.h"
34
35 #include "gl/xalloc.h"
36
37 /* Increases TABLE's reference count, indicating that it has an additional
38    owner.  An table that is shared among multiple owners must not be
39    modified. */
40 struct table *
41 table_ref (const struct table *table_)
42 {
43   struct table *table = CONST_CAST (struct table *, table_);
44   table->ref_cnt++;
45   return table;
46 }
47
48 /* Decreases TABLE's reference count, indicating that it has one fewer owner.
49    If TABLE no longer has any owners, it is freed. */
50 void
51 table_unref (struct table *table)
52 {
53   if (table != NULL)
54     {
55       assert (table->ref_cnt > 0);
56       if (--table->ref_cnt == 0)
57         table->klass->destroy (table);
58     }
59 }
60
61 /* Returns true if TABLE has more than one owner.  A table item that is shared
62    among multiple owners must not be modified. */
63 bool
64 table_is_shared (const struct table *table)
65 {
66   return table->ref_cnt > 1;
67 }
68
69 /* Sets the number of left header columns in TABLE to HL. */
70 void
71 table_set_hl (struct table *table, int hl)
72 {
73   assert (!table_is_shared (table));
74   table->h[TABLE_HORZ][0] = hl;
75 }
76
77 /* Sets the number of right header columns in TABLE to HR. */
78 void
79 table_set_hr (struct table *table, int hr)
80 {
81   assert (!table_is_shared (table));
82   table->h[TABLE_HORZ][1] = hr;
83 }
84
85 /* Sets the number of top header rows in TABLE to HT. */
86 void
87 table_set_ht (struct table *table, int ht)
88 {
89   assert (!table_is_shared (table));
90   table->h[TABLE_VERT][0] = ht;
91 }
92
93 /* Sets the number of top header rows in TABLE to HB. */
94 void
95 table_set_hb (struct table *table, int hb)
96 {
97   assert (!table_is_shared (table));
98   table->h[TABLE_VERT][1] = hb;
99 }
100 \f
101 /* Initializes TABLE as a table of the specified CLASS, initially with a
102    reference count of 1.
103
104    TABLE initially has NR rows and NC columns and no headers.  The table
105    implementation (or its client) may update the header rows and columns.
106
107    A table is an abstract class, that is, a plain struct table is not useful on
108    its own.  Thus, this function is normally called from the initialization
109    function of some subclass of table. */
110 void
111 table_init (struct table *table, const struct table_class *class,
112             int nc, int nr)
113 {
114   table->klass = class;
115   table->n[TABLE_HORZ] = nc;
116   table->n[TABLE_VERT] = nr;
117   table->h[TABLE_HORZ][0] = table->h[TABLE_HORZ][1] = 0;
118   table->h[TABLE_VERT][0] = table->h[TABLE_VERT][1] = 0;
119   table->ref_cnt = 1;
120 }
121 \f
122 struct area_style *
123 area_style_clone (struct pool *pool, const struct area_style *old)
124 {
125   struct area_style *new = pool_malloc (pool, sizeof *new);
126   *new = *old;
127   if (new->font_style.typeface)
128     new->font_style.typeface = pool_strdup (pool, new->font_style.typeface);
129   return new;
130 }
131
132 void
133 area_style_free (struct area_style *style)
134 {
135   if (style)
136     {
137       free (style->font_style.typeface);
138       free (style);
139     }
140 }
141
142 /* Initializes CELL with the contents of the table cell at column X and row Y
143    within TABLE.  When CELL is no longer needed, the caller is responsible for
144    freeing it by calling table_cell_free(CELL).
145
146    The caller must ensure that CELL is destroyed before TABLE is unref'ed. */
147 void
148 table_get_cell (const struct table *table, int x, int y,
149                 struct table_cell *cell)
150 {
151   assert (x >= 0 && x < table->n[TABLE_HORZ]);
152   assert (y >= 0 && y < table->n[TABLE_VERT]);
153
154   static const struct area_style default_style = AREA_STYLE_INITIALIZER;
155   cell->style = &default_style;
156
157   table->klass->get_cell (table, x, y, cell);
158 }
159
160 /* Returns one of the TAL_* enumeration constants (declared in output/table.h)
161    representing a rule running alongside one of the cells in TABLE.
162
163    Suppose NC is the number of columns in TABLE and NR is the number of rows.
164    Then, if AXIS is TABLE_HORZ, then 0 <= X <= NC and 0 <= Y < NR.  If (X,Y) =
165    (0,0), the return value is the rule that runs vertically on the left side of
166    cell (0,0); if (X,Y) = (1,0), it is the vertical rule between that cell and
167    cell (1,0); and so on, up to (NC,0), which runs vertically on the right of
168    cell (NC-1,0).
169
170    The following diagram illustrates the meaning of (X,Y) for AXIS = TABLE_HORZ
171    within a 7x7 table.  The '|' characters at the intersection of the X labels
172    and Y labels show the rule whose style would be returned by calling
173    table_get_rule with those X and Y values:
174
175                            0  1  2  3  4  5  6  7
176                            +--+--+--+--+--+--+--+
177                          0 |  |  |  |  |  |  |  |
178                            +--+--+--+--+--+--+--+
179                          1 |  |  |  |  |  |  |  |
180                            +--+--+--+--+--+--+--+
181                          2 |  |  |  |  |  |  |  |
182                            +--+--+--+--+--+--+--+
183                          3 |  |  |  |  |  |  |  |
184                            +--+--+--+--+--+--+--+
185                          4 |  |  |  |  |  |  |  |
186                            +--+--+--+--+--+--+--+
187                          5 |  |  |  |  |  |  |  |
188                            +--+--+--+--+--+--+--+
189                          6 |  |  |  |  |  |  |  |
190                            +--+--+--+--+--+--+--+
191
192    Similarly, if AXIS is TABLE_VERT, then 0 <= X < NC and 0 <= Y <= NR.  If
193    (X,Y) = (0,0), the return value is the rule that runs horizontally above
194    the top of cell (0,0); if (X,Y) = (0,1), it is the horizontal rule
195    between that cell and cell (0,1); and so on, up to (0,NR), which runs
196    horizontally below cell (0,NR-1). */
197 int
198 table_get_rule (const struct table *table, enum table_axis axis, int x, int y,
199                 struct cell_color *color)
200 {
201   assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ));
202   assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT));
203   *color = (struct cell_color) CELL_COLOR_BLACK;
204   return table->klass->get_rule (table, axis, x, y, color);
205 }
206
207 void
208 table_cell_format_footnote_markers (const struct table_cell *cell,
209                                     struct string *s)
210 {
211   for (size_t i = 0; i < cell->n_footnotes; i++)
212     {
213       if (i)
214         ds_put_byte (s, ',');
215       ds_put_cstr (s, cell->footnotes[i]->marker);
216     }
217 }
218
219 static const struct footnote **
220 add_footnotes (const struct footnote **refs, size_t n_refs,
221                const struct footnote **footnotes, size_t *allocated, size_t *n)
222 {
223   for (size_t i = 0; i < n_refs; i++)
224     {
225       const struct footnote *f = refs[i];
226       if (f->idx >= *allocated)
227         {
228           size_t new_allocated = (f->idx + 1) * 2;
229           footnotes = xrealloc (footnotes, new_allocated * sizeof *footnotes);
230           while (*allocated < new_allocated)
231             footnotes[(*allocated)++] = NULL;
232         }
233       footnotes[f->idx] = f;
234       if (f->idx >= *n)
235         *n = f->idx + 1;
236     }
237   return footnotes;
238 }
239
240 size_t
241 table_collect_footnotes (const struct table_item *item,
242                          const struct footnote ***footnotesp)
243 {
244   const struct footnote **footnotes = NULL;
245   size_t allocated = 0;
246   size_t n = 0;
247
248   struct table *t = item->table;
249   for (int y = 0; y < table_nr (t); y++)
250     {
251       struct table_cell cell;
252       for (int x = 0; x < table_nc (t); x = cell.d[TABLE_HORZ][1])
253         {
254           table_get_cell (t, x, y, &cell);
255
256           if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0])
257             footnotes = add_footnotes (cell.footnotes, cell.n_footnotes,
258                                        footnotes, &allocated, &n);
259         }
260     }
261
262   const struct table_item_text *title = table_item_get_title (item);
263   if (title)
264     footnotes = add_footnotes (title->footnotes, title->n_footnotes,
265                                footnotes, &allocated, &n);
266
267   const struct table_item_layers *layers = table_item_get_layers (item);
268   if (layers)
269     {
270       for (size_t i = 0; i < layers->n_layers; i++)
271         footnotes = add_footnotes (layers->layers[i].footnotes,
272                                    layers->layers[i].n_footnotes,
273                                    footnotes, &allocated, &n);
274     }
275
276   const struct table_item_text *caption = table_item_get_caption (item);
277   if (caption)
278     footnotes = add_footnotes (caption->footnotes, caption->n_footnotes,
279                                footnotes, &allocated, &n);
280
281   size_t n_nonnull = 0;
282   for (size_t i = 0; i < n; i++)
283     if (footnotes[i])
284       footnotes[n_nonnull++] = footnotes[i];
285
286   *footnotesp = footnotes;
287   return n_nonnull;
288 }
289 \f
290 /* Returns a table that contains a single cell, whose contents are the
291    left-aligned TEXT.  */
292 struct table *
293 table_from_string (const char *text)
294 {
295   struct tab_table *t = tab_create (1, 1);
296   tab_text (t, 0, 0, TAB_LEFT, text);
297   return &t->table;
298 }
299 \f
300 const char *
301 table_halign_to_string (enum table_halign halign)
302 {
303   switch (halign)
304     {
305     case TABLE_HALIGN_LEFT: return "left";
306     case TABLE_HALIGN_CENTER: return "center";
307     case TABLE_HALIGN_RIGHT: return "right";
308     case TABLE_HALIGN_DECIMAL: return "decimal";
309     case TABLE_HALIGN_MIXED: return "mixed";
310     default: return "**error**";
311     }
312 }
313
314 const char *
315 table_valign_to_string (enum table_valign valign)
316 {
317   switch (valign)
318     {
319     case TABLE_VALIGN_TOP: return "top";
320     case TABLE_VALIGN_CENTER: return "center";
321     case TABLE_VALIGN_BOTTOM: return "bottom";
322     default: return "**error**";
323     }
324 }
325
326 enum table_halign
327 table_halign_interpret (enum table_halign halign, bool numeric)
328 {
329   switch (halign)
330     {
331     case TABLE_HALIGN_LEFT:
332     case TABLE_HALIGN_CENTER:
333     case TABLE_HALIGN_RIGHT:
334       return halign;
335
336     case TABLE_HALIGN_MIXED:
337       return numeric ? TABLE_HALIGN_RIGHT : TABLE_HALIGN_LEFT;
338
339     case TABLE_HALIGN_DECIMAL:
340       return TABLE_HALIGN_DECIMAL;
341
342     default:
343       NOT_REACHED ();
344     }
345 }
346
347 void
348 font_style_copy (struct font_style *dst, const struct font_style *src)
349 {
350   *dst = *src;
351   if (dst->typeface)
352     dst->typeface = xstrdup (dst->typeface);
353 }
354
355 void
356 font_style_uninit (struct font_style *font)
357 {
358   if (font)
359     free (font->typeface);
360 }
361
362 void
363 area_style_copy (struct area_style *dst, const struct area_style *src)
364 {
365   font_style_copy (&dst->font_style, &src->font_style);
366   dst->cell_style = src->cell_style;
367 }
368
369 void
370 area_style_uninit (struct area_style *area)
371 {
372   if (area)
373     font_style_uninit (&area->font_style);
374 }
375
376 const char *
377 table_stroke_to_string (enum table_stroke stroke)
378 {
379   switch (stroke)
380     {
381     case TABLE_STROKE_NONE: return "none";
382     case TABLE_STROKE_SOLID: return "solid";
383     case TABLE_STROKE_DASHED: return "dashed";
384     case TABLE_STROKE_THICK: return "thick";
385     case TABLE_STROKE_THIN: return "thin";
386     case TABLE_STROKE_DOUBLE: return "double";
387     default:
388       return "**error**";
389     }
390 }
391
392 void
393 cell_color_dump (const struct cell_color *c)
394 {
395   if (c->alpha != 255)
396     printf ("rgba(%d, %d, %d, %d)", c->r, c->g, c->b, c->alpha);
397   else
398     printf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8, c->r, c->g, c->b);
399 }
400
401 void
402 font_style_dump (const struct font_style *f)
403 {
404   printf ("%s %dpx ", f->typeface, f->size);
405   cell_color_dump (&f->fg[0]);
406   putchar ('/');
407   cell_color_dump (&f->bg[0]);
408   if (!cell_color_equal (&f->fg[0], &f->fg[1])
409       || !cell_color_equal (&f->bg[0], &f->bg[1]))
410     {
411       printf (" alt=");
412       cell_color_dump (&f->fg[1]);
413       putchar ('/');
414       cell_color_dump (&f->bg[1]);
415     }
416   if (f->bold)
417     fputs (" bold", stdout);
418   if (f->italic)
419     fputs (" italic", stdout);
420   if (f->underline)
421     fputs (" underline", stdout);
422 }
423
424 void
425 cell_style_dump (const struct cell_style *c)
426 {
427   fputs (table_halign_to_string (c->halign), stdout);
428   if (c->halign == TABLE_HALIGN_DECIMAL)
429     printf ("(%.2gpx)", c->decimal_offset);
430   printf (" %s", table_valign_to_string (c->valign));
431   printf (" %d,%d,%d,%dpx",
432           c->margin[TABLE_HORZ][0], c->margin[TABLE_HORZ][1],
433           c->margin[TABLE_VERT][0], c->margin[TABLE_VERT][1]);
434 }