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