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