summaryrefslogtreecommitdiff
path: root/makeinfo/multi.c
blob: 43033a24eeae3d6c313d28d7cb39a03456a62b86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
/* multi.c -- multiple-column tables (@multitable) for makeinfo.
   $Id: multi.c,v 1.18 2007/07/01 21:20:33 karl Exp $

   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2007
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by phr@gnu.org (Paul Rubin).  */

#include "system.h"
#include "mbswidth.h"
#include "cmds.h"
#include "insertion.h"
#include "makeinfo.h"
#include "multi.h"
#include "xml.h"

#define MAXCOLS 100             /* remove this limit later @@ */


/*
 * Output environments.  This is a hack grafted onto existing
 * structure.  The "output environment" used to consist of the
 * global variables `output_paragraph', `fill_column', etc.
 * Routines like add_char would manipulate these variables.
 *
 * Now, when formatting a multitable, we maintain separate environments
 * for each column.  That way we can build up the columns separately
 * and write them all out at once.  The "current" output environment"
 * is still kept in those global variables, so that the old output
 * routines don't have to change.  But we provide routines to save
 * and restore these variables in an "environment table".  The
 * `select_output_environment' function switches from one output
 * environment to another.
 *
 * Environment #0 (i.e., element #0 of the table) is the regular
 * environment that is used when we're not formatting a multitable.
 *
 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
 * the table, when a multitable is active.
 */

/* contents of an output environment */
/* some more vars may end up being needed here later @@ */
static struct env
{
  unsigned char *output_paragraph;
  int output_paragraph_offset;
  int paragraph_buffer_len;
  int paragraph_is_open;
  int meta_char_pos;
  int current_indent;
  int fill_column;
} envs[MAXCOLS];                /* the environment table */

/* index in environment table of currently selected environment */
static int current_env_no;

/* current column number */
static int current_column_no;

/* We need to make a difference between template based widths and
   @columnfractions for HTML tables' sake.  Sigh.  */
static int seen_column_fractions;

/* column number of last column in current multitable */
static int last_column;

/* flags indicating whether horizontal and vertical separators need
   to be drawn, separating rows and columns in the current multitable. */
static int hsep, vsep;

/* whether this is the first row. */
static int first_row;

/* Called to handle a {...} template on the @multitable line.
   We're at the { and our first job is to find the matching }; as a side
   effect, we change *PARAMS to point to after it.  Our other job is to
   expand the template text and return the width of that string.  */
static unsigned
find_template_width (char **params)
{
  char *template, *xtemplate;
  unsigned len;
  char *start = *params;
  int brace_level = 0;

  /* The first character should be a {.  */
  if (!params || !*params || **params != '{')
    {
      line_error ("find_template width internal error: passed %s",
                  params ? *params : "null");
      return 0;
    }

  do
    {
      if (**params == '{' && (*params == start || (*params)[-1] != '@'))
        brace_level++;
      else if (**params == '}' && (*params)[-1] != '@')
        brace_level--;
      else if (**params == 0)
        {
          line_error (_("Missing } in @multitable template"));
          return 0;
        }
      (*params)++;
    }
  while (brace_level > 0);
  
  template = substring (start + 1, *params - 1); /* omit braces */
  xtemplate = expansion (template, 0);
  len = strlen (xtemplate);
  
  free (template);
  free (xtemplate);
  
  return len;
}

/* Direct current output to environment number N.  Used when
   switching work from one column of a multitable to the next.
   Returns previous environment number. */
