pivot-output: Really fix formatting of the corner cell.
[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/table.h"
34 #include "output/text-item.h"
35
36 #include "gl/xalloc.h"
37
38 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
39 #define H TABLE_HORZ
40 #define V TABLE_VERT
41
42 /* Increases TABLE's reference count, indicating that it has an additional
43    owner.  An table that is shared among multiple owners must not be
44    modified. */
45 struct table *
46 table_ref (const struct table *table_)
47 {
48   struct table *table = CONST_CAST (struct table *, table_);
49   table->ref_cnt++;
50   return table;
51 }
52
53 /* Decreases TABLE's reference count, indicating that it has one fewer owner.
54    If TABLE no longer has any owners, it is freed. */
55 void
56 table_unref (struct table *table)
57 {
58   if (table != NULL)
59     {
60       assert (table->ref_cnt > 0);
61       if (--table->ref_cnt == 0)
62         pool_destroy (table->container);
63     }
64 }
65
66 /* Returns true if TABLE has more than one owner.  A table item that is shared
67    among multiple owners must not be modified. */
68 bool
69 table_is_shared (const struct table *table)
70 {
71   return table->ref_cnt > 1;
72 }
73 \f
74 struct table_area_style *
75 table_area_style_clone (struct pool *pool, const struct table_area_style *old)
76 {
77   struct table_area_style *new = pool_malloc (pool, sizeof *new);
78   *new = *old;
79   if (new->font_style.typeface)
80     new->font_style.typeface = pool_strdup (pool, new->font_style.typeface);
81   return new;
82 }
83
84 void
85 table_area_style_free (struct table_area_style *style)
86 {
87   if (style)
88     {
89       free (style->font_style.typeface);
90       free (style);
91     }
92 }
93
94 void
95 table_cell_format_footnote_markers (const struct table_cell *cell,
96                                     struct string *s)
97 {
98   for (size_t i = 0; i < cell->n_footnotes; i++)
99     {
100       if (i)
101         ds_put_byte (s, ',');
102       ds_put_cstr (s, cell->footnotes[i]->marker);
103     }
104 }
105
106 static const struct footnote **
107 add_footnotes (const struct footnote **refs, size_t n_refs,
108                const struct footnote **footnotes, size_t *allocated, size_t *n)
109 {
110   for (size_t i = 0; i < n_refs; i++)
111     {
112       const struct footnote *f = refs[i];
113       if (f->idx >= *allocated)
114         {
115           size_t new_allocated = (f->idx + 1) * 2;
116           footnotes = xrealloc (footnotes, new_allocated * sizeof *footnotes);
117           while (*allocated < new_allocated)
118             footnotes[(*allocated)++] = NULL;
119         }
120       footnotes[f->idx] = f;
121       if (f->idx >= *n)
122         *n = f->idx + 1;
123     }
124   return footnotes;
125 }
126
127 size_t
128 table_collect_footnotes (const struct table_item *item,
129                          const struct footnote ***footnotesp)
130 {
131   const struct footnote **footnotes = NULL;
132   size_t allocated = 0;
133   size_t n = 0;
134
135   struct table *t = item->table;
136   for (int y = 0; y < t->n[V]; y++)
137     {
138       struct table_cell cell;
139       for (int x = 0; x < t->n[H]; x = cell.d[TABLE_HORZ][1])
140         {
141           table_get_cell (t, x, y, &cell);
142
143           if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0])
144             footnotes = add_footnotes (cell.footnotes, cell.n_footnotes,
145                                        footnotes, &allocated, &n);
146         }
147     }
148
149   const struct table_item_text *title = table_item_get_title (item);
150   if (title)
151     footnotes = add_footnotes (title->footnotes, title->n_footnotes,
152                                footnotes, &allocated, &n);
153
154   const struct table_item_layers *layers = table_item_get_layers (item);
155   if (layers)
156     {
157       for (size_t i = 0; i < layers->n_layers; i++)
158         footnotes = add_footnotes (layers->layers[i].footnotes,
159                                    layers->layers[i].n_footnotes,
160                                    footnotes, &allocated, &n);
161     }
162
163   const struct table_item_text *caption = table_item_get_caption (item);
164   if (caption)
165     footnotes = add_footnotes (caption->footnotes, caption->n_footnotes,
166                                footnotes, &allocated, &n);
167
168   size_t n_nonnull = 0;
169   for (size_t i = 0; i < n; i++)
170     if (footnotes[i])
171       footnotes[n_nonnull++] = footnotes[i];
172
173   *footnotesp = footnotes;
174   return n_nonnull;
175 }
176 \f
177 const char *
178 table_halign_to_string (enum table_halign halign)
179 {
180   switch (halign)
181     {
182     case TABLE_HALIGN_LEFT: return "left";
183     case TABLE_HALIGN_CENTER: return "center";
184     case TABLE_HALIGN_RIGHT: return "right";
185     case TABLE_HALIGN_DECIMAL: return "decimal";
186     case TABLE_HALIGN_MIXED: return "mixed";
187     default: return "**error**";
188     }
189 }
190
191 const char *
192 table_valign_to_string (enum table_valign valign)
193 {
194   switch (valign)
195     {
196     case TABLE_VALIGN_TOP: return "top";
197     case TABLE_VALIGN_CENTER: return "center";
198     case TABLE_VALIGN_BOTTOM: return "bottom";
199     default: return "**error**";
200     }
201 }
202
203 enum table_halign
204 table_halign_interpret (enum table_halign halign, bool numeric)
205 {
206   switch (halign)
207     {
208     case TABLE_HALIGN_LEFT:
209     case TABLE_HALIGN_CENTER:
210     case TABLE_HALIGN_RIGHT:
211       return halign;
212
213     case TABLE_HALIGN_MIXED:
214       return numeric ? TABLE_HALIGN_RIGHT : TABLE_HALIGN_LEFT;
215
216     case TABLE_HALIGN_DECIMAL:
217       return TABLE_HALIGN_DECIMAL;
218
219     default:
220       NOT_REACHED ();
221     }
222 }
223
224 void
225 font_style_copy (struct pool *container,
226                  struct font_style *dst, const struct font_style *src)
227 {
228   *dst = *src;
229   if (dst->typeface)
230     dst->typeface = pool_strdup (container, dst->typeface);
231 }
232
233 void
234 font_style_uninit (struct font_style *font)
235 {
236   if (font)
237     free (font->typeface);
238 }
239
240 void
241 table_area_style_copy (struct pool *container, struct table_area_style *dst,
242                        const struct table_area_style *src)
243 {
244   font_style_copy (container, &dst->font_style, &src->font_style);
245   dst->cell_style = src->cell_style;
246 }
247
248 void
249 table_area_style_uninit (struct table_area_style *area)
250 {
251   if (area)
252     font_style_uninit (&area->font_style);
253 }
254
255 const char *
256 table_stroke_to_string (enum table_stroke stroke)
257 {
258   switch (stroke)
259     {
260     case TABLE_STROKE_NONE: return "none";
261     case TABLE_STROKE_SOLID: return "solid";
262     case TABLE_STROKE_DASHED: return "dashed";
263     case TABLE_STROKE_THICK: return "thick";
264     case TABLE_STROKE_THIN: return "thin";
265     case TABLE_STROKE_DOUBLE: return "double";
266     default:
267       return "**error**";
268     }
269 }
270
271 void
272 cell_color_dump (const struct cell_color *c)
273 {
274   if (c->alpha != 255)
275     printf ("rgba(%d, %d, %d, %d)", c->r, c->g, c->b, c->alpha);
276   else
277     printf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8, c->r, c->g, c->b);
278 }
279
280 void
281 font_style_dump (const struct font_style *f)
282 {
283   printf ("%s %dpx ", f->typeface, f->size);
284   cell_color_dump (&f->fg[0]);
285   putchar ('/');
286   cell_color_dump (&f->bg[0]);
287   if (!cell_color_equal (&f->fg[0], &f->fg[1])
288       || !cell_color_equal (&f->bg[0], &f->bg[1]))
289     {
290       printf (" alt=");
291       cell_color_dump (&f->fg[1]);
292       putchar ('/');
293       cell_color_dump (&f->bg[1]);
294     }
295   if (f->bold)
296     fputs (" bold", stdout);
297   if (f->italic)
298     fputs (" italic", stdout);
299   if (f->underline)
300     fputs (" underline", stdout);
301 }
302
303 void
304 cell_style_dump (const struct cell_style *c)
305 {
306   fputs (table_halign_to_string (c->halign), stdout);
307   if (c->halign == TABLE_HALIGN_DECIMAL)
308     printf ("(%.2gpx)", c->decimal_offset);
309   printf (" %s", table_valign_to_string (c->valign));
310   printf (" %d,%d,%d,%dpx",
311           c->margin[TABLE_HORZ][0], c->margin[TABLE_HORZ][1],
312           c->margin[TABLE_VERT][0], c->margin[TABLE_VERT][1]);
313 }
314 \f
315
316 static const bool debugging = true;
317
318 /* Creates and returns a new table with NC columns and NR rows and initially no
319    header rows or columns.
320
321    Sets the number of header rows on each side of TABLE to HL on the
322    left, HR on the right, HT on the top, HB on the bottom.  Header rows
323    are repeated when a table is broken across multiple columns or
324    multiple pages.
325
326    The table's cells are initially empty. */
327 struct table *
328 table_create (int nc, int nr, int hl, int hr, int ht, int hb)
329 {
330   struct table *t;
331
332   t = pool_create_container (struct table, container);
333   t->n[TABLE_HORZ] = nc;
334   t->n[TABLE_VERT] = nr;
335   t->h[TABLE_HORZ][0] = hl;
336   t->h[TABLE_HORZ][1] = hr;
337   t->h[TABLE_VERT][0] = ht;
338   t->h[TABLE_VERT][1] = hb;
339   t->ref_cnt = 1;
340
341   t->cc = pool_calloc (t->container, nr * nc, sizeof *t->cc);
342   t->ct = pool_calloc (t->container, nr * nc, sizeof *t->ct);
343
344   t->rh = pool_nmalloc (t->container, nc, nr + 1);
345   memset (t->rh, TABLE_STROKE_NONE, nc * (nr + 1));
346
347   t->rv = pool_nmalloc (t->container, nr, nc + 1);
348   memset (t->rv, TABLE_STROKE_NONE, nr * (nc + 1));
349
350   memset (t->styles, 0, sizeof t->styles);
351   memset (t->rule_colors, 0, sizeof t->rule_colors);
352
353   return t;
354 }
355 \f
356 /* Rules. */
357
358 /* Draws a vertical line to the left of cells at horizontal position X
359    from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
360 void
361 table_vline (struct table *t, int style, int x, int y1, int y2)
362 {
363   if (debugging)
364     {
365       if (x < 0 || x > t->n[H]
366           || y1 < 0 || y1 >= t->n[V]
367           || y2 < 0 || y2 >= t->n[V])
368         {
369           printf ("bad vline: x=%d y=(%d,%d) in table size (%d,%d)\n",
370                   x, y1, y2, t->n[H], t->n[V]);
371           return;
372         }
373     }
374
375   assert (x >= 0);
376   assert (x <= t->n[H]);
377   assert (y1 >= 0);
378   assert (y2 >= y1);
379   assert (y2 <= t->n[V]);
380
381   if (style != -1)
382     {
383       int y;
384       for (y = y1; y <= y2; y++)
385         t->rv[x + (t->n[H] + 1) * y] = style;
386     }
387 }
388
389 /* Draws a horizontal line above cells at vertical position Y from X1
390    to X2 inclusive in style STYLE, if style is not -1. */
391 void
392 table_hline (struct table *t, int style, int x1, int x2, int y)
393 {
394   if (debugging)
395     {
396       if (y < 0 || y > t->n[V]
397           || x1 < 0 || x1 >= t->n[H]
398           || x2 < 0 || x2 >= t->n[H])
399         {
400           printf ("bad hline: x=(%d,%d) y=%d in table size (%d,%d)\n",
401                   x1, x2, y, t->n[H], t->n[V]);
402           return;
403         }
404     }
405
406   assert (y >= 0);
407   assert (y <= t->n[V]);
408   assert (x2 >= x1);
409   assert (x1 >= 0);
410   assert (x2 < t->n[H]);
411
412   if (style != -1)
413     {
414       int x;
415       for (x = x1; x <= x2; x++)
416         t->rh[x + t->n[H] * y] = style;
417     }
418 }
419
420 /* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
421    lines of style F_H and vertical lines of style F_V.  Fills the
422    interior of the box with horizontal lines of style I_H and vertical
423    lines of style I_V.  Any of the line styles may be -1 to avoid
424    drawing those lines.  This is distinct from 0, which draws a null
425    line. */
426 void
427 table_box (struct table *t, int f_h, int f_v, int i_h, int i_v,
428            int x1, int y1, int x2, int y2)
429 {
430   if (debugging)
431     {
432       if (x1 < 0 || x1 >= t->n[H]
433           || x2 < 0 || x2 >= t->n[H]
434           || y1 < 0 || y1 >= t->n[V]
435           || y2 < 0 || y2 >= t->n[V])
436         {
437           printf ("bad box: (%d,%d)-(%d,%d) in table size (%d,%d)\n",
438                   x1, y1, x2, y2, t->n[H], t->n[V]);
439           NOT_REACHED ();
440         }
441     }
442
443   assert (x2 >= x1);
444   assert (y2 >= y1);
445   assert (x1 >= 0);
446   assert (y1 >= 0);
447   assert (x2 < t->n[H]);
448   assert (y2 < t->n[V]);
449
450   if (f_h != -1)
451     {
452       int x;
453       for (x = x1; x <= x2; x++)
454         {
455           t->rh[x + t->n[H] * y1] = f_h;
456           t->rh[x + t->n[H] * (y2 + 1)] = f_h;
457         }
458     }
459   if (f_v != -1)
460     {
461       int y;
462       for (y = y1; y <= y2; y++)
463         {
464           t->rv[x1 + (t->n[H] + 1) * y] = f_v;
465           t->rv[(x2 + 1) + (t->n[H] + 1) * y] = f_v;
466         }
467     }
468
469   if (i_h != -1)
470     {
471       int y;
472
473       for (y = y1 + 1; y <= y2; y++)
474         {
475           int x;
476
477           for (x = x1; x <= x2; x++)
478             t->rh[x + t->n[H] * y] = i_h;
479         }
480     }
481   if (i_v != -1)
482     {
483       int x;
484
485       for (x = x1 + 1; x <= x2; x++)
486         {
487           int y;
488
489           for (y = y1; y <= y2; y++)
490             t->rv[x + (t->n[H] + 1) * y] = i_v;
491         }
492     }
493 }
494 \f
495 /* Cells. */
496
497 static void
498 do_table_text (struct table *table, int c, int r, unsigned opt, char *text)
499 {
500   assert (c >= 0);
501   assert (r >= 0);
502   assert (c < table->n[H]);
503   assert (r < table->n[V]);
504
505   if (debugging)
506     {
507       if (c < 0 || r < 0 || c >= table->n[H] || r >= table->n[V])
508         {
509           printf ("table_text(): bad cell (%d,%d) in table size (%d,%d)\n",
510                   c, r, table->n[H], table->n[V]);
511           return;
512         }
513     }
514
515   table->cc[c + r * table->n[H]] = text;
516   table->ct[c + r * table->n[H]] = opt;
517 }
518
519 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
520    TEXT. */
521 void
522 table_text (struct table *table, int c, int r, unsigned opt,
523           const char *text)
524 {
525   do_table_text (table, c, r, opt, pool_strdup (table->container, text));
526 }
527
528 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
529    FORMAT, which is formatted as if passed to printf. */
530 void
531 table_text_format (struct table *table, int c, int r, unsigned opt,
532                    const char *format, ...)
533 {
534   va_list args;
535
536   va_start (args, format);
537   do_table_text (table, c, r, opt,
538                  pool_vasprintf (table->container, format, args));
539   va_end (args);
540 }
541
542 static struct table_cell *
543 add_joined_cell (struct table *table, int x1, int y1, int x2, int y2,
544                  unsigned opt)
545 {
546   assert (x1 >= 0);
547   assert (y1 >= 0);
548   assert (y2 >= y1);
549   assert (x2 >= x1);
550   assert (y2 < table->n[V]);
551   assert (x2 < table->n[H]);
552
553   if (debugging)
554     {
555       if (x1 < 0 || x1 >= table->n[H]
556           || y1 < 0 || y1 >= table->n[V]
557           || x2 < x1 || x2 >= table->n[H]
558           || y2 < y1 || y2 >= table->n[V])
559         {
560           printf ("table_joint_text(): bad cell "
561                   "(%d,%d)-(%d,%d) in table size (%d,%d)\n",
562                   x1, y1, x2, y2, table->n[H], table->n[V]);
563           return NULL;
564         }
565     }
566
567   table_box (table, -1, -1, TABLE_STROKE_NONE, TABLE_STROKE_NONE,
568              x1, y1, x2, y2);
569
570   struct table_cell *cell = pool_alloc (table->container, sizeof *cell);
571   *cell = (struct table_cell) {
572     .d = { [TABLE_HORZ] = { x1, ++x2 },
573            [TABLE_VERT] = { y1, ++y2 } },
574     .options = opt,
575   };
576
577   void **cc = &table->cc[x1 + y1 * table->n[H]];
578   unsigned short *ct = &table->ct[x1 + y1 * table->n[H]];
579   const int ofs = table->n[H] - (x2 - x1);
580   for (int y = y1; y < y2; y++)
581     {
582       for (int x = x1; x < x2; x++)
583         {
584           *cc++ = cell;
585           *ct++ = opt | TAB_JOIN;
586         }
587
588       cc += ofs;
589       ct += ofs;
590     }
591
592   return cell;
593 }
594
595 /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
596    options OPT to have text value TEXT. */
597 void
598 table_joint_text (struct table *table, int x1, int y1, int x2, int y2,
599                   unsigned opt, const char *text)
600 {
601   char *s = pool_strdup (table->container, text);
602   if (x1 == x2 && y1 == y2)
603     do_table_text (table, x1, y1, opt, s);
604   else
605     add_joined_cell (table, x1, y1, x2, y2, opt)->text = s;
606 }
607
608 static struct table_cell *
609 get_joined_cell (struct table *table, int x, int y)
610 {
611   int index = x + y * table->n[H];
612   unsigned short opt = table->ct[index];
613   struct table_cell *cell;
614
615   if (opt & TAB_JOIN)
616     cell = table->cc[index];
617   else
618     {
619       char *text = table->cc[index];
620
621       cell = add_joined_cell (table, x, y, x, y, table->ct[index]);
622       cell->text = text ? text : pool_strdup (table->container, "");
623     }
624   return cell;
625 }
626
627 /* Sets the subscripts for column X, row Y in TABLE. */
628 void
629 table_add_subscripts (struct table *table, int x, int y,
630                       char **subscripts, size_t n_subscripts)
631 {
632   struct table_cell *cell = get_joined_cell (table, x, y);
633
634   cell->n_subscripts = n_subscripts;
635   cell->subscripts = pool_nalloc (table->container, n_subscripts,
636                                   sizeof *cell->subscripts);
637   for (size_t i = 0; i < n_subscripts; i++)
638     cell->subscripts[i] = pool_strdup (table->container, subscripts[i]);
639 }
640
641 /* Create a footnote in TABLE with MARKER (e.g. "a") as its marker and CONTENT
642    as its content.  The footnote will be styled as STYLE, which is mandatory.
643    IDX must uniquely identify the footnote within TABLE.
644
645    Returns the new footnote.  The return value is the only way to get to the
646    footnote later, so it is important for the caller to remember it. */
647 struct footnote *
648 table_create_footnote (struct table *table, size_t idx, const char *content,
649                        const char *marker, struct table_area_style *style)
650 {
651   assert (style);
652
653   struct footnote *f = pool_alloc (table->container, sizeof *f);
654   f->idx = idx;
655   f->content = pool_strdup (table->container, content);
656   f->marker = pool_strdup (table->container, marker);
657   f->style = style;
658   return f;
659 }
660
661 /* Attaches a reference to footnote F to the cell at column X, row Y in
662    TABLE. */
663 void
664 table_add_footnote (struct table *table, int x, int y,
665                     const struct footnote *f)
666 {
667   assert (f->style);
668
669   struct table_cell *cell = get_joined_cell (table, x, y);
670
671   cell->footnotes = pool_realloc (
672     table->container, cell->footnotes,
673     (cell->n_footnotes + 1) * sizeof *cell->footnotes);
674
675   cell->footnotes[cell->n_footnotes++] = f;
676 }
677
678 /* Overrides the style for column X, row Y in TABLE with STYLE.
679    Does not make a copy of STYLE, so it should either be allocated from
680    TABLE->container or have a lifetime that will outlive TABLE. */
681 void
682 table_add_style (struct table *table, int x, int y,
683                  const struct table_area_style *style)
684 {
685   get_joined_cell (table, x, y)->style = style;
686 }
687
688 /* Returns true if column C, row R has no contents, otherwise false. */
689 bool
690 table_cell_is_empty (const struct table *table, int c, int r)
691 {
692   return table->cc[c + r * table->n[H]] == NULL;
693 }
694 \f
695 /* Initializes CELL with the contents of the table cell at column X and row Y
696    within TABLE.  When CELL is no longer needed, the caller is responsible for
697    freeing it by calling table_cell_free(CELL).
698
699    The caller must ensure that CELL is destroyed before TABLE is unref'ed. */
700 void
701 table_get_cell (const struct table *t, int x, int y, struct table_cell *cell)
702 {
703   assert (x >= 0 && x < t->n[TABLE_HORZ]);
704   assert (y >= 0 && y < t->n[TABLE_VERT]);
705
706   int index = x + y * t->n[H];
707   unsigned short opt = t->ct[index];
708   const void *cc = t->cc[index];
709
710   const struct table_area_style *style
711     = t->styles[(opt & TAB_STYLE_MASK) >> TAB_STYLE_SHIFT];
712   if (opt & TAB_JOIN)
713     {
714       const struct table_cell *jc = cc;
715       *cell = *jc;
716       if (!cell->style)
717         cell->style = style;
718     }
719   else
720     *cell = (struct table_cell) {
721       .d = { [TABLE_HORZ] = { x, x + 1 },
722              [TABLE_VERT] = { y, y + 1 } },
723       .options = opt,
724       .text = CONST_CAST (char *, cc ? cc : ""),
725       .style = style,
726     };
727
728   assert (cell->style);
729 }
730
731 /* Returns one of the TAL_* enumeration constants (declared in output/table.h)
732    representing a rule running alongside one of the cells in TABLE.
733
734    Suppose NC is the number of columns in TABLE and NR is the number of rows.
735    Then, if AXIS is TABLE_HORZ, then 0 <= X <= NC and 0 <= Y < NR.  If (X,Y) =
736    (0,0), the return value is the rule that runs vertically on the left side of
737    cell (0,0); if (X,Y) = (1,0), it is the vertical rule between that cell and
738    cell (1,0); and so on, up to (NC,0), which runs vertically on the right of
739    cell (NC-1,0).
740
741    The following diagram illustrates the meaning of (X,Y) for AXIS = TABLE_HORZ
742    within a 7x7 table.  The '|' characters at the intersection of the X labels
743    and Y labels show the rule whose style would be returned by calling
744    table_get_rule with those X and Y values:
745
746                            0  1  2  3  4  5  6  7
747                            +--+--+--+--+--+--+--+
748                          0 |  |  |  |  |  |  |  |
749                            +--+--+--+--+--+--+--+
750                          1 |  |  |  |  |  |  |  |
751                            +--+--+--+--+--+--+--+
752                          2 |  |  |  |  |  |  |  |
753                            +--+--+--+--+--+--+--+
754                          3 |  |  |  |  |  |  |  |
755                            +--+--+--+--+--+--+--+
756                          4 |  |  |  |  |  |  |  |
757                            +--+--+--+--+--+--+--+
758                          5 |  |  |  |  |  |  |  |
759                            +--+--+--+--+--+--+--+
760                          6 |  |  |  |  |  |  |  |
761                            +--+--+--+--+--+--+--+
762
763    Similarly, if AXIS is TABLE_VERT, then 0 <= X < NC and 0 <= Y <= NR.  If
764    (X,Y) = (0,0), the return value is the rule that runs horizontally above
765    the top of cell (0,0); if (X,Y) = (0,1), it is the horizontal rule
766    between that cell and cell (0,1); and so on, up to (0,NR), which runs
767    horizontally below cell (0,NR-1). */
768 int
769 table_get_rule (const struct table *table, enum table_axis axis, int x, int y,
770                 struct cell_color *color)
771 {
772   assert (x >= 0 && x < table->n[TABLE_HORZ] + (axis == TABLE_HORZ));
773   assert (y >= 0 && y < table->n[TABLE_VERT] + (axis == TABLE_VERT));
774
775   uint8_t raw = (axis == TABLE_VERT
776                  ? table->rh[x + table->n[H] * y]
777                  : table->rv[x + (table->n[H] + 1) * y]);
778   struct cell_color *p = table->rule_colors[(raw & TAB_RULE_STYLE_MASK)
779                                             >> TAB_RULE_STYLE_SHIFT];
780   *color = p ? *p : (struct cell_color) CELL_COLOR_BLACK;
781   return (raw & TAB_RULE_TYPE_MASK) >> TAB_RULE_TYPE_SHIFT;
782 }