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