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