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