static int 
select_output_environment (int n)
{
  struct env *e = &envs[current_env_no];
  int old_env_no = current_env_no;

  /* stash current env info from global vars into the old environment */
  e->output_paragraph = output_paragraph;
  e->output_paragraph_offset = output_paragraph_offset;
  e->paragraph_buffer_len = paragraph_buffer_len;
  e->paragraph_is_open = paragraph_is_open;
  e->meta_char_pos = meta_char_pos;
  e->current_indent = current_indent;
  e->fill_column = fill_column;

  /* now copy new environment into global vars */
  current_env_no = n;
  e = &envs[current_env_no];
  output_paragraph = e->output_paragraph;
  output_paragraph_offset = e->output_paragraph_offset;
  /* All the elements in the output environment structures start out as
     zeros from the static declaration.  However, we don't want to try
     to malloc zero bytes when init_paragraph is called just below,
     after we return.  */
  paragraph_buffer_len = e->paragraph_buffer_len ? e->paragraph_buffer_len
                         : INITIAL_PARAGRAPH_BUFFER_LEN;
  paragraph_is_open = e->paragraph_is_open;
  meta_char_pos = e->meta_char_pos;
  current_indent = e->current_indent;
  fill_column = e->fill_column;

  return old_env_no;
}

/* Initialize environment number ENV_NO, of width WIDTH.
   The idea is that we're going to use one environment for each column of
   a multitable, so we can build them up separately and print them
   all out at the end. */
static int
setup_output_environment (int env_no, int width)
{
  int old_env = select_output_environment (env_no);

  /* clobber old environment and set width of new one */
  init_paragraph ();

  /* make our change */
  fill_column = width;

  /* Save new environment and restore previous one. */
  select_output_environment (old_env);

  return env_no;
}

/* Read the parameters for a multitable from the current command
   line, save the parameters away, and return the
   number of columns. */
static int
setup_multitable_parameters (void)
{
  char *params = insertion_stack->item_function;
  int nchars;
  float columnfrac;
  char command[200]; /* xx no fixed limits */
  int i = 1;

  /* We implement @hsep and @vsep even though TeX doesn't.
     We don't get mixing of @columnfractions and templates right,
     but TeX doesn't either.  */
  hsep = vsep = 0;

  /* Assume no @columnfractions per default.  */
  seen_column_fractions = 0;

  while (*params) {
    while (whitespace (*params))
      params++;

    if (*params == '@') {
      sscanf (params, "%200s", command);
      nchars = strlen (command);
      params += nchars;
      if (strcmp (command, "@hsep") == 0)
        hsep++;
      else if (strcmp (command, "@vsep") == 0)
        vsep++;
      else if (strcmp (command, "@columnfractions") == 0) {
        seen_column_fractions = 1;
        /* Clobber old environments and create new ones, starting at #1.
           Environment #0 is the normal output, so don't mess with it. */
        for ( ; i <= MAXCOLS; i++) {
          if (sscanf (params, "%f", &columnfrac) < 1)
            goto done;
          /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least)
             doesn't support it.  So skip whitespace (preceding the
             number) and then non-whitespace (the number).  */
          while (*params && (*params == ' ' || *params == '\t'))
            params++;
          /* Hmm, but what about @columnfractions 3foo.  Oh well, 
             it's invalid input anyway.  */
          while (*params && *params != ' ' && *params != '\t'
                 && *params != '\n' && *params != '@')
            params++;

          {
            /* For html/xml/docbook, translate fractions into integer
               percentages, adding .005 to avoid rounding problems.  For
               info, we want the character width.  */
            int width = xml || html ? (columnfrac + .005) * 100
                        : (columnfrac * (fill_column - current_indent) + .5);
            setup_output_environment (i, width);
          }
        }
      }

    } else if (*params == '{') {
      unsigned template_width = find_template_width (&params);

      /* This gives us two spaces between columns.  Seems reasonable.
         How to take into account current_indent here?  */
      setup_output_environment (i++, template_width + 2);
      
    } else {
      warning (_("ignoring stray text `%s' after @multitable"), params);
      break;
    }
  }

done:
  flush_output ();
  inhibit_output_flushing ();

  last_column = i - 1;
  return last_column;
}

/* Output a row.  Calls insert, but also flushes the buffered output
   when we see a newline, since in multitable every line is a separate
   paragraph.  */
static void
out_char (int ch)
{
  if (html || xml)
    add_char (ch);
  else
    {
      int env = select_output_environment (0);
      insert (ch);
      if (ch == '\n')
	{
	  uninhibit_output_flushing ();
	  flush_output ();
	  inhibit_output_flushing ();
	}
      select_output_environment (env);
    }
}


