1 /* $NetBSD: multi.c,v 1.2 2021/06/07 20:27:59 cjep Exp $ */
2
3 /* multi.c -- multiple-column tables (@multitable) for makeinfo.
4 Id: multi.c,v 1.8 2004/04/11 17:56:47 karl Exp
5
6 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004 Free Software
7 Foundation, Inc.
8
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.
13
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.
18
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.
22
23 Originally written by phr@gnu.org (Paul Rubin). */
24
25 #include "system.h"
26 #include "cmds.h"
27 #include "insertion.h"
28 #include "makeinfo.h"
29 #include "multi.h"
30 #include "xml.h"
31
32 #define MAXCOLS 100 /* remove this limit later @@ */
33
34
35 /*
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.
40 *
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.
49 *
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.
52 *
53 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
54 * the table, when a multitable is active.
55 */
56
57 /* contents of an output environment */
58 /* some more vars may end up being needed here later @@ */
59 struct env
60 {
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 */
69
70 /* index in environment table of currently selected environment */
71 static int current_env_no;
72
73 /* current column number */
74 static int current_column_no;
75
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;
79
80 /* column number of last column in current multitable */
81 static int last_column;
82
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;
86
87 /* whether this is the first row. */
88 static int first_row;
89
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
find_template_width(char ** params)95 find_template_width (char **params)
96 {
97 char *template, *xtemplate;
98 unsigned len;
99 char *start = *params;
100 int brace_level = 0;
101
102 /* The first character should be a {. */
103 if (!params || !*params || **params != '{')
104 {
105 line_error ("find_template width internal error: passed %s",
106 params ? *params : "null");
107 return 0;
108 }
109
110 do
111 {
112 if (**params == '{' && (*params == start || (*params)[-1] != '@'))
113 brace_level++;
114 else if (**params == '}' && (*params)[-1] != '@')
115 brace_level--;
116 else if (**params == 0)
117 {
118 line_error (_("Missing } in @multitable template"));
119 return 0;
120 }
121 (*params)++;
122 }
123 while (brace_level > 0);
124
125 template = substring (start + 1, *params - 1); /* omit braces */
126 xtemplate = expansion (template, 0);
127 len = strlen (xtemplate);
128
129 free (template);
130 free (xtemplate);
131
132 return len;
133 }
134
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
select_output_environment(int n)139 select_output_environment (int n)
140 {
141 struct env *e = &envs[current_env_no];
142 int old_env_no = current_env_no;
143
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;
152
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;
164 }
165
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
setup_output_environment(int env_no,int width)171 setup_output_environment (int env_no, int width)
172 {
173 int old_env = select_output_environment (env_no);
174
175 /* clobber old environment and set width of new one */
176 init_paragraph ();
177
178 /* make our change */
179 fill_column = width;
180
181 /* Save new environment and restore previous one. */
182 select_output_environment (old_env);
183
184 return env_no;
185 }
186
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
setup_multitable_parameters(void)191 setup_multitable_parameters (void)
192 {
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;
198
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;
203
204 /* Assume no @columnfractions per default. */
205 seen_column_fractions = 0;
206
207 while (*params) {
208 while (whitespace (*params))
209 params++;
210
211 if (*params == '@') {
212 sscanf (params, "%199s", 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++;
236
237 {
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);
244 }
245 }
246 }
247
248 } else if (*params == '{') {
249 unsigned template_width = find_template_width (¶ms);
250
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);
254
255 } else {
256 warning (_("ignoring stray text `%s' after @multitable"), params);
257 break;
258 }
259 }
260
261 done:
262 flush_output ();
263 inhibit_output_flushing ();
264
265 last_column = i - 1;
266 return last_column;
267 }
268
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
out_char(int ch)273 out_char (int ch)
274 {
275 if (html || xml)
276 add_char (ch);
277 else
278 {
279 int env = select_output_environment (0);
280 insert (ch);
281 if (ch == '\n')
282 {
283 uninhibit_output_flushing ();
284 flush_output ();
285 inhibit_output_flushing ();
286 }
287 select_output_environment (env);
288 }
289 }
290
291
292 static void
draw_horizontal_separator(void)293 draw_horizontal_separator (void)
294 {
295 int i, j, s;
296
297 if (html)
298 {
299 add_word ("<hr>");
300 return;
301 }
302 if (xml)
303 return;
304
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 ('+');
314 }
315 out_char (' ');
316 out_char ('\n');
317 }
318
319
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
327 }
328 dump out the saved paragraphs and free the storage
329 }
330
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. */
336
337 void
do_multitable(void)338 do_multitable (void)
339 {
340 int ncolumns;
341
342 if (multitable_active)
343 {
344 line_error ("Multitables cannot be nested");
345 return;
346 }
347
348 close_single_paragraph ();
349
350 if (xml)
351 {
352 xml_no_para = 1;
353 if (output_paragraph[output_paragraph_offset-1] == '\n')
354 output_paragraph_offset--;
355 }
356
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;
362
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)
369 {
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);
376 }
377
378 if (hsep)
379 draw_horizontal_separator ();
380
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) */
386
387 ++multitable_active;
388 }
389
390 /* advance to the next environment number */
391 static void
nselect_next_environment(void)392 nselect_next_environment (void)
393 {
394 if (current_env_no >= last_column) {
395 line_error (_("Too many columns in multitable item (max %d)"), last_column);
396 return;
397 }
398 select_output_environment (current_env_no + 1);
399 }
400
401
402 /* do anything needed at the beginning of processing a
403 multitable column. */
404 static void
init_column(void)405 init_column (void)
406 {
407 /* don't indent 1st paragraph in the item */
408 cm_noindent ();
409
410 /* throw away possible whitespace after @item or @tab command */
411 skip_whitespace ();
412 }
413
414 static void
output_multitable_row(void)415 output_multitable_row (void)
416 {
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;
422
423 for (i = 0; i <= last_column; i++)
424 offset[i] = 0;
425
426 /* select the current environment, to make sure the env variables
427 get updated */
428 select_output_environment (current_env_no);
429
430 #define CHAR_ADDR(n) (offset[i] + (n))
431 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])
432
433 /* remove trailing whitespace from each column */
434 for (i = 1; i <= last_column; i++) {
435 while (envs[i].output_paragraph_offset &&
436 cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1)))
437 envs[i].output_paragraph_offset--;
438
439 if (i == current_env_no)
440 output_paragraph_offset = envs[i].output_paragraph_offset;
441 }
442
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;
453 }
454 }
455 if (!remaining)
456 break;
457
458 for (s = 0; s < envs[0].current_indent; s++)
459 out_char (' ');
460
461 if (vsep)
462 out_char ('|');
463
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));
471 }
472 offset[i] += j + 1; /* skip last text plus skip the newline */
473
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 */
481 }
482 out_char ('\n'); /* end of line */
483 had_newline = 1;
484 }
485
486 /* If completely blank item, get blank line despite no other output. */
487 if (!had_newline)
488 out_char ('\n'); /* end of line */
489
490 if (hsep)
491 draw_horizontal_separator ();
492
493 /* Now dispose of the buffered output. */
494 for (i = 1; i <= last_column; i++) {
495 select_output_environment (i);
496 init_paragraph ();
497 }
498 }
499
500 int after_headitem = 0;
501 int headitem_row = 0;
502
503 /* start a new item (row) of a multitable */
504 int
multitable_item(void)505 multitable_item (void)
506 {
507 if (!multitable_active) {
508 line_error ("multitable_item internal error: no active multitable");
509 xexit (1);
510 }
511
512 current_column_no = 1;
513
514 if (html)
515 {
516 if (!first_row)
517 /* <br> for non-tables browsers. */
518 add_word_args ("<br></%s></tr>", after_headitem ? "th" : "td");
519
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");
527
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;
536 }
537 /* else if (docbook)*/ /* 05-08 */
538 else if (xml)
539 {
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;
548 }
549 first_row = 0;
550
551 if (current_env_no > 0) {
552 output_multitable_row ();
553 }
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);
560 }
561
562 init_column ();
563
564 if (headitem_flag)
565 hsep = 1;
566 else
567 hsep = 0;
568
569 if (headitem_flag)
570 after_headitem = 1;
571 else
572 after_headitem = 0;
573 headitem_flag = 0;
574
575 return 0;
576 }
577
578 #undef CHAR_AT
579 #undef CHAR_ADDR
580
581 /* select a new column in current row of multitable */
582 void
cm_tab(void)583 cm_tab (void)
584 {
585 if (!multitable_active)
586 error (_("ignoring @tab outside of multitable"));
587
588 current_column_no++;
589
590 if (html)
591 {
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");
601 }
602 /* else if (docbook)*/ /* 05-08 */
603 else if (xml)
604 xml_end_multitable_column ();
605 else
606 nselect_next_environment ();
607
608 init_column ();
609 }
610
611 /* close a multitable, flushing its output and resetting
612 whatever needs resetting */
613 void
end_multitable(void)614 end_multitable (void)
615 {
616 if (!html && !docbook)
617 output_multitable_row ();
618
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 ();
626
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 ();
632
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);
638 }
639 #endif
640 }
641