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