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