turns printfs back on
[freebsd-src/fkvm-freebsd.git] / contrib / texinfo / makeinfo / multi.c
blob06c548fb5e9fa22bbe9321b2a724752fb02b68fc
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
5 Foundation, Inc.
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)
10 any later version.
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). */
23 #include "system.h"
24 #include "cmds.h"
25 #include "insertion.h"
26 #include "makeinfo.h"
27 #include "multi.h"
28 #include "xml.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 @@ */
57 struct env
59 unsigned char *output_paragraph;
60 int output_paragraph_offset;
61 int meta_char_pos;
62 int output_column;
63 int paragraph_is_open;
64 int current_indent;
65 int fill_column;
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. */
86 static int 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. */
92 static unsigned
93 find_template_width (char **params)
95 char *template, *xtemplate;
96 unsigned len;
97 char *start = *params;
98 int brace_level = 0;
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");
105 return 0;
110 if (**params == '{' && (*params == start || (*params)[-1] != '@'))
111 brace_level++;
112 else if (**params == '}' && (*params)[-1] != '@')
113 brace_level--;
114 else if (**params == 0)
116 line_error (_("Missing } in @multitable template"));
117 return 0;
119 (*params)++;
121 while (brace_level > 0);
123 template = substring (start + 1, *params - 1); /* omit braces */
124 xtemplate = expansion (template, 0);
125 len = strlen (xtemplate);
127 free (template);
128 free (xtemplate);
130 return len;
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. */
136 static int
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 */
152 current_env_no = n;
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;
161 return old_env_no;
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. */
168 static int
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 */
174 init_paragraph ();
176 /* make our change */
177 fill_column = width;
179 /* Save new environment and restore previous one. */
180 select_output_environment (old_env);
182 return env_no;
185 /* Read the parameters for a multitable from the current command
186 line, save the parameters away, and return the
187 number of columns. */
188 static int
189 setup_multitable_parameters (void)
191 char *params = insertion_stack->item_function;
192 int nchars;
193 float columnfrac;
194 char command[200]; /* xx no fixed limits */
195 int i = 1;
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. */
200 hsep = vsep = 0;
202 /* Assume no @columnfractions per default. */
203 seen_column_fractions = 0;
205 while (*params) {
206 while (whitespace (*params))
207 params++;
209 if (*params == '@') {
210 sscanf (params, "%200s", command);
211 nchars = strlen (command);
212 params += nchars;
213 if (strcmp (command, "@hsep") == 0)
214 hsep++;
215 else if (strcmp (command, "@vsep") == 0)
216 vsep++;
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)
223 goto done;
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'))
228 params++;
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 != '@')
233 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 (&params);
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);
253 } else {
254 warning (_("ignoring stray text `%s' after @multitable"), params);
255 break;
259 done:
260 flush_output ();
261 inhibit_output_flushing ();
263 last_column = i - 1;
264 return last_column;
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
269 paragraph. */
270 static void
271 out_char (int ch)
273 if (html || xml)
274 add_char (ch);
275 else
277 int env = select_output_environment (0);
278 insert (ch);
279 if (ch == '\n')
281 uninhibit_output_flushing ();
282 flush_output ();
283 inhibit_output_flushing ();
285 select_output_environment (env);
290 static void
291 draw_horizontal_separator (void)
293 int i, j, s;
295 if (html)
297 add_word ("<hr>");
298 return;
300 if (xml)
301 return;
303 for (s = 0; s < envs[0].current_indent; s++)
304 out_char (' ');
305 if (vsep)
306 out_char ('+');
307 for (i = 1; i <= last_column; i++) {
308 for (j = 0; j <= envs[i].fill_column; j++)
309 out_char ('-');
310 if (vsep)
311 out_char ('+');
313 out_char (' ');
314 out_char ('\n');
318 /* multitable strategy:
319 for each item {
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. */
335 void
336 do_multitable (void)
338 int ncolumns;
340 if (multitable_active)
342 line_error ("Multitables cannot be nested");
343 return;
346 close_single_paragraph ();
348 if (xml)
350 xml_no_para = 1;
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
357 accordingly. */
358 ncolumns = setup_multitable_parameters ();
359 first_row = 1;
361 /* <p> for non-tables browsers. @multitable implicitly ends the
362 current paragraph, so this is ok. */
363 if (html)
364 add_html_block_elt ("<p><table summary=\"\">");
365 /* else if (docbook)*/ /* 05-08 */
366 else if (xml)
368 int *widths = xmalloc (ncolumns * sizeof (int));
369 int i;
370 for (i=0; i<ncolumns; i++)
371 widths[i] = envs[i+1].fill_column;
372 xml_begin_multitable (ncolumns, widths);
373 free (widths);
376 if (hsep)
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) */
385 ++multitable_active;
388 /* advance to the next environment number */
389 static void
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);
394 return;
396 select_output_environment (current_env_no + 1);
400 /* do anything needed at the beginning of processing a
401 multitable column. */
402 static void
403 init_column (void)
405 /* don't indent 1st paragraph in the item */
406 cm_noindent ();
408 /* throw away possible whitespace after @item or @tab command */
409 skip_whitespace ();
412 static void
413 output_multitable_row (void)
415 /* offset in the output paragraph of the next char needing
416 to be output for that column. */
417 int offset[MAXCOLS];
418 int i, j, s, remaining;
419 int had_newline = 0;
421 for (i = 0; i <= last_column; i++)
422 offset[i] = 0;
424 /* select the current environment, to make sure the env variables
425 get updated */
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
443 columns. */
444 for (;;) {
445 remaining = 0;
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) {
449 remaining = 1;
450 break;
453 if (!remaining)
454 break;
456 for (s = 0; s < envs[0].current_indent; s++)
457 out_char (' ');
459 if (vsep)
460 out_char ('|');
462 for (i = 1; i <= last_column; i++) {
463 for (s = 0; s < envs[i].current_indent; s++)
464 out_char (' ');
465 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
466 if (CHAR_AT (j) == '\n')
467 break;
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++)
476 out_char (' ');
477 if (vsep)
478 out_char ('|'); /* draw column separator */
480 out_char ('\n'); /* end of line */
481 had_newline = 1;
484 /* If completely blank item, get blank line despite no other output. */
485 if (!had_newline)
486 out_char ('\n'); /* end of line */
488 if (hsep)
489 draw_horizontal_separator ();
491 /* Now dispose of the buffered output. */
492 for (i = 1; i <= last_column; i++) {
493 select_output_environment (i);
494 init_paragraph ();
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");
507 xexit (1);
510 current_column_no = 1;
512 if (html)
514 if (!first_row)
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);
522 else
523 add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
524 headitem_flag ? "th" : "td");
526 if (headitem_flag)
527 after_headitem = 1;
528 else
529 after_headitem = 0;
530 first_row = 0;
531 headitem_row = headitem_flag;
532 headitem_flag = 0;
533 return 0;
535 /* else if (docbook)*/ /* 05-08 */
536 else if (xml)
538 xml_end_multitable_row (first_row);
539 if (headitem_flag)
540 after_headitem = 1;
541 else
542 after_headitem = 0;
543 first_row = 0;
544 headitem_flag = 0;
545 return 0;
547 first_row = 0;
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"),
556 current_env_no);
557 xexit (1);
560 init_column ();
562 if (headitem_flag)
563 hsep = 1;
564 else
565 hsep = 0;
567 if (headitem_flag)
568 after_headitem = 1;
569 else
570 after_headitem = 0;
571 headitem_flag = 0;
573 return 0;
576 #undef CHAR_AT
577 #undef CHAR_ADDR
579 /* select a new column in current row of multitable */
580 void
581 cm_tab (void)
583 if (!multitable_active)
584 error (_("ignoring @tab outside of multitable"));
586 current_column_no++;
588 if (html)
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);
595 else
596 add_word_args ("</%s><%s valign=\"top\">",
597 headitem_row ? "th" : "td",
598 headitem_row ? "th" : "td");
600 /* else if (docbook)*/ /* 05-08 */
601 else if (xml)
602 xml_end_multitable_column ();
603 else
604 nselect_next_environment ();
606 init_column ();
609 /* close a multitable, flushing its output and resetting
610 whatever needs resetting */
611 void
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 ();
625 if (html)
626 add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td");
627 /* else if (docbook)*/ /* 05-08 */
628 else if (xml)
629 xml_end_multitable ();
631 #if 0
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);
637 #endif