Thu Jan 1 23:16:41 2004 Ben Pfaff <blp@gnu.org>
[pspp] / 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           if (d->class->text_set_font_by_name != NULL)
807             d->class->text_set_font_by_name (d, "FIXED");
808           else 
809             {
810               /* FIXME */
811             }
812         }
813     }
814
815   tab_text (t, 0, 0, options &~ TAT_PRINTF, buf);
816   tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
817   if (options & TAT_NOWRAP)
818     tab_dim (t, nowrap_dim);
819   else
820     tab_dim (t, wrap_dim);
821   tab_submit (t);
822
823   if (options & TAT_FIX)
824     {
825       struct outp_driver *d;
826
827       for (d = outp_drivers (NULL); d; d = outp_drivers (d))
828         if (d->class->text_set_font_by_name != NULL)
829           d->class->text_set_font_by_name (d, "PROP");
830         else 
831           {
832             /* FIXME */
833           }
834     }
835   
836   if (options & TAT_PRINTF)
837     local_free (buf);
838 }
839
840 /* Set table flags to FLAGS. */
841 void
842 tab_flags (struct tab_table *t, unsigned flags)
843 {
844   assert (t != NULL);
845   t->flags = flags;
846 }
847
848 /* Easy, type-safe way to submit a tab table to som. */
849 void
850 tab_submit (struct tab_table *t)
851 {
852   struct som_table s;
853
854   assert (t != NULL);
855   s.class = &tab_table_class;
856   s.ext = t;
857   som_submit (&s);
858   tab_destroy (t);
859 }
860 \f
861 /* Editing. */
862
863 /* Set table row and column offsets for all functions that affect
864    cells or rules. */
865 void
866 tab_offset (struct tab_table *t, int col, int row)
867 {
868   int diff = 0;
869
870   assert (t != NULL);
871 #if GLOBAL_DEBUGGING
872   if (row < -1 || row >= t->nr)
873     {
874       printf ("tab_offset(): row=%d in %d-row table\n", row, t->nr);
875       abort ();
876     }
877   if (col < -1 || col >= t->nc)
878     {
879       printf ("tab_offset(): col=%d in %d-column table\n", col, t->nc);
880       abort ();
881     }
882 #endif
883
884   if (row != -1)
885     diff += (row - t->row_ofs) * t->cf, t->row_ofs = row;
886   if (col != -1)
887     diff += (col - t->col_ofs), t->col_ofs = col;
888
889   t->cc += diff;
890   t->ct += diff;
891 }
892
893 /* Increment the row offset by one. If the table is too small,
894    increase its size. */
895 void
896 tab_next_row (struct tab_table *t)
897 {
898   assert (t != NULL);
899   t->cc += t->cf;
900   t->ct += t->cf;
901   if (++t->row_ofs >= t->nr)
902     tab_realloc (t, -1, t->nr * 4 / 3);
903 }
904 \f
905 static struct tab_table *t;
906 static struct outp_driver *d;
907 int tab_hit;
908
909 /* Set the current table to TABLE. */
910 static void
911 tabi_table (struct som_table *table)
912 {
913   assert (table != NULL);
914   t = table->ext;
915   tab_offset (t, 0, 0);
916   
917   assert (t->w == NULL && t->h == NULL);
918   t->w = pool_alloc (t->container, sizeof *t->w * t->nc);
919   t->h = pool_alloc (t->container, sizeof *t->h * t->nr);
920 }
921
922 /* Set the current output device to DRIVER. */
923 static void
924 tabi_driver (struct outp_driver *driver)
925 {
926   int i;
927
928   assert (driver != NULL);
929   d = driver;
930   
931   /* Figure out sizes of rules. */
932   for (t->hr_tot = i = 0; i <= t->nr; i++)
933     t->hr_tot += t->hrh[i] = d->horiz_line_spacing[t->trh[i]];
934   for (t->vr_tot = i = 0; i <= t->nc; i++)
935     t->vr_tot += t->wrv[i] = d->vert_line_spacing[t->trv[i]];
936
937 #if GLOBAL_DEBUGGING
938   for (i = 0; i < t->nr; i++)
939     t->h[i] = -1;
940   for (i = 0; i < t->nc; i++)
941     t->w[i] = -1;
942 #endif
943
944   assert (t->dim != NULL);
945   t->dim (t, d);
946
947 #if GLOBAL_DEBUGGING
948   {
949     int error = 0;
950
951     for (i = 0; i < t->nr; i++)
952       {
953         if (t->h[i] == -1)
954           {
955             printf ("Table row %d height not initialized.\n", i);
956             error = 1;
957           }
958         assert (t->h[i] > 0);
959       }
960     
961     for (i = 0; i < t->nc; i++)
962       {
963         if (t->w[i] == -1)
964           {
965             printf ("Table column %d width not initialized.\n", i);
966             error = 1;
967           }
968         assert (t->w[i] > 0);
969       }
970   }
971 #endif
972     
973   /* Add up header sizes. */
974   for (i = 0, t->wl = t->wrv[0]; i < t->l; i++)
975     t->wl += t->w[i] + t->wrv[i + 1];
976   for (i = 0, t->ht = t->hrh[0]; i < t->t; i++)
977     t->ht += t->h[i] + t->hrh[i + 1];
978   for (i = t->nc - t->r, t->wr = t->wrv[i]; i < t->nc; i++)
979     t->wr += t->w[i] + t->wrv[i + 1];
980   for (i = t->nr - t->b, t->hb = t->hrh[i]; i < t->nr; i++)
981     t->hb += t->h[i] + t->hrh[i + 1];
982   
983   /* Title. */
984   if (!(t->flags & SOMF_NO_TITLE))
985     t->ht += d->font_height;
986 }
987
988 /* Return the number of columns and rows in the table into N_COLUMNS
989    and N_ROWS, respectively. */
990 static void
991 tabi_count (int *n_columns, int *n_rows)
992 {
993   assert (n_columns != NULL && n_rows != NULL);
994   *n_columns = t->nc;
995   *n_rows = t->nr;
996 }
997
998 static void tabi_cumulate (int cumtype, int start, int *end, int max, int *actual);
999
1000 /* Return the horizontal and vertical size of the entire table,
1001    including headers, for the current output device, into HORIZ and
1002    VERT. */
1003 static void
1004 tabi_area (int *horiz, int *vert)
1005 {
1006   assert (horiz != NULL && vert != NULL);
1007   
1008   {
1009     int w, c;
1010     
1011     for (c = t->l + 1, w = t->wl + t->wr + t->w[t->l];
1012          c < t->nc - t->r; c++)
1013       w += t->w[c] + t->wrv[c];
1014     *horiz = w;
1015   }
1016   
1017   {
1018     int h, r;
1019     for (r = t->t + 1, h = t->ht + t->hb + t->h[t->t];
1020          r < t->nr - t->b; r++)
1021       h += t->h[r] + t->hrh[r];
1022     *vert = h;
1023   }
1024 }
1025
1026 /* Return the column style for this table into STYLE. */
1027 static void
1028 tabi_columns (int *style)
1029 {
1030   assert (style != NULL);
1031   *style = t->col_style;
1032 }
1033
1034 /* Return the number of header rows/columns on the left, right, top,
1035    and bottom sides into HL, HR, HT, and HB, respectively. */
1036 static void
1037 tabi_headers (int *hl, int *hr, int *ht, int *hb)
1038 {
1039   assert (hl != NULL && hr != NULL && ht != NULL && hb != NULL);
1040   *hl = t->l;
1041   *hr = t->r;
1042   *ht = t->t;
1043   *hb = t->b;
1044 }
1045
1046 /* Determines the number of rows or columns (including appropriate
1047    headers), depending on CUMTYPE, that will fit into the space
1048    specified.  Takes rows/columns starting at index START and attempts
1049    to fill up available space MAX.  Returns in END the index of the
1050    last row/column plus one; returns in ACTUAL the actual amount of
1051    space the selected rows/columns (including appropriate headers)
1052    filled. */
1053 static void
1054 tabi_cumulate (int cumtype, int start, int *end, int max, int *actual)
1055 {
1056   int n;
1057   int *d;
1058   int *r;
1059   int total;
1060   
1061   assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
1062   if (cumtype == SOM_ROWS)
1063     {
1064       assert (start >= 0 && start < t->nr);
1065       n = t->nr - t->b;
1066       d = &t->h[start];
1067       r = &t->hrh[start + 1];
1068       total = t->ht + t->hb;
1069     } else {
1070       assert (start >= 0 && start < t->nc);
1071       n = t->nc - t->r;
1072       d = &t->w[start];
1073       r = &t->wrv[start + 1];
1074       total = t->wl + t->wr;
1075     }
1076   
1077   total += *d++;
1078   if (total > max)
1079     {
1080       if (end)
1081         *end = start;
1082       if (actual)
1083         *actual = 0;
1084       return;
1085     }
1086     
1087   {
1088     int x;
1089       
1090     for (x = start + 1; x < n; x++)
1091       {
1092         int amt = *d++ + *r++;
1093         
1094         total += amt;
1095         if (total > max)
1096           {
1097             total -= amt;
1098             break;
1099           }
1100       }
1101
1102     if (end)
1103       *end = x;
1104     
1105     if (actual)
1106       *actual = total;
1107   }
1108 }
1109
1110 /* Return flags set for the current table into FLAGS. */
1111 static void
1112 tabi_flags (unsigned *flags)
1113 {
1114   assert (flags != NULL);
1115   *flags = t->flags;
1116 }
1117
1118 /* Render title for current table, with major index X and minor index
1119    Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
1120    if Y is nonzero. */
1121 static void
1122 tabi_title (int x, int y)
1123 {
1124   char buf[1024];
1125   char *cp;
1126
1127   if (t->flags & SOMF_NO_TITLE)
1128     return;
1129   
1130   cp = spprintf (buf, "%d.%d", table_num, subtable_num);
1131   if (x && y)
1132     cp = spprintf (cp, "(%d:%d)", x, y);
1133   else if (x)
1134     cp = spprintf (cp, "(%d)", x);
1135   if (cur_proc)
1136     cp = spprintf (cp, " %s", cur_proc);
1137   cp = stpcpy (cp, ".  ");
1138   if (!ls_empty_p (&t->title))
1139     {
1140       memcpy (cp, ls_value (&t->title), ls_length (&t->title));
1141       cp += ls_length (&t->title);
1142     }
1143   *cp = 0;
1144   
1145   {
1146     struct outp_text text;
1147
1148     text.options = OUTP_T_JUST_LEFT | OUTP_T_HORZ | OUTP_T_VERT;
1149     ls_init (&text.s, buf, cp - buf);
1150     text.h = d->width;
1151     text.v = d->font_height;
1152     text.x = 0;
1153     text.y = d->cp_y;
1154     d->class->text_draw (d, &text);
1155   }
1156 }
1157
1158 static int render_strip (int x, int y, int r, int c1, int c2, int r1, int r2);
1159
1160 /* Draws the table region in rectangle (X1,Y1)-(X2,Y2), where column
1161    X2 and row Y2 are not included in the rectangle, at the current
1162    position on the current output device.  Draws headers as well. */
1163 static void
1164 tabi_render (int x1, int y1, int x2, int y2)
1165 {
1166   int i, y;
1167   int ranges[3][2];
1168   
1169   tab_hit++;
1170
1171   y = d->cp_y;
1172   if (!(t->flags & SOMF_NO_TITLE))
1173     y += d->font_height;
1174
1175   /* Top headers. */
1176   ranges[0][0] = 0;
1177   ranges[0][1] = t->t * 2 + 1;
1178
1179   /* Requested rows. */
1180   ranges[1][0] = y1 * 2 + 1;
1181   ranges[1][1] = y2 * 2;
1182
1183   /* Bottom headers. */
1184   ranges[2][0] = (t->nr - t->b) * 2;
1185   ranges[2][1] = t->nr * 2 + 1;
1186
1187   for (i = 0; i < 3; i++) 
1188     {
1189       int r;
1190
1191       for (r = ranges[i][0]; r < ranges[i][1]; r++) 
1192         {
1193           int x = d->cp_x;
1194           x += render_strip (x, y, r, 0, t->l * 2 + 1, y1, y2);
1195           x += render_strip (x, y, r, x1 * 2 + 1, x2 * 2, y1, y2);
1196           x += render_strip (x, y, r, (t->nc - t->r) * 2,
1197                              t->nc * 2 + 1, y1, y2);
1198           y += (r & 1) ? t->h[r / 2] : t->hrh[r / 2]; 
1199         }
1200     }
1201 }
1202
1203 struct som_table_class tab_table_class =
1204   {
1205     tabi_table,
1206     tabi_driver,
1207     
1208     tabi_count,
1209     tabi_area,
1210     NULL,
1211     NULL,
1212     tabi_columns,
1213     NULL,
1214     tabi_headers,
1215     NULL,
1216     tabi_cumulate,
1217     tabi_flags,
1218     
1219     NULL,
1220     NULL,
1221
1222     tabi_title,
1223     tabi_render,
1224   };
1225 \f
1226 /* Render contiguous strip consisting of columns C1...C2, exclusive,
1227    on row R, at location (X,Y).  Return width of the strip thus
1228    rendered.
1229
1230    Renders joined cells, even those outside the strip, within the
1231    rendering region (C1,R1)-(C2,R2).
1232
1233    For the purposes of counting rows and columns in this function
1234    only, horizontal rules are considered rows and vertical rules are
1235    considered columns.
1236
1237    FIXME: Doesn't use r1?  Huh?  */
1238 static int
1239 render_strip (int x, int y, int r, int c1, int c2, int r1 unused, int r2)
1240 {
1241   int x_origin = x;
1242
1243   /* Horizontal rules. */
1244   if ((r & 1) == 0)
1245     {
1246       int hrh = t->hrh[r / 2];
1247       int c;
1248
1249       for (c = c1; c < c2; c++)
1250         {
1251           if (c & 1)
1252             {
1253               int style = t->rh[(c / 2) + (r / 2 * t->cf)];
1254
1255               if (style != TAL_0)
1256                 {
1257                   const struct color clr = {0, 0, 0, 0};
1258                   struct rect rct;
1259
1260                   rct.x1 = x;
1261                   rct.y1 = y;
1262                   rct.x2 = x + t->w[c / 2];
1263                   rct.y2 = y + hrh;
1264                   d->class->line_horz (d, &rct, &clr, style);
1265                 }
1266               x += t->w[c / 2];
1267             } else {
1268               const struct color clr = {0, 0, 0, 0};
1269               struct rect rct;
1270               struct outp_styles s;
1271
1272               rct.x1 = x;
1273               rct.y1 = y;
1274               rct.x2 = x + t->wrv[c / 2];
1275               rct.y2 = y + hrh;
1276
1277               s.t = r > 0 ? t->rv[(c / 2) + (t->cf + 1) * (r / 2 - 1)] : 0;
1278               s.b = r < 2 * t->nr ? t->rv[(c / 2) + (t->cf + 1) * (r / 2)] : 0;
1279               s.l = c > 0 ? t->rh[(c / 2 - 1) + t->cf * (r / 2)] : 0;
1280               s.r = c < 2 * t->nc ? t->rh[(c / 2) + t->cf * (r / 2)] : 0;
1281
1282               if (s.t | s.b | s.l | s.r)
1283                 d->class->line_intersection (d, &rct, &clr, &s);
1284               
1285               x += t->wrv[c / 2];
1286             }
1287         }
1288     } else {
1289       int c;
1290
1291       for (c = c1; c < c2; c++)
1292         {
1293           if (c & 1)
1294             {
1295               const int index = (c / 2) + (r / 2 * t->cf);
1296
1297               if (!(t->ct[index] & TAB_JOIN))
1298                 {
1299                   struct outp_text text;
1300
1301                   text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
1302                                   | OUTP_T_HORZ | OUTP_T_VERT);
1303                   if ((t->ct[index] & TAB_EMPTY) == 0)
1304                     {
1305                       text.s = t->cc[index];
1306                       assert (!ls_null_p (&text.s));
1307                       text.h = t->w[c / 2];
1308                       text.v = t->h[r / 2];
1309                       text.x = x;
1310                       text.y = y;
1311                       d->class->text_draw (d, &text);
1312                     }
1313                 } else {
1314                   struct tab_joined_cell *j =
1315                     (struct tab_joined_cell *) ls_value (&t->cc[index]);
1316
1317                   if (j->hit != tab_hit)
1318                     {
1319                       j->hit = tab_hit;
1320
1321                       if (j->x1 == c / 2 && j->y1 == r / 2
1322                           && j->x2 <= c2 && j->y2 <= r2)
1323                         {
1324                           struct outp_text text;
1325
1326                           text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
1327                                           | OUTP_T_HORZ | OUTP_T_VERT);
1328                           text.s = j->contents;
1329                           text.x = x;
1330                           text.y = y;
1331                           
1332                           {
1333                             int c;
1334
1335                             for (c = j->x1, text.h = -t->wrv[j->x2];
1336                                  c < j->x2; c++)
1337                               text.h += t->w[c] + t->wrv[c + 1];
1338                           }
1339                           
1340                           {
1341                             int r;
1342
1343                             for (r = j->y1, text.v = -t->hrh[j->y2];
1344                                  r < j->y2; r++)
1345                               text.v += t->h[r] + t->hrh[r + 1];
1346                           }
1347                           d->class->text_draw (d, &text);
1348                         }
1349                     }
1350                 }
1351               x += t->w[c / 2];
1352             } else {
1353               int style = t->rv[(c / 2) + (r / 2 * (t->cf + 1))];
1354
1355               if (style != TAL_0)
1356                 {
1357                   const struct color clr = {0, 0, 0, 0};
1358                   struct rect rct;
1359
1360                   rct.x1 = x;
1361                   rct.y1 = y;
1362                   rct.x2 = x + t->wrv[c / 2];
1363                   rct.y2 = y + t->h[r / 2];
1364                   d->class->line_vert (d, &rct, &clr, style);
1365                 }
1366               x += t->wrv[c / 2];
1367             }
1368         }
1369     }
1370
1371   return x - x_origin;
1372 }
1373