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