1 /* multi.c -- multitable stuff for makeinfo. 2 $Id: multi.c,v 1.3 2000/02/09 02:18:42 espie Exp $ 3 4 Copyright (C) 1996, 97, 98, 99 Free Software Foundation, Inc. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software Foundation, 18 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 19 20 #include "system.h" 21 #include "insertion.h" 22 #include "makeinfo.h" 23 24 #define MAXCOLS 100 /* remove this limit later @@ */ 25 26 27 /* 28 * Output environments. This is a hack grafted onto existing 29 * structure. The "output environment" used to consist of the 30 * global variables `output_paragraph', `fill_column', etc. 31 * Routines like add_char would manipulate these variables. 32 * 33 * Now, when formatting a multitable, we maintain separate environments 34 * for each column. That way we can build up the columns separately 35 * and write them all out at once. The "current" output environment" 36 * is still kept in those global variables, so that the old output 37 * routines don't have to change. But we provide routines to save 38 * and restore these variables in an "environment table". The 39 * `select_output_environment' function switches from one output 40 * environment to another. 41 * 42 * Environment #0 (i.e., element #0 of the table) is the regular 43 * environment that is used when we're not formatting a multitable. 44 * 45 * Environment #N (where N = 1,2,3,...) is the env. for column #N of 46 * the table, when a multitable is active. 47 */ 48 49 /* contents of an output environment */ 50 /* some more vars may end up being needed here later @@ */ 51 struct env 52 { 53 unsigned char *output_paragraph; 54 int output_paragraph_offset; 55 int meta_char_pos; 56 int output_column; 57 int paragraph_is_open; 58 int current_indent; 59 int fill_column; 60 } envs[MAXCOLS]; /* the environment table */ 61 62 /* index in environment table of currently selected environment */ 63 static int current_env_no; 64 65 /* column number of last column in current multitable */ 66 static int last_column; 67 68 /* flags indicating whether horizontal and vertical separators need 69 to be drawn, separating rows and columns in the current multitable. */ 70 static int hsep, vsep; 71 72 /* whether this is the first row. */ 73 static int first_row; 74 75 static void output_multitable_row (); 76 77 /* Output a row. Calls insert, but also flushes the buffered output 78 when we see a newline, since in multitable every line is a separate 79 paragraph. */ 80 static void 81 out_char (ch) 82 int ch; 83 { 84 if (html) 85 add_char (ch); 86 else 87 { 88 int env = select_output_environment (0); 89 insert (ch); 90 if (ch == '\n') 91 { 92 uninhibit_output_flushing (); 93 flush_output (); 94 inhibit_output_flushing (); 95 } 96 select_output_environment (env); 97 } 98 } 99 100 101 void 102 draw_horizontal_separator () 103 { 104 int i, j, s; 105 106 if (html) 107 { 108 add_word ("<hr>"); 109 return; 110 } 111 112 for (s = 0; s < envs[0].current_indent; s++) 113 out_char (' '); 114 if (vsep) 115 out_char ('+'); 116 for (i = 1; i <= last_column; i++) { 117 for (j = 0; j <= envs[i].fill_column; j++) 118 out_char ('-'); 119 if (vsep) 120 out_char ('+'); 121 } 122 out_char ('\n'); 123 } 124 125 126 /* multitable strategy: 127 for each item { 128 for each column in an item { 129 initialize a new paragraph 130 do ordinary formatting into the new paragraph 131 save the paragraph away 132 repeat if there are more paragraphs in the column 133 } 134 dump out the saved paragraphs and free the storage 135 } 136 137 For HTML we construct a simple HTML 3.2 table with <br>s inserted 138 to help non-tables browsers. `@item' inserts a <tr> and `@tab' 139 inserts <td>; we also try to close <tr>. The only real 140 alternative is to rely on the info formatting engine and present 141 preformatted text. */ 142 143 void 144 do_multitable () 145 { 146 int ncolumns; 147 148 if (multitable_active) 149 { 150 line_error ("Multitables cannot be nested"); 151 return; 152 } 153 154 close_single_paragraph (); 155 156 /* scan the current item function to get the field widths 157 and number of columns, and set up the output environment list 158 accordingly. */ 159 ncolumns = setup_multitable_parameters (); 160 first_row = 1; 161 162 /* <p> for non-tables browsers. @multitable implicitly ends the 163 current paragraph, so this is ok. */ 164 if (html) 165 add_word ("<p><table>"); 166 167 if (hsep) 168 draw_horizontal_separator (); 169 170 /* The next @item command will direct stdout into the first column 171 and start processing. @tab will then switch to the next column, 172 and @item will flush out the saved output and return to the first 173 column. Environment #1 is the first column. (Environment #0 is 174 the normal output) */ 175 176 ++multitable_active; 177 } 178 179 /* Called to handle a {...} template on the @multitable line. 180 We're at the { and our first job is to find the matching }; as a side 181 effect, we change *PARAMS to point to after it. Our other job is to 182 expand the template text and return the width of that string. */ 183 static unsigned 184 find_template_width (params) 185 char **params; 186 { 187 char *template, *xtemplate; 188 unsigned len; 189 char *start = *params; 190 int brace_level = 0; 191 192 /* The first character should be a {. */ 193 if (!params || !*params || **params != '{') 194 { 195 line_error ("find_template width internal error: passed %s", 196 params ? *params : "null"); 197 return 0; 198 } 199 200 do 201 { 202 if (**params == '{' && (*params)[-1] != '@') 203 brace_level++; 204 else if (**params == '}' && (*params)[-1] != '@') 205 brace_level--; 206 else if (**params == 0) 207 { 208 line_error (_("Missing } in @multitable template")); 209 return 0; 210 } 211 (*params)++; 212 } 213 while (brace_level > 0); 214 215 template = substring (start + 1, *params - 1); /* omit braces */ 216 xtemplate = expansion (template, 0); 217 len = strlen (xtemplate); 218 219 free (template); 220 free (xtemplate); 221 222 return len; 223 } 224 225 226 /* Read the parameters for a multitable from the current command 227 line, save the parameters away, and return the 228 number of columns. */ 229 int 230 setup_multitable_parameters () 231 { 232 char *params = insertion_stack->item_function; 233 int nchars; 234 float columnfrac; 235 char command[200]; /* xx no fixed limits */ 236 int i = 1; 237 238 /* We implement @hsep and @vsep even though TeX doesn't. 239 We don't get mixing of @columnfractions and templates right, 240 but TeX doesn't either. */ 241 hsep = vsep = 0; 242 243 while (*params) { 244 while (whitespace (*params)) 245 params++; 246 247 if (*params == '@') { 248 sscanf (params, "%200s", command); 249 nchars = strlen (command); 250 params += nchars; 251 if (strcmp (command, "@hsep") == 0) 252 hsep++; 253 else if (strcmp (command, "@vsep") == 0) 254 vsep++; 255 else if (strcmp (command, "@columnfractions") == 0) { 256 /* Clobber old environments and create new ones, starting at #1. 257 Environment #0 is the normal output, so don't mess with it. */ 258 for ( ; i <= MAXCOLS; i++) { 259 if (sscanf (params, "%f", &columnfrac) < 1) 260 goto done; 261 /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least) 262 doesn't support it. So skip whitespace (preceding the 263 number) and then non-whitespace (the number). */ 264 while (*params && (*params == ' ' || *params == '\t')) 265 params++; 266 /* Hmm, but what about @columnfractions 3foo. Well, I suppose 267 it's invalid input anyway. */ 268 while (*params && *params != ' ' && *params != '\t' 269 && *params != '\n' && *params != '@') 270 params++; 271 setup_output_environment (i, 272 (int) (columnfrac * (fill_column - current_indent) + .5)); 273 } 274 } 275 276 } else if (*params == '{') { 277 unsigned template_width = find_template_width (¶ms); 278 279 /* This gives us two spaces between columns. Seems reasonable. 280 How to take into account current_indent here? */ 281 setup_output_environment (i++, template_width + 2); 282 283 } else { 284 warning (_("ignoring stray text `%s' after @multitable"), params); 285 break; 286 } 287 } 288 289 done: 290 flush_output (); 291 inhibit_output_flushing (); 292 293 last_column = i - 1; 294 return last_column; 295 } 296 297 /* Initialize environment number ENV_NO, of width WIDTH. 298 The idea is that we're going to use one environment for each column of 299 a multitable, so we can build them up separately and print them 300 all out at the end. */ 301 int 302 setup_output_environment (env_no, width) 303 int env_no; 304 int width; 305 { 306 int old_env = select_output_environment (env_no); 307 308 /* clobber old environment and set width of new one */ 309 init_paragraph (); 310 311 /* make our change */ 312 fill_column = width; 313 314 /* Save new environment and restore previous one. */ 315 select_output_environment (old_env); 316 317 return env_no; 318 } 319 320 /* Direct current output to environment number N. Used when 321 switching work from one column of a multitable to the next. 322 Returns previous environment number. */ 323 int 324 select_output_environment (n) 325 int n; 326 { 327 struct env *e = &envs[current_env_no]; 328 int old_env_no = current_env_no; 329 330 /* stash current env info from global vars into the old environment */ 331 e->output_paragraph = output_paragraph; 332 e->output_paragraph_offset = output_paragraph_offset; 333 e->meta_char_pos = meta_char_pos; 334 e->output_column = output_column; 335 e->paragraph_is_open = paragraph_is_open; 336 e->current_indent = current_indent; 337 e->fill_column = fill_column; 338 339 /* now copy new environment into global vars */ 340 current_env_no = n; 341 e = &envs[current_env_no]; 342 output_paragraph = e->output_paragraph; 343 output_paragraph_offset = e->output_paragraph_offset; 344 meta_char_pos = e->meta_char_pos; 345 output_column = e->output_column; 346 paragraph_is_open = e->paragraph_is_open; 347 current_indent = e->current_indent; 348 fill_column = e->fill_column; 349 return old_env_no; 350 } 351 352 /* advance to the next environment number */ 353 void 354 nselect_next_environment () 355 { 356 if (current_env_no >= last_column) { 357 line_error (_("Too many columns in multitable item (max %d)"), last_column); 358 return; 359 } 360 select_output_environment (current_env_no + 1); 361 } 362 363 364 /* do anything needed at the beginning of processing a 365 multitable column. */ 366 void 367 init_column () 368 { 369 /* don't indent 1st paragraph in the item */ 370 cm_noindent (); 371 372 /* throw away possible whitespace after @item or @tab command */ 373 skip_whitespace (); 374 } 375 376 /* start a new item (row) of a multitable */ 377 int 378 multitable_item () 379 { 380 if (!multitable_active) { 381 line_error ("multitable_item internal error: no active multitable"); 382 xexit (1); 383 } 384 385 if (html) 386 { 387 if (!first_row) 388 add_word ("<br></tr>"); /* <br> for non-tables browsers. */ 389 add_word ("<tr align=\"left\"><td>"); 390 first_row = 0; 391 return; 392 } 393 first_row = 0; 394 395 if (current_env_no > 0) { 396 output_multitable_row (); 397 } 398 /* start at column 1 */ 399 select_output_environment (1); 400 if (!output_paragraph) { 401 line_error (_("Cannot select column #%d in multitable"), current_env_no); 402 exit (1); 403 } 404 405 init_column (); 406 407 return 0; 408 } 409 410 static void 411 output_multitable_row () 412 { 413 /* offset in the output paragraph of the next char needing 414 to be output for that column. */ 415 int offset[MAXCOLS]; 416 int i, j, s, remaining; 417 int had_newline = 0; 418 419 for (i = 0; i <= last_column; i++) 420 offset[i] = 0; 421 422 /* select the current environment, to make sure the env variables 423 get updated */ 424 select_output_environment (current_env_no); 425 426 #define CHAR_ADDR(n) (offset[i] + (n)) 427 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)]) 428 429 /* remove trailing whitespace from each column */ 430 for (i = 1; i <= last_column; i++) { 431 if (envs[i].output_paragraph_offset) 432 while (cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1))) 433 envs[i].output_paragraph_offset--; 434 435 if (i == current_env_no) 436 output_paragraph_offset = envs[i].output_paragraph_offset; 437 } 438 439 /* read the current line from each column, outputting them all 440 pasted together. Do this til all lines are output from all 441 columns. */ 442 for (;;) { 443 remaining = 0; 444 /* first, see if there is any work to do */ 445 for (i = 1; i <= last_column; i++) { 446 if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) { 447 remaining = 1; 448 break; 449 } 450 } 451 if (!remaining) 452 break; 453 454 for (s = 0; s < envs[0].current_indent; s++) 455 out_char (' '); 456 457 if (vsep) 458 out_char ('|'); 459 460 for (i = 1; i <= last_column; i++) { 461 for (s = 0; s < envs[i].current_indent; s++) 462 out_char (' '); 463 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) { 464 if (CHAR_AT (j) == '\n') 465 break; 466 out_char (CHAR_AT (j)); 467 } 468 offset[i] += j + 1; /* skip last text plus skip the newline */ 469 470 /* Do not output trailing blanks if we're in the last column and 471 there will be no trailing |. */ 472 if (i < last_column && !vsep) 473 for (; j <= envs[i].fill_column; j++) 474 out_char (' '); 475 if (vsep) 476 out_char ('|'); /* draw column separator */ 477 } 478 out_char ('\n'); /* end of line */ 479 had_newline = 1; 480 } 481 482 /* If completely blank item, get blank line despite no other output. */ 483 if (!had_newline) 484 out_char ('\n'); /* end of line */ 485 486 if (hsep) 487 draw_horizontal_separator (); 488 489 /* Now dispose of the buffered output. */ 490 for (i = 1; i <= last_column; i++) { 491 select_output_environment (i); 492 init_paragraph (); 493 } 494 } 495 496 #undef CHAR_AT 497 #undef CHAR_ADDR 498 499 /* select a new column in current row of multitable */ 500 void 501 cm_tab () 502 { 503 if (!multitable_active) 504 error (_("ignoring @tab outside of multitable")); 505 506 if (html) 507 add_word ("<td>"); 508 else 509 nselect_next_environment (); 510 511 init_column (); 512 } 513 514 /* close a multitable, flushing its output and resetting 515 whatever needs resetting */ 516 void 517 end_multitable () 518 { 519 if (!html) 520 output_multitable_row (); 521 522 /* Multitables cannot be nested. Otherwise, we'd have to save the 523 previous output environment number on a stack somewhere, and then 524 restore to that environment. */ 525 select_output_environment (0); 526 multitable_active = 0; 527 uninhibit_output_flushing (); 528 close_insertion_paragraph (); 529 530 if (html) 531 add_word ("<br></tr></table>\n"); 532 533 #if 0 534 printf (_("** Multicolumn output from last row:\n")); 535 for (i = 1; i <= last_column; i++) { 536 select_output_environment (i); 537 printf (_("* column #%d: output = %s\n"), i, output_paragraph); 538 } 539 #endif 540 } 541