3 /* multi.c -- multiple-column tables (@multitable) for makeinfo.
4 Id: multi.c,v 1.8 2004/04/11 17:56:47 karl Exp
6 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004 Free Software
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software Foundation,
21 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 Originally written by phr@gnu.org (Paul Rubin). */
27 #include "insertion.h"
32 #define MAXCOLS 100 /* remove this limit later @@ */
36 * Output environments. This is a hack grafted onto existing
37 * structure. The "output environment" used to consist of the
38 * global variables `output_paragraph', `fill_column', etc.
39 * Routines like add_char would manipulate these variables.
41 * Now, when formatting a multitable, we maintain separate environments
42 * for each column. That way we can build up the columns separately
43 * and write them all out at once. The "current" output environment"
44 * is still kept in those global variables, so that the old output
45 * routines don't have to change. But we provide routines to save
46 * and restore these variables in an "environment table". The
47 * `select_output_environment' function switches from one output
48 * environment to another.
50 * Environment #0 (i.e., element #0 of the table) is the regular
51 * environment that is used when we're not formatting a multitable.
53 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
54 * the table, when a multitable is active.
57 /* contents of an output environment */
58 /* some more vars may end up being needed here later @@ */
61 unsigned char *output_paragraph
;
62 int output_paragraph_offset
;
65 int paragraph_is_open
;
68 } envs
[MAXCOLS
]; /* the environment table */
70 /* index in environment table of currently selected environment */
71 static int current_env_no
;
73 /* current column number */
74 static int current_column_no
;
76 /* We need to make a difference between template based widths and
77 @columnfractions for HTML tables' sake. Sigh. */
78 static int seen_column_fractions
;
80 /* column number of last column in current multitable */
81 static int last_column
;
83 /* flags indicating whether horizontal and vertical separators need
84 to be drawn, separating rows and columns in the current multitable. */
85 static int hsep
, vsep
;
87 /* whether this is the first row. */
90 /* Called to handle a {...} template on the @multitable line.
91 We're at the { and our first job is to find the matching }; as a side
92 effect, we change *PARAMS to point to after it. Our other job is to
93 expand the template text and return the width of that string. */
95 find_template_width (char **params
)
97 char *template, *xtemplate
;
99 char *start
= *params
;
102 /* The first character should be a {. */
103 if (!params
|| !*params
|| **params
!= '{')
105 line_error ("find_template width internal error: passed %s",
106 params
? *params
: "null");
112 if (**params
== '{' && (*params
== start
|| (*params
)[-1] != '@'))
114 else if (**params
== '}' && (*params
)[-1] != '@')
116 else if (**params
== 0)
118 line_error (_("Missing } in @multitable template"));
123 while (brace_level
> 0);
125 template = substring (start
+ 1, *params
- 1); /* omit braces */
126 xtemplate
= expansion (template, 0);
127 len
= strlen (xtemplate
);
135 /* Direct current output to environment number N. Used when
136 switching work from one column of a multitable to the next.
137 Returns previous environment number. */
139 select_output_environment (int n
)
141 struct env
*e
= &envs
[current_env_no
];
142 int old_env_no
= current_env_no
;
144 /* stash current env info from global vars into the old environment */
145 e
->output_paragraph
= output_paragraph
;
146 e
->output_paragraph_offset
= output_paragraph_offset
;
147 e
->meta_char_pos
= meta_char_pos
;
148 e
->output_column
= output_column
;
149 e
->paragraph_is_open
= paragraph_is_open
;
150 e
->current_indent
= current_indent
;
151 e
->fill_column
= fill_column
;
153 /* now copy new environment into global vars */
155 e
= &envs
[current_env_no
];
156 output_paragraph
= e
->output_paragraph
;
157 output_paragraph_offset
= e
->output_paragraph_offset
;
158 meta_char_pos
= e
->meta_char_pos
;
159 output_column
= e
->output_column
;
160 paragraph_is_open
= e
->paragraph_is_open
;
161 current_indent
= e
->current_indent
;
162 fill_column
= e
->fill_column
;
166 /* Initialize environment number ENV_NO, of width WIDTH.
167 The idea is that we're going to use one environment for each column of
168 a multitable, so we can build them up separately and print them
169 all out at the end. */
171 setup_output_environment (int env_no
, int width
)
173 int old_env
= select_output_environment (env_no
);
175 /* clobber old environment and set width of new one */
178 /* make our change */
181 /* Save new environment and restore previous one. */
182 select_output_environment (old_env
);
187 /* Read the parameters for a multitable from the current command
188 line, save the parameters away, and return the
189 number of columns. */
191 setup_multitable_parameters (void)
193 char *params
= insertion_stack
->item_function
;
196 char command
[200]; /* xx no fixed limits */
199 /* We implement @hsep and @vsep even though TeX doesn't.
200 We don't get mixing of @columnfractions and templates right,
201 but TeX doesn't either. */
204 /* Assume no @columnfractions per default. */
205 seen_column_fractions
= 0;
208 while (whitespace (*params
))
211 if (*params
== '@') {
212 sscanf (params
, "%200s", command
);
213 nchars
= strlen (command
);
215 if (strcmp (command
, "@hsep") == 0)
217 else if (strcmp (command
, "@vsep") == 0)
219 else if (strcmp (command
, "@columnfractions") == 0) {
220 seen_column_fractions
= 1;
221 /* Clobber old environments and create new ones, starting at #1.
222 Environment #0 is the normal output, so don't mess with it. */
223 for ( ; i
<= MAXCOLS
; i
++) {
224 if (sscanf (params
, "%f", &columnfrac
) < 1)
226 /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least)
227 doesn't support it. So skip whitespace (preceding the
228 number) and then non-whitespace (the number). */
229 while (*params
&& (*params
== ' ' || *params
== '\t'))
231 /* Hmm, but what about @columnfractions 3foo. Oh well,
232 it's invalid input anyway. */
233 while (*params
&& *params
!= ' ' && *params
!= '\t'
234 && *params
!= '\n' && *params
!= '@')
238 /* For html/xml/docbook, translate fractions into integer
239 percentages, adding .005 to avoid rounding problems. For
240 info, we want the character width. */
241 int width
= xml
|| html
? (columnfrac
+ .005) * 100
242 : (columnfrac
* (fill_column
- current_indent
) + .5);
243 setup_output_environment (i
, width
);
248 } else if (*params
== '{') {
249 unsigned template_width
= find_template_width (¶ms
);
251 /* This gives us two spaces between columns. Seems reasonable.
252 How to take into account current_indent here? */
253 setup_output_environment (i
++, template_width
+ 2);
256 warning (_("ignoring stray text `%s' after @multitable"), params
);
263 inhibit_output_flushing ();
269 /* Output a row. Calls insert, but also flushes the buffered output
270 when we see a newline, since in multitable every line is a separate
279 int env
= select_output_environment (0);
283 uninhibit_output_flushing ();
285 inhibit_output_flushing ();
287 select_output_environment (env
);
293 draw_horizontal_separator (void)
305 for (s
= 0; s
< envs
[0].current_indent
; s
++)
309 for (i
= 1; i
<= last_column
; i
++) {
310 for (j
= 0; j
<= envs
[i
].fill_column
; j
++)
320 /* multitable strategy:
322 for each column in an item {
323 initialize a new paragraph
324 do ordinary formatting into the new paragraph
325 save the paragraph away
326 repeat if there are more paragraphs in the column
328 dump out the saved paragraphs and free the storage
331 For HTML we construct a simple HTML 3.2 table with <br>s inserted
332 to help non-tables browsers. `@item' inserts a <tr> and `@tab'
333 inserts <td>; we also try to close <tr>. The only real
334 alternative is to rely on the info formatting engine and present
335 preformatted text. */
342 if (multitable_active
)
344 line_error ("Multitables cannot be nested");
348 close_single_paragraph ();
353 if (output_paragraph
[output_paragraph_offset
-1] == '\n')
354 output_paragraph_offset
--;
357 /* scan the current item function to get the field widths
358 and number of columns, and set up the output environment list
360 ncolumns
= setup_multitable_parameters ();
363 /* <p> for non-tables browsers. @multitable implicitly ends the
364 current paragraph, so this is ok. */
366 add_html_block_elt ("<p><table summary=\"\">");
367 /* else if (docbook)*/ /* 05-08 */
370 int *widths
= xmalloc (ncolumns
* sizeof (int));
372 for (i
=0; i
<ncolumns
; i
++)
373 widths
[i
] = envs
[i
+1].fill_column
;
374 xml_begin_multitable (ncolumns
, widths
);
379 draw_horizontal_separator ();
381 /* The next @item command will direct stdout into the first column
382 and start processing. @tab will then switch to the next column,
383 and @item will flush out the saved output and return to the first
384 column. Environment #1 is the first column. (Environment #0 is
385 the normal output) */
390 /* advance to the next environment number */
392 nselect_next_environment (void)
394 if (current_env_no
>= last_column
) {
395 line_error (_("Too many columns in multitable item (max %d)"), last_column
);
398 select_output_environment (current_env_no
+ 1);
402 /* do anything needed at the beginning of processing a
403 multitable column. */
407 /* don't indent 1st paragraph in the item */
410 /* throw away possible whitespace after @item or @tab command */
415 output_multitable_row (void)
417 /* offset in the output paragraph of the next char needing
418 to be output for that column. */
420 int i
, j
, s
, remaining
;
423 for (i
= 0; i
<= last_column
; i
++)
426 /* select the current environment, to make sure the env variables
428 select_output_environment (current_env_no
);
430 #define CHAR_ADDR(n) (offset[i] + (n))
431 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])
433 /* remove trailing whitespace from each column */
434 for (i
= 1; i
<= last_column
; i
++) {
435 if (envs
[i
].output_paragraph_offset
)
436 while (cr_or_whitespace (CHAR_AT (envs
[i
].output_paragraph_offset
- 1)))
437 envs
[i
].output_paragraph_offset
--;
439 if (i
== current_env_no
)
440 output_paragraph_offset
= envs
[i
].output_paragraph_offset
;
443 /* read the current line from each column, outputting them all
444 pasted together. Do this til all lines are output from all
448 /* first, see if there is any work to do */
449 for (i
= 1; i
<= last_column
; i
++) {
450 if (CHAR_ADDR (0) < envs
[i
].output_paragraph_offset
) {
458 for (s
= 0; s
< envs
[0].current_indent
; s
++)
464 for (i
= 1; i
<= last_column
; i
++) {
465 for (s
= 0; s
< envs
[i
].current_indent
; s
++)
467 for (j
= 0; CHAR_ADDR (j
) < envs
[i
].output_paragraph_offset
; j
++) {
468 if (CHAR_AT (j
) == '\n')
470 out_char (CHAR_AT (j
));
472 offset
[i
] += j
+ 1; /* skip last text plus skip the newline */
474 /* Do not output trailing blanks if we're in the last column and
475 there will be no trailing |. */
476 if (i
< last_column
&& !vsep
)
477 for (; j
<= envs
[i
].fill_column
; j
++)
480 out_char ('|'); /* draw column separator */
482 out_char ('\n'); /* end of line */
486 /* If completely blank item, get blank line despite no other output. */
488 out_char ('\n'); /* end of line */
491 draw_horizontal_separator ();
493 /* Now dispose of the buffered output. */
494 for (i
= 1; i
<= last_column
; i
++) {
495 select_output_environment (i
);
500 int after_headitem
= 0;
501 int headitem_row
= 0;
503 /* start a new item (row) of a multitable */
505 multitable_item (void)
507 if (!multitable_active
) {
508 line_error ("multitable_item internal error: no active multitable");
512 current_column_no
= 1;
517 /* <br> for non-tables browsers. */
518 add_word_args ("<br></%s></tr>", after_headitem
? "th" : "td");
520 if (seen_column_fractions
)
521 add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">",
522 headitem_flag
? "th" : "td",
523 envs
[current_column_no
].fill_column
);
525 add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
526 headitem_flag
? "th" : "td");
533 headitem_row
= headitem_flag
;
537 /* else if (docbook)*/ /* 05-08 */
540 xml_end_multitable_row (first_row
);
551 if (current_env_no
> 0) {
552 output_multitable_row ();
554 /* start at column 1 */
555 select_output_environment (1);
556 if (!output_paragraph
) {
557 line_error (_("[unexpected] cannot select column #%d in multitable"),
581 /* select a new column in current row of multitable */
585 if (!multitable_active
)
586 error (_("ignoring @tab outside of multitable"));
592 if (seen_column_fractions
)
593 add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">",
594 headitem_row
? "th" : "td",
595 headitem_row
? "th" : "td",
596 envs
[current_column_no
].fill_column
);
598 add_word_args ("</%s><%s valign=\"top\">",
599 headitem_row
? "th" : "td",
600 headitem_row
? "th" : "td");
602 /* else if (docbook)*/ /* 05-08 */
604 xml_end_multitable_column ();
606 nselect_next_environment ();
611 /* close a multitable, flushing its output and resetting
612 whatever needs resetting */
614 end_multitable (void)
616 if (!html
&& !docbook
)
617 output_multitable_row ();
619 /* Multitables cannot be nested. Otherwise, we'd have to save the
620 previous output environment number on a stack somewhere, and then
621 restore to that environment. */
622 select_output_environment (0);
623 multitable_active
= 0;
624 uninhibit_output_flushing ();
625 close_insertion_paragraph ();
628 add_word_args ("<br></%s></tr></table>\n", headitem_row
? "th" : "td");
629 /* else if (docbook)*/ /* 05-08 */
631 xml_end_multitable ();
634 printf (_("** Multicolumn output from last row:\n"));
635 for (i
= 1; i
<= last_column
; i
++) {
636 select_output_environment (i
);
637 printf (_("* column #%d: output = %s\n"), i
, output_paragraph
);