18b822ba70c5c7c4405e3f28f54c7d2c752e3daa
[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 0 rows and columns and no headers.  The table
105    implementation should update the numbers of rows and columns.  The table
106    implementation (or its client) may update the header rows and columns.
107
108    A table is an abstract class, that is, a plain struct table is not useful on
109    its own.  Thus, this function is normally called from the initialization
110    function of some subclass of table. */
111 void
112 table_init (struct table *table, const struct table_class *class)
113 {
114   table->klass = class;
115   table->n[TABLE_HORZ] = table->n[TABLE_VERT] = 0;
116   table->h[TABLE_HORZ][0] = table->h[TABLE_HORZ][1] = 0;
117   table->h[TABLE_VERT][0] = table->h[TABLE_VERT][1] = 0;
118   table->ref_cnt = 1;
119 }
120
121 /* Sets the number of columns in TABLE to NC. */
122 void
123 table_set_nc (struct table *table, int nc)
124 {
125   assert (!table_is_shared (table));
126   table->n[TABLE_HORZ] = nc;
127 }
128
129 /* Sets the number of rows in TABLE to NR. */
130 void
131 table_set_nr (struct table *table, int nr)
132 {
133   assert (!table_is_shared (table));
134   table->n[TABLE_VERT] = nr;
135 }
136 \f
137 struct area_style *
138 area_style_clone (struct pool *pool, const struct area_style *old)
139 {
140   struct area_style *new = pool_malloc (pool, sizeof *new);
141   *new = *old;
142   if (new->font_style.typeface)
143     new->font_style.typeface = pool_strdup (pool, new->font_style.typeface);
144   return new;
145 }
146
147 void
148 area_style_free (struct area_style *style)
149 {
150   if (style)
151     {
152       free (style->font_style.typeface);
153       free (style);
154     }
155 }
156
157 /* Initializes CELL with the contents of the table cell at column X and row Y
158    within TABLE.  When CELL is no longer needed, the caller is responsible for
159    freeing it by calling table_cell_free(CELL).
160
161    The caller must ensure that CELL is destroyed before TABLE is unref'ed. */
162 void
163 table_get_cell (const struct table *table, int x, int y,
164                 struct table_cell *cell)
165 {
166   assert (x >= 0 && x < table->n[TABLE_HORZ]);
167   assert (y >= 0 && y < table->n[TABLE_VERT]);
168
169   static const struct area_style default_style = AREA_STYLE_INITIALIZER;
170   cell->style = &default_style;
171
172   table->klass->get_cell (table, x, y, cell);
173 }
174
175 /* Frees CELL, which should have been initialized by calling
176    table_get_cell(). */
177 void
178 table_cell_free (struct table_cell *cell)
179 {
180   if (cell->destructor != NULL)
181     cell->destructor (cell->destructor_aux);
182 }
183
184 /* Returns one of the TAL_* enumeration constants (declared in output/table.h)
185    representing a rule running alongside one of the cells in TABLE.
186
187    Suppose NC is the number of columns in TABLE and NR is the number of rows.
188    Then, if AXIS is TABLE_HORZ, then 0 <= X <= NC and 0 <= Y < NR.  If (X,Y) =
189    (0,0), the return value is the rule that runs vertically on the left side of
190    cell (0,0); if (X,Y) = (1,0), it is the vertical rule between that cell and
191    cell (1,0); and so on, up to (NC,0), which runs vertically on the right of
192    cell (NC-1,0).
193
194    The following diagram illustrates the meaning of (X,Y) for AXIS = TABLE_HORZ
195    within a 7x7 table.  The '|' characters at the intersection of the X labels
196    and Y labels show the rule whose style would be returned by calling
197    table_get_rule with those X and Y values:
198
199                            0  1  2  3  4  5  6  7
200                            +--+--+--+--+--+--+--+
201                          0 |  |  |  |  |  |  |  |
202                            +--+--+--+--+--+--+--+
203                          1 |  |  |  |  |  |  |  |
204                            +--+--+--+--+--+--+--+
205                          2 |  |  |  |  |  |  |  |
206                            +--+--+--+--+--+--+--+
207                          3 |  |  |  |  |  |  |  |
208                            +--+--+--+--+--+--+--+
209                          4 |  |  |  |  |  |  |  |
210                            +--+--+--+--+--+--+--+
211                          5 |  |  |  |  |  |  |  |
212                            +--+--+--+--+--+--+--+
213                          6 |  |  |  |  |  |  |  |
214                            +--+--+--+--+--+--+--+
215
216    Similarly, if AXIS is TABLE_VERT, then 0 <= X < NC and 0 <= Y <= NR.  If
217    (X,Y) = (0,0), the return value is the rule that runs horizontally above
218    the top of cell (0,0); if (X,Y) = (0,1), it is the horizontal rule
219    between that cell and cell (0,1); and so on, up to (0,NR), which runs
220    horizontally below cell (0,NR-1). */
221 int
222 table_get_rule (const struct table *table, enum table_axis axis, int x, int y,
223                 struct cell_color *color)
224 {
225   assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ));
226   assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT));
227   *color = (struct cell_color) CELL_COLOR_BLACK;
228   return table->klass->get_rule (table, axis, x, y, color);
229 }
230
231 void
232 table_cell_format_footnote_markers (const struct table_cell *cell,
233                                     struct string *s)
234 {
235   for (size_t i = 0; i < cell->n_footnotes; i++)
236     {
237       if (i)
238         ds_put_byte (s, ',');
239       ds_put_cstr (s, cell->footnotes[i]->marker);
240     }
241 }
242
243 static const struct footnote **
244 add_footnotes (const struct footnote **refs, size_t n_refs,
245                const struct footnote **footnotes, size_t *allocated, size_t *n)
246 {
247   for (size_t i = 0; i < n_refs; i++)
248     {
249       const struct footnote *f = refs[i];
250       if (f->idx >= *allocated)
251         {
252           size_t new_allocated = (f->idx + 1) * 2;
253           footnotes = xrealloc (footnotes, new_allocated * sizeof *footnotes);
254           while (*allocated < new_allocated)
255             footnotes[(*allocated)++] = NULL;
256         }
257       footnotes[f->idx] = f;
258       if (f->idx >= *n)
259         *n = f->idx + 1;
260     }
261   return footnotes;
262 }
263
264 size_t
265 table_collect_footnotes (const struct table_item *item,
266                          const struct footnote ***footnotesp)
267 {
268   const struct footnote **footnotes = NULL;
269   size_t allocated = 0;
270   size_t n = 0;
271
272   struct table *t = item->table;
273   for (int y = 0; y < table_nr (t); y++)
274     {
275       struct table_cell cell;
276       for (int x = 0; x < table_nc (t); x = cell.d[TABLE_HORZ][1])
277         {
278           table_get_cell (t, x, y, &cell);
279
280           if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0])
281             footnotes = add_footnotes (cell.footnotes, cell.n_footnotes,
282                                        footnotes, &allocated, &n);
283           table_cell_free (&cell);
284         }
285     }
286
287   const struct table_item_text *title = table_item_get_title (item);
288   if (title)
289     footnotes = add_footnotes (title->footnotes, title->n_footnotes,
290                                footnotes, &allocated, &n);
291
292   const struct table_item_text *caption = table_item_get_caption (item);
293   if (caption)
294     footnotes = add_footnotes (caption->footnotes, caption->n_footnotes,
295                                footnotes, &allocated, &n);
296
297   *footnotesp = footnotes;
298   return n;
299 }
300 \f
301 const char *
302 table_halign_to_string (enum table_halign halign)
303 {
304   switch (halign)
305     {
306     case TABLE_HALIGN_LEFT: return "left";
307     case TABLE_HALIGN_CENTER: return "center";
308     case TABLE_HALIGN_RIGHT: return "right";
309     case TABLE_HALIGN_DECIMAL: return "decimal";
310     case TABLE_HALIGN_MIXED: return "mixed";
311     default: return "**error**";
312     }
313 }
314
315 const char *
316 table_valign_to_string (enum table_valign valign)
317 {
318   switch (valign)
319     {
320     case TABLE_VALIGN_TOP: return "top";
321     case TABLE_VALIGN_CENTER: return "center";
322     case TABLE_VALIGN_BOTTOM: return "bottom";
323     default: return "**error**";
324     }
325 }
326
327 enum table_halign
328 table_halign_interpret (enum table_halign halign, bool numeric)
329 {
330   switch (halign)
331     {
332     case TABLE_HALIGN_LEFT:
333     case TABLE_HALIGN_CENTER:
334     case TABLE_HALIGN_RIGHT:
335       return halign;
336
337     case TABLE_HALIGN_MIXED:
338       return numeric ? TABLE_HALIGN_RIGHT : TABLE_HALIGN_LEFT;
339
340     case TABLE_HALIGN_DECIMAL:
341       return TABLE_HALIGN_DECIMAL;
342
343     default:
344       NOT_REACHED ();
345     }
346 }
347
348 void
349 font_style_copy (struct font_style *dst, const struct font_style *src)
350 {
351   *dst = *src;
352   if (dst->typeface)
353     dst->typeface = xstrdup (dst->typeface);
354 }
355
356 void
357 font_style_uninit (struct font_style *font)
358 {
359   if (font)
360     free (font->typeface);
361 }
362
363 void
364 area_style_copy (struct area_style *dst, const struct area_style *src)
365 {
366   font_style_copy (&dst->font_style, &src->font_style);
367   dst->cell_style = src->cell_style;
368 }
369
370 void
371 area_style_uninit (struct area_style *area)
372 {
373   if (area)
374     font_style_uninit (&area->font_style);
375 }
376
377 const char *
378 table_stroke_to_string (enum table_stroke stroke)
379 {
380   switch (stroke)
381     {
382     case TABLE_STROKE_NONE: return "none";
383     case TABLE_STROKE_SOLID: return "solid";
384     case TABLE_STROKE_DASHED: return "dashed";
385     case TABLE_STROKE_THICK: return "thick";
386     case TABLE_STROKE_THIN: return "thin";
387     case TABLE_STROKE_DOUBLE: return "double";
388     default:
389       return "**error**";
390     }
391 }
392
393 void
394 cell_color_dump (const struct cell_color *c)
395 {
396   if (c->alpha != 255)
397     printf ("rgba(%d, %d, %d, %d)", c->r, c->g, c->b, c->alpha);
398   else
399     printf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8, c->r, c->g, c->b);
400 }
401
402 void
403 font_style_dump (const struct font_style *f)
404 {
405   printf ("%s %dpx ", f->typeface, f->size);
406   cell_color_dump (&f->fg[0]);
407   putchar ('/');
408   cell_color_dump (&f->bg[0]);
409   if (!cell_color_equal (&f->fg[0], &f->fg[1])
410       || !cell_color_equal (&f->bg[0], &f->bg[1]))
411     {
412       printf (" alt=");
413       cell_color_dump (&f->fg[1]);
414       putchar ('/');
415       cell_color_dump (&f->bg[1]);
416     }
417   if (f->bold)
418     fputs (" bold", stdout);
419   if (f->italic)
420     fputs (" italic", stdout);
421   if (f->underline)
422     fputs (" underline", stdout);
423 }
424
425 void
426 cell_style_dump (const struct cell_style *c)
427 {
428   fputs (table_halign_to_string (c->halign), stdout);
429   if (c->halign == TABLE_HALIGN_DECIMAL)
430     printf ("(%.2gpx)", c->decimal_offset);
431   printf (" %s", table_valign_to_string (c->valign));
432   printf (" %d,%d,%d,%dpx",
433           c->margin[TABLE_HORZ][0], c->margin[TABLE_HORZ][1],
434           c->margin[TABLE_VERT][0], c->margin[TABLE_VERT][1]);
435 }