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