xref: /openbsd-src/gnu/usr.bin/texinfo/makeinfo/multi.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
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 (&params);
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