static void
draw_horizontal_separator (void)
{
  int i, j, s;

  if (html)
    {
      add_word ("<hr>");
      return;
    }
  if (xml)
    return;

  for (s = 0; s < envs[0].current_indent; s++)
    out_char (' ');
  if (vsep)
    out_char ('+');
  for (i = 1; i <= last_column; i++) {
    for (j = 0; j <= envs[i].fill_column; j++)
      out_char ('-');
    if (vsep)
      out_char ('+');
  }
  out_char (' ');
  out_char ('\n');
}


/* multitable strategy:
    for each item {
       for each column in an item {
        initialize a new paragraph
        do ordinary formatting into the new paragraph
        save the paragraph away
        repeat if there are more paragraphs in the column
      }
      dump out the saved paragraphs and free the storage
    }

   For HTML we construct a simple HTML 3.2 table with <br>s inserted
   to help non-tables browsers.  `@item' inserts a <tr> and `@tab'
   inserts <td>; we also try to close <tr>.  The only real
   alternative is to rely on the info formatting engine and present
   preformatted text.  */

void
do_multitable (void)
{
  int ncolumns;

  if (multitable_active)
    {
      line_error ("Multitables cannot be nested");
      return;
    }

  close_single_paragraph ();

  if (xml)
    {
      xml_no_para = 1;
      if (output_paragraph_offset > 0
	  && output_paragraph[output_paragraph_offset-1] == '\n')
        output_paragraph_offset--;
    }

  /* scan the current item function to get the field widths
     and number of columns, and set up the output environment list
     accordingly. */
  ncolumns = setup_multitable_parameters ();
  first_row = 1;

  /* <p> for non-tables browsers.  @multitable implicitly ends the
     current paragraph, so this is ok.  */
  if (html)
    add_html_block_elt ("<p><table summary=\"\">");
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    {
      int *widths = xmalloc (ncolumns * sizeof (int));
      int i;
      for (i=0; i<ncolumns; i++)
	widths[i] = envs[i+1].fill_column;
      xml_begin_multitable (ncolumns, widths);
      free (widths);
    }

  if (hsep)
    draw_horizontal_separator ();

  /* The next @item command will direct stdout into the first column
     and start processing.  @tab will then switch to the next column,
     and @item will flush out the saved output and return to the first
     column.  Environment #1 is the first column.  (Environment #0 is
     the normal output) */

  ++multitable_active;
}

/* advance to the next environment number */
static void
nselect_next_environment (void)
{
  if (current_env_no >= last_column) {
    line_error (_("Too many columns in multitable item (max %d)"), last_column);
    return;
  }
  select_output_environment (current_env_no + 1);
}


/* do anything needed at the beginning of processing a
   multitable column. */
static void
init_column (void)
{
  /* don't indent 1st paragraph in the item */
  cm_noindent ();

  /* throw away possible whitespace after @item or @tab command */
  skip_whitespace ();
}

static void
output_multitable_row (void)
{
  /* offset in the output paragraph of the next char needing
     to be output for that column. */
  int offset[MAXCOLS];
  int i, j, s, remaining;
  int had_newline = 0;

  for (i = 0; i <= last_column; i++)
    offset[i] = 0;

  /* select the current environment, to make sure the env variables
     get updated */
  select_output_environment (current_env_no);

#define CHAR_ADDR(n) (offset[i] + (n))
#define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])

  /* remove trailing whitespace from each column */
  for (i = 1; i <= last_column; i++) {
    while (envs[i].output_paragraph_offset
           && cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1)))
      envs[i].output_paragraph_offset--;

    if (i == current_env_no)
      output_paragraph_offset = envs[i].output_paragraph_offset;
  }

  /* read the current line from each column, outputting them all
     pasted together.  Do this til all lines are output from all
     columns.  */
  for (;;) {
    remaining = 0;
    /* first, see if there is any work to do */
    for (i = 1; i <= last_column; i++) {
      if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) {
        remaining = 1;
        break;
      }
    }
    if (!remaining)
      break;
    
    for (s = 0; s < envs[0].current_indent; s++)
      out_char (' ');
    
    if (vsep)
      out_char ('|');

    for (i = 1; i <= last_column; i++) {
      for (s = 0; s < envs[i].current_indent; s++)
        out_char (' ');
      for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
        if (CHAR_AT (j) == '\n')
          break;
        out_char (CHAR_AT (j));
      }
      /* Do not output trailing blanks if we're in the last column and
         there will be no trailing |.  */
      if (i < last_column && !vsep)
        for (s = mbsnwidth ((char *)&CHAR_AT (0), j, 0);
	     s <= envs[i].fill_column; s++)
          out_char (' ');
      if (vsep)
        out_char ('|'); /* draw column separator */

      offset[i] += j + 1;       /* skip last text plus skip the newline */
    }
    out_char ('\n');    /* end of line */
    had_newline = 1;
  }
  
  /* If completely blank item, get blank line despite no other output.  */
  if (!had_newline)
    out_char ('\n');    /* end of line */

  if (hsep)
    draw_horizontal_separator ();

  /* Now dispose of the buffered output. */
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    init_paragraph ();
  }
}

