Delete tab_raw function and tab_alloc macro.
[pspp-builds.git] / src / output / table.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2009 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 "table.h"
20
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <limits.h>
24 #include <stdlib.h>
25
26 #include "output.h"
27 #include "manager.h"
28
29 #include <data/data-out.h>
30 #include <data/format.h>
31 #include <data/value.h>
32 #include <libpspp/assertion.h>
33 #include <libpspp/compiler.h>
34 #include <libpspp/misc.h>
35 #include <libpspp/pool.h>
36
37 #include <data/settings.h>
38
39 #include "error.h"
40 #include "minmax.h"
41 #include "xalloc.h"
42
43 #include "gettext.h"
44 #define _(msgid) gettext (msgid)
45 \f
46 const struct som_table_class tab_table_class;
47
48 /* Returns the font to use for a cell with the given OPTIONS. */
49 static enum outp_font
50 options_to_font (unsigned options)
51 {
52   return (options & TAB_FIX ? OUTP_FIXED
53           : options & TAB_EMPH ? OUTP_EMPHASIS
54           : OUTP_PROPORTIONAL);
55 }
56
57 /* Creates a table with NC columns and NR rows. */
58 struct tab_table *
59 tab_create (int nc, int nr)
60 {
61   struct tab_table *t;
62
63   t = pool_create_container (struct tab_table, container);
64   t->ref_cnt = 1;
65   t->col_style = TAB_COL_NONE;
66   t->title = NULL;
67   t->flags = SOMF_NONE;
68   t->nr = nr;
69   t->nc = t->cf = nc;
70   t->l = t->r = t->t = t->b = 0;
71
72   t->cc = pool_nmalloc (t->container, nr * nc, sizeof *t->cc);
73   t->ct = pool_malloc (t->container, nr * nc);
74   memset (t->ct, TAB_EMPTY, nc * nr);
75
76   t->rh = pool_nmalloc (t->container, nc, nr + 1);
77   memset (t->rh, 0, nc * (nr + 1));
78
79   t->rv = pool_nmalloc (t->container, nr, nc + 1);
80   memset (t->rv, UCHAR_MAX, nr * (nc + 1));
81
82   t->dim = NULL;
83   t->col_ofs = t->row_ofs = 0;
84
85   return t;
86 }
87
88 /* Increases T's reference count and, if this causes T's
89    reference count to reach 0, destroys T. */
90 void
91 tab_destroy (struct tab_table *t)
92 {
93   assert (t->ref_cnt > 0);
94   if (--t->ref_cnt > 0)
95     return;
96   if (t->dim_free != NULL)
97     t->dim_free (t->dim_aux);
98   free (t->title);
99   pool_destroy (t->container);
100 }
101
102 /* Increases T's reference count. */
103 void
104 tab_ref (struct tab_table *t)
105 {
106   assert (t->ref_cnt > 0);
107   t->ref_cnt++;
108 }
109
110 /* Sets the width and height of a table, in columns and rows,
111    respectively.  Use only to reduce the size of a table, since it
112    does not change the amount of allocated memory. */
113 void
114 tab_resize (struct tab_table *t, int nc, int nr)
115 {
116   assert (t != NULL);
117   if (nc != -1)
118     {
119       assert (nc + t->col_ofs <= t->cf);
120       t->nc = nc + t->col_ofs;
121     }
122   if (nr != -1)
123     {
124       assert (nr + t->row_ofs <= tab_nr (t));
125       t->nr = nr + t->row_ofs;
126     }
127 }
128
129 /* Changes either or both dimensions of a table.  Consider using the
130    above routine instead if it won't waste a lot of space.
131
132    Changing the number of columns in a table is particularly expensive
133    in space and time.  Avoid doing such.  FIXME: In fact, transferring
134    of rules isn't even implemented yet. */
135 void
136 tab_realloc (struct tab_table *t, int nc, int nr)
137 {
138   int ro, co;
139
140   assert (t != NULL);
141   ro = t->row_ofs;
142   co = t->col_ofs;
143   if (ro || co)
144     tab_offset (t, 0, 0);
145
146   if (nc == -1)
147     nc = tab_nc (t);
148   if (nr == -1)
149     nr = tab_nr (t);
150
151   assert (nc == tab_nc (t));
152
153   if (nc > t->cf)
154     {
155       int mr1 = MIN (nr, tab_nr (t));
156       int mc1 = MIN (nc, tab_nc (t));
157
158       struct substring *new_cc;
159       unsigned char *new_ct;
160       int r;
161
162       new_cc = pool_nmalloc (t->container, nr * nc, sizeof *new_cc);
163       new_ct = pool_malloc (t->container, nr * nc);
164       for (r = 0; r < mr1; r++)
165         {
166           memcpy (&new_cc[r * nc], &t->cc[r * tab_nc (t)], mc1 * sizeof *t->cc);
167           memcpy (&new_ct[r * nc], &t->ct[r * tab_nc (t)], mc1);
168           memset (&new_ct[r * nc + tab_nc (t)], TAB_EMPTY, nc - tab_nc (t));
169         }
170       pool_free (t->container, t->cc);
171       pool_free (t->container, t->ct);
172       t->cc = new_cc;
173       t->ct = new_ct;
174       t->cf = nc;
175     }
176   else if (nr != tab_nr (t))
177     {
178       t->cc = pool_nrealloc (t->container, t->cc, nr * nc, sizeof *t->cc);
179       t->ct = pool_realloc (t->container, t->ct, nr * nc);
180
181       t->rh = pool_nrealloc (t->container, t->rh, nc, nr + 1);
182       t->rv = pool_nrealloc (t->container, t->rv, nr, nc + 1);
183
184       if (nr > tab_nr (t))
185         {
186           memset (&t->rh[nc * (tab_nr (t) + 1)], TAL_0, (nr - tab_nr (t)) * nc);
187           memset (&t->rv[(nc + 1) * tab_nr (t)], UCHAR_MAX,
188                   (nr - tab_nr (t)) * (nc + 1));
189         }
190     }
191
192   memset (&t->ct[nc * tab_nr (t)], TAB_EMPTY, nc * (nr - tab_nr (t)));
193
194   t->nr = nr;
195   t->nc = nc;
196
197   if (ro || co)
198     tab_offset (t, co, ro);
199 }
200
201 /* Sets the number of header rows on each side of TABLE to L on the
202    left, R on the right, T on the top, B on the bottom.  Header rows
203    are repeated when a table is broken across multiple columns or
204    multiple pages. */
205 void
206 tab_headers (struct tab_table *table, int l, int r, int t, int b)
207 {
208   assert (table != NULL);
209   assert (l < table->nc);
210   assert (r < table->nc);
211   assert (t < table->nr);
212   assert (b < table->nr);
213
214
215   table->l = l;
216   table->r = r;
217   table->t = t;
218   table->b = b;
219 }
220
221 /* Set up table T so that, when it is an appropriate size, it will be
222    displayed across the page in columns.
223
224    STYLE is a TAB_COL_* constant. */
225 void
226 tab_columns (struct tab_table *t, int style)
227 {
228   assert (t != NULL);
229   t->col_style = style;
230 }
231 \f
232 /* Rules. */
233
234 /* Draws a vertical line to the left of cells at horizontal position X
235    from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
236 void
237 tab_vline (struct tab_table *t, int style, int x, int y1, int y2)
238 {
239   assert (t != NULL);
240
241 #if DEBUGGING
242   if (x + t->col_ofs < 0 || x + t->col_ofs > tab_nc (t)
243       || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= tab_nr (t)
244       || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= tab_nr (t))
245     {
246       printf (_("bad vline: x=%d+%d=%d y=(%d+%d=%d,%d+%d=%d) in "
247                 "table size (%d,%d)\n"),
248               x, t->col_ofs, x + t->col_ofs,
249               y1, t->row_ofs, y1 + t->row_ofs,
250               y2, t->row_ofs, y2 + t->row_ofs,
251               tab_nc (t), tab_nr (t));
252       return;
253     }
254 #endif
255
256   x += t->col_ofs;
257   y1 += t->row_ofs;
258   y2 += t->row_ofs;
259
260   assert (x  > 0);
261   assert (x  < tab_nc (t));
262   assert (y1 >= 0);
263   assert (y2 >= y1);
264   assert (y2 <=  tab_nr (t));
265
266   if (style != -1)
267     {
268       int y;
269       for (y = y1; y <= y2; y++)
270         t->rv[x + (t->cf + 1) * y] = style;
271     }
272 }
273
274 /* Draws a horizontal line above cells at vertical position Y from X1
275    to X2 inclusive in style STYLE, if style is not -1. */
276 void
277 tab_hline (struct tab_table * t, int style, int x1, int x2, int y)
278 {
279   assert (t != NULL);
280
281   x1 += t->col_ofs;
282   x2 += t->col_ofs;
283   y += t->row_ofs;
284
285   assert (y >= 0);
286   assert (y <= tab_nr (t));
287   assert (x2 >= x1 );
288   assert (x1 >= 0 );
289   assert (x2 < tab_nc (t));
290
291   if (style != -1)
292     {
293       int x;
294       for (x = x1; x <= x2; x++)
295         t->rh[x + t->cf * y] = style;
296     }
297 }
298
299 /* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
300    lines of style F_H and vertical lines of style F_V.  Fills the
301    interior of the box with horizontal lines of style I_H and vertical
302    lines of style I_V.  Any of the line styles may be -1 to avoid
303    drawing those lines.  This is distinct from 0, which draws a null
304    line. */
305 void
306 tab_box (struct tab_table *t, int f_h, int f_v, int i_h, int i_v,
307          int x1, int y1, int x2, int y2)
308 {
309   assert (t != NULL);
310
311 #if DEBUGGING
312   if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= tab_nc (t)
313       || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= tab_nc (t)
314       || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= tab_nr (t)
315       || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= tab_nr (t))
316     {
317       printf (_("bad box: (%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) "
318                 "in table size (%d,%d)\n"),
319               x1, t->col_ofs, x1 + t->col_ofs,
320               y1, t->row_ofs, y1 + t->row_ofs,
321               x2, t->col_ofs, x2 + t->col_ofs,
322               y2, t->row_ofs, y2 + t->row_ofs,
323               tab_nc (t), tab_nr (t));
324       NOT_REACHED ();
325     }
326 #endif
327
328   x1 += t->col_ofs;
329   x2 += t->col_ofs;
330   y1 += t->row_ofs;
331   y2 += t->row_ofs;
332
333   assert (x2 >= x1);
334   assert (y2 >= y1);
335   assert (x1 >= 0);
336   assert (y1 >= 0);
337   assert (x2 < tab_nc (t));
338   assert (y2 < tab_nr (t));
339
340   if (f_h != -1)
341     {
342       int x;
343       for (x = x1; x <= x2; x++)
344         {
345           t->rh[x + t->cf * y1] = f_h;
346           t->rh[x + t->cf * (y2 + 1)] = f_h;
347         }
348     }
349   if (f_v != -1)
350     {
351       int y;
352       for (y = y1; y <= y2; y++)
353         {
354           t->rv[x1 + (t->cf + 1) * y] = f_v;
355           t->rv[(x2 + 1) + (t->cf + 1) * y] = f_v;
356         }
357     }
358
359   if (i_h != -1)
360     {
361       int y;
362
363       for (y = y1 + 1; y <= y2; y++)
364         {
365           int x;
366
367           for (x = x1; x <= x2; x++)
368             t->rh[x + t->cf * y] = i_h;
369         }
370     }
371   if (i_v != -1)
372     {
373       int x;
374
375       for (x = x1 + 1; x <= x2; x++)
376         {
377           int y;
378
379           for (y = y1; y <= y2; y++)
380             t->rv[x + (t->cf + 1) * y] = i_v;
381         }
382     }
383 }
384
385 /* Set the title of table T to TITLE, which is formatted as if
386    passed to printf(). */
387 void
388 tab_title (struct tab_table *t, const char *title, ...)
389 {
390   va_list args;
391
392   assert (t != NULL && title != NULL);
393   va_start (args, title);
394   t->title = xvasprintf (title, args);
395   va_end (args);
396 }
397
398 /* Set DIM_FUNC, which will be passed auxiliary data AUX, as the
399    dimension function for table T.
400
401    DIM_FUNC must not assume that it is called from the same
402    context as tab_dim; for example, table T might be kept in
403    memory and, thus, DIM_FUNC might be called after the currently
404    running command completes.  If it is non-null, FREE_FUNC is
405    called when the table is destroyed, to allow any data
406    allocated for use by DIM_FUNC to be freed.  */
407 void
408 tab_dim (struct tab_table *t,
409          tab_dim_func *dim_func, tab_dim_free_func *free_func, void *aux)
410 {
411   assert (t->dim == NULL);
412   t->dim = dim_func;
413   t->dim_free = free_func;
414   t->dim_aux = aux;
415 }
416
417 /* Returns the natural width of column C in table T for driver D, that
418    is, the smallest width necessary to display all its cells without
419    wrapping.  The width will be no larger than the page width minus
420    left and right rule widths. */
421 int
422 tab_natural_width (const struct tab_rendering *r, int col)
423 {
424   const struct tab_table *t = r->table;
425   int width, row, max_width;
426
427   assert (col >= 0 && col < tab_nc (t));
428
429   width = 0;
430   for (row = 0; row < tab_nr (t); row++)
431     {
432       struct outp_text text;
433       unsigned char opt = t->ct[col + row * t->cf];
434       int w;
435
436       if (opt & (TAB_JOIN | TAB_EMPTY))
437         continue;
438
439       text.string = t->cc[col + row * t->cf];
440       text.justification = OUTP_LEFT;
441       text.font = options_to_font (opt);
442       text.h = text.v = INT_MAX;
443
444       r->driver->class->text_metrics (r->driver, &text, &w, NULL);
445       if (w > width)
446         width = w;
447     }
448
449   if (width == 0)
450     {
451       /* FIXME: This is an ugly kluge to compensate for the fact
452          that we don't let joined cells contribute to column
453          widths. */
454       width = r->driver->prop_em_width * 8;
455     }
456
457   max_width = r->driver->width - r->wrv[0] - r->wrv[tab_nc (t)];
458   return MIN (width, max_width);
459 }
460
461 /* Returns the natural height of row R in table T for driver D, that
462    is, the minimum height necessary to display the information in the
463    cell at the widths set for each column. */
464 int
465 tab_natural_height (const struct tab_rendering *r, int row)
466 {
467   const struct tab_table *t = r->table;
468   int height, col;
469
470   assert (row >= 0 && row < tab_nr (t));
471
472   height = r->driver->font_height;
473   for (col = 0; col < tab_nc (t); col++)
474     {
475       struct outp_text text;
476       unsigned char opt = t->ct[col + row * t->cf];
477       int h;
478
479       if (opt & (TAB_JOIN | TAB_EMPTY))
480         continue;
481
482       text.string = t->cc[col + row * t->cf];
483       text.justification = OUTP_LEFT;
484       text.font = options_to_font (opt);
485       text.h = r->w[col];
486       text.v = INT_MAX;
487       r->driver->class->text_metrics (r->driver, &text, NULL, &h);
488
489       if (h > height)
490         height = h;
491     }
492
493   return height;
494 }
495
496 /* Callback function to set all columns and rows to their natural
497    dimensions.  Not really meant to be called directly.  */
498 void
499 tab_natural_dimensions (struct tab_rendering *r, void *aux UNUSED)
500 {
501   const struct tab_table *t = r->table;
502   int i;
503
504   for (i = 0; i < tab_nc (t); i++)
505     r->w[i] = tab_natural_width (r, i);
506
507   for (i = 0; i < tab_nr (t); i++)
508     r->h[i] = tab_natural_height (r, i);
509 }
510
511 \f
512 /* Cells. */
513
514 /* Sets cell (C,R) in TABLE, with options OPT, to have a value taken
515    from V, displayed with format spec F. */
516 void
517 tab_value (struct tab_table *table, int c, int r, unsigned char opt,
518            const union value *v, const struct fmt_spec *f)
519 {
520   char *contents;
521
522   assert (table != NULL && v != NULL && f != NULL);
523 #if DEBUGGING
524   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
525       || c + table->col_ofs >= tab_nc (table)
526       || r + table->row_ofs >= tab_nr (table))
527     {
528       printf ("tab_value(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
529               "(%d,%d)\n",
530               c, table->col_ofs, c + table->col_ofs,
531               r, table->row_ofs, r + table->row_ofs,
532               tab_nc (table), tab_nr (table));
533       return;
534     }
535 #endif
536
537   contents = pool_alloc (table->container, f->w);
538   table->cc[c + r * table->cf] = ss_buffer (contents, f->w);
539   table->ct[c + r * table->cf] = opt;
540
541   data_out (v, f, contents);
542 }
543
544 /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL
545    with NDEC decimal places. */
546 void
547 tab_fixed (struct tab_table *table, int c, int r, unsigned char opt,
548            double val, int w, int d)
549 {
550   char *contents;
551   char buf[40], *cp;
552
553   struct fmt_spec f;
554   union value double_value;
555
556   assert (table != NULL && w <= 40);
557
558   assert (c >= 0);
559   assert (c < tab_nc (table));
560   assert (r >= 0);
561   assert (r < tab_nr (table));
562
563   f = fmt_for_output (FMT_F, w, d);
564
565 #if DEBUGGING
566   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
567       || c + table->col_ofs >= tab_nc (table)
568       || r + table->row_ofs >= tab_nr (table))
569     {
570       printf ("tab_fixed(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
571               "(%d,%d)\n",
572               c, table->col_ofs, c + table->col_ofs,
573               r, table->row_ofs, r + table->row_ofs,
574               tab_nc (table), tab_nr (table));
575       return;
576     }
577 #endif
578
579   double_value.f = val;
580   data_out (&double_value, &f, buf);
581
582   cp = buf;
583   while (isspace ((unsigned char) *cp) && cp < &buf[w])
584     cp++;
585   f.w = w - (cp - buf);
586
587   contents = pool_alloc (table->container, f.w);
588   table->cc[c + r * table->cf] = ss_buffer (contents, f.w);
589   table->ct[c + r * table->cf] = opt;
590   memcpy (contents, cp, f.w);
591 }
592
593 /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL as
594    formatted by FMT.
595    If FMT is null, then the default print format will be used.
596 */
597 void
598 tab_double (struct tab_table *table, int c, int r, unsigned char opt,
599            double val, const struct fmt_spec *fmt)
600 {
601   int w;
602   char *contents;
603   char buf[40], *cp;
604
605   union value double_value;
606
607   assert (table != NULL);
608
609   assert (c >= 0);
610   assert (c < tab_nc (table));
611   assert (r >= 0);
612   assert (r < tab_nr (table));
613
614   if ( fmt == NULL)
615     fmt = settings_get_format ();
616
617   fmt_check_output (fmt);
618
619 #if DEBUGGING
620   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
621       || c + table->col_ofs >= tab_nc (table)
622       || r + table->row_ofs >= tab_nr (table))
623     {
624       printf ("tab_double(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
625               "(%d,%d)\n",
626               c, table->col_ofs, c + table->col_ofs,
627               r, table->row_ofs, r + table->row_ofs,
628               tab_nc (table), tab_nr (table));
629       return;
630     }
631 #endif
632
633   double_value.f = val;
634   data_out (&double_value, fmt, buf);
635
636   cp = buf;
637   while (isspace ((unsigned char) *cp) && cp < &buf[fmt->w])
638     cp++;
639   w = fmt->w - (cp - buf);
640
641   contents = pool_alloc (table->container, w);
642   table->cc[c + r * table->cf] = ss_buffer (contents, w);
643   table->ct[c + r * table->cf] = opt;
644   memcpy (contents, cp, w);
645 }
646
647
648 static void
649 do_tab_text (struct tab_table *table, int c, int r, unsigned opt, char *text)
650 {
651   assert (c >= 0 );
652   assert (r >= 0 );
653   assert (c < tab_nc (table));
654   assert (r < tab_nr (table));
655
656 #if DEBUGGING
657   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
658       || c + table->col_ofs >= tab_nc (table)
659       || r + table->row_ofs >= tab_nr (table))
660     {
661       printf ("tab_text(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
662               "(%d,%d)\n",
663               c, table->col_ofs, c + table->col_ofs,
664               r, table->row_ofs, r + table->row_ofs,
665               tab_nc (table), tab_nr (table));
666       return;
667     }
668 #endif
669
670   table->cc[c + r * table->cf] = ss_cstr (text);
671   table->ct[c + r * table->cf] = opt;
672 }
673
674 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
675    TEXT. */
676 void
677 tab_text (struct tab_table *table, int c, int r, unsigned opt,
678           const char *text)
679 {
680   do_tab_text (table, c, r, opt, pool_strdup (table->container, text));
681 }
682
683 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
684    FORMAT, which is formatted as if passed to printf. */
685 void
686 tab_text_format (struct tab_table *table, int c, int r, unsigned opt,
687                  const char *format, ...)
688 {
689   va_list args;
690
691   va_start (args, format);
692   do_tab_text (table, c, r, opt,
693                pool_vasprintf (table->container, format, args));
694   va_end (args);
695 }
696
697 static void
698 do_tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2,
699                    unsigned opt, char *text)
700 {
701   struct tab_joined_cell *j;
702
703   assert (x1 + table->col_ofs >= 0);
704   assert (y1 + table->row_ofs >= 0);
705   assert (y2 >= y1);
706   assert (x2 >= x1);
707   assert (y2 + table->row_ofs < tab_nr (table));
708   assert (x2 + table->col_ofs < tab_nc (table));
709
710 #if DEBUGGING
711   if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= tab_nc (table)
712       || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= tab_nr (table)
713       || x2 < x1 || x2 + table->col_ofs >= tab_nc (table)
714       || y2 < y2 || y2 + table->row_ofs >= tab_nr (table))
715     {
716       printf ("tab_joint_text(): bad cell "
717               "(%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n",
718               x1, table->col_ofs, x1 + table->col_ofs,
719               y1, table->row_ofs, y1 + table->row_ofs,
720               x2, table->col_ofs, x2 + table->col_ofs,
721               y2, table->row_ofs, y2 + table->row_ofs,
722               tab_nc (table), tab_nr (table));
723       return;
724     }
725 #endif
726
727   tab_box (table, -1, -1, TAL_0, TAL_0, x1, y1, x2, y2);
728
729   j = pool_alloc (table->container, sizeof *j);
730   j->x1 = x1 + table->col_ofs;
731   j->y1 = y1 + table->row_ofs;
732   j->x2 = ++x2 + table->col_ofs;
733   j->y2 = ++y2 + table->row_ofs;
734   j->contents = ss_cstr (text);
735
736   opt |= TAB_JOIN;
737
738   {
739     struct substring *cc = &table->cc[x1 + y1 * table->cf];
740     unsigned char *ct = &table->ct[x1 + y1 * table->cf];
741     const int ofs = table->cf - (x2 - x1);
742
743     int y;
744
745     for (y = y1; y < y2; y++)
746       {
747         int x;
748
749         for (x = x1; x < x2; x++)
750           {
751             *cc++ = ss_buffer ((char *) j, 0);
752             *ct++ = opt;
753           }
754
755         cc += ofs;
756         ct += ofs;
757       }
758   }
759 }
760
761 /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
762    options OPT to have text value TEXT. */
763 void
764 tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2,
765                 unsigned opt, const char *text)
766 {
767   do_tab_joint_text (table, x1, y1, x2, y2, opt,
768                      pool_strdup (table->container, text));
769 }
770
771 /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them
772    with options OPT to have text value FORMAT, which is formatted
773    as if passed to printf. */
774 void
775 tab_joint_text_format (struct tab_table *table, int x1, int y1, int x2, int y2,
776                        unsigned opt, const char *format, ...)
777 {
778   va_list args;
779
780   va_start (args, format);
781   do_tab_joint_text (table, x1, y1, x2, y2, opt,
782                      pool_vasprintf (table->container, format, args));
783   va_end (args);
784 }
785 \f
786 /* Miscellaneous. */
787
788 /* Sets the widths of all the columns and heights of all the rows in
789    table T for driver D. */
790 static void
791 nowrap_dim (struct tab_rendering *r, void *aux UNUSED)
792 {
793   r->w[0] = tab_natural_width (r, 0);
794   r->h[0] = r->driver->font_height;
795 }
796
797 /* Sets the widths of all the columns and heights of all the rows in
798    table T for driver D. */
799 static void
800 wrap_dim (struct tab_rendering *r, void *aux UNUSED)
801 {
802   r->w[0] = tab_natural_width (r, 0);
803   r->h[0] = tab_natural_height (r, 0);
804 }
805
806 static void
807 do_tab_output_text (struct tab_table *t, int options, char *text)
808 {
809   do_tab_text (t, 0, 0, options, text);
810   tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
811   tab_dim (t, options & TAT_NOWRAP ? nowrap_dim : wrap_dim, NULL, NULL);
812   tab_submit (t);
813 }
814
815 /* Outputs TEXT as a table with a single cell having cell options
816    OPTIONS, which is a combination of the TAB_* and TAT_*
817    constants.  */
818 void
819 tab_output_text (int options, const char *text)
820 {
821   struct tab_table *table = tab_create (1, 1);
822   do_tab_output_text (table, options, pool_strdup (table->container, text));
823 }
824
825 /* Outputs FORMAT as a table with a single cell having cell
826    options OPTIONS, which is a combination of the TAB_* and TAT_*
827    constants.  FORMAT is formatted as if it was passed through
828    printf. */
829 void
830 tab_output_text_format (int options, const char *format, ...)
831 {
832   struct tab_table *table;
833   va_list args;
834
835   table = tab_create (1, 1);
836
837   va_start (args, format);
838   do_tab_output_text (table, options,
839                       pool_vasprintf (table->container, format, args));
840   va_end (args);
841 }
842
843 /* Set table flags to FLAGS. */
844 void
845 tab_flags (struct tab_table *t, unsigned flags)
846 {
847   assert (t != NULL);
848   t->flags = flags;
849 }
850
851 /* Easy, type-safe way to submit a tab table to som. */
852 void
853 tab_submit (struct tab_table *t)
854 {
855   struct som_entity s;
856
857   assert (t != NULL);
858   s.class = &tab_table_class;
859   s.ext = t;
860   s.type = SOM_TABLE;
861   som_submit (&s);
862   tab_destroy (t);
863 }
864 \f
865 /* Editing. */
866
867 /* Set table row and column offsets for all functions that affect
868    cells or rules. */
869 void
870 tab_offset (struct tab_table *t, int col, int row)
871 {
872   int diff = 0;
873
874   assert (t != NULL);
875 #if DEBUGGING
876   if (row < -1 || row > tab_nr (t))
877     {
878       printf ("tab_offset(): row=%d in %d-row table\n", row, tab_nr (t));
879       NOT_REACHED ();
880     }
881   if (col < -1 || col > tab_nc (t))
882     {
883       printf ("tab_offset(): col=%d in %d-column table\n", col, tab_nc (t));
884       NOT_REACHED ();
885     }
886 #endif
887
888   if (row != -1)
889     diff += (row - t->row_ofs) * t->cf, t->row_ofs = row;
890   if (col != -1)
891     diff += (col - t->col_ofs), t->col_ofs = col;
892
893   t->cc += diff;
894   t->ct += diff;
895 }
896
897 /* Increment the row offset by one. If the table is too small,
898    increase its size. */
899 void
900 tab_next_row (struct tab_table *t)
901 {
902   assert (t != NULL);
903   t->cc += t->cf;
904   t->ct += t->cf;
905   if (++t->row_ofs >= tab_nr (t))
906     tab_realloc (t, -1, tab_nr (t) * 4 / 3);
907 }
908 \f
909 /* Return the number of columns and rows in the table into N_COLUMNS
910    and N_ROWS, respectively. */
911 static void
912 tabi_count (struct som_entity *t_, int *n_columns, int *n_rows)
913 {
914   struct tab_table *t = t_->ext;
915   *n_columns = t->nc;
916   *n_rows = t->nr;
917 }
918
919 /* Return the column style for this table into STYLE. */
920 static void
921 tabi_columns (struct som_entity *t_, int *style)
922 {
923   struct tab_table *t = t_->ext;
924   *style = t->col_style;
925 }
926
927 /* Return the number of header rows/columns on the left, right, top,
928    and bottom sides into HL, HR, HT, and HB, respectively. */
929 static void
930 tabi_headers (struct som_entity *t_, int *hl, int *hr, int *ht, int *hb)
931 {
932   struct tab_table *t = t_->ext;
933   *hl = t->l;
934   *hr = t->r;
935   *ht = t->t;
936   *hb = t->b;
937 }
938
939 /* Return flags set for the current table into FLAGS. */
940 static void
941 tabi_flags (struct som_entity *t_, unsigned *flags)
942 {
943   struct tab_table *t = t_->ext;
944   *flags = t->flags;
945 }
946
947 /* Returns the line style to use for spacing purposes for a rule
948    of the given TYPE. */
949 static enum outp_line_style
950 rule_to_spacing_type (unsigned char type)
951 {
952   switch (type)
953     {
954     case TAL_0:
955       return OUTP_L_NONE;
956     case TAL_GAP:
957     case TAL_1:
958       return OUTP_L_SINGLE;
959     case TAL_2:
960       return OUTP_L_DOUBLE;
961     default:
962       NOT_REACHED ();
963     }
964 }
965
966 static void *
967 tabi_render_init (struct som_entity *t_, struct outp_driver *driver,
968                   int hl, int hr, int ht, int hb)
969 {
970   const struct tab_table *t = t_->ext;
971   struct tab_rendering *r;
972   int col, row;
973   int i;
974
975   tab_offset (t_->ext, 0, 0);
976
977   r = xmalloc (sizeof *r);
978   r->table = t;
979   r->driver = driver;
980   r->w = xnmalloc (tab_nc (t), sizeof *r->w);
981   r->h = xnmalloc (tab_nr (t), sizeof *r->h);
982   r->hrh = xnmalloc (tab_nr (t) + 1, sizeof *r->hrh);
983   r->wrv = xnmalloc (tab_nc (t) + 1, sizeof *r->wrv);
984   r->l = hl;
985   r->r = hr;
986   r->t = ht;
987   r->b = hb;
988
989   /* Figure out sizes of rules. */
990   for (row = 0; row <= tab_nr (t); row++)
991     {
992       int width = 0;
993       for (col = 0; col < tab_nc (t); col++)
994         {
995           unsigned char rh = t->rh[col + row * t->cf];
996           int w = driver->horiz_line_width[rule_to_spacing_type (rh)];
997           if (w > width)
998             width = w;
999         }
1000       r->hrh[row] = width;
1001     }
1002
1003   for (col = 0; col <= tab_nc (t); col++)
1004     {
1005       int width = 0;
1006       for (row = 0; row < tab_nr (t); row++)
1007         {
1008           unsigned char *rv = &t->rv[col + row * (t->cf + 1)];
1009           int w;
1010           if (*rv == UCHAR_MAX)
1011             *rv = col != 0 && col != tab_nc (t) ? TAL_GAP : TAL_0;
1012           w = driver->vert_line_width[rule_to_spacing_type (*rv)];
1013           if (w > width)
1014             width = w;
1015         }
1016       r->wrv[col] = width;
1017     }
1018
1019   /* Determine row heights and columns widths. */
1020   for (i = 0; i < tab_nr (t); i++)
1021     r->h[i] = -1;
1022   for (i = 0; i < tab_nc (t); i++)
1023     r->w[i] = -1;
1024
1025   t->dim (r, t->dim_aux);
1026
1027   for (i = 0; i < tab_nr (t); i++)
1028     if (r->h[i] < 0)
1029       error (0, 0, "height of table row %d is %d (not initialized?)",
1030              i, r->h[i]);
1031   for (i = 0; i < tab_nc (t); i++)
1032     if (r->w[i] < 0)
1033       error (0, 0, "width of table column %d is %d (not initialized?)",
1034              i, r->w[i]);
1035
1036   /* Add up header sizes. */
1037   for (i = 0, r->wl = r->wrv[0]; i < r->l; i++)
1038     r->wl += r->w[i] + r->wrv[i + 1];
1039   for (i = 0, r->ht = r->hrh[0]; i < r->t; i++)
1040     r->ht += r->h[i] + r->hrh[i + 1];
1041   for (i = tab_nc (t) - r->r, r->wr = r->wrv[i]; i < tab_nc (t); i++)
1042     r->wr += r->w[i] + r->wrv[i + 1];
1043   for (i = tab_nr (t) - r->b, r->hb = r->hrh[i]; i < tab_nr (t); i++)
1044     r->hb += r->h[i] + r->hrh[i + 1];
1045
1046   /* Title. */
1047   if (!(t->flags & SOMF_NO_TITLE))
1048     r->ht += driver->font_height;
1049
1050   return r;
1051 }
1052
1053 static void
1054 tabi_render_free (void *r_)
1055 {
1056   struct tab_rendering *r = r_;
1057
1058   free (r->w);
1059   free (r->h);
1060   free (r->hrh);
1061   free (r->wrv);
1062   free (r);
1063 }
1064
1065 /* Return the horizontal and vertical size of the entire table,
1066    including headers, for the current output device, into HORIZ and
1067    VERT. */
1068 static void
1069 tabi_area (void *r_, int *horiz, int *vert)
1070 {
1071   struct tab_rendering *r = r_;
1072   const struct tab_table *t = r->table;
1073   int width, col;
1074   int height, row;
1075
1076   width = 0;
1077   for (col = r->l + 1, width = r->wl + r->wr + r->w[tab_l (t)];
1078        col < tab_nc (t) - r->r; col++)
1079     width += r->w[col] + r->wrv[col];
1080   *horiz = width;
1081
1082   height = 0;
1083   for (row = r->t + 1, height = r->ht + r->hb + r->h[tab_t (t)];
1084        row < tab_nr (t) - tab_b (t); row++)
1085     height += r->h[row] + r->hrh[row];
1086   *vert = height;
1087 }
1088
1089 /* Determines the number of rows or columns (including appropriate
1090    headers), depending on CUMTYPE, that will fit into the space
1091    specified.  Takes rows/columns starting at index START and attempts
1092    to fill up available space MAX.  Returns in END the index of the
1093    last row/column plus one; returns in ACTUAL the actual amount of
1094    space the selected rows/columns (including appropriate headers)
1095    filled. */
1096 static void
1097 tabi_cumulate (void *r_, int cumtype, int start, int *end,
1098                int max, int *actual)
1099 {
1100   const struct tab_rendering *r = r_;
1101   const struct tab_table *t = r->table;
1102   int limit;
1103   int *cells, *rules;
1104   int total;
1105   int idx;
1106
1107   assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
1108   if (cumtype == SOM_ROWS)
1109     {
1110       assert (start >= 0 && start < tab_nr (t));
1111       limit = tab_nr (t) - r->b;
1112       cells = &r->h[start];
1113       rules = &r->hrh[start + 1];
1114       total = r->ht + r->hb;
1115     }
1116   else
1117     {
1118       assert (start >= 0 && start < tab_nc (t));
1119       limit = tab_nc (t) - tab_r (t);
1120       cells = &r->w[start];
1121       rules = &r->wrv[start + 1];
1122       total = r->wl + r->wr;
1123     }
1124
1125   total += *cells++;
1126   if (total > max)
1127     {
1128       if (end)
1129         *end = start;
1130       if (actual)
1131         *actual = 0;
1132       return;
1133     }
1134
1135   for (idx = start + 1; idx < limit; idx++)
1136     {
1137       int amt = *cells++ + *rules++;
1138
1139       total += amt;
1140       if (total > max)
1141         {
1142           total -= amt;
1143           break;
1144         }
1145     }
1146
1147   if (end)
1148     *end = idx;
1149
1150   if (actual)
1151     *actual = total;
1152 }
1153
1154 /* Render title for current table, with major index X and minor index
1155    Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
1156    if Y is nonzero. */
1157 static void
1158 tabi_title (void *r_, int x, int y, int table_num, int subtable_num,
1159             const char *command_name)
1160 {
1161   const struct tab_rendering *r = r_;
1162   const struct tab_table *t = r->table;
1163   struct outp_text text;
1164   struct string title;
1165
1166   if (t->flags & SOMF_NO_TITLE)
1167     return;
1168
1169   ds_init_empty (&title);
1170   ds_put_format (&title,"%d.%d", table_num, subtable_num);
1171   if (x && y)
1172     ds_put_format (&title, "(%d:%d)", x, y);
1173   else if (x)
1174     ds_put_format (&title, "(%d)", x);
1175   if (command_name != NULL)
1176     ds_put_format (&title, " %s", command_name);
1177   ds_put_cstr (&title, ".  ");
1178   if (t->title != NULL)
1179     ds_put_cstr (&title, t->title);
1180
1181   text.font = OUTP_PROPORTIONAL;
1182   text.justification = OUTP_LEFT;
1183   text.string = ds_ss (&title);
1184   text.h = r->driver->width;
1185   text.v = r->driver->font_height;
1186   text.x = 0;
1187   text.y = r->driver->cp_y;
1188   r->driver->class->text_draw (r->driver, &text);
1189
1190   ds_destroy (&title);
1191 }
1192
1193 static int render_strip (const struct tab_rendering *,
1194                          int x, int y, int r, int c1, int c2, int r1, int r2);
1195
1196 static void
1197 add_range (int ranges[][2], int *np, int start, int end)
1198 {
1199   int n = *np;
1200   if (n == 0 || start > ranges[n - 1][1])
1201     {
1202       ranges[n][0] = start;
1203       ranges[n][1] = end;
1204       ++*np;
1205     }
1206   else
1207     ranges[n - 1][1] = end;
1208 }
1209
1210 /* Draws table region (C0,R0)-(C1,R1), plus headers, at the
1211    current position on the current output device.  */
1212 static void
1213 tabi_render (void *r_, int c0, int r0, int c1, int r1)
1214 {
1215   const struct tab_rendering *r = r_;
1216   const struct tab_table *t = r->table;
1217   int rows[3][2], cols[3][2];
1218   int n_row_ranges, n_col_ranges;
1219   int y, i;
1220
1221   /* Rows to render, counting horizontal rules as rows.  */
1222   n_row_ranges = 0;
1223   add_range (rows, &n_row_ranges, 0, tab_t (t) * 2 + 1);
1224   add_range (rows, &n_row_ranges, r0 * 2 + 1, r1 * 2);
1225   add_range (rows, &n_row_ranges, (tab_nr (t) - tab_b (t)) * 2,
1226              tab_nr (t) * 2 + 1);
1227
1228   /* Columns to render, counting vertical rules as columns. */
1229   n_col_ranges = 0;
1230   add_range (cols, &n_col_ranges, 0, r->l * 2 + 1);
1231   add_range (cols, &n_col_ranges, c0 * 2 + 1, c1 * 2);
1232   add_range (cols, &n_col_ranges, (tab_nc (t) - r->r) * 2, tab_nc (t) * 2 + 1);
1233
1234   y = r->driver->cp_y;
1235   if (!(t->flags & SOMF_NO_TITLE))
1236     y += r->driver->font_height;
1237   for (i = 0; i < n_row_ranges; i++)
1238     {
1239       int row;
1240
1241       for (row = rows[i][0]; row < rows[i][1]; row++)
1242         {
1243           int x, j;
1244
1245           x = r->driver->cp_x;
1246           for (j = 0; j < n_col_ranges; j++)
1247             x = render_strip (r, x, y, row,
1248                               cols[j][0], cols[j][1],
1249                               rows[i][0], rows[i][1]);
1250
1251           y += (row & 1) ? r->h[row / 2] : r->hrh[row / 2];
1252         }
1253     }
1254 }
1255
1256 const struct som_table_class tab_table_class =
1257   {
1258     tabi_count,
1259     tabi_columns,
1260     tabi_headers,
1261     tabi_flags,
1262
1263     tabi_render_init,
1264     tabi_render_free,
1265
1266     tabi_area,
1267     tabi_cumulate,
1268     tabi_title,
1269     tabi_render,
1270   };
1271 \f
1272 static enum outp_justification
1273 translate_justification (unsigned int opt)
1274 {
1275   switch (opt & TAB_ALIGN_MASK)
1276     {
1277     case TAB_RIGHT:
1278       return OUTP_RIGHT;
1279     case TAB_LEFT:
1280       return OUTP_LEFT;
1281     case TAB_CENTER:
1282       return OUTP_CENTER;
1283     default:
1284       NOT_REACHED ();
1285     }
1286 }
1287
1288 /* Returns the line style to use for drawing a rule of the given
1289    TYPE. */
1290 static enum outp_line_style
1291 rule_to_draw_type (unsigned char type)
1292 {
1293   switch (type)
1294     {
1295     case TAL_0:
1296     case TAL_GAP:
1297       return OUTP_L_NONE;
1298     case TAL_1:
1299       return OUTP_L_SINGLE;
1300     case TAL_2:
1301       return OUTP_L_DOUBLE;
1302     default:
1303       NOT_REACHED ();
1304     }
1305 }
1306
1307 /* Returns the horizontal rule at the given column and row. */
1308 static int
1309 get_hrule (const struct tab_table *t, int col, int row)
1310 {
1311   return t->rh[col + row * t->cf];
1312 }
1313
1314 /* Returns the vertical rule at the given column and row. */
1315 static int
1316 get_vrule (const struct tab_table *t, int col, int row)
1317 {
1318   return t->rv[col + row * (t->cf + 1)];
1319 }
1320
1321 /* Renders the horizontal rule at the given column and row
1322    at (X,Y) on the page. */
1323 static void
1324 render_horz_rule (const struct tab_rendering *r,
1325                   int x, int y, int col, int row)
1326 {
1327   enum outp_line_style style;
1328   style = rule_to_draw_type (get_hrule (r->table, col, row));
1329   if (style != OUTP_L_NONE)
1330     r->driver->class->line (r->driver, x, y, x + r->w[col], y + r->hrh[row],
1331                             OUTP_L_NONE, style, OUTP_L_NONE, style);
1332 }
1333
1334 /* Renders the vertical rule at the given column and row
1335    at (X,Y) on the page. */
1336 static void
1337 render_vert_rule (const struct tab_rendering *r,
1338                   int x, int y, int col, int row)
1339 {
1340   enum outp_line_style style;
1341   style = rule_to_draw_type (get_vrule (r->table, col, row));
1342   if (style != OUTP_L_NONE)
1343     r->driver->class->line (r->driver, x, y, x + r->wrv[col], y + r->h[row],
1344                             style, OUTP_L_NONE, style, OUTP_L_NONE);
1345 }
1346
1347 /* Renders the rule intersection at the given column and row
1348    at (X,Y) on the page. */
1349 static void
1350 render_rule_intersection (const struct tab_rendering *r,
1351                           int x, int y, int col, int row)
1352 {
1353   const struct tab_table *t = r->table;
1354
1355   /* Bounds of intersection. */
1356   int x0 = x;
1357   int y0 = y;
1358   int x1 = x + r->wrv[col];
1359   int y1 = y + r->hrh[row];
1360
1361   /* Lines on each side of intersection. */
1362   int top = row > 0 ? get_vrule (t, col, row - 1) : TAL_0;
1363   int left = col > 0 ? get_hrule (t, col - 1, row) : TAL_0;
1364   int bottom = row < tab_nr (t) ? get_vrule (t, col, row) : TAL_0;
1365   int right = col < tab_nc (t) ? get_hrule (t, col, row) : TAL_0;
1366
1367   /* Output style for each line. */
1368   enum outp_line_style o_top = rule_to_draw_type (top);
1369   enum outp_line_style o_left = rule_to_draw_type (left);
1370   enum outp_line_style o_bottom = rule_to_draw_type (bottom);
1371   enum outp_line_style o_right = rule_to_draw_type (right);
1372
1373   if (o_top != OUTP_L_NONE || o_left != OUTP_L_NONE
1374       || o_bottom != OUTP_L_NONE || o_right != OUTP_L_NONE)
1375     r->driver->class->line (r->driver, x0, y0, x1, y1,
1376                             o_top, o_left, o_bottom, o_right);
1377 }
1378
1379 /* Returns the width of columns C1...C2 exclusive,
1380    including interior but not exterior rules. */
1381 static int
1382 strip_width (const struct tab_rendering *r, int c1, int c2)
1383 {
1384   int width = 0;
1385   int c;
1386
1387   for (c = c1; c < c2; c++)
1388     width += r->w[c] + r->wrv[c + 1];
1389   if (c1 < c2)
1390     width -= r->wrv[c2];
1391   return width;
1392 }
1393
1394 /* Returns the height of rows R1...R2 exclusive,
1395    including interior but not exterior rules. */
1396 static int
1397 strip_height (const struct tab_rendering *r, int r1, int r2)
1398 {
1399   int height = 0;
1400   int row;
1401
1402   for (row = r1; row < r2; row++)
1403     height += r->h[row] + r->hrh[row + 1];
1404   if (r1 < r2)
1405     height -= r->hrh[r2];
1406   return height;
1407 }
1408
1409 /* Renders the cell at the given column and row at (X,Y) on the
1410    page.  Also renders joined cells that extend as far to the
1411    right as C1 and as far down as R1. */
1412 static void
1413 render_cell (const struct tab_rendering *r,
1414              int x, int y, int col, int row, int c1, int r1)
1415 {
1416   const struct tab_table *t = r->table;
1417   const int index = col + (row * t->cf);
1418   unsigned char type = t->ct[index];
1419   struct substring *content = &t->cc[index];
1420
1421   if (!(type & TAB_JOIN))
1422     {
1423       if (!(type & TAB_EMPTY))
1424         {
1425           struct outp_text text;
1426           text.font = options_to_font (type);
1427           text.justification = translate_justification (type);
1428           text.string = *content;
1429           text.h = r->w[col];
1430           text.v = r->h[row];
1431           text.x = x;
1432           text.y = y;
1433           r->driver->class->text_draw (r->driver, &text);
1434         }
1435     }
1436   else
1437     {
1438       struct tab_joined_cell *j
1439         = (struct tab_joined_cell *) ss_data (*content);
1440
1441       if (j->x1 == col && j->y1 == row)
1442         {
1443           struct outp_text text;
1444           text.font = options_to_font (type);
1445           text.justification = translate_justification (type);
1446           text.string = j->contents;
1447           text.x = x;
1448           text.y = y;
1449           text.h = strip_width (r, j->x1, MIN (j->x2, c1));
1450           text.v = strip_height (r, j->y1, MIN (j->y2, r1));
1451           r->driver->class->text_draw (r->driver, &text);
1452         }
1453     }
1454 }
1455
1456 /* Render contiguous strip consisting of columns C0...C1, exclusive,
1457    on row ROW, at (X,Y).  Returns X position after rendering.
1458    Also renders joined cells that extend beyond that strip,
1459    cropping them to lie within rendering region (C0,R0)-(C1,R1).
1460    C0 and C1 count vertical rules as columns.
1461    ROW counts horizontal rules as rows, but R0 and R1 do not. */
1462 static int
1463 render_strip (const struct tab_rendering *r,
1464               int x, int y, int row, int c0, int c1, int r0 UNUSED, int r1)
1465 {
1466   int col;
1467
1468   for (col = c0; col < c1; col++)
1469     if (col & 1)
1470       {
1471         if (row & 1)
1472           render_cell (r, x, y, col / 2, row / 2, c1 / 2, r1);
1473         else
1474           render_horz_rule (r, x, y, col / 2, row / 2);
1475         x += r->w[col / 2];
1476       }
1477     else
1478       {
1479         if (row & 1)
1480           render_vert_rule (r, x, y, col / 2, row / 2);
1481         else
1482           render_rule_intersection (r, x, y, col / 2, row / 2);
1483         x += r->wrv[col / 2];
1484       }
1485
1486   return x;
1487 }