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