Fixed a bug apparent when using the FREQUENCIES command with the html driver
[pspp-builds.git] / src / tab.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 /* AIX requires this to be the first thing in the file.  */
21 #include <config.h>
22 #if __GNUC__
23 #define alloca __builtin_alloca
24 #else
25 #if HAVE_ALLOCA_H
26 #include <alloca.h>
27 #else
28 #ifdef _AIX
29 #pragma alloca
30 #else
31 #ifndef alloca                  /* predefined by HP cc +Olibcalls */
32 char *alloca ();
33 #endif
34 #endif
35 #endif
36 #endif
37
38 #include <ctype.h>
39 #include <assert.h>
40 #include <stdarg.h>
41 #include <limits.h>
42 #include <stdlib.h>
43 #include "alloc.h"
44 #include "command.h"
45 #include "format.h"
46 #include "magic.h"
47 #include "misc.h"
48 #include "output.h"
49 #include "pool.h"
50 #include "som.h"
51 #include "tab.h"
52 #include "var.h"
53
54 #undef DEBUGGING
55 /*#define DEBUGGING 1 */
56 #include "debug-print.h"
57 \f
58 extern struct som_table_class tab_table_class;
59
60 #if DEBUGGING
61 #define DEFFIRST(NAME, LABEL) LABEL,
62 #define DEFTAB(NAME, LABEL) LABEL,
63 static const char *tab_names[] =
64   {
65 #include "tab.def"
66   };
67 #undef DEFFIRST
68 #undef DEFTAB
69 #endif
70
71 /* Creates a table with NC columns and NR rows.  If REALLOCABLE is
72    nonzero then the table's size can be increased later; otherwise,
73    its size can only be reduced. */
74 struct tab_table *
75 tab_create (int nc, int nr, int reallocable)
76 {
77   void *(*alloc_func) (struct pool *, size_t);
78
79   struct tab_table *t;
80   
81   {
82     struct pool *container = pool_create ();
83     t = pool_alloc (container, sizeof *t);
84     t->container = container;
85   }
86   
87   t->col_style = TAB_COL_NONE;
88   t->col_group = 0;
89   ls_null (&t->title);
90   t->flags = SOMF_NONE;
91   t->nr = nr;
92   t->nc = t->cf = nc;
93   t->l = t->r = t->t = t->b = 0;
94
95   alloc_func = reallocable ? pool_malloc : pool_alloc;
96 #if GLOBAL_DEBUGGING
97   t->reallocable = reallocable;
98 #endif
99
100   t->cc = alloc_func (t->container, nr * nc * sizeof *t->cc);
101   t->ct = alloc_func (t->container, nr * nc);
102   memset (t->ct, TAB_EMPTY, nc * nr);
103
104   t->rh = alloc_func (t->container, nc * (nr + 1));
105   memset (t->rh, 0, nc * (nr + 1));
106
107   t->hrh = alloc_func (t->container, sizeof *t->hrh * (nr + 1));
108   memset (t->hrh, 0, sizeof *t->hrh * (nr + 1));
109
110   t->trh = alloc_func (t->container, nr + 1);
111   memset (t->trh, 0, nr + 1);
112
113   t->rv = alloc_func (t->container, (nc + 1) * nr);
114   memset (t->rv, 0, (nc + 1) * nr);
115
116   t->wrv = alloc_func (t->container, sizeof *t->wrv * (nc + 1));
117   memset (t->wrv, 0, sizeof *t->wrv * (nc + 1));
118
119   t->trv = alloc_func (t->container, nc + 1);
120   memset (t->trv, 0, nc + 1);
121
122   t->dim = NULL;
123   t->w = t->h = NULL;
124   t->col_ofs = t->row_ofs = 0;
125   
126   return t;
127 }
128
129 /* Destroys table T. */
130 void
131 tab_destroy (struct tab_table *t)
132 {
133   assert (t != NULL);
134   pool_destroy (t->container);
135   t->container=0;
136 }
137
138 /* Sets the width and height of a table, in columns and rows,
139    respectively.  Use only to reduce the size of a table, since it
140    does not change the amount of allocated memory. */
141 void
142 tab_resize (struct tab_table *t, int nc, int nr)
143 {
144   assert (t != NULL);
145   if (nc != -1)
146     {
147       assert (nc + t->col_ofs <= t->cf);
148       t->nc = nc + t->col_ofs;
149     }
150   if (nr != -1)
151     {
152       assert (nr + t->row_ofs <= t->nr);
153       t->nr = nr + t->row_ofs;
154     }
155 }
156
157 /* Changes either or both dimensions of a table.  Consider using the
158    above routine instead if it won't waste a lot of space.
159
160    Changing the number of columns in a table is particularly expensive
161    in space and time.  Avoid doing such.  FIXME: In fact, transferring
162    of rules isn't even implemented yet. */
163 void
164 tab_realloc (struct tab_table *t, int nc, int nr)
165 {
166   int ro, co;
167   
168   assert (t != NULL);
169 #if GLOBAL_DEBUGGING
170   assert (t->reallocable);
171 #endif
172   ro = t->row_ofs;
173   co = t->col_ofs;
174   if (ro || co)
175     tab_offset (t, 0, 0);
176
177   if (nc == -1)
178     nc = t->nc;
179   if (nr == -1)
180     nr = t->nr;
181   
182   assert (nc == t->nc);
183   
184   if (nc > t->cf)
185     {
186       int mr1 = min (nr, t->nr);
187       int mc1 = min (nc, t->nc);
188       
189       struct len_string *new_cc;
190       unsigned char *new_ct;
191       int r;
192
193       new_cc = pool_malloc (t->container, nr * nc * sizeof *new_cc);
194       new_ct = pool_malloc (t->container, nr * nc);
195       for (r = 0; r < mr1; r++)
196         {
197           memcpy (&new_cc[r * nc], &t->cc[r * t->nc], mc1 * sizeof *t->cc);
198           memcpy (&new_ct[r * nc], &t->ct[r * t->nc], mc1);
199           memset (&new_ct[r * nc + t->nc], TAB_EMPTY, nc - t->nc);
200         }
201       pool_free (t->container, t->cc);
202       pool_free (t->container, t->ct);
203       t->cc = new_cc;
204       t->ct = new_ct;
205       t->cf = nc;
206     }
207   else if (nr != t->nr)
208     {
209       t->cc = pool_realloc (t->container, t->cc, nr * nc * sizeof *t->cc);
210       t->ct = pool_realloc (t->container, t->ct, nr * nc);
211
212       t->rh = pool_realloc (t->container, t->rh, nc * (nr + 1));
213       t->rv = pool_realloc (t->container, t->rv, (nc + 1) * nr);
214       t->trh = pool_realloc (t->container, t->trh, nr + 1);
215       t->hrh = pool_realloc (t->container, t->hrh,
216                              sizeof *t->hrh * (nr + 1));
217       
218       if (nr > t->nr)
219         {
220           memset (&t->rh[nc * (t->nr + 1)], 0, (nr - t->nr) * nc);
221           memset (&t->rv[(nc + 1) * t->nr], 0, (nr - t->nr) * (nc + 1));
222           memset (&t->trh[t->nr + 1], 0, nr - t->nr);
223         }
224     }
225
226   memset (&t->ct[nc * t->nr], TAB_EMPTY, nc * (nr - t->nr));
227   
228   t->nr = nr;
229   t->nc = nc;
230
231   if (ro || co)
232     tab_offset (t, co, ro);
233 }
234
235 /* Sets the number of header rows on each side of TABLE to L on the
236    left, R on the right, T on the top, B on the bottom.  Header rows
237    are repeated when a table is broken across multiple columns or
238    multiple pages. */
239 void
240 tab_headers (struct tab_table *table, int l, int r, int t, int b)
241 {
242   assert (table != NULL);
243   table->l = l;
244   table->r = r;
245   table->t = t;
246   table->b = b;
247 }
248
249 /* Set up table T so that, when it is an appropriate size, it will be
250    displayed across the page in columns.
251
252    STYLE is a TAB_COL_* constant.  GROUP is the number of rows to take
253    as a unit. */
254 void
255 tab_columns (struct tab_table *t, int style, int group)
256 {
257   assert (t != NULL);
258   t->col_style = style;
259   t->col_group = group;
260 }
261 \f
262 /* Rules. */
263
264 /* Draws a vertical line to the left of cells at horizontal position X
265    from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
266 void
267 tab_vline (struct tab_table *t, int style, int x, int y1, int y2)
268 {
269   int y;
270
271   assert (t != NULL);
272 #if GLOBAL_DEBUGGING
273   if (x + t->col_ofs < 0 || x + t->col_ofs > t->nc
274       || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr
275       || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
276     {
277       printf (_("bad vline: x=%d+%d=%d y=(%d+%d=%d,%d+%d=%d) in "
278                 "table size (%d,%d)\n"),
279               x, t->col_ofs, x + t->col_ofs,
280               y1, t->row_ofs, y1 + t->row_ofs,
281               y2, t->row_ofs, y2 + t->row_ofs,
282               t->nc, t->nr);
283       return;
284     }
285 #endif
286
287   x += t->col_ofs;
288   y1 += t->row_ofs;
289   y2 += t->row_ofs;
290
291   if (style != -1)
292     {
293       if ((style & TAL_SPACING) == 0)
294         for (y = y1; y <= y2; y++)
295           t->rv[x + (t->cf + 1) * y] = style;
296       t->trv[x] |= (1 << (style & ~TAL_SPACING));
297     }
298 }
299
300 /* Draws a horizontal line above cells at vertical position Y from X1
301    to X2 inclusive in style STYLE, if style is not -1. */
302 void
303 tab_hline (struct tab_table * t, int style, int x1, int x2, int y)
304 {
305   int x;
306
307   assert (t != NULL);
308 #if GLOBAL_DEBUGGING
309   if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= t->nc 
310       || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= t->nc
311       || y + t->row_ofs < 0 || y + t->row_ofs > t->nr)
312     {
313       printf (_("bad hline: x=(%d+%d=%d,%d+%d=%d) y=%d+%d=%d "
314                 "in table size (%d,%d)\n"),
315               x1, t->col_ofs, x1 + t->col_ofs,
316               x2, t->col_ofs, x2 + t->col_ofs,
317               y, t->row_ofs, y + t->row_ofs,
318               t->nc, t->nr);
319       return;
320     }
321 #endif
322
323   x1 += t->col_ofs;
324   x2 += t->col_ofs;
325   y += t->row_ofs;
326
327   if (style != -1)
328     {
329       if ((style & TAL_SPACING) == 0)
330         for (x = x1; x <= x2; x++)
331           t->rh[x + t->cf * y] = style;
332       t->trh[y] |= (1 << (style & ~TAL_SPACING));
333     }
334 }
335
336 /* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
337    lines of style F_H and vertical lines of style F_V.  Fills the
338    interior of the box with horizontal lines of style I_H and vertical
339    lines of style I_V.  Any of the line styles may be -1 to avoid
340    drawing those lines.  This is distinct from 0, which draws a null
341    line. */
342 void
343 tab_box (struct tab_table *t, int f_h, int f_v, int i_h, int i_v,
344          int x1, int y1, int x2, int y2)
345 {
346   assert (t != NULL);
347 #if GLOBAL_DEBUGGING
348   if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= t->nc 
349       || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= t->nc
350       || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr 
351       || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
352     {
353       printf (_("bad box: (%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) "
354                 "in table size (%d,%d)\n"),
355               x1, t->col_ofs, x1 + t->col_ofs,
356               y1, t->row_ofs, y1 + t->row_ofs,
357               x2, t->col_ofs, x2 + t->col_ofs,
358               y2, t->row_ofs, y2 + t->row_ofs,
359               t->nc, t->nr);
360       abort ();
361     }
362 #endif
363
364   x1 += t->col_ofs;
365   x2 += t->col_ofs;
366   y1 += t->row_ofs;
367   y2 += t->row_ofs;
368
369   if (f_h != -1)
370     {
371       int x;
372       if ((f_h & TAL_SPACING) == 0)
373         for (x = x1; x <= x2; x++)
374           {
375             t->rh[x + t->cf * y1] = f_h;
376             t->rh[x + t->cf * (y2 + 1)] = f_h;
377           }
378       t->trh[y1] |= (1 << (f_h & ~TAL_SPACING));
379       t->trh[y2 + 1] |= (1 << (f_h & ~TAL_SPACING));
380     }
381   if (f_v != -1)
382     {
383       int y;
384       if ((f_v & TAL_SPACING) == 0)
385         for (y = y1; y <= y2; y++)
386           {
387             t->rv[x1 + (t->cf + 1) * y] = f_v;
388             t->rv[(x2 + 1) + (t->cf + 1) * y] = f_v;
389           }
390       t->trv[x1] |= (1 << (f_v & ~TAL_SPACING));
391       t->trv[x2 + 1] |= (1 << (f_v & ~TAL_SPACING));
392     }
393
394   if (i_h != -1)
395     {
396       int y;
397       
398       for (y = y1 + 1; y <= y2; y++)
399         {
400           int x;
401
402           if ((i_h & TAL_SPACING) == 0)
403             for (x = x1; x <= x2; x++)
404               t->rh[x + t->cf * y] = i_h;
405
406           t->trh[y] |= (1 << (i_h & ~TAL_SPACING));
407         }
408     }
409   if (i_v != -1)
410     {
411       int x;
412       
413       for (x = x1 + 1; x <= x2; x++)
414         {
415           int y;
416           
417           if ((i_v & TAL_SPACING) == 0)
418             for (y = y1; y <= y2; y++)
419               t->rv[x + (t->cf + 1) * y] = i_v;
420
421           t->trv[x] |= (1 << (i_v & ~TAL_SPACING));
422         }
423     }
424 }
425
426 /* Formats text TEXT and arguments ARGS as indicated in OPT and sets
427    the resultant string into S in TABLE's pool. */
428 static void
429 text_format (struct tab_table *table, int opt, const char *text, va_list args,
430              struct len_string *s)
431 {
432   int len;
433   
434   assert (table != NULL && text != NULL && s != NULL);
435   
436   if (opt & TAT_PRINTF)
437     {
438       char *temp_buf = local_alloc (1024);
439       
440       len = nvsprintf (temp_buf, text, args);
441       text = temp_buf;
442     }
443   else
444     len = strlen (text);
445
446   ls_create_buffer (table->container, s, text, len);
447   
448   if (opt & TAT_PRINTF)
449     local_free (text);
450 }
451
452 /* Set the title of table T to TITLE, which is formatted with printf
453    if FORMAT is nonzero. */
454 void
455 tab_title (struct tab_table *t, int format, const char *title, ...)
456 {
457   va_list args;
458
459   assert (t != NULL && title != NULL);
460   va_start (args, title);
461   text_format (t, format ? TAT_PRINTF : TAT_NONE, title, args, &t->title);
462   va_end (args);
463 }
464
465 /* Set DIM_FUNC as the dimension function for table T. */
466 void
467 tab_dim (struct tab_table *t, tab_dim_func *dim_func)
468 {
469   assert (t != NULL && t->dim == NULL);
470   t->dim = dim_func;
471 }
472
473 /* Returns the natural width of column C in table T for driver D, that
474    is, the smallest width necessary to display all its cells without
475    wrapping.  The width will be no larger than the page width minus
476    left and right rule widths. */
477 int
478 tab_natural_width (struct tab_table *t, struct outp_driver *d, int c)
479 {
480   int width;
481
482   assert (t != NULL && c >= 0 && c < t->nc);
483   {
484     int r;
485
486     for (width = r = 0; r < t->nr; r++)
487       {
488         struct outp_text text;
489         unsigned char opt = t->ct[c + r * t->cf];
490                 
491         if (opt & (TAB_JOIN | TAB_EMPTY))
492           continue;
493
494         text.s = t->cc[c + r * t->cf];
495         assert (!ls_null_p (&text.s));
496         text.options = OUTP_T_JUST_LEFT;
497
498         d->class->text_metrics (d, &text);
499         if (text.h > width)
500           width = text.h;
501       }
502   }
503
504   if (width == 0)
505     {
506       width = d->prop_em_width * 8;
507 #if GLOBAL_DEBUGGING
508       printf ("warning: table column %d contains no data.\n", c);
509 #endif
510     }
511   
512   {
513     const int clamp = d->width - t->wrv[0] - t->wrv[t->nc];
514     
515     if (width > clamp)
516       width = clamp;
517   }
518
519   return width;
520 }
521
522 /* Returns the natural height of row R in table T for driver D, that
523    is, the minimum height necessary to display the information in the
524    cell at the widths set for each column. */
525 int
526 tab_natural_height (struct tab_table *t, struct outp_driver *d, int r)
527 {
528   int height;
529
530   assert (t != NULL && r >= 0 && r < t->nr);
531   
532   {
533     int c;
534     
535     for (height = d->font_height, c = 0; c < t->nc; c++)
536       {
537         struct outp_text text;
538         unsigned char opt = t->ct[c + r * t->cf];
539
540         assert (t->w[c] != NOT_INT);
541         if (opt & (TAB_JOIN | TAB_EMPTY))
542           continue;
543
544         text.s = t->cc[c + r * t->cf];
545         assert (!ls_null_p (&text.s));
546         text.options = OUTP_T_HORZ | OUTP_T_JUST_LEFT;
547         text.h = t->w[c];
548         d->class->text_metrics (d, &text);
549
550         if (text.v > height)
551           height = text.v;
552       }
553   }
554
555   return height;
556 }
557
558 /* Callback function to set all columns and rows to their natural
559    dimensions.  Not really meant to be called directly.  */
560 void
561 tab_natural_dimensions (struct tab_table *t, struct outp_driver *d)
562 {
563   int i;
564
565   assert (t != NULL);
566   
567   for (i = 0; i < t->nc; i++)
568     t->w[i] = tab_natural_width (t, d, i);
569   
570   for (i = 0; i < t->nr; i++)
571     t->h[i] = tab_natural_height (t, d, i);
572 }
573
574 \f
575 /* Cells. */
576
577 /* Sets cell (C,R) in TABLE, with options OPT, to have a value taken
578    from V, displayed with format spec F. */
579 void
580 tab_value (struct tab_table *table, int c, int r, unsigned char opt,
581            const union value *v, const struct fmt_spec *f)
582 {
583   char *contents;
584   union value temp_val;
585
586   assert (table != NULL && v != NULL && f != NULL);
587 #if GLOBAL_DEBUGGING
588   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
589       || c + table->col_ofs >= table->nc
590       || r + table->row_ofs >= table->nr)
591     {
592       printf ("tab_value(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
593               "(%d,%d)\n",
594               c, table->col_ofs, c + table->col_ofs,
595               r, table->row_ofs, r + table->row_ofs,
596               table->nc, table->nr);
597       return;
598     }
599 #endif
600
601   contents = pool_alloc (table->container, f->w);
602   ls_init (&table->cc[c + r * table->cf], contents, f->w);
603   table->ct[c + r * table->cf] = opt;
604   
605   if (formats[f->type].cat & FCAT_STRING)
606     {
607       temp_val.c = (char *) v->s;
608       v = &temp_val;
609     }
610   data_out (contents, f, v);
611 }
612
613 /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL
614    with NDEC decimal places. */
615 void
616 tab_float (struct tab_table *table, int c, int r, unsigned char opt,
617            double val, int w, int d)
618 {
619   char *contents;
620   char buf[40], *cp;
621   
622   struct fmt_spec f;
623
624   assert (table != NULL && w <= 40);
625   
626   f.type = FMT_F;
627   f.w = w;
628   f.d = d;
629   
630 #if GLOBAL_DEBUGGING
631   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
632       || c + table->col_ofs >= table->nc
633       || r + table->row_ofs >= table->nr)
634     {
635       printf ("tab_float(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
636               "(%d,%d)\n",
637               c, table->col_ofs, c + table->col_ofs,
638               r, table->row_ofs, r + table->row_ofs,
639               table->nc, table->nr);
640       return;
641     }
642 #endif
643
644   data_out (buf, &f, (union value *) &val);
645   cp = buf;
646   while (isspace ((unsigned char) *cp) && cp < &buf[w])
647     cp++;
648   f.w = w - (cp - buf);
649
650   contents = pool_alloc (table->container, f.w);
651   ls_init (&table->cc[c + r * table->cf], contents, f.w);
652   table->ct[c + r * table->cf] = opt;
653   memcpy (contents, cp, f.w);
654 }
655
656 /* Sets cell (C,R) in TABLE, with options OPT, to have text value
657    TEXT. */
658 void
659 tab_text (struct tab_table *table, int c, int r, unsigned opt, const char *text, ...)
660 {
661   va_list args;
662
663   assert (table != NULL && text != NULL);
664 #if GLOBAL_DEBUGGING
665   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
666       || c + table->col_ofs >= table->nc
667       || r + table->row_ofs >= table->nr)
668     {
669       printf ("tab_text(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
670               "(%d,%d)\n",
671               c, table->col_ofs, c + table->col_ofs,
672               r, table->row_ofs, r + table->row_ofs,
673               table->nc, table->nr);
674       return;
675     }
676 #endif
677     
678   va_start (args, text);
679   text_format (table, opt, text, args, &table->cc[c + r * table->cf]);
680   table->ct[c + r * table->cf] = opt;
681   va_end (args);
682 }
683
684 /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
685    options OPT to have text value TEXT. */
686 void
687 tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2,
688                 unsigned opt, const char *text, ...)
689 {
690   struct tab_joined_cell *j;
691
692   assert (table != NULL && text != NULL);
693 #if GLOBAL_DEBUGGING
694   if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= table->nc
695       || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= table->nr
696       || x2 < x1 || x2 + table->col_ofs >= table->nc
697       || y2 < y2 || y2 + table->row_ofs >= table->nr)
698     {
699       printf ("tab_joint_text(): bad cell "
700               "(%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n",
701               x1, table->col_ofs, x1 + table->col_ofs,
702               y1, table->row_ofs, y1 + table->row_ofs,
703               x2, table->col_ofs, x2 + table->col_ofs,
704               y2, table->row_ofs, y2 + table->row_ofs,
705               table->nc, table->nr);
706       return;
707     }
708 #endif
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   
717   {
718     va_list args;
719     
720     va_start (args, text);
721     text_format (table, opt, text, args, &j->contents);
722     va_end (args);
723   }
724   
725   opt |= TAB_JOIN;
726   
727   {
728     struct len_string *cc = &table->cc[x1 + y1 * table->cf];
729     unsigned char *ct = &table->ct[x1 + y1 * table->cf];
730     const int ofs = table->cf - (x2 - x1);
731
732     int y;
733     
734     for (y = y1; y < y2; y++)
735       {
736         int x;
737         
738         for (x = x1; x < x2; x++)
739           {
740             ls_init (cc++, (char *) j, 0);
741             *ct++ = opt;
742           }
743         
744         cc += ofs;
745         ct += ofs;
746       }
747   }
748 }
749
750 /* Sets cell (C,R) in TABLE, with options OPT, to contents STRING. */
751 void
752 tab_raw (struct tab_table *table, int c, int r, unsigned opt,
753          struct len_string *string)
754 {
755   assert (table != NULL && string != NULL);
756   
757 #if GLOBAL_DEBUGGING
758   if (c + table->col_ofs < 0 || r + table->row_ofs < 0
759       || c + table->col_ofs >= table->nc
760       || r + table->row_ofs >= table->nr)
761     {
762       printf ("tab_float(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
763               "(%d,%d)\n",
764               c, table->col_ofs, c + table->col_ofs,
765               r, table->row_ofs, r + table->row_ofs,
766               table->nc, table->nr);
767       return;
768     }
769 #endif
770
771   table->cc[c + r * table->cf] = *string;
772   table->ct[c + r * table->cf] = opt;
773 }
774 \f
775 /* Miscellaneous. */
776
777 /* Sets the widths of all the columns and heights of all the rows in
778    table T for driver D. */
779 static void
780 nowrap_dim (struct tab_table *t, struct outp_driver *d)
781 {
782   t->w[0] = tab_natural_width (t, d, 0);
783   t->h[0] = d->font_height;
784 }
785
786 /* Sets the widths of all the columns and heights of all the rows in
787    table T for driver D. */
788 static void
789 wrap_dim (struct tab_table *t, struct outp_driver *d)
790 {
791   t->w[0] = tab_natural_width (t, d, 0);
792   t->h[0] = tab_natural_height (t, d, 0);
793 }
794
795 /* Outputs text BUF as a table with a single cell having cell options
796    OPTIONS, which is a combination of the TAB_* and TAT_*
797    constants. */
798 void
799 tab_output_text (int options, const char *buf, ...)
800 {
801   struct tab_table *t = tab_create (1, 1, 0);
802
803   assert (buf != NULL);
804   if (options & TAT_PRINTF)
805     {
806       va_list args;
807       char *temp_buf = local_alloc (4096);
808       
809       va_start (args, buf);
810       nvsprintf (temp_buf, buf, args);
811       buf = temp_buf;
812       va_end (args);
813     }
814   
815   if (options & TAT_FIX)
816     {
817       struct outp_driver *d;
818
819       for (d = outp_drivers (NULL); d; d = outp_drivers (d))
820         {
821           if (!d->page_open)
822             d->class->open_page (d);
823           
824           d->class->text_set_font_by_name (d, "FIXED");
825         }
826     }
827
828   tab_text (t, 0, 0, options &~ TAT_PRINTF, buf);
829   tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
830   if (options & TAT_NOWRAP)
831     tab_dim (t, nowrap_dim);
832   else
833     tab_dim (t, wrap_dim);
834   tab_submit (t);
835
836   if (options & TAT_FIX)
837     {
838       struct outp_driver *d;
839
840       for (d = outp_drivers (NULL); d; d = outp_drivers (d))
841         d->class->text_set_font_by_name (d, "PROP");
842     }
843   
844   if (options & TAT_PRINTF)
845     local_free (buf);
846 }
847
848 /* Set table flags to FLAGS. */
849 void
850 tab_flags (struct tab_table *t, unsigned flags)
851 {
852   assert (t != NULL);
853   t->flags = flags;
854 }
855
856 /* Easy, type-safe way to submit a tab table to som. */
857 void
858 tab_submit (struct tab_table *t)
859 {
860   struct som_table s;
861
862   assert (t != NULL);
863   s.class = &tab_table_class;
864   s.ext = t;
865   som_submit (&s);
866   tab_destroy (t);
867 }
868 \f
869 /* Editing. */
870
871 /* Set table row and column offsets for all functions that affect
872    cells or rules. */
873 void
874 tab_offset (struct tab_table *t, int col, int row)
875 {
876   int diff = 0;
877
878   assert (t != NULL);
879 #if GLOBAL_DEBUGGING
880   if (row < -1 || row >= t->nr)
881     {
882       printf ("tab_offset(): row=%d in %d-row table\n", row, t->nr);
883       abort ();
884     }
885   if (col < -1 || col >= t->nc)
886     {
887       printf ("tab_offset(): col=%d in %d-column table\n", col, t->nc);
888       abort ();
889     }
890 #endif
891
892   if (row != -1)
893     diff += (row - t->row_ofs) * t->cf, t->row_ofs = row;
894   if (col != -1)
895     diff += (col - t->col_ofs), t->col_ofs = col;
896
897   t->cc += diff;
898   t->ct += diff;
899 }
900
901 /* Increment the row offset by one. If the table is too small,
902    increase its size. */
903 void
904 tab_next_row (struct tab_table *t)
905 {
906   assert (t != NULL);
907   t->cc += t->cf;
908   t->ct += t->cf;
909   if (++t->row_ofs >= t->nr)
910     tab_realloc (t, -1, t->nr * 4 / 3);
911 }
912 \f
913 static struct tab_table *t;
914 static struct outp_driver *d;
915 int tab_hit;
916
917 /* Set the current table to TABLE. */
918 static void
919 tabi_table (struct som_table *table)
920 {
921   assert (table != NULL);
922   t = table->ext;
923   tab_offset (t, 0, 0);
924   
925   assert (t->w == NULL && t->h == NULL);
926   t->w = pool_alloc (t->container, sizeof *t->w * t->nc);
927   t->h = pool_alloc (t->container, sizeof *t->h * t->nr);
928 }
929
930 /* Set the current output device to DRIVER. */
931 static void
932 tabi_driver (struct outp_driver *driver)
933 {
934   int i;
935
936   assert (driver != NULL);
937   d = driver;
938   
939   /* Figure out sizes of rules. */
940   for (t->hr_tot = i = 0; i <= t->nr; i++)
941     t->hr_tot += t->hrh[i] = d->horiz_line_spacing[t->trh[i]];
942   for (t->vr_tot = i = 0; i <= t->nc; i++)
943     t->vr_tot += t->wrv[i] = d->vert_line_spacing[t->trv[i]];
944
945 #if GLOBAL_DEBUGGING
946   for (i = 0; i < t->nr; i++)
947     t->h[i] = -1;
948   for (i = 0; i < t->nc; i++)
949     t->w[i] = -1;
950 #endif
951
952   assert (t->dim != NULL);
953   t->dim (t, d);
954
955 #if GLOBAL_DEBUGGING
956   {
957     int error = 0;
958
959     for (i = 0; i < t->nr; i++)
960       {
961         if (t->h[i] == -1)
962           {
963             printf ("Table row %d height not initialized.\n", i);
964             error = 1;
965           }
966         assert (t->h[i] > 0);
967       }
968     
969     for (i = 0; i < t->nc; i++)
970       {
971         if (t->w[i] == -1)
972           {
973             printf ("Table column %d width not initialized.\n", i);
974             error = 1;
975           }
976         assert (t->w[i] > 0);
977       }
978   }
979 #endif
980     
981   /* Add up header sizes. */
982   for (i = 0, t->wl = t->wrv[0]; i < t->l; i++)
983     t->wl += t->w[i] + t->wrv[i + 1];
984   for (i = 0, t->ht = t->hrh[0]; i < t->t; i++)
985     t->ht += t->h[i] + t->hrh[i + 1];
986   for (i = t->nc - t->r, t->wr = t->wrv[i]; i < t->nc; i++)
987     t->wr += t->w[i] + t->wrv[i + 1];
988   for (i = t->nr - t->b, t->hb = t->hrh[i]; i < t->nr; i++)
989     t->hb += t->h[i] + t->hrh[i + 1];
990   
991   /* Title. */
992   if (!(t->flags & SOMF_NO_TITLE))
993     t->ht += d->font_height;
994 }
995
996 /* Return the number of columns and rows in the table into N_COLUMNS
997    and N_ROWS, respectively. */
998 static void
999 tabi_count (int *n_columns, int *n_rows)
1000 {
1001   assert (n_columns != NULL && n_rows != NULL);
1002   *n_columns = t->nc;
1003   *n_rows = t->nr;
1004 }
1005
1006 static void tabi_cumulate (int cumtype, int start, int *end, int max, int *actual);
1007
1008 /* Return the horizontal and vertical size of the entire table,
1009    including headers, for the current output device, into HORIZ and
1010    VERT. */
1011 static void
1012 tabi_area (int *horiz, int *vert)
1013 {
1014   assert (horiz != NULL && vert != NULL);
1015   
1016   {
1017     int w, c;
1018     
1019     for (c = t->l + 1, w = t->wl + t->wr + t->w[t->l];
1020          c < t->nc - t->r; c++)
1021       w += t->w[c] + t->wrv[c];
1022     *horiz = w;
1023   }
1024   
1025   {
1026     int h, r;
1027     for (r = t->t + 1, h = t->ht + t->hb + t->h[t->t];
1028          r < t->nr - t->b; r++)
1029       h += t->h[r] + t->hrh[r];
1030     *vert = h;
1031   }
1032 }
1033
1034 /* Return the column style for this table into STYLE. */
1035 static void
1036 tabi_columns (int *style)
1037 {
1038   assert (style != NULL);
1039   *style = t->col_style;
1040 }
1041
1042 /* Return the number of header rows/columns on the left, right, top,
1043    and bottom sides into HL, HR, HT, and HB, respectively. */
1044 static void
1045 tabi_headers (int *hl, int *hr, int *ht, int *hb)
1046 {
1047   assert (hl != NULL && hr != NULL && ht != NULL && hb != NULL);
1048   *hl = t->l;
1049   *hr = t->r;
1050   *ht = t->t;
1051   *hb = t->b;
1052 }
1053
1054 /* Determines the number of rows or columns (including appropriate
1055    headers), depending on CUMTYPE, that will fit into the space
1056    specified.  Takes rows/columns starting at index START and attempts
1057    to fill up available space MAX.  Returns in END the index of the
1058    last row/column plus one; returns in ACTUAL the actual amount of
1059    space the selected rows/columns (including appropriate headers)
1060    filled. */
1061 static void
1062 tabi_cumulate (int cumtype, int start, int *end, int max, int *actual)
1063 {
1064   int n;
1065   int *d;
1066   int *r;
1067   int total;
1068   
1069   assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
1070   if (cumtype == SOM_ROWS)
1071     {
1072       assert (start >= 0 && start < t->nr);
1073       n = t->nr - t->b;
1074       d = &t->h[start];
1075       r = &t->hrh[start + 1];
1076       total = t->ht + t->hb;
1077     } else {
1078       assert (start >= 0 && start < t->nc);
1079       n = t->nc - t->r;
1080       d = &t->w[start];
1081       r = &t->wrv[start + 1];
1082       total = t->wl + t->wr;
1083     }
1084   
1085   total += *d++;
1086   if (total > max)
1087     {
1088       if (end)
1089         *end = start;
1090       if (actual)
1091         *actual = 0;
1092       return;
1093     }
1094     
1095   {
1096     int x;
1097       
1098     for (x = start + 1; x < n; x++)
1099       {
1100         int amt = *d++ + *r++;
1101         
1102         total += amt;
1103         if (total > max)
1104           {
1105             total -= amt;
1106             break;
1107           }
1108       }
1109
1110     if (end)
1111       *end = x;
1112     
1113     if (actual)
1114       *actual = total;
1115   }
1116 }
1117
1118 /* Return flags set for the current table into FLAGS. */
1119 static void
1120 tabi_flags (unsigned *flags)
1121 {
1122   assert (flags != NULL);
1123   *flags = t->flags;
1124 }
1125
1126 /* Render title for current table, with major index X and minor index
1127    Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
1128    if Y is nonzero. */
1129 static void
1130 tabi_title (int x, int y)
1131 {
1132   char buf[1024];
1133   char *cp;
1134
1135   if (t->flags & SOMF_NO_TITLE)
1136     return;
1137   
1138   cp = spprintf (buf, "%d.%d", table_num, subtable_num);
1139   if (x && y)
1140     cp = spprintf (cp, "(%d:%d)", x, y);
1141   else if (x)
1142     cp = spprintf (cp, "(%d)", x);
1143   if (cur_proc)
1144     cp = spprintf (cp, " %s", cur_proc);
1145   cp = stpcpy (cp, ".  ");
1146   if (!ls_empty_p (&t->title))
1147     {
1148       memcpy (cp, ls_value (&t->title), ls_length (&t->title));
1149       cp += ls_length (&t->title);
1150     }
1151   *cp = 0;
1152   
1153   {
1154     struct outp_text text;
1155
1156     text.options = OUTP_T_JUST_LEFT | OUTP_T_HORZ | OUTP_T_VERT;
1157     ls_init (&text.s, buf, cp - buf);
1158     text.h = d->width;
1159     text.v = d->font_height;
1160     text.x = 0;
1161     text.y = d->cp_y;
1162     d->class->text_draw (d, &text);
1163   }
1164 }
1165
1166 static int render_strip (int x, int y, int r, int c1, int c2, int r1, int r2);
1167
1168 /* Execute BODY for each value of X from A to B exclusive. */
1169 #define UNROLL_LOOP(X, A, B, BODY)              \
1170         do                                      \
1171           {                                     \
1172             for (X = A; X < B; X++)             \
1173               {                                 \
1174                 BODY                            \
1175               }                                 \
1176           }                                     \
1177         while (0)
1178
1179 /* Execute PREP, then BODY for each specified value of X: A1...A2, B1...B2,
1180    C1...C2, in each case not including the second value. */
1181 #define UNROLL_3_LOOPS(X, A1, A2, B1, B2, C1, C2, BODY) \
1182         do                                              \
1183           {                                             \
1184             UNROLL_LOOP (X, A1, A2, BODY);              \
1185             UNROLL_LOOP (X, B1, B2, BODY);              \
1186             UNROLL_LOOP (X, C1, C2, BODY);              \
1187           }                                             \
1188         while (0)
1189
1190 /* Draws the table region in rectangle (X1,Y1)-(X2,Y2), where column
1191    X2 and row Y2 are not included in the rectangle, at the current
1192    position on the current output device.  Draws headers as well. */
1193 static void
1194 tabi_render (int x1, int y1, int x2, int y2)
1195 {
1196   int y, r;
1197   
1198   tab_hit++;
1199   y = d->cp_y;
1200   if (!(t->flags & SOMF_NO_TITLE))
1201     y += d->font_height;
1202   UNROLL_3_LOOPS (r, 0, t->t * 2 + 1, y1 * 2 + 1, y2 * 2,
1203                   (t->nr - t->b) * 2, t->nr * 2 + 1,
1204
1205                   int x = d->cp_x;
1206                   x += render_strip (x, y, r, 0, t->l * 2 + 1, y1, y2);
1207                   x += render_strip (x, y, r, x1 * 2 + 1, x2 * 2, y1, y2);
1208                   x += render_strip (x, y, r, (t->nc - t->r) * 2,
1209                                      t->nc * 2 + 1, y1, y2);
1210                   y += (r & 1) ? t->h[r / 2] : t->hrh[r / 2];
1211                   );
1212 }
1213
1214 struct som_table_class tab_table_class =
1215   {
1216     tabi_table,
1217     tabi_driver,
1218     
1219     tabi_count,
1220     tabi_area,
1221     NULL,
1222     NULL,
1223     tabi_columns,
1224     NULL,
1225     tabi_headers,
1226     NULL,
1227     tabi_cumulate,
1228     tabi_flags,
1229     
1230     NULL,
1231     NULL,
1232
1233     tabi_title,
1234     tabi_render,
1235   };
1236 \f
1237 /* Render contiguous strip consisting of columns C1...C2, exclusive,
1238    on row R, at location (X,Y).  Return width of the strip thus
1239    rendered.
1240
1241    Renders joined cells, even those outside the strip, within the
1242    rendering region (C1,R1)-(C2,R2).
1243
1244    For the purposes of counting rows and columns in this function
1245    only, horizontal rules are considered rows and vertical rules are
1246    considered columns.
1247
1248    FIXME: Doesn't use r1?  Huh?  */
1249 static int
1250 render_strip (int x, int y, int r, int c1, int c2, int r1 unused, int r2)
1251 {
1252   int x_origin = x;
1253
1254   /* Horizontal rules. */
1255   if ((r & 1) == 0)
1256     {
1257       int hrh = t->hrh[r / 2];
1258       int c;
1259
1260       for (c = c1; c < c2; c++)
1261         {
1262           if (c & 1)
1263             {
1264               int style = t->rh[(c / 2) + (r / 2 * t->cf)];
1265
1266               if (style != TAL_0)
1267                 {
1268                   const struct color clr = {0, 0, 0, 0};
1269                   struct rect rct;
1270
1271                   rct.x1 = x;
1272                   rct.y1 = y;
1273                   rct.x2 = x + t->w[c / 2];
1274                   rct.y2 = y + hrh;
1275                   d->class->line_horz (d, &rct, &clr, style);
1276                 }
1277               x += t->w[c / 2];
1278             } else {
1279               const struct color clr = {0, 0, 0, 0};
1280               struct rect rct;
1281               struct outp_styles s;
1282
1283               rct.x1 = x;
1284               rct.y1 = y;
1285               rct.x2 = x + t->wrv[c / 2];
1286               rct.y2 = y + hrh;
1287
1288               s.t = r > 0 ? t->rv[(c / 2) + (t->cf + 1) * (r / 2 - 1)] : 0;
1289               s.b = r < 2 * t->nr ? t->rv[(c / 2) + (t->cf + 1) * (r / 2)] : 0;
1290               s.l = c > 0 ? t->rh[(c / 2 - 1) + t->cf * (r / 2)] : 0;
1291               s.r = c < 2 * t->nc ? t->rh[(c / 2) + t->cf * (r / 2)] : 0;
1292
1293               if (s.t | s.b | s.l | s.r)
1294                 d->class->line_intersection (d, &rct, &clr, &s);
1295               
1296               x += t->wrv[c / 2];
1297             }
1298         }
1299     } else {
1300       int c;
1301
1302       for (c = c1; c < c2; c++)
1303         {
1304           if (c & 1)
1305             {
1306               const int index = (c / 2) + (r / 2 * t->cf);
1307
1308               if (!(t->ct[index] & TAB_JOIN))
1309                 {
1310                   struct outp_text text;
1311
1312                   text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
1313                                   | OUTP_T_HORZ | OUTP_T_VERT);
1314                   if ((t->ct[index] & TAB_EMPTY) == 0)
1315                     {
1316                       text.s = t->cc[index];
1317                       assert (!ls_null_p (&text.s));
1318                       text.h = t->w[c / 2];
1319                       text.v = t->h[r / 2];
1320                       text.x = x;
1321                       text.y = y;
1322                       d->class->text_draw (d, &text);
1323                     }
1324                 } else {
1325                   struct tab_joined_cell *j =
1326                     (struct tab_joined_cell *) ls_value (&t->cc[index]);
1327
1328                   if (j->hit != tab_hit)
1329                     {
1330                       j->hit = tab_hit;
1331
1332                       if (j->x1 == c / 2 && j->y1 == r / 2
1333                           && j->x2 <= c2 && j->y2 <= r2)
1334                         {
1335                           struct outp_text text;
1336
1337                           text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
1338                                           | OUTP_T_HORZ | OUTP_T_VERT);
1339                           text.s = j->contents;
1340                           text.x = x;
1341                           text.y = y;
1342                           
1343                           {
1344                             int c;
1345
1346                             for (c = j->x1, text.h = -t->wrv[j->x2];
1347                                  c < j->x2; c++)
1348                               text.h += t->w[c] + t->wrv[c + 1];
1349                           }
1350                           
1351                           {
1352                             int r;
1353
1354                             for (r = j->y1, text.v = -t->hrh[j->y2];
1355                                  r < j->y2; r++)
1356                               text.v += t->h[r] + t->hrh[r + 1];
1357                           }
1358                           d->class->text_draw (d, &text);
1359                         }
1360                     }
1361                 }
1362               x += t->w[c / 2];
1363             } else {
1364               int style = t->rv[(c / 2) + (r / 2 * (t->cf + 1))];
1365
1366               if (style != TAL_0)
1367                 {
1368                   const struct color clr = {0, 0, 0, 0};
1369                   struct rect rct;
1370
1371                   rct.x1 = x;
1372                   rct.y1 = y;
1373                   rct.x2 = x + t->wrv[c / 2];
1374                   rct.y2 = y + t->h[r / 2];
1375                   d->class->line_vert (d, &rct, &clr, style);
1376                 }
1377               x += t->wrv[c / 2];
1378             }
1379         }
1380     }
1381
1382   return x - x_origin;
1383 }
1384