1// -*- C++ -*-
2/* Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
3 *
4 *  Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp
5 *
6 *  html-table.h
7 *
8 *  provides the methods necessary to handle indentation and tab
9 *  positions using html tables.
10 */
11
12/*
13This file is part of groff.
14
15groff is free software; you can redistribute it and/or modify it under
16the terms of the GNU General Public License as published by the Free
17Software Foundation; either version 2, or (at your option) any later
18version.
19
20groff is distributed in the hope that it will be useful, but WITHOUT ANY
21WARRANTY; without even the implied warranty of MERCHANTABILITY or
22FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
23for more details.
24
25You should have received a copy of the GNU General Public License along
26with groff; see the file COPYING.  If not, write to the Free Software
27Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
28
29#include "driver.h"
30#include "stringclass.h"
31#include "cset.h"
32#include "html-table.h"
33#include "ctype.h"
34#include "html.h"
35#include "html-text.h"
36
37#if !defined(TRUE)
38#   define TRUE  (1==1)
39#endif
40#if !defined(FALSE)
41#   define FALSE (1==0)
42#endif
43
44tabs::tabs ()
45  : tab(NULL)
46{
47}
48
49tabs::~tabs ()
50{
51  delete_list();
52}
53
54/*
55 *  delete_list - frees the tab list and sets tab to NULL.
56 */
57
58void tabs::delete_list (void)
59{
60  tab_position *p = tab;
61  tab_position *q;
62
63  while (p != NULL) {
64    q = p;
65    p = p->next;
66    delete q;
67  }
68  tab = NULL;
69}
70
71void tabs::clear (void)
72{
73  delete_list();
74}
75
76/*
77 *  compatible - returns TRUE if the tab stops in, s, do
78 *               not conflict with the current tab stops.
79 *               The new tab stops are _not_ placed into
80 *               this class.
81 */
82
83int tabs::compatible (const char *s)
84{
85  char align;
86  int  total=0;
87  tab_position *last = tab;
88
89  if (last == NULL)
90    return FALSE;  // no tab stops defined
91
92  // move over tag name
93  while ((*s != (char)0) && !isspace(*s))
94    s++;
95
96  while (*s != (char)0 && last != NULL) {
97    // move over white space
98    while ((*s != (char)0) && isspace(*s))
99      s++;
100    // collect alignment
101    align = *s;
102    // move over alignment
103    s++;
104    // move over white space
105    while ((*s != (char)0) && isspace(*s))
106      s++;
107    // collect tab position
108    total = atoi(s);
109    // move over tab position
110    while ((*s != (char)0) && !isspace(*s))
111      s++;
112    if (last->alignment != align || last->position != total)
113      return FALSE;
114
115    last = last->next;
116  }
117  return TRUE;
118}
119
120/*
121 *  init - scans the string, s, and initializes the tab stops.
122 */
123
124void tabs::init (const char *s)
125{
126  char align;
127  int  total=0;
128  tab_position *last = NULL;
129
130  clear(); // remove any tab stops
131
132  // move over tag name
133  while ((*s != (char)0) && !isspace(*s))
134    s++;
135
136  while (*s != (char)0) {
137    // move over white space
138    while ((*s != (char)0) && isspace(*s))
139      s++;
140    // collect alignment
141    align = *s;
142    // move over alignment
143    s++;
144    // move over white space
145    while ((*s != (char)0) && isspace(*s))
146      s++;
147    // collect tab position
148    total = atoi(s);
149    // move over tab position
150    while ((*s != (char)0) && !isspace(*s))
151      s++;
152    if (last == NULL) {
153      tab = new tab_position;
154      last = tab;
155    } else {
156      last->next = new tab_position;
157      last = last->next;
158    }
159    last->alignment = align;
160    last->position = total;
161    last->next = NULL;
162  }
163}
164
165/*
166 *  check_init - define tab stops using, s, providing none already exist.
167 */
168
169void tabs::check_init (const char *s)
170{
171  if (tab == NULL)
172    init(s);
173}
174
175/*
176 *  find_tab - returns the tab number corresponding to the position, pos.
177 */
178
179int tabs::find_tab (int pos)
180{
181  tab_position *p;
182  int i=0;
183
184  for (p = tab; p != NULL; p = p->next) {
185    i++;
186    if (p->position == pos)
187      return i;
188  }
189  return 0;
190}
191
192/*
193 *  get_tab_pos - returns the, nth, tab position
194 */
195
196int tabs::get_tab_pos (int n)
197{
198  tab_position *p;
199
200  n--;
201  for (p = tab; (p != NULL) && (n>0); p = p->next) {
202    n--;
203    if (n == 0)
204      return p->position;
205  }
206  return 0;
207}
208
209char tabs::get_tab_align (int n)
210{
211  tab_position *p;
212
213  n--;
214  for (p = tab; (p != NULL) && (n>0); p = p->next) {
215    n--;
216    if (n == 0)
217      return p->alignment;
218  }
219  return 'L';
220}
221
222/*
223 *  dump_tab - display tab positions
224 */
225
226void tabs::dump_tabs (void)
227{
228  int i=1;
229  tab_position *p;
230
231  for (p = tab; p != NULL; p = p->next) {
232    printf("tab %d is %d\n", i, p->position);
233    i++;
234  }
235}
236
237/*
238 *  html_table - methods
239 */
240
241html_table::html_table (simple_output *op, int linelen)
242  : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE)
243{
244  tab_stops = new tabs();
245}
246
247html_table::~html_table ()
248{
249  cols *c;
250  if (tab_stops != NULL)
251    delete tab_stops;
252
253  c = columns;
254  while (columns != NULL) {
255    columns = columns->next;
256    delete c;
257    c = columns;
258  }
259}
260
261/*
262 *  remove_cols - remove a list of columns as defined by, c.
263 */
264
265void html_table::remove_cols (cols *c)
266{
267  cols *p;
268
269  while (c != NULL) {
270    p = c;
271    c = c->next;
272    delete p;
273  }
274}
275
276/*
277 *  set_linelength - sets the line length value in this table.
278 *                   It also adds an extra blank column to the
279 *                   table should linelen exceed the last column.
280 */
281
282void html_table::set_linelength (int linelen)
283{
284  cols *p = NULL;
285  cols *c;
286  linelength = linelen;
287
288  for (c = columns; c != NULL; c = c->next) {
289    if (c->right > linelength) {
290      c->right = linelength;
291      remove_cols(c->next);
292      c->next = NULL;
293      return;
294    }
295    p = c;
296  }
297  if (p != NULL && p->right > 0)
298    add_column(p->no+1, p->right, linelength, 'L');
299}
300
301/*
302 *  get_effective_linelength -
303 */
304
305int html_table::get_effective_linelength (void)
306{
307  if (columns != NULL)
308    return linelength - columns->left;
309  else
310    return linelength;
311}
312
313/*
314 *  add_indent - adds the indent to a table.
315 */
316
317void html_table::add_indent (int indent)
318{
319  if (columns != NULL && columns->left > indent)
320    add_column(0, indent, columns->left, 'L');
321}
322
323/*
324 *  emit_table_header - emits the html header for this table.
325 */
326
327void html_table::emit_table_header (int space)
328{
329  if (columns == NULL)
330    return;
331
332  // dump_table();
333
334  last_col = NULL;
335  if (linelength > 0) {
336    out->nl();
337    out->nl();
338
339    out->put_string("<table width=\"100%\"")
340      .put_string(" border=0 rules=\"none\" frame=\"void\"\n")
341      .put_string("       cellspacing=\"0\" cellpadding=\"0\"");
342    out->put_string(">")
343      .nl();
344    out->put_string("<tr valign=\"top\" align=\"left\"");
345    if (space) {
346      out->put_string(" style=\"margin-top: ");
347      out->put_string(STYLE_VERTICAL_SPACE);
348      out->put_string("\"");
349    }
350    out->put_string(">").nl();
351  }
352}
353
354/*
355 *  get_right - returns the right most position of this column.
356 */
357
358int html_table::get_right (cols *c)
359{
360  if (c != NULL && c->right > 0)
361    return c->right;
362  if (c->next != NULL)
363    return c->left;
364  return linelength;
365}
366
367/*
368 *  set_space - assigns start_space. Used to determine the
369 *              vertical alignment when generating the next table row.
370 */
371
372void html_table::set_space (int space)
373{
374  start_space = space;
375}
376
377/*
378 *  emit_col - moves onto column, n.
379 */
380
381void html_table::emit_col (int n)
382{
383  cols *c = columns;
384  cols *b = columns;
385  int   width = 0;
386
387  // must be a different row
388  if (last_col != NULL && n <= last_col->no)
389    emit_new_row();
390
391  while (c != NULL && c->no < n)
392    c = c->next;
393
394  // can we find column, n?
395  if (c != NULL && c->no == n) {
396    // shutdown previous column
397    if (last_col != NULL)
398      out->put_string("</td>").nl();
399
400    // find previous column
401    if (last_col == NULL)
402      b = columns;
403    else
404      b = last_col;
405
406    // have we a gap?
407    if (last_col != NULL) {
408      if (is_gap(b))
409	out->put_string("<td width=\"")
410	    .put_number(is_gap(b))
411	    .put_string("%\"></td>")
412	    .nl();
413      b = b->next;
414    }
415
416    // move across to column n
417    while (b != c) {
418      // we compute the difference after converting positions
419      // to avoid rounding errors
420      width = (get_right(b)*100 + get_effective_linelength()/2)
421		/ get_effective_linelength()
422	      - (b->left*100 + get_effective_linelength()/2)
423		  /get_effective_linelength();
424      if (width)
425	out->put_string("<td width=\"")
426	    .put_number(width)
427	    .put_string("%\"></td>")
428	    .nl();
429      // have we a gap?
430      if (is_gap(b))
431	out->put_string("<td width=\"")
432	    .put_number(is_gap(b))
433	    .put_string("%\"></td>")
434	    .nl();
435      b = b->next;
436    }
437    width = (get_right(b)*100 + get_effective_linelength()/2)
438	      / get_effective_linelength()
439	    - (b->left*100 + get_effective_linelength()/2)
440		/get_effective_linelength();
441    switch (b->alignment) {
442    case 'C':
443      out->put_string("<td width=\"")
444	  .put_number(width)
445	  .put_string("%\" align=center>")
446	  .nl();
447      break;
448    case 'R':
449      out->put_string("<td width=\"")
450	  .put_number(width)
451	  .put_string("%\" align=right>")
452	  .nl();
453      break;
454    default:
455      out->put_string("<td width=\"")
456	  .put_number(width)
457	  .put_string("%\">")
458	  .nl();
459    }
460    // remember column, b
461    last_col = b;
462  }
463}
464
465/*
466 *  finish_row -
467 */
468
469void html_table::finish_row (void)
470{
471  int n = 0;
472  cols *c;
473
474  if (last_col != NULL) {
475    for (c = last_col->next; c != NULL; c = c->next)
476      n = c->no;
477
478    if (n > 0)
479      emit_col(n);
480    out->put_string("</td>").nl();
481  }
482}
483
484/*
485 *  emit_new_row - move to the next row.
486 */
487
488void html_table::emit_new_row (void)
489{
490  finish_row();
491
492  out->put_string("<tr valign=\"top\" align=\"left\"");
493  if (start_space) {
494    out->put_string(" style=\"margin-top: ");
495    out->put_string(STYLE_VERTICAL_SPACE);
496    out->put_string("\"");
497  }
498  out->put_string(">").nl();
499  start_space = FALSE;
500  last_col = NULL;
501}
502
503void html_table::emit_finish_table (void)
504{
505  finish_row();
506  out->put_string("</table>");
507}
508
509/*
510 *  add_column - adds a column. It returns FALSE if hstart..hend
511 *               crosses into a different columns.
512 */
513
514int html_table::add_column (int coln, int hstart, int hend, char align)
515{
516  cols *c = get_column(coln);
517
518  if (c == NULL)
519    return insert_column(coln, hstart, hend, align);
520  else
521    return modify_column(c, hstart, hend, align);
522}
523
524/*
525 *  get_column - returns the column, coln.
526 */
527
528cols *html_table::get_column (int coln)
529{
530  cols *c = columns;
531
532  while (c != NULL && coln != c->no)
533    c = c->next;
534
535  if (c != NULL && coln == c->no)
536    return c;
537  else
538    return NULL;
539}
540
541/*
542 *  insert_column - inserts a column, coln.
543 *                  It returns TRUE if it does not bump into
544 *                  another column.
545 */
546
547int html_table::insert_column (int coln, int hstart, int hend, char align)
548{
549  cols *c = columns;
550  cols *l = columns;
551  cols *n = NULL;
552
553  while (c != NULL && c->no < coln) {
554    l = c;
555    c = c->next;
556  }
557  if (l != NULL && l->no>coln && hend > l->left)
558    return FALSE;	// new column bumps into previous one
559
560  l = NULL;
561  c = columns;
562  while (c != NULL && c->no < coln) {
563    l = c;
564    c = c->next;
565  }
566
567  if ((l != NULL) && (hstart < l->right))
568    return FALSE;	// new column bumps into previous one
569
570  if ((l != NULL) && (l->next != NULL) &&
571      (l->next->left < hend))
572    return FALSE;  // new column bumps into next one
573
574  n = new cols;
575  if (l == NULL) {
576    n->next = columns;
577    columns = n;
578  } else {
579    n->next = l->next;
580    l->next = n;
581  }
582  n->left = hstart;
583  n->right = hend;
584  n->no = coln;
585  n->alignment = align;
586  return TRUE;
587}
588
589/*
590 *  modify_column - given a column, c, modify the width to
591 *                  contain hstart..hend.
592 *                  It returns TRUE if it does not clash with
593 *                  the next or previous column.
594 */
595
596int html_table::modify_column (cols *c, int hstart, int hend, char align)
597{
598  cols *l = columns;
599
600  while (l != NULL && l->next != c)
601    l = l->next;
602
603  if ((l != NULL) && (hstart < l->right))
604    return FALSE;	// new column bumps into previous one
605
606  if ((c->next != NULL) && (c->next->left < hend))
607    return FALSE;  // new column bumps into next one
608
609  if (c->left > hstart)
610    c->left = hstart;
611
612  if (c->right < hend)
613    c->right = hend;
614
615  c->alignment = align;
616
617  return TRUE;
618}
619
620/*
621 *  find_tab_column - finds the column number for position, pos.
622 *                    It searches through the list tab stops.
623 */
624
625int html_table::find_tab_column (int pos)
626{
627  // remember the first column is reserved for untabbed glyphs
628  return tab_stops->find_tab(pos)+1;
629}
630
631/*
632 *  find_column - find the column number for position, pos.
633 *                It searches through the list of columns.
634 */
635
636int html_table::find_column (int pos)
637{
638  int   p=0;
639  cols *c;
640
641  for (c = columns; c != NULL; c = c->next) {
642    if (c->left > pos)
643      return p;
644    p = c->no;
645  }
646  return p;
647}
648
649/*
650 *  no_columns - returns the number of table columns (rather than tabs)
651 */
652
653int html_table::no_columns (void)
654{
655  int n=0;
656  cols *c;
657
658  for (c = columns; c != NULL; c = c->next)
659    n++;
660  return n;
661}
662
663/*
664 *  is_gap - returns the gap between column, c, and the next column.
665 */
666
667int html_table::is_gap (cols *c)
668{
669  if (c == NULL || c->right <= 0 || c->next == NULL)
670    return 0;
671  else
672    // we compute the difference after converting positions
673    // to avoid rounding errors
674    return (c->next->left*100 + get_effective_linelength()/2)
675	     / get_effective_linelength()
676	   - (c->right*100 + get_effective_linelength()/2)
677	       / get_effective_linelength();
678}
679
680/*
681 *  no_gaps - returns the number of table gaps between the columns
682 */
683
684int html_table::no_gaps (void)
685{
686  int n=0;
687  cols *c;
688
689  for (c = columns; c != NULL; c = c->next)
690    if (is_gap(c))
691      n++;
692  return n;
693}
694
695/*
696 *  get_tab_pos - returns the, nth, tab position
697 */
698
699int html_table::get_tab_pos (int n)
700{
701  return tab_stops->get_tab_pos(n);
702}
703
704char html_table::get_tab_align (int n)
705{
706  return tab_stops->get_tab_align(n);
707}
708
709
710void html_table::dump_table (void)
711{
712  if (columns != NULL) {
713    cols *c;
714    for (c = columns; c != NULL; c = c->next) {
715      printf("column %d  %d..%d  %c\n", c->no, c->left, c->right, c->alignment);
716    }
717  } else
718    tab_stops->dump_tabs();
719}
720
721/*
722 *  html_indent - creates an indent with indentation, ind, given
723 *                a line length of linelength.
724 */
725
726html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength)
727{
728  table = new html_table(op, linelength);
729
730  table->add_column(1, ind+pageoffset, linelength, 'L');
731  table->add_indent(pageoffset);
732  in = ind;
733  pg = pageoffset;
734  ll = linelength;
735}
736
737html_indent::~html_indent (void)
738{
739  end();
740  delete table;
741}
742
743void html_indent::begin (int space)
744{
745  if (in + pg == 0) {
746    if (space) {
747      table->out->put_string(" style=\"margin-top: ");
748      table->out->put_string(STYLE_VERTICAL_SPACE);
749      table->out->put_string("\"");
750    }
751  }
752  else {
753    //
754    // we use exactly the same mechanism for calculating
755    // indentation as html_table::emit_col
756    //
757    table->out->put_string(" style=\"margin-left:")
758      .put_number(((in + pg) * 100 + ll/2) / ll -
759		  (ll/2)/ll)
760      .put_string("%;");
761
762    if (space) {
763      table->out->put_string(" margin-top: ");
764      table->out->put_string(STYLE_VERTICAL_SPACE);
765    }
766    table->out->put_string("\"");
767  }
768}
769
770void html_indent::end (void)
771{
772}
773
774/*
775 *  get_reg - collects the registers as supplied during initialization.
776 */
777
778void html_indent::get_reg (int *ind, int *pageoffset, int *linelength)
779{
780  *ind = in;
781  *pageoffset = pg;
782  *linelength = ll;
783}
784