output: Always use helper functions to access struct tab_table members.
[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, int reallocable UNUSED)
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 /* Formats text TEXT and arguments ARGS as indicated in OPT in
386    TABLE's pool and returns the resultant string. */
387 static struct substring
388 text_format (struct tab_table *table, int opt, const char *text, va_list args)
389 {
390   assert (table != NULL && text != NULL);
391
392   return ss_cstr (opt & TAT_PRINTF
393                   ? pool_vasprintf (table->container, text, args)
394                   : pool_strdup (table->container, text));
395 }
396
397 /* Set the title of table T to TITLE, which is formatted as if
398    passed to printf(). */
399 void
400 tab_title (struct tab_table *t, const char *title, ...)
401 {
402   va_list args;
403
404   assert (t != NULL && title != NULL);
405   va_start (args, title);
406   t->title = xvasprintf (title, args);
407   va_end (args);
408 }
409
410 /* Set DIM_FUNC, which will be passed auxiliary data AUX, as the
411    dimension function for table T.
412
413    DIM_FUNC must not assume that it is called from the same
414    context as tab_dim; for example, table T might be kept in
415    memory and, thus, DIM_FUNC might be called after the currently
416    running command completes.  If it is non-null, FREE_FUNC is
417    called when the table is destroyed, to allow any data
418    allocated for use by DIM_FUNC to be freed.  */
419 void
420 tab_dim (struct tab_table *t,
421          tab_dim_func *dim_func, tab_dim_free_func *free_func, void *aux)
422 {
423   assert (t->dim == NULL);
424   t->dim = dim_func;
425   t->dim_free = free_func;
426   t->dim_aux = aux;
427 }
428
429 /* Returns the natural width of column C in table T for driver D, that
430    is, the smallest width necessary to display all its cells without
431    wrapping.  The width will be no larger than the page width minus
432    left and right rule widths. */
433 int
434 tab_natural_width (const struct tab_rendering *r, int col)
435 {
436   const struct tab_table *t = r->table;
437   int width, row, max_width;
438
439   assert (col >= 0 && col < tab_nc (t));
440
441   width = 0;
442   for (row = 0; row < tab_nr (t); row++)
443     {
444       struct outp_text text;
445       unsigned char opt = t->ct[col + row * t->cf];
446       int w;
447
448       if (opt & (TAB_JOIN | TAB_EMPTY))
449         continue;
450
451       text.string = t->cc[col + row * t->cf];
452       text.justification = OUTP_LEFT;
453       text.font = options_to_font (opt);
454       text.h = text.v = INT_MAX;
455
456       r->driver->class->text_metrics (r->driver, &text, &w, NULL);
457       if (w > width)
458         width = w;
459     }
460
461   if (width == 0)
462     {
463       /* FIXME: This is an ugly kluge to compensate for the fact
464          that we don't let joined cells contribute to column
465          widths. */
466       width = r->driver->prop_em_width * 8;
467     }
468
469   max_width = r->driver->width - r->wrv[0] - r->wrv[tab_nc (t)];
470   return MIN (width, max_width);
471 }
472
473 /* Returns the natural height of row R in table T for driver D, that
474    is, the minimum height necessary to display the information in the
475    cell at the widths set for each column. */
476 int
477 tab_natural_height (const struct tab_rendering *r, int row)
478 {
479   const struct tab_table *t = r->table;
480   int height, col;
481
482   assert (row >= 0 && row < tab_nr (t));
483
484   height = r->driver->font_height;
485   for (col = 0; col < tab_nc (t); col++)
486     {
487       struct outp_text text;
488       unsigned char opt = t->ct[col + row * t->cf];
489       int h;
490
491       if (opt & (TAB_JOIN | TAB_EMPTY))
492         continue;
493
494       text.string = t->cc[col + row * t->cf];
495       text.justification = OUTP_LEFT;
496       text.font = options_to_font (opt);
497       text.h = r->w[col];
498       text.v = INT_MAX;
499       r->driver->class->text_metrics (r->driver, &text, NULL, &h);
500
501       if (h > height)
502         height = h;
503     }
504
505   return height;
506 }
507
508 /* Callback function to set all columns and rows to their natural
509    dimensions.  Not really meant to be called directly.  */
510 void
511 tab_natural_dimensions (struct tab_rendering *r, void *aux UNUSED)
512 {
513   const struct tab_table *t = r->table;
514   int i;
515
516   for (i = 0; i < tab_nc (t); i++)
517     r->w[i] = tab_natural_width (r, i);
518
519   for (i = 0; i < tab_nr (t); i++)
520     r->h[i] = tab_natural_height (r, i);
521 }
522
523 \f
524 /* Cells. */
525
526 /* Sets cell (C,R) in TABLE, with options OPT, to have a value taken
527    from V, displayed with format spec F. */
528 void
529 tab_value (struct tab_table *table, int c, int r, unsigned char opt,
530            const union value *v, const struct fmt_spec *f)
531 {
532   char *contents;
533
534   assert (table != NULL && v != NULL && f != NULL);
535 #if DEBUGGING
536   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
537       || c + table->col_ofs >= tab_nc (table)
538       || r + table->row_ofs >= tab_nr (table))
539     {
540       printf ("tab_value(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
541               "(%d,%d)\n",
542               c, table->col_ofs, c + table->col_ofs,
543               r, table->row_ofs, r + table->row_ofs,
544               tab_nc (table), tab_nr (table));
545       return;
546     }
547 #endif
548
549   contents = pool_alloc (table->container, f->w);
550   table->cc[c + r * table->cf] = ss_buffer (contents, f->w);
551   table->ct[c + r * table->cf] = opt;
552
553   data_out (v, f, contents);
554 }
555
556 /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL
557    with NDEC decimal places. */
558 void
559 tab_fixed (struct tab_table *table, int c, int r, unsigned char opt,
560            double val, int w, int d)
561 {
562   char *contents;
563   char buf[40], *cp;
564
565   struct fmt_spec f;
566   union value double_value;
567
568   assert (table != NULL && w <= 40);
569
570   assert (c >= 0);
571   assert (c < tab_nc (table));
572   assert (r >= 0);
573   assert (r < tab_nr (table));
574
575   f = fmt_for_output (FMT_F, w, d);
576
577 #if DEBUGGING
578   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
579       || c + table->col_ofs >= tab_nc (table)
580       || r + table->row_ofs >= tab_nr (table))
581     {
582       printf ("tab_fixed(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
583               "(%d,%d)\n",
584               c, table->col_ofs, c + table->col_ofs,
585               r, table->row_ofs, r + table->row_ofs,
586               tab_nc (table), tab_nr (table));
587       return;
588     }
589 #endif
590
591   double_value.f = val;
592   data_out (&double_value, &f, buf);
593
594   cp = buf;
595   while (isspace ((unsigned char) *cp) && cp < &buf[w])
596     cp++;
597   f.w = w - (cp - buf);
598
599   contents = pool_alloc (table->container, f.w);
600   table->cc[c + r * table->cf] = ss_buffer (contents, f.w);
601   table->ct[c + r * table->cf] = opt;
602   memcpy (contents, cp, f.w);
603 }
604
605 /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL as
606    formatted by FMT.
607    If FMT is null, then the default print format will be used.
608 */
609 void
610 tab_double (struct tab_table *table, int c, int r, unsigned char opt,
611            double val, const struct fmt_spec *fmt)
612 {
613   int w;
614   char *contents;
615   char buf[40], *cp;
616
617   union value double_value;
618
619   assert (table != NULL);
620
621   assert (c >= 0);
622   assert (c < tab_nc (table));
623   assert (r >= 0);
624   assert (r < tab_nr (table));
625
626   if ( fmt == NULL)
627     fmt = settings_get_format ();
628
629   fmt_check_output (fmt);
630
631 #if DEBUGGING
632   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
633       || c + table->col_ofs >= tab_nc (table)
634       || r + table->row_ofs >= tab_nr (table))
635     {
636       printf ("tab_double(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
637               "(%d,%d)\n",
638               c, table->col_ofs, c + table->col_ofs,
639               r, table->row_ofs, r + table->row_ofs,
640               tab_nc (table), tab_nr (table));
641       return;
642     }
643 #endif
644
645   double_value.f = val;
646   data_out (&double_value, fmt, buf);
647
648   cp = buf;
649   while (isspace ((unsigned char) *cp) && cp < &buf[fmt->w])
650     cp++;
651   w = fmt->w - (cp - buf);
652
653   contents = pool_alloc (table->container, w);
654   table->cc[c + r * table->cf] = ss_buffer (contents, w);
655   table->ct[c + r * table->cf] = opt;
656   memcpy (contents, cp, w);
657 }
658
659
660 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
661    TEXT. */
662 void
663 tab_text (struct tab_table *table, int c, int r, unsigned opt, const char *text, ...)
664 {
665   va_list args;
666
667   assert (table != NULL && text != NULL);
668
669   assert (c >= 0 );
670   assert (r >= 0 );
671   assert (c < tab_nc (table));
672   assert (r < tab_nr (table));
673
674
675 #if DEBUGGING
676   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
677       || c + table->col_ofs >= tab_nc (table)
678       || r + table->row_ofs >= tab_nr (table))
679     {
680       printf ("tab_text(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
681               "(%d,%d)\n",
682               c, table->col_ofs, c + table->col_ofs,
683               r, table->row_ofs, r + table->row_ofs,
684               tab_nc (table), tab_nr (table));
685       return;
686     }
687 #endif
688
689   va_start (args, text);
690   table->cc[c + r * table->cf] = text_format (table, opt, text, args);
691   table->ct[c + r * table->cf] = opt;
692   va_end (args);
693 }
694
695 /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
696    options OPT to have text value TEXT. */
697 void
698 tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2,
699                 unsigned opt, const char *text, ...)
700 {
701   struct tab_joined_cell *j;
702
703   assert (table != NULL && text != NULL);
704
705   assert (x1 + table->col_ofs >= 0);
706   assert (y1 + table->row_ofs >= 0);
707   assert (y2 >= y1);
708   assert (x2 >= x1);
709   assert (y2 + table->row_ofs < tab_nr (table));
710   assert (x2 + table->col_ofs < tab_nc (table));
711
712 #if DEBUGGING
713   if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= tab_nc (table)
714       || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= tab_nr (table)
715       || x2 < x1 || x2 + table->col_ofs >= tab_nc (table)
716       || y2 < y2 || y2 + table->row_ofs >= tab_nr (table))
717     {
718       printf ("tab_joint_text(): bad cell "
719               "(%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n",
720               x1, table->col_ofs, x1 + table->col_ofs,
721               y1, table->row_ofs, y1 + table->row_ofs,
722               x2, table->col_ofs, x2 + table->col_ofs,
723               y2, table->row_ofs, y2 + table->row_ofs,
724               tab_nc (table), tab_nr (table));
725       return;
726     }
727 #endif
728
729   tab_box (table, -1, -1, TAL_0, TAL_0, x1, y1, x2, y2);
730
731   j = pool_alloc (table->container, sizeof *j);
732   j->x1 = x1 + table->col_ofs;
733   j->y1 = y1 + table->row_ofs;
734   j->x2 = ++x2 + table->col_ofs;
735   j->y2 = ++y2 + table->row_ofs;
736
737   {
738     va_list args;
739
740     va_start (args, text);
741     j->contents = text_format (table, opt, text, args);
742     va_end (args);
743   }
744
745   opt |= TAB_JOIN;
746
747   {
748     struct substring *cc = &table->cc[x1 + y1 * table->cf];
749     unsigned char *ct = &table->ct[x1 + y1 * table->cf];
750     const int ofs = table->cf - (x2 - x1);
751
752     int y;
753
754     for (y = y1; y < y2; y++)
755       {
756         int x;
757
758         for (x = x1; x < x2; x++)
759           {
760             *cc++ = ss_buffer ((char *) j, 0);
761             *ct++ = opt;
762           }
763
764         cc += ofs;
765         ct += ofs;
766       }
767   }
768 }
769
770 /* Sets cell (C,R) in TABLE, with options OPT, to contents STRING. */
771 void
772 tab_raw (struct tab_table *table, int c, int r, unsigned opt,
773          struct substring *string)
774 {
775   assert (table != NULL && string != NULL);
776
777 #if DEBUGGING
778   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
779       || c + table->col_ofs >= tab_nc (table)
780       || r + table->row_ofs >= tab_nr (table))
781     {
782       printf ("tab_raw(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
783               "(%d,%d)\n",
784               c, table->col_ofs, c + table->col_ofs,
785               r, table->row_ofs, r + table->row_ofs,
786               tab_nc (table), tab_nr (table));
787       return;
788     }
789 #endif
790
791   table->cc[c + r * table->cf] = *string;
792   table->ct[c + r * table->cf] = opt;
793 }
794 \f
795 /* Miscellaneous. */
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 nowrap_dim (struct tab_rendering *r, void *aux UNUSED)
801 {
802   r->w[0] = tab_natural_width (r, 0);
803   r->h[0] = r->driver->font_height;
804 }
805
806 /* Sets the widths of all the columns and heights of all the rows in
807    table T for driver D. */
808 static void
809 wrap_dim (struct tab_rendering *r, void *aux UNUSED)
810 {
811   r->w[0] = tab_natural_width (r, 0);
812   r->h[0] = tab_natural_height (r, 0);
813 }
814
815 /* Outputs text BUF 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 *buf, ...)
820 {
821   struct tab_table *t = tab_create (1, 1, 0);
822   char *tmp_buf = NULL;
823
824   if (options & TAT_PRINTF)
825     {
826       va_list args;
827
828       va_start (args, buf);
829       buf = tmp_buf = xvasprintf (buf, args);
830       va_end (args);
831     }
832
833   tab_text (t, 0, 0, options & ~TAT_PRINTF, buf);
834   tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
835   tab_dim (t, options & TAT_NOWRAP ? nowrap_dim : wrap_dim, NULL, NULL);
836   tab_submit (t);
837
838   free (tmp_buf);
839 }
840
841 /* Set table flags to FLAGS. */
842 void
843 tab_flags (struct tab_table *t, unsigned flags)
844 {
845   assert (t != NULL);
846   t->flags = flags;
847 }
848
849 /* Easy, type-safe way to submit a tab table to som. */
850 void
851 tab_submit (struct tab_table *t)
852 {
853   struct som_entity s;
854
855   assert (t != NULL);
856   s.class = &tab_table_class;
857   s.ext = t;
858   s.type = SOM_TABLE;
859   som_submit (&s);
860   tab_destroy (t);
861 }
862 \f
863 /* Editing. */
864
865 /* Set table row and column offsets for all functions that affect
866    cells or rules. */
867 void
868 tab_offset (struct tab_table *t, int col, int row)
869 {
870   int diff = 0;
871
872   assert (t != NULL);
873 #if DEBUGGING
874   if (row < -1 || row > tab_nr (t))
875     {
876       printf ("tab_offset(): row=%d in %d-row table\n", row, tab_nr (t));
877       NOT_REACHED ();
878     }
879   if (col < -1 || col > tab_nc (t))
880     {
881       printf ("tab_offset(): col=%d in %d-column table\n", col, tab_nc (t));
882       NOT_REACHED ();
883     }
884 #endif
885
886   if (row != -1)
887     diff += (row - t->row_ofs) * t->cf, t->row_ofs = row;
888   if (col != -1)
889     diff += (col - t->col_ofs), t->col_ofs = col;
890
891   t->cc += diff;
892   t->ct += diff;
893 }
894
895 /* Increment the row offset by one. If the table is too small,
896    increase its size. */
897 void
898 tab_next_row (struct tab_table *t)
899 {
900   assert (t != NULL);
901   t->cc += t->cf;
902   t->ct += t->cf;
903   if (++t->row_ofs >= tab_nr (t))
904     tab_realloc (t, -1, tab_nr (t) * 4 / 3);
905 }
906 \f
907 /* Return the number of columns and rows in the table into N_COLUMNS
908    and N_ROWS, respectively. */
909 static void
910 tabi_count (struct som_entity *t_, int *n_columns, int *n_rows)
911 {
912   struct tab_table *t = t_->ext;
913   *n_columns = t->nc;
914   *n_rows = t->nr;
915 }
916
917 /* Return the column style for this table into STYLE. */
918 static void
919 tabi_columns (struct som_entity *t_, int *style)
920 {
921   struct tab_table *t = t_->ext;
922   *style = t->col_style;
923 }
924
925 /* Return the number of header rows/columns on the left, right, top,
926    and bottom sides into HL, HR, HT, and HB, respectively. */
927 static void
928 tabi_headers (struct som_entity *t_, int *hl, int *hr, int *ht, int *hb)
929 {
930   struct tab_table *t = t_->ext;
931   *hl = t->l;
932   *hr = t->r;
933   *ht = t->t;
934   *hb = t->b;
935 }
936
937 /* Return flags set for the current table into FLAGS. */
938 static void
939 tabi_flags (struct som_entity *t_, unsigned *flags)
940 {
941   struct tab_table *t = t_->ext;
942   *flags = t->flags;
943 }
944
945 /* Returns the line style to use for spacing purposes for a rule
946    of the given TYPE. */
947 static enum outp_line_style
948 rule_to_spacing_type (unsigned char type)
949 {
950   switch (type)
951     {
952     case TAL_0:
953       return OUTP_L_NONE;
954     case TAL_GAP:
955     case TAL_1:
956       return OUTP_L_SINGLE;
957     case TAL_2:
958       return OUTP_L_DOUBLE;
959     default:
960       NOT_REACHED ();
961     }
962 }
963
964 static void *
965 tabi_render_init (struct som_entity *t_, struct outp_driver *driver,
966                   int hl, int hr, int ht, int hb)
967 {
968   const struct tab_table *t = t_->ext;
969   struct tab_rendering *r;
970   int col, row;
971   int i;
972
973   tab_offset (t_->ext, 0, 0);
974
975   r = xmalloc (sizeof *r);
976   r->table = t;
977   r->driver = driver;
978   r->w = xnmalloc (tab_nc (t), sizeof *r->w);
979   r->h = xnmalloc (tab_nr (t), sizeof *r->h);
980   r->hrh = xnmalloc (tab_nr (t) + 1, sizeof *r->hrh);
981   r->wrv = xnmalloc (tab_nc (t) + 1, sizeof *r->wrv);
982   r->l = hl;
983   r->r = hr;
984   r->t = ht;
985   r->b = hb;
986
987   /* Figure out sizes of rules. */
988   for (row = 0; row <= tab_nr (t); row++)
989     {
990       int width = 0;
991       for (col = 0; col < tab_nc (t); col++)
992         {
993           unsigned char rh = t->rh[col + row * t->cf];
994           int w = driver->horiz_line_width[rule_to_spacing_type (rh)];
995           if (w > width)
996             width = w;
997         }
998       r->hrh[row] = width;
999     }
1000
1001   for (col = 0; col <= tab_nc (t); col++)
1002     {
1003       int width = 0;
1004       for (row = 0; row < tab_nr (t); row++)
1005         {
1006           unsigned char *rv = &t->rv[col + row * (t->cf + 1)];
1007           int w;
1008           if (*rv == UCHAR_MAX)
1009             *rv = col != 0 && col != tab_nc (t) ? TAL_GAP : TAL_0;
1010           w = driver->vert_line_width[rule_to_spacing_type (*rv)];
1011           if (w > width)
1012             width = w;
1013         }
1014       r->wrv[col] = width;
1015     }
1016
1017   /* Determine row heights and columns widths. */
1018   for (i = 0; i < tab_nr (t); i++)
1019     r->h[i] = -1;
1020   for (i = 0; i < tab_nc (t); i++)
1021     r->w[i] = -1;
1022
1023   t->dim (r, t->dim_aux);
1024
1025   for (i = 0; i < tab_nr (t); i++)
1026     if (r->h[i] < 0)
1027       error (0, 0, "height of table row %d is %d (not initialized?)",
1028              i, r->h[i]);
1029   for (i = 0; i < tab_nc (t); i++)
1030     if (r->w[i] < 0)
1031       error (0, 0, "width of table column %d is %d (not initialized?)",
1032              i, r->w[i]);
1033
1034   /* Add up header sizes. */
1035   for (i = 0, r->wl = r->wrv[0]; i < r->l; i++)
1036     r->wl += r->w[i] + r->wrv[i + 1];
1037   for (i = 0, r->ht = r->hrh[0]; i < r->t; i++)
1038     r->ht += r->h[i] + r->hrh[i + 1];
1039   for (i = tab_nc (t) - r->r, r->wr = r->wrv[i]; i < tab_nc (t); i++)
1040     r->wr += r->w[i] + r->wrv[i + 1];
1041   for (i = tab_nr (t) - r->b, r->hb = r->hrh[i]; i < tab_nr (t); i++)
1042     r->hb += r->h[i] + r->hrh[i + 1];
1043
1044   /* Title. */
1045   if (!(t->flags & SOMF_NO_TITLE))
1046     r->ht += driver->font_height;
1047
1048   return r;
1049 }
1050
1051 static void
1052 tabi_render_free (void *r_)
1053 {
1054   struct tab_rendering *r = r_;
1055
1056   free (r->w);
1057   free (r->h);
1058   free (r->hrh);
1059   free (r->wrv);
1060   free (r);
1061 }
1062
1063 /* Return the horizontal and vertical size of the entire table,
1064    including headers, for the current output device, into HORIZ and
1065    VERT. */
1066 static void
1067 tabi_area (void *r_, int *horiz, int *vert)
1068 {
1069   struct tab_rendering *r = r_;
1070   const struct tab_table *t = r->table;
1071   int width, col;
1072   int height, row;
1073
1074   width = 0;
1075   for (col = r->l + 1, width = r->wl + r->wr + r->w[tab_l (t)];
1076        col < tab_nc (t) - r->r; col++)
1077     width += r->w[col] + r->wrv[col];
1078   *horiz = width;
1079
1080   height = 0;
1081   for (row = r->t + 1, height = r->ht + r->hb + r->h[tab_t (t)];
1082        row < tab_nr (t) - tab_b (t); row++)
1083     height += r->h[row] + r->hrh[row];
1084   *vert = height;
1085 }
1086
1087 /* Determines the number of rows or columns (including appropriate
1088    headers), depending on CUMTYPE, that will fit into the space
1089    specified.  Takes rows/columns starting at index START and attempts
1090    to fill up available space MAX.  Returns in END the index of the
1091    last row/column plus one; returns in ACTUAL the actual amount of
1092    space the selected rows/columns (including appropriate headers)
1093    filled. */
1094 static void
1095 tabi_cumulate (void *r_, int cumtype, int start, int *end,
1096                int max, int *actual)
1097 {
1098   const struct tab_rendering *r = r_;
1099   const struct tab_table *t = r->table;
1100   int limit;
1101   int *cells, *rules;
1102   int total;
1103   int idx;
1104
1105   assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
1106   if (cumtype == SOM_ROWS)
1107     {
1108       assert (start >= 0 && start < tab_nr (t));
1109       limit = tab_nr (t) - r->b;
1110       cells = &r->h[start];
1111       rules = &r->hrh[start + 1];
1112       total = r->ht + r->hb;
1113     }
1114   else
1115     {
1116       assert (start >= 0 && start < tab_nc (t));
1117       limit = tab_nc (t) - tab_r (t);
1118       cells = &r->w[start];
1119       rules = &r->wrv[start + 1];
1120       total = r->wl + r->wr;
1121     }
1122
1123   total += *cells++;
1124   if (total > max)
1125     {
1126       if (end)
1127         *end = start;
1128       if (actual)
1129         *actual = 0;
1130       return;
1131     }
1132
1133   for (idx = start + 1; idx < limit; idx++)
1134     {
1135       int amt = *cells++ + *rules++;
1136
1137       total += amt;
1138       if (total > max)
1139         {
1140           total -= amt;
1141           break;
1142         }
1143     }
1144
1145   if (end)
1146     *end = idx;
1147
1148   if (actual)
1149     *actual = total;
1150 }
1151
1152 /* Render title for current table, with major index X and minor index
1153    Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
1154    if Y is nonzero. */
1155 static void
1156 tabi_title (void *r_, int x, int y, int table_num, int subtable_num,
1157             const char *command_name)
1158 {
1159   const struct tab_rendering *r = r_;
1160   const struct tab_table *t = r->table;
1161   struct outp_text text;
1162   struct string title;
1163
1164   if (t->flags & SOMF_NO_TITLE)
1165     return;
1166
1167   ds_init_empty (&title);
1168   ds_put_format (&title,"%d.%d", table_num, subtable_num);
1169   if (x && y)
1170     ds_put_format (&title, "(%d:%d)", x, y);
1171   else if (x)
1172     ds_put_format (&title, "(%d)", x);
1173   if (command_name != NULL)
1174     ds_put_format (&title, " %s", command_name);
1175   ds_put_cstr (&title, ".  ");
1176   if (t->title != NULL)
1177     ds_put_cstr (&title, t->title);
1178
1179   text.font = OUTP_PROPORTIONAL;
1180   text.justification = OUTP_LEFT;
1181   text.string = ds_ss (&title);
1182   text.h = r->driver->width;
1183   text.v = r->driver->font_height;
1184   text.x = 0;
1185   text.y = r->driver->cp_y;
1186   r->driver->class->text_draw (r->driver, &text);
1187
1188   ds_destroy (&title);
1189 }
1190
1191 static int render_strip (const struct tab_rendering *,
1192                          int x, int y, int r, int c1, int c2, int r1, int r2);
1193
1194 static void
1195 add_range (int ranges[][2], int *np, int start, int end)
1196 {
1197   int n = *np;
1198   if (n == 0 || start > ranges[n - 1][1])
1199     {
1200       ranges[n][0] = start;
1201       ranges[n][1] = end;
1202       ++*np;
1203     }
1204   else
1205     ranges[n - 1][1] = end;
1206 }
1207
1208 /* Draws table region (C0,R0)-(C1,R1), plus headers, at the
1209    current position on the current output device.  */
1210 static void
1211 tabi_render (void *r_, int c0, int r0, int c1, int r1)
1212 {
1213   const struct tab_rendering *r = r_;
1214   const struct tab_table *t = r->table;
1215   int rows[3][2], cols[3][2];
1216   int n_row_ranges, n_col_ranges;
1217   int y, i;
1218
1219   /* Rows to render, counting horizontal rules as rows.  */
1220   n_row_ranges = 0;
1221   add_range (rows, &n_row_ranges, 0, tab_t (t) * 2 + 1);
1222   add_range (rows, &n_row_ranges, r0 * 2 + 1, r1 * 2);
1223   add_range (rows, &n_row_ranges, (tab_nr (t) - tab_b (t)) * 2,
1224              tab_nr (t) * 2 + 1);
1225
1226   /* Columns to render, counting vertical rules as columns. */
1227   n_col_ranges = 0;
1228   add_range (cols, &n_col_ranges, 0, r->l * 2 + 1);
1229   add_range (cols, &n_col_ranges, c0 * 2 + 1, c1 * 2);
1230   add_range (cols, &n_col_ranges, (tab_nc (t) - r->r) * 2, tab_nc (t) * 2 + 1);
1231
1232   y = r->driver->cp_y;
1233   if (!(t->flags & SOMF_NO_TITLE))
1234     y += r->driver->font_height;
1235   for (i = 0; i < n_row_ranges; i++)
1236     {
1237       int row;
1238
1239       for (row = rows[i][0]; row < rows[i][1]; row++)
1240         {
1241           int x, j;
1242
1243           x = r->driver->cp_x;
1244           for (j = 0; j < n_col_ranges; j++)
1245             x = render_strip (r, x, y, row,
1246                               cols[j][0], cols[j][1],
1247                               rows[i][0], rows[i][1]);
1248
1249           y += (row & 1) ? r->h[row / 2] : r->hrh[row / 2];
1250         }
1251     }
1252 }
1253
1254 const struct som_table_class tab_table_class =
1255   {
1256     tabi_count,
1257     tabi_columns,
1258     tabi_headers,
1259     tabi_flags,
1260
1261     tabi_render_init,
1262     tabi_render_free,
1263
1264     tabi_area,
1265     tabi_cumulate,
1266     tabi_title,
1267     tabi_render,
1268   };
1269 \f
1270 static enum outp_justification
1271 translate_justification (unsigned int opt)
1272 {
1273   switch (opt & TAB_ALIGN_MASK)
1274     {
1275     case TAB_RIGHT:
1276       return OUTP_RIGHT;
1277     case TAB_LEFT:
1278       return OUTP_LEFT;
1279     case TAB_CENTER:
1280       return OUTP_CENTER;
1281     default:
1282       NOT_REACHED ();
1283     }
1284 }
1285
1286 /* Returns the line style to use for drawing a rule of the given
1287    TYPE. */
1288 static enum outp_line_style
1289 rule_to_draw_type (unsigned char type)
1290 {
1291   switch (type)
1292     {
1293     case TAL_0:
1294     case TAL_GAP:
1295       return OUTP_L_NONE;
1296     case TAL_1:
1297       return OUTP_L_SINGLE;
1298     case TAL_2:
1299       return OUTP_L_DOUBLE;
1300     default:
1301       NOT_REACHED ();
1302     }
1303 }
1304
1305 /* Returns the horizontal rule at the given column and row. */
1306 static int
1307 get_hrule (const struct tab_table *t, int col, int row)
1308 {
1309   return t->rh[col + row * t->cf];
1310 }
1311
1312 /* Returns the vertical rule at the given column and row. */
1313 static int
1314 get_vrule (const struct tab_table *t, int col, int row)
1315 {
1316   return t->rv[col + row * (t->cf + 1)];
1317 }
1318
1319 /* Renders the horizontal rule at the given column and row
1320    at (X,Y) on the page. */
1321 static void
1322 render_horz_rule (const struct tab_rendering *r,
1323                   int x, int y, int col, int row)
1324 {
1325   enum outp_line_style style;
1326   style = rule_to_draw_type (get_hrule (r->table, col, row));
1327   if (style != OUTP_L_NONE)
1328     r->driver->class->line (r->driver, x, y, x + r->w[col], y + r->hrh[row],
1329                             OUTP_L_NONE, style, OUTP_L_NONE, style);
1330 }
1331
1332 /* Renders the vertical rule at the given column and row
1333    at (X,Y) on the page. */
1334 static void
1335 render_vert_rule (const struct tab_rendering *r,
1336                   int x, int y, int col, int row)
1337 {
1338   enum outp_line_style style;
1339   style = rule_to_draw_type (get_vrule (r->table, col, row));
1340   if (style != OUTP_L_NONE)
1341     r->driver->class->line (r->driver, x, y, x + r->wrv[col], y + r->h[row],
1342                             style, OUTP_L_NONE, style, OUTP_L_NONE);
1343 }
1344
1345 /* Renders the rule intersection at the given column and row
1346    at (X,Y) on the page. */
1347 static void
1348 render_rule_intersection (const struct tab_rendering *r,
1349                           int x, int y, int col, int row)
1350 {
1351   const struct tab_table *t = r->table;
1352
1353   /* Bounds of intersection. */
1354   int x0 = x;
1355   int y0 = y;
1356   int x1 = x + r->wrv[col];
1357   int y1 = y + r->hrh[row];
1358
1359   /* Lines on each side of intersection. */
1360   int top = row > 0 ? get_vrule (t, col, row - 1) : TAL_0;
1361   int left = col > 0 ? get_hrule (t, col - 1, row) : TAL_0;
1362   int bottom = row < tab_nr (t) ? get_vrule (t, col, row) : TAL_0;
1363   int right = col < tab_nc (t) ? get_hrule (t, col, row) : TAL_0;
1364
1365   /* Output style for each line. */
1366   enum outp_line_style o_top = rule_to_draw_type (top);
1367   enum outp_line_style o_left = rule_to_draw_type (left);
1368   enum outp_line_style o_bottom = rule_to_draw_type (bottom);
1369   enum outp_line_style o_right = rule_to_draw_type (right);
1370
1371   if (o_top != OUTP_L_NONE || o_left != OUTP_L_NONE
1372       || o_bottom != OUTP_L_NONE || o_right != OUTP_L_NONE)
1373     r->driver->class->line (r->driver, x0, y0, x1, y1,
1374                             o_top, o_left, o_bottom, o_right);
1375 }
1376
1377 /* Returns the width of columns C1...C2 exclusive,
1378    including interior but not exterior rules. */
1379 static int
1380 strip_width (const struct tab_rendering *r, int c1, int c2)
1381 {
1382   int width = 0;
1383   int c;
1384
1385   for (c = c1; c < c2; c++)
1386     width += r->w[c] + r->wrv[c + 1];
1387   if (c1 < c2)
1388     width -= r->wrv[c2];
1389   return width;
1390 }
1391
1392 /* Returns the height of rows R1...R2 exclusive,
1393    including interior but not exterior rules. */
1394 static int
1395 strip_height (const struct tab_rendering *r, int r1, int r2)
1396 {
1397   int height = 0;
1398   int row;
1399
1400   for (row = r1; row < r2; row++)
1401     height += r->h[row] + r->hrh[row + 1];
1402   if (r1 < r2)
1403     height -= r->hrh[r2];
1404   return height;
1405 }
1406
1407 /* Renders the cell at the given column and row at (X,Y) on the
1408    page.  Also renders joined cells that extend as far to the
1409    right as C1 and as far down as R1. */
1410 static void
1411 render_cell (const struct tab_rendering *r,
1412              int x, int y, int col, int row, int c1, int r1)
1413 {
1414   const struct tab_table *t = r->table;
1415   const int index = col + (row * t->cf);
1416   unsigned char type = t->ct[index];
1417   struct substring *content = &t->cc[index];
1418
1419   if (!(type & TAB_JOIN))
1420     {
1421       if (!(type & TAB_EMPTY))
1422         {
1423           struct outp_text text;
1424           text.font = options_to_font (type);
1425           text.justification = translate_justification (type);
1426           text.string = *content;
1427           text.h = r->w[col];
1428           text.v = r->h[row];
1429           text.x = x;
1430           text.y = y;
1431           r->driver->class->text_draw (r->driver, &text);
1432         }
1433     }
1434   else
1435     {
1436       struct tab_joined_cell *j
1437         = (struct tab_joined_cell *) ss_data (*content);
1438
1439       if (j->x1 == col && j->y1 == row)
1440         {
1441           struct outp_text text;
1442           text.font = options_to_font (type);
1443           text.justification = translate_justification (type);
1444           text.string = j->contents;
1445           text.x = x;
1446           text.y = y;
1447           text.h = strip_width (r, j->x1, MIN (j->x2, c1));
1448           text.v = strip_height (r, j->y1, MIN (j->y2, r1));
1449           r->driver->class->text_draw (r->driver, &text);
1450         }
1451     }
1452 }
1453
1454 /* Render contiguous strip consisting of columns C0...C1, exclusive,
1455    on row ROW, at (X,Y).  Returns X position after rendering.
1456    Also renders joined cells that extend beyond that strip,
1457    cropping them to lie within rendering region (C0,R0)-(C1,R1).
1458    C0 and C1 count vertical rules as columns.
1459    ROW counts horizontal rules as rows, but R0 and R1 do not. */
1460 static int
1461 render_strip (const struct tab_rendering *r,
1462               int x, int y, int row, int c0, int c1, int r0 UNUSED, int r1)
1463 {
1464   int col;
1465
1466   for (col = c0; col < c1; col++)
1467     if (col & 1)
1468       {
1469         if (row & 1)
1470           render_cell (r, x, y, col / 2, row / 2, c1 / 2, r1);
1471         else
1472           render_horz_rule (r, x, y, col / 2, row / 2);
1473         x += r->w[col / 2];
1474       }
1475     else
1476       {
1477         if (row & 1)
1478           render_vert_rule (r, x, y, col / 2, row / 2);
1479         else
1480           render_rule_intersection (r, x, y, col / 2, row / 2);
1481         x += r->wrv[col / 2];
1482       }
1483
1484   return x;
1485 }