1 /* multi.c -- multiple-column tables (@multitable) for makeinfo.
2 $Id: multi.c,v 1.8 2004/04/11 17:56:47 karl Exp $
4 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004 Free Software
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software Foundation,
19 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 Originally written by phr@gnu.org (Paul Rubin). */
25 #include "insertion.h"
30 #define MAXCOLS 100 /* remove this limit later @@ */
34 * Output environments. This is a hack grafted onto existing
35 * structure. The "output environment" used to consist of the
36 * global variables `output_paragraph', `fill_column', etc.
37 * Routines like add_char would manipulate these variables.
39 * Now, when formatting a multitable, we maintain separate environments
40 * for each column. That way we can build up the columns separately
41 * and write them all out at once. The "current" output environment"
42 * is still kept in those global variables, so that the old output
43 * routines don't have to change. But we provide routines to save
44 * and restore these variables in an "environment table". The
45 * `select_output_environment' function switches from one output
46 * environment to another.
48 * Environment #0 (i.e., element #0 of the table) is the regular
49 * environment that is used when we're not formatting a multitable.
51 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
52 * the table, when a multitable is active.
55 /* contents of an output environment */
56 /* some more vars may end up being needed here later @@ */
59 unsigned char *output_paragraph
;
60 int output_paragraph_offset
;
63 int paragraph_is_open
;
66 } envs
[MAXCOLS
]; /* the environment table */
68 /* index in environment table of currently selected environment */
69 static int current_env_no
;
71 /* current column number */
72 static int current_column_no
;
74 /* We need to make a difference between template based widths and
75 @columnfractions for HTML tables' sake. Sigh. */
76 static int seen_column_fractions
;
78 /* column number of last column in current multitable */
79 static int last_column
;
81 /* flags indicating whether horizontal and vertical separators need
82 to be drawn, separating rows and columns in the current multitable. */
83 static int hsep
, vsep
;
85 /* whether this is the first row. */
88 /* Called to handle a {...} template on the @multitable line.
89 We're at the { and our first job is to find the matching }; as a side
90 effect, we change *PARAMS to point to after it. Our other job is to
91 expand the template text and return the width of that string. */
93 find_template_width (char **params
)
95 char *template, *xtemplate
;
97 char *start
= *params
;
100 /* The first character should be a {. */
101 if (!params
|| !*params
|| **params
!= '{')
103 line_error ("find_template width internal error: passed %s",
104 params
? *params
: "null");
110 if (**params
== '{' && (*params
== start
|| (*params
)[-1] != '@'))
112 else if (**params
== '}' && (*params
)[-1] != '@')
114 else if (**params
== 0)
116 line_error (_("Missing } in @multitable template"));
121 while (brace_level
> 0);
123 template = substring (start
+ 1, *params
- 1); /* omit braces */
124 xtemplate
= expansion (template, 0);
125 len
= strlen (xtemplate
);
133 /* Direct current output to environment number N. Used when
134 switching work from one column of a multitable to the next.
135 Returns previous environment number. */
137 select_output_environment (int n
)
139 struct env
*e
= &envs
[current_env_no
];
140 int old_env_no
= current_env_no
;
142 /* stash current env info from global vars into the old environment */
143 e
->output_paragraph
= output_paragraph
;
144 e
->output_paragraph_offset
= output_paragraph_offset
;
145 e
->meta_char_pos
= meta_char_pos
;
146 e
->output_column
= output_column
;
147 e
->paragraph_is_open
= paragraph_is_open
;
148 e
->current_indent
= current_indent
;
149 e
->fill_column
= fill_column
;
151 /* now copy new environment into global vars */
153 e
= &envs
[current_env_no
];
154 output_paragraph
= e
->output_paragraph
;
155 output_paragraph_offset
= e
->output_paragraph_offset
;
156 meta_char_pos
= e
->meta_char_pos
;
157 output_column
= e
->output_column
;
158 paragraph_is_open
= e
->paragraph_is_open
;
159 current_indent
= e
->current_indent
;
160 fill_column
= e
->fill_column
;
164 /* Initialize environment number ENV_NO, of width WIDTH.
165 The idea is that we're going to use one environment for each column of
166 a multitable, so we can build them up separately and print them
167 all out at the end. */
169 setup_output_environment (int env_no
, int width
)
171 int old_env
= select_output_environment (env_no
);
173 /* clobber old environment and set width of new one */
176 /* make our change */
179 /* Save new environment and restore previous one. */
180 select_output_environment (old_env
);
185 /* Read the parameters for a multitable from the current command
186 line, save the parameters away, and return the
187 number of columns. */
189 setup_multitable_parameters (void)
191 char *params
= insertion_stack
->item_function
;
194 char command
[200]; /* xx no fixed limits */
197 /* We implement @hsep and @vsep even though TeX doesn't.
198 We don't get mixing of @columnfractions and templates right,
199 but TeX doesn't either. */
202 /* Assume no @columnfractions per default. */
203 seen_column_fractions
= 0;
206 while (whitespace (*params
))
209 if (*params
== '@') {
210 sscanf (params
, "%200s", command
);
211 nchars
= strlen (command
);
213 if (strcmp (command
, "@hsep") == 0)
215 else if (strcmp (command
, "@vsep") == 0)
217 else if (strcmp (command
, "@columnfractions") == 0) {
218 seen_column_fractions
= 1;
219 /* Clobber old environments and create new ones, starting at #1.
220 Environment #0 is the normal output, so don't mess with it. */
221 for ( ; i
<= MAXCOLS
; i
++) {
222 if (sscanf (params
, "%f", &columnfrac
) < 1)
224 /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least)
225 doesn't support it. So skip whitespace (preceding the
226 number) and then non-whitespace (the number). */
227 while (*params
&& (*params
== ' ' || *params
== '\t'))
229 /* Hmm, but what about @columnfractions 3foo. Oh well,
230 it's invalid input anyway. */
231 while (*params
&& *params
!= ' ' && *params
!= '\t'
232 && *params
!= '\n' && *params
!= '@')
236 /* For html/xml/docbook, translate fractions into integer
237 percentages, adding .005 to avoid rounding problems. For
238 info, we want the character width. */
239 int width
= xml
|| html
? (columnfrac
+ .005) * 100
240 : (columnfrac
* (fill_column
- current_indent
) + .5);
241 setup_output_environment (i
, width
);
246 } else if (*params
== '{') {
247 unsigned template_width
= find_template_width (¶ms
);
249 /* This gives us two spaces between columns. Seems reasonable.
250 How to take into account current_indent here? */
251 setup_output_environment (i
++, template_width
+ 2);
254 warning (_("ignoring stray text `%s' after @multitable"), params
);
261 inhibit_output_flushing ();
267 /* Output a row. Calls insert, but also flushes the buffered output
268 when we see a newline, since in multitable every line is a separate
277 int env
= select_output_environment (0);
281 uninhibit_output_flushing ();
283 inhibit_output_flushing ();
285 select_output_environment (env
);
291 draw_horizontal_separator (void)
303 for (s
= 0; s
< envs
[0].current_indent
; s
++)
307 for (i
= 1; i
<= last_column
; i
++) {
308 for (j
= 0; j
<= envs
[i
].fill_column
; j
++)
318 /* multitable strategy:
320 for each column in an item {
321 initialize a new paragraph
322 do ordinary formatting into the new paragraph
323 save the paragraph away
324 repeat if there are more paragraphs in the column
326 dump out the saved paragraphs and free the storage
329 For HTML we construct a simple HTML 3.2 table with <br>s inserted
330 to help non-tables browsers. `@item' inserts a <tr> and `@tab'
331 inserts <td>; we also try to close <tr>. The only real
332 alternative is to rely on the info formatting engine and present
333 preformatted text. */
340 if (multitable_active
)
342 line_error ("Multitables cannot be nested");
346 close_single_paragraph ();
351 if (output_paragraph
[output_paragraph_offset
-1] == '\n')
352 output_paragraph_offset
--;
355 /* scan the current item function to get the field widths
356 and number of columns, and set up the output environment list
358 ncolumns
= setup_multitable_parameters ();
361 /* <p> for non-tables browsers. @multitable implicitly ends the
362 current paragraph, so this is ok. */
364 add_html_block_elt ("<p><table summary=\"\">");
365 /* else if (docbook)*/ /* 05-08 */
368 int *widths
= xmalloc (ncolumns
* sizeof (int));
370 for (i
=0; i
<ncolumns
; i
++)
371 widths
[i
] = envs
[i
+1].fill_column
;
372 xml_begin_multitable (ncolumns
, widths
);
377 draw_horizontal_separator ();
379 /* The next @item command will direct stdout into the first column
380 and start processing. @tab will then switch to the next column,
381 and @item will flush out the saved output and return to the first
382 column. Environment #1 is the first column. (Environment #0 is
383 the normal output) */
388 /* advance to the next environment number */
390 nselect_next_environment (void)
392 if (current_env_no
>= last_column
) {
393 line_error (_("Too many columns in multitable item (max %d)"), last_column
);
396 select_output_environment (current_env_no
+ 1);
400 /* do anything needed at the beginning of processing a
401 multitable column. */
405 /* don't indent 1st paragraph in the item */
408 /* throw away possible whitespace after @item or @tab command */
413 output_multitable_row (void)
415 /* offset in the output paragraph of the next char needing
416 to be output for that column. */
418 int i
, j
, s
, remaining
;
421 for (i
= 0; i
<= last_column
; i
++)
424 /* select the current environment, to make sure the env variables
426 select_output_environment (current_env_no
);
428 #define CHAR_ADDR(n) (offset[i] + (n))
429 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])
431 /* remove trailing whitespace from each column */
432 for (i
= 1; i
<= last_column
; i
++) {
433 if (envs
[i
].output_paragraph_offset
)
434 while (cr_or_whitespace (CHAR_AT (envs
[i
].output_paragraph_offset
- 1)))
435 envs
[i
].output_paragraph_offset
--;
437 if (i
== current_env_no
)
438 output_paragraph_offset
= envs
[i
].output_paragraph_offset
;
441 /* read the current line from each column, outputting them all
442 pasted together. Do this til all lines are output from all
446 /* first, see if there is any work to do */
447 for (i
= 1; i
<= last_column
; i
++) {
448 if (CHAR_ADDR (0) < envs
[i
].output_paragraph_offset
) {
456 for (s
= 0; s
< envs
[0].current_indent
; s
++)
462 for (i
= 1; i
<= last_column
; i
++) {
463 for (s
= 0; s
< envs
[i
].current_indent
; s
++)
465 for (j
= 0; CHAR_ADDR (j
) < envs
[i
].output_paragraph_offset
; j
++) {
466 if (CHAR_AT (j
) == '\n')
468 out_char (CHAR_AT (j
));
470 offset
[i
] += j
+ 1; /* skip last text plus skip the newline */
472 /* Do not output trailing blanks if we're in the last column and
473 there will be no trailing |. */
474 if (i
< last_column
&& !vsep
)
475 for (; j
<= envs
[i
].fill_column
; j
++)
478 out_char ('|'); /* draw column separator */
480 out_char ('\n'); /* end of line */
484 /* If completely blank item, get blank line despite no other output. */
486 out_char ('\n'); /* end of line */
489 draw_horizontal_separator ();
491 /* Now dispose of the buffered output. */
492 for (i
= 1; i
<= last_column
; i
++) {
493 select_output_environment (i
);
498 int after_headitem
= 0;
499 int headitem_row
= 0;
501 /* start a new item (row) of a multitable */
503 multitable_item (void)
505 if (!multitable_active
) {
506 line_error ("multitable_item internal error: no active multitable");
510 current_column_no
= 1;
515 /* <br> for non-tables browsers. */
516 add_word_args ("<br></%s></tr>", after_headitem
? "th" : "td");
518 if (seen_column_fractions
)
519 add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">",
520 headitem_flag
? "th" : "td",
521 envs
[current_column_no
].fill_column
);
523 add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
524 headitem_flag
? "th" : "td");
531 headitem_row
= headitem_flag
;
535 /* else if (docbook)*/ /* 05-08 */
538 xml_end_multitable_row (first_row
);
549 if (current_env_no
> 0) {
550 output_multitable_row ();
552 /* start at column 1 */
553 select_output_environment (1);
554 if (!output_paragraph
) {
555 line_error (_("[unexpected] cannot select column #%d in multitable"),
579 /* select a new column in current row of multitable */
583 if (!multitable_active
)
584 error (_("ignoring @tab outside of multitable"));
590 if (seen_column_fractions
)
591 add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">",
592 headitem_row
? "th" : "td",
593 headitem_row
? "th" : "td",
594 envs
[current_column_no
].fill_column
);
596 add_word_args ("</%s><%s valign=\"top\">",
597 headitem_row
? "th" : "td",
598 headitem_row
? "th" : "td");
600 /* else if (docbook)*/ /* 05-08 */
602 xml_end_multitable_column ();
604 nselect_next_environment ();
609 /* close a multitable, flushing its output and resetting
610 whatever needs resetting */
612 end_multitable (void)
614 if (!html
&& !docbook
)
615 output_multitable_row ();
617 /* Multitables cannot be nested. Otherwise, we'd have to save the
618 previous output environment number on a stack somewhere, and then
619 restore to that environment. */
620 select_output_environment (0);
621 multitable_active
= 0;
622 uninhibit_output_flushing ();
623 close_insertion_paragraph ();
626 add_word_args ("<br></%s></tr></table>\n", headitem_row
? "th" : "td");
627 /* else if (docbook)*/ /* 05-08 */
629 xml_end_multitable ();
632 printf (_("** Multicolumn output from last row:\n"));
633 for (i
= 1; i
<= last_column
; i
++) {
634 select_output_environment (i
);
635 printf (_("* column #%d: output = %s\n"), i
, output_paragraph
);