No empty .Rs/.Re
[netbsd-mini2440.git] / gnu / dist / texinfo / makeinfo / multi.c
blob57dec4db4fdb3fb7895c1f3c6ffd3316efd7d28c
1 /* $NetBSD$ */
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
7 Foundation, Inc.
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)
12 any later version.
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). */
25 #include "system.h"
26 #include "cmds.h"
27 #include "insertion.h"
28 #include "makeinfo.h"
29 #include "multi.h"
30 #include "xml.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 @@ */
59 struct env
61 unsigned char *output_paragraph;
62 int output_paragraph_offset;
63 int meta_char_pos;
64 int output_column;
65 int paragraph_is_open;
66 int current_indent;
67 int fill_column;
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. */
88 static int 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. */
94 static unsigned
95 find_template_width (char **params)
97 char *template, *xtemplate;
98 unsigned len;
99 char *start = *params;
100 int brace_level = 0;
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");
107 return 0;
112 if (**params == '{' && (*params == start || (*params)[-1] != '@'))
113 brace_level++;
114 else if (**params == '}' && (*params)[-1] != '@')
115 brace_level--;
116 else if (**params == 0)
118 line_error (_("Missing } in @multitable template"));
119 return 0;
121 (*params)++;
123 while (brace_level > 0);
125 template = substring (start + 1, *params - 1); /* omit braces */
126 xtemplate = expansion (template, 0);
127 len = strlen (xtemplate);
129 free (template);
130 free (xtemplate);
132 return len;
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. */
138 static int
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 */
154 current_env_no = n;
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;
163 return old_env_no;
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. */
170 static int
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 */
176 init_paragraph ();
178 /* make our change */
179 fill_column = width;
181 /* Save new environment and restore previous one. */
182 select_output_environment (old_env);
184 return env_no;
187 /* Read the parameters for a multitable from the current command
188 line, save the parameters away, and return the
189 number of columns. */
190 static int
191 setup_multitable_parameters (void)
193 char *params = insertion_stack->item_function;
194 int nchars;
195 float columnfrac;
196 char command[200]; /* xx no fixed limits */
197 int i = 1;
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. */
202 hsep = vsep = 0;
204 /* Assume no @columnfractions per default. */
205 seen_column_fractions = 0;
207 while (*params) {
208 while (whitespace (*params))
209 params++;
211 if (*params == '@') {
212 sscanf (params, "%200s", command);
213 nchars = strlen (command);
214 params += nchars;
215 if (strcmp (command, "@hsep") == 0)
216 hsep++;
217 else if (strcmp (command, "@vsep") == 0)
218 vsep++;
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)
225 goto done;
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'))
230 params++;
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 != '@')
235 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 (&params);
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);
255 } else {
256 warning (_("ignoring stray text `%s' after @multitable"), params);
257 break;
261 done:
262 flush_output ();
263 inhibit_output_flushing ();
265 last_column = i - 1;
266 return last_column;
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
271 paragraph. */
272 static void
273 out_char (int ch)
275 if (html || xml)
276 add_char (ch);
277 else
279 int env = select_output_environment (0);
280 insert (ch);
281 if (ch == '\n')
283 uninhibit_output_flushing ();
284 flush_output ();
285 inhibit_output_flushing ();
287 select_output_environment (env);
292 static void
293 draw_horizontal_separator (void)
295 int i, j, s;
297 if (html)
299 add_word ("<hr>");
300 return;
302 if (xml)
303 return;
305 for (s = 0; s < envs[0].current_indent; s++)
306 out_char (' ');
307 if (vsep)
308 out_char ('+');
309 for (i = 1; i <= last_column; i++) {
310 for (j = 0; j <= envs[i].fill_column; j++)
311 out_char ('-');
312 if (vsep)
313 out_char ('+');
315 out_char (' ');
316 out_char ('\n');
320 /* multitable strategy:
321 for each item {
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. */
337 void
338 do_multitable (void)
340 int ncolumns;
342 if (multitable_active)
344 line_error ("Multitables cannot be nested");
345 return;
348 close_single_paragraph ();
350 if (xml)
352 xml_no_para = 1;
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
359 accordingly. */
360 ncolumns = setup_multitable_parameters ();
361 first_row = 1;
363 /* <p> for non-tables browsers. @multitable implicitly ends the
364 current paragraph, so this is ok. */
365 if (html)
366 add_html_block_elt ("<p><table summary=\"\">");
367 /* else if (docbook)*/ /* 05-08 */
368 else if (xml)
370 int *widths = xmalloc (ncolumns * sizeof (int));
371 int i;
372 for (i=0; i<ncolumns; i++)
373 widths[i] = envs[i+1].fill_column;
374 xml_begin_multitable (ncolumns, widths);
375 free (widths);
378 if (hsep)
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) */
387 ++multitable_active;
390 /* advance to the next environment number */
391 static void
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);
396 return;
398 select_output_environment (current_env_no + 1);
402 /* do anything needed at the beginning of processing a
403 multitable column. */
404 static void
405 init_column (void)
407 /* don't indent 1st paragraph in the item */
408 cm_noindent ();
410 /* throw away possible whitespace after @item or @tab command */
411 skip_whitespace ();
414 static void
415 output_multitable_row (void)
417 /* offset in the output paragraph of the next char needing
418 to be output for that column. */
419 int offset[MAXCOLS];
420 int i, j, s, remaining;
421 int had_newline = 0;
423 for (i = 0; i <= last_column; i++)
424 offset[i] = 0;
426 /* select the current environment, to make sure the env variables
427 get updated */
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
445 columns. */
446 for (;;) {
447 remaining = 0;
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) {
451 remaining = 1;
452 break;
455 if (!remaining)
456 break;
458 for (s = 0; s < envs[0].current_indent; s++)
459 out_char (' ');
461 if (vsep)
462 out_char ('|');
464 for (i = 1; i <= last_column; i++) {
465 for (s = 0; s < envs[i].current_indent; s++)
466 out_char (' ');
467 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
468 if (CHAR_AT (j) == '\n')
469 break;
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++)
478 out_char (' ');
479 if (vsep)
480 out_char ('|'); /* draw column separator */
482 out_char ('\n'); /* end of line */
483 had_newline = 1;
486 /* If completely blank item, get blank line despite no other output. */
487 if (!had_newline)
488 out_char ('\n'); /* end of line */
490 if (hsep)
491 draw_horizontal_separator ();
493 /* Now dispose of the buffered output. */
494 for (i = 1; i <= last_column; i++) {
495 select_output_environment (i);
496 init_paragraph ();
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");
509 xexit (1);
512 current_column_no = 1;
514 if (html)
516 if (!first_row)
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);
524 else
525 add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
526 headitem_flag ? "th" : "td");
528 if (headitem_flag)
529 after_headitem = 1;
530 else
531 after_headitem = 0;
532 first_row = 0;
533 headitem_row = headitem_flag;
534 headitem_flag = 0;
535 return 0;
537 /* else if (docbook)*/ /* 05-08 */
538 else if (xml)
540 xml_end_multitable_row (first_row);
541 if (headitem_flag)
542 after_headitem = 1;
543 else
544 after_headitem = 0;
545 first_row = 0;
546 headitem_flag = 0;
547 return 0;
549 first_row = 0;
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"),
558 current_env_no);
559 xexit (1);
562 init_column ();
564 if (headitem_flag)
565 hsep = 1;
566 else
567 hsep = 0;
569 if (headitem_flag)
570 after_headitem = 1;
571 else
572 after_headitem = 0;
573 headitem_flag = 0;
575 return 0;
578 #undef CHAR_AT
579 #undef CHAR_ADDR
581 /* select a new column in current row of multitable */
582 void
583 cm_tab (void)
585 if (!multitable_active)
586 error (_("ignoring @tab outside of multitable"));
588 current_column_no++;
590 if (html)
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);
597 else
598 add_word_args ("</%s><%s valign=\"top\">",
599 headitem_row ? "th" : "td",
600 headitem_row ? "th" : "td");
602 /* else if (docbook)*/ /* 05-08 */
603 else if (xml)
604 xml_end_multitable_column ();
605 else
606 nselect_next_environment ();
608 init_column ();
611 /* close a multitable, flushing its output and resetting
612 whatever needs resetting */
613 void
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 ();
627 if (html)
628 add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td");
629 /* else if (docbook)*/ /* 05-08 */
630 else if (xml)
631 xml_end_multitable ();
633 #if 0
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);
639 #endif