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