1 /* $NetBSD: multi.c,v 1.5 2025/12/31 22:18:50 oster 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 (at) 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 /* 37 * Output environments. This is a hack grafted onto existing 38 * structure. The "output environment" used to consist of the 39 * global variables `output_paragraph', `fill_column', etc. 40 * Routines like add_char would manipulate these variables. 41 * 42 * Now, when formatting a multitable, we maintain separate environments 43 * for each column. That way we can build up the columns separately 44 * and write them all out at once. The "current" output environment" 45 * is still kept in those global variables, so that the old output 46 * routines don't have to change. But we provide routines to save 47 * and restore these variables in an "environment table". The 48 * `select_output_environment' function switches from one output 49 * environment to another. 50 * 51 * Environment #0 (i.e., element #0 of the table) is the regular 52 * environment that is used when we're not formatting a multitable. 53 * 54 * Environment #N (where N = 1,2,3,...) is the env. for column #N of 55 * the table, when a multitable is active. 56 */ 57 58 /* contents of an output environment */ 59 /* some more vars may end up being needed here later @@ */ 60 struct env 61 { 62 unsigned char *output_paragraph; 63 int output_paragraph_offset; 64 int meta_char_pos; 65 int output_column; 66 int paragraph_is_open; 67 int current_indent; 68 int fill_column; 69 } envs[MAXCOLS]; /* the environment table */ 70 71 /* index in environment table of currently selected environment */ 72 static int current_env_no; 73 74 /* current column number */ 75 static int current_column_no; 76 77 /* We need to make a difference between template based widths and 78 @columnfractions for HTML tables' sake. Sigh. */ 79 static int seen_column_fractions; 80 81 /* column number of last column in current multitable */ 82 static int last_column; 83 84 /* flags indicating whether horizontal and vertical separators need 85 to be drawn, separating rows and columns in the current multitable. */ 86 static int hsep, vsep; 87 88 /* whether this is the first row. */ 89 static int first_row; 90 91 /* Called to handle a {...} template on the @multitable line. 93 We're at the { and our first job is to find the matching }; as a side 94 effect, we change *PARAMS to point to after it. Our other job is to 95 expand the template text and return the width of that string. */ 96 static unsigned 97 find_template_width (char **params) 98 { 99 char *template, *xtemplate; 100 unsigned len; 101 char *start = *params; 102 int brace_level = 0; 103 104 /* The first character should be a {. */ 105 if (!params || !*params || **params != '{') 106 { 107 line_error ("find_template width internal error: passed %s", 108 params ? *params : "null"); 109 return 0; 110 } 111 112 do 113 { 114 if (**params == '{' && (*params == start || (*params)[-1] != '@')) 115 brace_level++; 116 else if (**params == '}' && (*params)[-1] != '@') 117 brace_level--; 118 else if (**params == 0) 119 { 120 line_error (_("Missing } in @multitable template")); 121 return 0; 122 } 123 (*params)++; 124 } 125 while (brace_level > 0); 126 127 template = substring (start + 1, *params - 1); /* omit braces */ 128 xtemplate = expansion (template, 0); 129 len = strlen (xtemplate); 130 131 free (template); 132 free (xtemplate); 133 134 return len; 135 } 136 137 /* Direct current output to environment number N. Used when 138 switching work from one column of a multitable to the next. 139 Returns previous environment number. */ 140 static int 141 select_output_environment (int n) 142 { 143 struct env *e = &envs[current_env_no]; 144 int old_env_no = current_env_no; 145 146 /* stash current env info from global vars into the old environment */ 147 e->output_paragraph = output_paragraph; 148 e->output_paragraph_offset = output_paragraph_offset; 149 e->meta_char_pos = meta_char_pos; 150 e->output_column = output_column; 151 e->paragraph_is_open = paragraph_is_open; 152 e->current_indent = current_indent; 153 e->fill_column = fill_column; 154 155 /* now copy new environment into global vars */ 156 current_env_no = n; 157 e = &envs[current_env_no]; 158 output_paragraph = e->output_paragraph; 159 output_paragraph_offset = e->output_paragraph_offset; 160 meta_char_pos = e->meta_char_pos; 161 output_column = e->output_column; 162 paragraph_is_open = e->paragraph_is_open; 163 current_indent = e->current_indent; 164 fill_column = e->fill_column; 165 return old_env_no; 166 } 167 168 /* Initialize environment number ENV_NO, of width WIDTH. 169 The idea is that we're going to use one environment for each column of 170 a multitable, so we can build them up separately and print them 171 all out at the end. */ 172 static int 173 setup_output_environment (int env_no, int width) 174 { 175 int old_env = select_output_environment (env_no); 176 177 /* clobber old environment and set width of new one */ 178 init_paragraph (); 179 180 /* make our change */ 181 fill_column = width; 182 183 /* Save new environment and restore previous one. */ 184 select_output_environment (old_env); 185 186 return env_no; 187 } 188 189 /* Read the parameters for a multitable from the current command 190 line, save the parameters away, and return the 191 number of columns. */ 192 static int 193 setup_multitable_parameters (void) 194 { 195 char *params = insertion_stack->item_function; 196 int nchars; 197 float columnfrac; 198 char command[200]; /* xx no fixed limits */ 199 int i = 1; 200 201 /* We implement @hsep and @vsep even though TeX doesn't. 202 We don't get mixing of @columnfractions and templates right, 203 but TeX doesn't either. */ 204 hsep = vsep = 0; 205 206 /* Assume no @columnfractions per default. */ 207 seen_column_fractions = 0; 208 209 while (*params) { 210 while (whitespace (*params)) 211 params++; 212 213 if (*params == '@') { 214 sscanf (params, "%199s", command); 215 nchars = strlen (command); 216 params += nchars; 217 if (strcmp (command, "@hsep") == 0) 218 hsep++; 219 else if (strcmp (command, "@vsep") == 0) 220 vsep++; 221 else if (strcmp (command, "@columnfractions") == 0) { 222 seen_column_fractions = 1; 223 /* Clobber old environments and create new ones, starting at #1. 224 Environment #0 is the normal output, so don't mess with it. */ 225 for ( ; i <= MAXCOLS; i++) { 226 if (sscanf (params, "%f", &columnfrac) < 1) 227 goto done; 228 /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least) 229 doesn't support it. So skip whitespace (preceding the 230 number) and then non-whitespace (the number). */ 231 while (*params && (*params == ' ' || *params == '\t')) 232 params++; 233 /* Hmm, but what about @columnfractions 3foo. Oh well, 234 it's invalid input anyway. */ 235 while (*params && *params != ' ' && *params != '\t' 236 && *params != '\n' && *params != '@') 237 params++; 238 239 { 240 /* For html/xml/docbook, translate fractions into integer 241 percentages, adding .005 to avoid rounding problems. For 242 info, we want the character width. */ 243 int width = xml || html ? (columnfrac + .005) * 100 244 : (columnfrac * (fill_column - current_indent) + .5); 245 setup_output_environment (i, width); 246 } 247 } 248 } 249 250 } else if (*params == '{') { 251 unsigned template_width = find_template_width (¶ms); 252 253 /* This gives us two spaces between columns. Seems reasonable. 254 How to take into account current_indent here? */ 255 setup_output_environment (i++, template_width + 2); 256 257 } else { 258 warning (_("ignoring stray text `%s' after @multitable"), params); 259 break; 260 } 261 } 262 263 done: 264 flush_output (); 265 inhibit_output_flushing (); 266 267 last_column = i - 1; 268 return last_column; 269 } 270 271 /* Output a row. Calls insert, but also flushes the buffered output 272 when we see a newline, since in multitable every line is a separate 273 paragraph. */ 274 static void 275 out_char (int ch) 276 { 277 if (html || xml) 278 add_char (ch); 279 else 280 { 281 int env = select_output_environment (0); 282 insert (ch); 283 if (ch == '\n') 284 { 285 uninhibit_output_flushing (); 286 flush_output (); 287 inhibit_output_flushing (); 288 } 289 select_output_environment (env); 290 } 291 } 292 293 294 static void 295 draw_horizontal_separator (void) 296 { 297 int i, j, s; 298 299 if (html) 300 { 301 add_word ("<hr>"); 302 return; 303 } 304 if (xml) 305 return; 306 307 for (s = 0; s < envs[0].current_indent; s++) 308 out_char (' '); 309 if (vsep) 310 out_char ('+'); 311 for (i = 1; i <= last_column; i++) { 312 for (j = 0; j <= envs[i].fill_column; j++) 313 out_char ('-'); 314 if (vsep) 315 out_char ('+'); 316 } 317 out_char (' '); 318 out_char ('\n'); 319 } 320 321 322 /* multitable strategy: 324 for each item { 325 for each column in an item { 326 initialize a new paragraph 327 do ordinary formatting into the new paragraph 328 save the paragraph away 329 repeat if there are more paragraphs in the column 330 } 331 dump out the saved paragraphs and free the storage 332 } 333 334 For HTML we construct a simple HTML 3.2 table with <br>s inserted 335 to help non-tables browsers. `@item' inserts a <tr> and `@tab' 336 inserts <td>; we also try to close <tr>. The only real 337 alternative is to rely on the info formatting engine and present 338 preformatted text. */ 339 340 void 341 do_multitable (void) 342 { 343 int ncolumns; 344 345 if (multitable_active) 346 { 347 line_error ("Multitables cannot be nested"); 348 return; 349 } 350 351 close_single_paragraph (); 352 353 if (xml) 354 { 355 xml_no_para = 1; 356 if (output_paragraph[output_paragraph_offset-1] == '\n') 357 output_paragraph_offset--; 358 } 359 360 /* scan the current item function to get the field widths 361 and number of columns, and set up the output environment list 362 accordingly. */ 363 ncolumns = setup_multitable_parameters (); 364 first_row = 1; 365 366 /* <p> for non-tables browsers. @multitable implicitly ends the 367 current paragraph, so this is ok. */ 368 if (html) 369 add_html_block_elt ("<p><table summary=\"\">"); 370 /* else if (docbook)*/ /* 05-08 */ 371 else if (xml) 372 { 373 int *widths = xmalloc (ncolumns * sizeof (int)); 374 int i; 375 for (i=0; i<ncolumns; i++) 376 widths[i] = envs[i+1].fill_column; 377 xml_begin_multitable (ncolumns, widths); 378 free (widths); 379 } 380 381 if (hsep) 382 draw_horizontal_separator (); 383 384 /* The next @item command will direct stdout into the first column 385 and start processing. @tab will then switch to the next column, 386 and @item will flush out the saved output and return to the first 387 column. Environment #1 is the first column. (Environment #0 is 388 the normal output) */ 389 390 ++multitable_active; 391 } 392 393 /* advance to the next environment number */ 394 static void 395 nselect_next_environment (void) 396 { 397 if (current_env_no >= last_column) { 398 line_error (_("Too many columns in multitable item (max %d)"), last_column); 399 return; 400 } 401 select_output_environment (current_env_no + 1); 402 } 403 404 405 /* do anything needed at the beginning of processing a 407 multitable column. */ 408 static void 409 init_column (void) 410 { 411 /* don't indent 1st paragraph in the item */ 412 cm_noindent (0, 0, 0); 413 414 /* throw away possible whitespace after @item or @tab command */ 415 skip_whitespace (); 416 } 417 418 static void 419 output_multitable_row (void) 420 { 421 /* offset in the output paragraph of the next char needing 422 to be output for that column. */ 423 int offset[MAXCOLS]; 424 int i, j, s, remaining; 425 int had_newline = 0; 426 427 for (i = 0; i <= last_column; i++) 428 offset[i] = 0; 429 430 /* select the current environment, to make sure the env variables 431 get updated */ 432 select_output_environment (current_env_no); 433 434 #define CHAR_ADDR(n) (offset[i] + (n)) 435 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)]) 436 437 /* remove trailing whitespace from each column */ 438 for (i = 1; i <= last_column; i++) { 439 while (envs[i].output_paragraph_offset && 440 cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1))) 441 envs[i].output_paragraph_offset--; 442 443 if (i == current_env_no) 444 output_paragraph_offset = envs[i].output_paragraph_offset; 445 } 446 447 /* read the current line from each column, outputting them all 448 pasted together. Do this til all lines are output from all 449 columns. */ 450 for (;;) { 451 remaining = 0; 452 /* first, see if there is any work to do */ 453 for (i = 1; i <= last_column; i++) { 454 if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) { 455 remaining = 1; 456 break; 457 } 458 } 459 if (!remaining) 460 break; 461 462 for (s = 0; s < envs[0].current_indent; s++) 463 out_char (' '); 464 465 if (vsep) 466 out_char ('|'); 467 468 for (i = 1; i <= last_column; i++) { 469 for (s = 0; s < envs[i].current_indent; s++) 470 out_char (' '); 471 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) { 472 if (CHAR_AT (j) == '\n') 473 break; 474 out_char (CHAR_AT (j)); 475 } 476 offset[i] += j + 1; /* skip last text plus skip the newline */ 477 478 /* Do not output trailing blanks if we're in the last column and 479 there will be no trailing |. */ 480 if (i < last_column && !vsep) 481 for (; j <= envs[i].fill_column; j++) 482 out_char (' '); 483 if (vsep) 484 out_char ('|'); /* draw column separator */ 485 } 486 out_char ('\n'); /* end of line */ 487 had_newline = 1; 488 } 489 490 /* If completely blank item, get blank line despite no other output. */ 491 if (!had_newline) 492 out_char ('\n'); /* end of line */ 493 494 if (hsep) 495 draw_horizontal_separator (); 496 497 /* Now dispose of the buffered output. */ 498 for (i = 1; i <= last_column; i++) { 499 select_output_environment (i); 500 init_paragraph (); 501 } 502 } 503 504 int after_headitem = 0; 505 int headitem_row = 0; 506 507 /* start a new item (row) of a multitable */ 508 int 509 multitable_item (void) 510 { 511 if (!multitable_active) { 512 line_error ("multitable_item internal error: no active multitable"); 513 xexit (1); 514 } 515 516 current_column_no = 1; 517 518 if (html) 519 { 520 if (!first_row) 521 /* <br> for non-tables browsers. */ 522 add_word_args ("<br></%s></tr>", after_headitem ? "th" : "td"); 523 524 if (seen_column_fractions) 525 add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">", 526 headitem_flag ? "th" : "td", 527 envs[current_column_no].fill_column); 528 else 529 add_word_args ("<tr align=\"left\"><%s valign=\"top\">", 530 headitem_flag ? "th" : "td"); 531 532 if (headitem_flag) 533 after_headitem = 1; 534 else 535 after_headitem = 0; 536 first_row = 0; 537 headitem_row = headitem_flag; 538 headitem_flag = 0; 539 return 0; 540 } 541 /* else if (docbook)*/ /* 05-08 */ 542 else if (xml) 543 { 544 xml_end_multitable_row (first_row); 545 if (headitem_flag) 546 after_headitem = 1; 547 else 548 after_headitem = 0; 549 first_row = 0; 550 headitem_flag = 0; 551 return 0; 552 } 553 first_row = 0; 554 555 if (current_env_no > 0) { 556 output_multitable_row (); 557 } 558 /* start at column 1 */ 559 select_output_environment (1); 560 if (!output_paragraph) { 561 line_error (_("[unexpected] cannot select column #%d in multitable"), 562 current_env_no); 563 xexit (1); 564 } 565 566 init_column (); 567 568 if (headitem_flag) 569 hsep = 1; 570 else 571 hsep = 0; 572 573 if (headitem_flag) 574 after_headitem = 1; 575 else 576 after_headitem = 0; 577 headitem_flag = 0; 578 579 return 0; 580 } 581 582 #undef CHAR_AT 583 #undef CHAR_ADDR 584 585 /* select a new column in current row of multitable */ 586 void 587 cm_tab (int arg, int arg2, int arg3) 588 { 589 if (!multitable_active) 590 error (_("ignoring @tab outside of multitable")); 591 592 current_column_no++; 593 594 if (html) 595 { 596 if (seen_column_fractions) 597 add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">", 598 headitem_row ? "th" : "td", 599 headitem_row ? "th" : "td", 600 envs[current_column_no].fill_column); 601 else 602 add_word_args ("</%s><%s valign=\"top\">", 603 headitem_row ? "th" : "td", 604 headitem_row ? "th" : "td"); 605 } 606 /* else if (docbook)*/ /* 05-08 */ 607 else if (xml) 608 xml_end_multitable_column (); 609 else 610 nselect_next_environment (); 611 612 init_column (); 613 } 614 615 /* close a multitable, flushing its output and resetting 616 whatever needs resetting */ 617 void 618 end_multitable (void) 619 { 620 if (!html && !docbook) 621 output_multitable_row (); 622 623 /* Multitables cannot be nested. Otherwise, we'd have to save the 624 previous output environment number on a stack somewhere, and then 625 restore to that environment. */ 626 select_output_environment (0); 627 multitable_active = 0; 628 uninhibit_output_flushing (); 629 close_insertion_paragraph (); 630 631 if (html) 632 add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td"); 633 /* else if (docbook)*/ /* 05-08 */ 634 else if (xml) 635 xml_end_multitable (); 636 637 #if 0 638 printf (_("** Multicolumn output from last row:\n")); 639 for (i = 1; i <= last_column; i++) { 640 select_output_environment (i); 641 printf (_("* column #%d: output = %s\n"), i, output_paragraph); 642 } 643 #endif 644 } 645