int after_headitem = 0;
int headitem_row = 0;

/* start a new item (row) of a multitable */
int
multitable_item (void)
{
  if (!multitable_active) {
    line_error ("multitable_item internal error: no active multitable");
    xexit (1);
  }

  current_column_no = 1;

  if (html)
    {
      if (!first_row)
        /* <br> for non-tables browsers. */
	add_word_args ("<br></%s></tr>", after_headitem ? "th" : "td");

      if (seen_column_fractions)
        add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">",
            headitem_flag ? "th" : "td",
            envs[current_column_no].fill_column);
      else
        add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
            headitem_flag ? "th" : "td");

      if (headitem_flag)
        after_headitem = 1;
      else
        after_headitem = 0;
      first_row = 0;
      headitem_row = headitem_flag;
      headitem_flag = 0;
      return 0;
    }
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    {
      xml_end_multitable_row (first_row);
      if (headitem_flag)
        after_headitem = 1;
      else
        after_headitem = 0;
      first_row = 0;
      headitem_flag = 0;
      return 0;
    }
  first_row = 0;

  if (current_env_no > 0) {
    output_multitable_row ();
  }
  /* start at column 1 */
  select_output_environment (1);
  if (!output_paragraph) {
    line_error (_("[unexpected] cannot select column #%d in multitable"),
                current_env_no);
    xexit (1);
  }

  init_column ();

  if (headitem_flag)
    hsep = 1;
  else
    hsep = 0;

  if (headitem_flag)
    after_headitem = 1;
  else
    after_headitem = 0;
  headitem_flag = 0;

  return 0;
}

#undef CHAR_AT
#undef CHAR_ADDR

/* select a new column in current row of multitable */
void
cm_tab (void)
{
  if (!multitable_active)
    error (_("ignoring @tab outside of multitable"));

  current_column_no++;
  
  if (html)
    {
      if (seen_column_fractions)
        add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">",
            headitem_row ? "th" : "td",
            headitem_row ? "th" : "td",
            envs[current_column_no].fill_column);
      else
        add_word_args ("</%s><%s valign=\"top\">",
            headitem_row ? "th" : "td",
            headitem_row ? "th" : "td");
    }
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    xml_end_multitable_column ();
  else
    nselect_next_environment ();

  init_column ();
}

/* close a multitable, flushing its output and resetting
   whatever needs resetting */
void
end_multitable (void)
{
  if (!html && !docbook) 
    output_multitable_row ();

  /* Multitables cannot be nested.  Otherwise, we'd have to save the
     previous output environment number on a stack somewhere, and then
     restore to that environment.  */
  select_output_environment (0);
  multitable_active = 0;
  uninhibit_output_flushing ();
  close_insertion_paragraph ();

  if (html)
    add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td");
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    xml_end_multitable ();

#if 0
  printf (_("** Multicolumn output from last row:\n"));
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    printf (_("* column #%d: output = %s\n"), i, output_paragraph);
  }
#endif
}