1// -*- C++ -*-
2/* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005
3 * Free Software Foundation, Inc.
4 *
5 *  Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp
6 *  but it owes a huge amount of ideas and raw code from
7 *  James Clark (jjc@jclark.com) grops/ps.cpp.
8 */
9
10/*
11This file is part of groff.
12
13groff is free software; you can redistribute it and/or modify it under
14the terms of the GNU General Public License as published by the Free
15Software Foundation; either version 2, or (at your option) any later
16version.
17
18groff is distributed in the hope that it will be useful, but WITHOUT ANY
19WARRANTY; without even the implied warranty of MERCHANTABILITY or
20FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
21for more details.
22
23You should have received a copy of the GNU General Public License along
24with groff; see the file COPYING.  If not, write to the Free Software
25Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
26
27#include "driver.h"
28#include "stringclass.h"
29#include "cset.h"
30#include "html.h"
31#include "html-text.h"
32#include "html-table.h"
33
34#include <time.h>
35
36#ifdef HAVE_UNISTD_H
37#include <unistd.h>
38#endif
39
40#include <stdio.h>
41#include <fcntl.h>
42#include <string.h>
43
44extern "C" const char *Version_string;
45
46#if !defined(TRUE)
47#   define TRUE  (1==1)
48#endif
49#if !defined(FALSE)
50#   define FALSE (1==0)
51#endif
52
53#define MAX_LINE_LENGTH                60            /* maximum characters we want in a line      */
54#define SIZE_INCREMENT                  2            /* font size increment <big> = +2            */
55#define CENTER_TOLERANCE                2            /* how many pixels off center do we allow    */
56#define ANCHOR_TEMPLATE         "heading"            /* if simple anchor is set we use this       */
57#define UNICODE_DESC_START           0x80            /* all character entities above this are     */
58                                                     /* either encoded by their glyph names or if */
59                                                     /* there is no name then we use &#nnn;       */
60typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT;
61typedef enum {col_tag, tab_tag, tab0_tag, none} colType;
62
63#undef DEBUG_TABLES
64// #define DEBUG_TABLES
65
66/*
67 *  prototypes
68 */
69
70char *get_html_translation (font *f, const string &name);
71int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single);
72
73
74static int auto_links = TRUE;                        /* by default we enable automatic links at  */
75                                                     /* top of the document.                     */
76static int auto_rule  = TRUE;                        /* by default we enable an automatic rule   */
77                                                     /* at the top and bottom of the document    */
78static int simple_anchors = FALSE;                   /* default to anchors with heading text     */
79static int manufacture_headings = FALSE;             /* default is to use the Hn html headings,  */
80                                                     /* rather than manufacture our own.         */
81static color *default_background = NULL;             /* has user requested initial bg color?     */
82static string job_name;                              /* if set then the output is split into     */
83                                                     /* multiple files with `job_name'-%d.html   */
84static int multiple_files = FALSE;                   /* must we the output be divided into       */
85                                                     /* multiple html files, one for each        */
86                                                     /* heading?                                 */
87static int base_point_size = 0;                      /* which troff font size maps onto html     */
88                                                     /* size 3?                                  */
89static int split_level = 2;                          /* what heading level to split at?          */
90static string head_info;                             /* user supplied information to be placed   */
91                                                     /* into <head> </head>                      */
92
93
94/*
95 *  start with a few favorites
96 */
97
98void stop () {}
99
100static int min (int a, int b)
101{
102  if (a < b)
103    return a;
104  else
105    return b;
106}
107
108static int max (int a, int b)
109{
110  if (a > b)
111    return a;
112  else
113    return b;
114}
115
116/*
117 *  is_intersection - returns TRUE if range a1..a2 intersects with b1..b2
118 */
119
120static int is_intersection (int a1, int a2, int b1, int b2)
121{
122  // easier to prove NOT outside limits
123  return ! ((a1 > b2) || (a2 < b1));
124}
125
126/*
127 *  is_digit - returns TRUE if character, ch, is a digit.
128 */
129
130static int is_digit (char ch)
131{
132  return (ch >= '0') && (ch <= '9');
133}
134
135/*
136 *  the classes and methods for maintaining a list of files.
137 */
138
139struct file {
140  FILE    *fp;
141  file    *next;
142  int      new_output_file;
143  int      require_links;
144  string   output_file_name;
145
146  file     (FILE *f);
147};
148
149/*
150 *  file - initialize all fields to NULL
151 */
152
153file::file (FILE *f)
154  : fp(f), next(NULL), new_output_file(FALSE),
155    require_links(FALSE), output_file_name("")
156{
157}
158
159class files {
160public:
161              files              ();
162  FILE       *get_file           (void);
163  void        start_of_list      (void);
164  void        move_next          (void);
165  void        add_new_file       (FILE *f);
166  void        set_file_name      (string name);
167  void        set_links_required (void);
168  int         are_links_required (void);
169  int         is_new_output_file (void);
170  string      file_name          (void);
171  string      next_file_name     (void);
172private:
173  file       *head;
174  file       *tail;
175  file       *ptr;
176};
177
178/*
179 *  files - create an empty list of files.
180 */
181
182files::files ()
183  : head(NULL), tail(NULL), ptr(NULL)
184{
185}
186
187/*
188 *  get_file - returns the FILE associated with ptr.
189 */
190
191FILE *files::get_file (void)
192{
193  if (ptr)
194    return ptr->fp;
195  else
196    return NULL;
197}
198
199/*
200 *  start_of_list - reset the ptr to the start of the list.
201 */
202
203void files::start_of_list (void)
204{
205  ptr = head;
206}
207
208/*
209 *  move_next - moves the ptr to the next element on the list.
210 */
211
212void files::move_next (void)
213{
214  if (ptr != NULL)
215    ptr = ptr->next;
216}
217
218/*
219 *  add_new_file - adds a new file, f, to the list.
220 */
221
222void files::add_new_file (FILE *f)
223{
224  if (head == NULL) {
225    head = new file(f);
226    tail = head;
227  } else {
228    tail->next = new file(f);
229    tail       = tail->next;
230  }
231  ptr = tail;
232}
233
234/*
235 *  set_file_name - sets the final file name to contain the html
236 *                  data to name.
237 */
238
239void files::set_file_name (string name)
240{
241  if (ptr != NULL) {
242    ptr->output_file_name = name;
243    ptr->new_output_file = TRUE;
244  }
245}
246
247/*
248 *  set_links_required - issue links when processing this component
249 *                       of the file.
250 */
251
252void files::set_links_required (void)
253{
254  if (ptr != NULL)
255    ptr->require_links = TRUE;
256}
257
258/*
259 *  are_links_required - returns TRUE if this section of the file
260 *                       requires that links should be issued.
261 */
262
263int files::are_links_required (void)
264{
265  if (ptr != NULL)
266    return ptr->require_links;
267  return FALSE;
268}
269
270/*
271 *  is_new_output_file - returns TRUE if this component of the file
272 *                       is the start of a new output file.
273 */
274
275int files::is_new_output_file (void)
276{
277  if (ptr != NULL)
278    return ptr->new_output_file;
279  return FALSE;
280}
281
282/*
283 *  file_name - returns the name of the file.
284 */
285
286string files::file_name (void)
287{
288  if (ptr != NULL)
289    return ptr->output_file_name;
290  return string("");
291}
292
293/*
294 *  next_file_name - returns the name of the next file.
295 */
296
297string files::next_file_name (void)
298{
299  if (ptr != NULL && ptr->next != NULL)
300    return ptr->next->output_file_name;
301  return string("");
302}
303
304/*
305 *  the class and methods for styles
306 */
307
308struct style {
309  font        *f;
310  int          point_size;
311  int          font_no;
312  int          height;
313  int          slant;
314  color        col;
315               style       ();
316               style       (font *, int, int, int, int, color);
317  int          operator == (const style &) const;
318  int          operator != (const style &) const;
319};
320
321style::style()
322  : f(NULL)
323{
324}
325
326style::style(font *p, int sz, int h, int sl, int no, color c)
327  : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c)
328{
329}
330
331int style::operator==(const style &s) const
332{
333  return (f == s.f && point_size == s.point_size
334	  && height == s.height && slant == s.slant && col == s.col);
335}
336
337int style::operator!=(const style &s) const
338{
339  return !(*this == s);
340}
341
342/*
343 *  the class and methods for retaining ascii text
344 */
345
346struct char_block {
347  enum { SIZE = 256 };
348  char         *buffer;
349  int           used;
350  char_block   *next;
351
352  char_block();
353  char_block(int length);
354  ~char_block();
355};
356
357char_block::char_block()
358: buffer(NULL), used(0), next(NULL)
359{
360}
361
362char_block::char_block(int length)
363: used(0), next(NULL)
364{
365  buffer = new char[max(length, char_block::SIZE)];
366  if (buffer == NULL)
367    fatal("out of memory error");
368}
369
370char_block::~char_block()
371{
372  if (buffer != NULL)
373    a_delete buffer;
374}
375
376class char_buffer {
377public:
378  char_buffer();
379  ~char_buffer();
380  char  *add_string(const char *, unsigned int);
381  char  *add_string(const string &);
382private:
383  char_block *head;
384  char_block *tail;
385};
386
387char_buffer::char_buffer()
388: head(NULL), tail(NULL)
389{
390}
391
392char_buffer::~char_buffer()
393{
394  while (head != NULL) {
395    char_block *temp = head;
396    head = head->next;
397    delete temp;
398  }
399}
400
401char *char_buffer::add_string (const char *s, unsigned int length)
402{
403  int i=0;
404  unsigned int old_used;
405
406  if (s == NULL || length == 0)
407    return NULL;
408
409  if (tail == NULL) {
410    tail = new char_block(length+1);
411    head = tail;
412  } else {
413    if (tail->used + length+1 > char_block::SIZE) {
414      tail->next  = new char_block(length+1);
415      tail        = tail->next;
416    }
417  }
418
419  old_used = tail->used;
420  do {
421    tail->buffer[tail->used] = s[i];
422    tail->used++;
423    i++;
424    length--;
425  } while (length>0);
426
427  // add terminating nul character
428
429  tail->buffer[tail->used] = '\0';
430  tail->used++;
431
432  // and return start of new string
433
434  return &tail->buffer[old_used];
435}
436
437char *char_buffer::add_string (const string &s)
438{
439  return add_string(s.contents(), s.length());
440}
441
442/*
443 *  the classes and methods for maintaining glyph positions.
444 */
445
446class text_glob {
447public:
448  void text_glob_html      (style *s, char *str, int length,
449			    int min_vertical, int min_horizontal,
450			    int max_vertical, int max_horizontal);
451  void text_glob_special   (style *s, char *str, int length,
452			    int min_vertical, int min_horizontal,
453			    int max_vertical, int max_horizontal);
454  void text_glob_line      (style *s,
455			    int min_vertical, int min_horizontal,
456			    int max_vertical, int max_horizontal,
457			    int thickness);
458  void text_glob_auto_image(style *s, char *str, int length,
459			    int min_vertical, int min_horizontal,
460			    int max_vertical, int max_horizontal);
461  void text_glob_tag       (style *s, char *str, int length,
462			    int min_vertical, int min_horizontal,
463			    int max_vertical, int max_horizontal);
464
465  text_glob                (void);
466  ~text_glob               (void);
467  int  is_a_line           (void);
468  int  is_a_tag            (void);
469  int  is_eol              (void);
470  int  is_auto_img         (void);
471  int  is_br               (void);
472  int  is_in               (void);
473  int  is_po               (void);
474  int  is_ti               (void);
475  int  is_ll               (void);
476  int  is_ce               (void);
477  int  is_tl               (void);
478  int  is_eo_tl            (void);
479  int  is_eol_ce           (void);
480  int  is_col              (void);
481  int  is_tab              (void);
482  int  is_tab0             (void);
483  int  is_ta               (void);
484  int  is_tab_ts           (void);
485  int  is_tab_te           (void);
486  int  is_nf               (void);
487  int  is_fi               (void);
488  int  is_eo_h             (void);
489  int  get_arg             (void);
490  int  get_tab_args        (char *align);
491
492  void        remember_table (html_table *t);
493  html_table *get_table      (void);
494
495  style           text_style;
496  const char     *text_string;
497  unsigned int    text_length;
498  int             minv, minh, maxv, maxh;
499  int             is_tag;               // is this a .br, .sp, .tl etc
500  int             is_img_auto;          // image created by eqn delim
501  int             is_special;           // text has come via 'x X html:'
502  int             is_line;              // is the command a <line>?
503  int             thickness;            // the thickness of a line
504  html_table     *tab;                  // table description
505
506private:
507  text_glob           (style *s, const char *str, int length,
508		       int min_vertical , int min_horizontal,
509		       int max_vertical , int max_horizontal,
510		       bool is_troff_command,
511		       bool is_auto_image, bool is_special_command,
512		       bool is_a_line    , int  thickness);
513};
514
515text_glob::text_glob (style *s, const char *str, int length,
516		      int min_vertical, int min_horizontal,
517		      int max_vertical, int max_horizontal,
518		      bool is_troff_command,
519		      bool is_auto_image, bool is_special_command,
520		      bool is_a_line_flag, int line_thickness)
521  : text_style(*s), text_string(str), text_length(length),
522    minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal),
523    is_tag(is_troff_command), is_img_auto(is_auto_image), is_special(is_special_command),
524    is_line(is_a_line_flag), thickness(line_thickness), tab(NULL)
525{
526}
527
528text_glob::text_glob ()
529  : text_string(NULL), text_length(0), minv(-1), minh(-1), maxv(-1), maxh(-1),
530    is_tag(FALSE), is_special(FALSE), is_line(FALSE), thickness(0), tab(NULL)
531{
532}
533
534text_glob::~text_glob ()
535{
536  if (tab != NULL)
537    delete tab;
538}
539
540/*
541 *  text_glob_html - used to place html text into the glob buffer.
542 */
543
544void text_glob::text_glob_html (style *s, char *str, int length,
545				int min_vertical , int min_horizontal,
546				int max_vertical , int max_horizontal)
547{
548  text_glob *g = new text_glob(s, str, length,
549			       min_vertical, min_horizontal, max_vertical, max_horizontal,
550			       FALSE, FALSE, FALSE, FALSE, 0);
551  *this = *g;
552  delete g;
553}
554
555/*
556 *  text_glob_html - used to place html specials into the glob buffer.
557 *                   This text is essentially html commands coming through
558 *                   from the macro sets, with special designated sequences of
559 *                   characters translated into html. See add_and_encode.
560 */
561
562void text_glob::text_glob_special (style *s, char *str, int length,
563				   int min_vertical , int min_horizontal,
564				   int max_vertical , int max_horizontal)
565{
566  text_glob *g = new text_glob(s, str, length,
567			       min_vertical, min_horizontal, max_vertical, max_horizontal,
568			       FALSE, FALSE, TRUE, FALSE, 0);
569  *this = *g;
570  delete g;
571}
572
573/*
574 *  text_glob_line - record horizontal draw line commands.
575 */
576
577void text_glob::text_glob_line (style *s,
578				int min_vertical , int min_horizontal,
579				int max_vertical , int max_horizontal,
580				int thickness_value)
581{
582  text_glob *g = new text_glob(s, "", 0,
583			       min_vertical, min_horizontal, max_vertical, max_horizontal,
584			       FALSE, FALSE, FALSE, TRUE, thickness_value);
585  *this = *g;
586  delete g;
587}
588
589/*
590 *  text_glob_auto_image - record the presence of a .auto-image tag command.
591 *                         Used to mark that an image has been created automatically
592 *                         by a preprocessor and (pre-grohtml/troff) combination.
593 *                         Under some circumstances images may not be created.
594 *                         (consider .EQ
595 *                                   delim $$
596 *                                   .EN
597 *                                   .TS
598 *                                   tab(!), center;
599 *                                   l!l.
600 *                                   $1 over x$!recripical of x
601 *                                   .TE
602 *
603 *                          the first auto-image marker is created via .EQ/.EN pair
604 *                          and no image is created.
605 *                          The second auto-image marker occurs at $1 over x$
606 *                          Currently this image will not be created
607 *                          as the whole of the table is created as an image.
608 *                          (Once html tables are handled by grohtml this will change.
609 *                           Shortly this will be the case).
610 */
611
612void text_glob::text_glob_auto_image(style *s, char *str, int length,
613				     int min_vertical, int min_horizontal,
614				     int max_vertical, int max_horizontal)
615{
616  text_glob *g = new text_glob(s, str, length,
617			       min_vertical, min_horizontal, max_vertical, max_horizontal,
618			       TRUE, TRUE, FALSE, FALSE, 0);
619  *this = *g;
620  delete g;
621}
622
623/*
624 *  text_glob_tag - records a troff tag.
625 */
626
627void text_glob::text_glob_tag (style *s, char *str, int length,
628			       int min_vertical, int min_horizontal,
629			       int max_vertical, int max_horizontal)
630{
631  text_glob *g = new text_glob(s, str, length,
632			       min_vertical, min_horizontal, max_vertical, max_horizontal,
633			       TRUE, FALSE, FALSE, FALSE, 0);
634  *this = *g;
635  delete g;
636}
637
638/*
639 *  is_a_line - returns TRUE if glob should be converted into an <hr>
640 */
641
642int text_glob::is_a_line (void)
643{
644  return is_line;
645}
646
647/*
648 *  is_a_tag - returns TRUE if glob contains a troff directive.
649 */
650
651int text_glob::is_a_tag (void)
652{
653  return is_tag;
654}
655
656/*
657 *  is_eol - returns TRUE if glob contains the tag eol
658 */
659
660int text_glob::is_eol (void)
661{
662  return is_tag && (strcmp(text_string, "devtag:.eol") == 0);
663}
664
665/*
666 *  is_eol_ce - returns TRUE if glob contains the tag eol.ce
667 */
668
669int text_glob::is_eol_ce (void)
670{
671  return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0);
672}
673
674/*
675 *  is_tl - returns TRUE if glob contains the tag .tl
676 */
677
678int text_glob::is_tl (void)
679{
680  return is_tag && (strcmp(text_string, "devtag:.tl") == 0);
681}
682
683/*
684 *  is_eo_tl - returns TRUE if glob contains the tag eo.tl
685 */
686
687int text_glob::is_eo_tl (void)
688{
689  return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0);
690}
691
692/*
693 *  is_nf - returns TRUE if glob contains the tag .fi 0
694 */
695
696int text_glob::is_nf (void)
697{
698  return is_tag && (strncmp(text_string, "devtag:.fi",
699			    strlen("devtag:.fi")) == 0) &&
700         (get_arg() == 0);
701}
702
703/*
704 *  is_fi - returns TRUE if glob contains the tag .fi 1
705 */
706
707int text_glob::is_fi (void)
708{
709  return( is_tag && (strncmp(text_string, "devtag:.fi",
710			     strlen("devtag:.fi")) == 0) &&
711	  (get_arg() == 1) );
712}
713
714/*
715 *  is_eo_h - returns TRUE if glob contains the tag .eo.h
716 */
717
718int text_glob::is_eo_h (void)
719{
720  return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0);
721}
722
723/*
724 *  is_ce - returns TRUE if glob contains the tag .ce
725 */
726
727int text_glob::is_ce (void)
728{
729  return is_tag && (strncmp(text_string, "devtag:.ce",
730			    strlen("devtag:.ce")) == 0);
731}
732
733/*
734 *  is_in - returns TRUE if glob contains the tag .in
735 */
736
737int text_glob::is_in (void)
738{
739  return is_tag && (strncmp(text_string, "devtag:.in ",
740			    strlen("devtag:.in ")) == 0);
741}
742
743/*
744 *  is_po - returns TRUE if glob contains the tag .po
745 */
746
747int text_glob::is_po (void)
748{
749  return is_tag && (strncmp(text_string, "devtag:.po ",
750			    strlen("devtag:.po ")) == 0);
751}
752
753/*
754 *  is_ti - returns TRUE if glob contains the tag .ti
755 */
756
757int text_glob::is_ti (void)
758{
759  return is_tag && (strncmp(text_string, "devtag:.ti ",
760			    strlen("devtag:.ti ")) == 0);
761}
762
763/*
764 *  is_ll - returns TRUE if glob contains the tag .ll
765 */
766
767int text_glob::is_ll (void)
768{
769  return is_tag && (strncmp(text_string, "devtag:.ll ",
770			    strlen("devtag:.ll ")) == 0);
771}
772
773/*
774 *  is_col - returns TRUE if glob contains the tag .col
775 */
776
777int text_glob::is_col (void)
778{
779  return is_tag && (strncmp(text_string, "devtag:.col",
780			    strlen("devtag:.col")) == 0);
781}
782
783/*
784 *  is_tab_ts - returns TRUE if glob contains the tag .tab_ts
785 */
786
787int text_glob::is_tab_ts (void)
788{
789  return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0);
790}
791
792/*
793 *  is_tab_te - returns TRUE if glob contains the tag .tab_te
794 */
795
796int text_glob::is_tab_te (void)
797{
798  return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0);
799}
800
801/*
802 *  is_ta - returns TRUE if glob contains the tag .ta
803 */
804
805int text_glob::is_ta (void)
806{
807  return is_tag && (strncmp(text_string, "devtag:.ta ",
808			    strlen("devtag:.ta ")) == 0);
809}
810
811/*
812 *  is_tab - returns TRUE if glob contains the tag tab
813 */
814
815int text_glob::is_tab (void)
816{
817  return is_tag && (strncmp(text_string, "devtag:tab ",
818			    strlen("devtag:tab ")) == 0);
819}
820
821/*
822 *  is_tab0 - returns TRUE if glob contains the tag tab0
823 */
824
825int text_glob::is_tab0 (void)
826{
827  return is_tag && (strncmp(text_string, "devtag:tab0",
828			    strlen("devtag:tab0")) == 0);
829}
830
831/*
832 *  is_auto_img - returns TRUE if the glob contains an automatically
833 *                generated image.
834 */
835
836int text_glob::is_auto_img (void)
837{
838  return is_img_auto;
839}
840
841/*
842 *  is_br - returns TRUE if the glob is a tag containing a .br
843 *          or an implied .br. Note that we do not include .nf or .fi
844 *          as grohtml will place a .br after these commands if they
845 *          should break the line.
846 */
847
848int text_glob::is_br (void)
849{
850  return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) ||
851			(strncmp("devtag:.sp", text_string,
852				 strlen("devtag:.sp")) == 0));
853}
854
855int text_glob::get_arg (void)
856{
857  if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
858    const char *p = text_string;
859
860    while ((*p != (char)0) && (!isspace(*p)))
861      p++;
862    while ((*p != (char)0) && (isspace(*p)))
863      p++;
864    if (*p == (char)0)
865      return -1;
866    return atoi(p);
867  }
868  return -1;
869}
870
871/*
872 *  get_tab_args - returns the tab position and alignment of the tab tag
873 */
874
875int text_glob::get_tab_args (char *align)
876{
877  if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
878    const char *p = text_string;
879
880    // firstly the alignment C|R|L
881    while ((*p != (char)0) && (!isspace(*p)))
882      p++;
883    while ((*p != (char)0) && (isspace(*p)))
884      p++;
885    *align = *p;
886    // now the int value
887    while ((*p != (char)0) && (!isspace(*p)))
888      p++;
889    while ((*p != (char)0) && (isspace(*p)))
890      p++;
891    if (*p == (char)0)
892      return -1;
893    return atoi(p);
894  }
895  return -1;
896}
897
898/*
899 *  remember_table - saves table, t, in the text_glob.
900 */
901
902void text_glob::remember_table (html_table *t)
903{
904  if (tab != NULL)
905    delete tab;
906  tab = t;
907}
908
909/*
910 *  get_table - returns the stored table description.
911 */
912
913html_table *text_glob::get_table (void)
914{
915  return tab;
916}
917
918/*
919 *  the class and methods used to construct ordered double linked
920 *  lists.  In a previous implementation we used templates via
921 *  #include "ordered-list.h", but this does assume that all C++
922 *  compilers can handle this feature. Pragmatically it is safer to
923 *  assume this is not the case.
924 */
925
926struct element_list {
927  element_list *right;
928  element_list *left;
929  text_glob    *datum;
930  int           lineno;
931  int           minv, minh, maxv, maxh;
932
933  element_list  (text_glob *d,
934		 int line_number,
935		 int min_vertical, int min_horizontal,
936		 int max_vertical, int max_horizontal);
937  element_list  ();
938  ~element_list ();
939};
940
941element_list::element_list ()
942  : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1), maxv(-1), maxh(-1)
943{
944}
945
946/*
947 *  element_list - create a list element assigning the datum and region parameters.
948 */
949
950element_list::element_list (text_glob *in,
951			    int line_number,
952			    int min_vertical, int min_horizontal,
953			    int max_vertical, int max_horizontal)
954  : right(0), left(0), datum(in), lineno(line_number),
955    minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal)
956{
957}
958
959element_list::~element_list ()
960{
961  if (datum != NULL)
962    delete datum;
963}
964
965class list {
966public:
967       list             ();
968      ~list             ();
969  int  is_less          (element_list *a, element_list *b);
970  void add              (text_glob *in,
971		         int line_number,
972		         int min_vertical, int min_horizontal,
973		         int max_vertical, int max_horizontal);
974  void                  sub_move_right      (void);
975  void                  move_right          (void);
976  void                  move_left           (void);
977  int                   is_empty            (void);
978  int                   is_equal_to_tail    (void);
979  int                   is_equal_to_head    (void);
980  void                  start_from_head     (void);
981  void                  start_from_tail     (void);
982  void                  insert              (text_glob *in);
983  void                  move_to             (text_glob *in);
984  text_glob            *move_right_get_data (void);
985  text_glob            *move_left_get_data  (void);
986  text_glob            *get_data            (void);
987private:
988  element_list *head;
989  element_list *tail;
990  element_list *ptr;
991};
992
993/*
994 *  list - construct an empty list.
995 */
996
997list::list ()
998  : head(NULL), tail(NULL), ptr(NULL)
999{
1000}
1001
1002/*
1003 *  ~list - destroy a complete list.
1004 */
1005
1006list::~list()
1007{
1008  element_list *temp=head;
1009
1010  do {
1011    temp = head;
1012    if (temp != NULL) {
1013      head = head->right;
1014      delete temp;
1015    }
1016  } while ((head != NULL) && (head != tail));
1017}
1018
1019/*
1020 *  is_less - returns TRUE if a is left of b if on the same line or
1021 *            if a is higher up the page than b.
1022 */
1023
1024int list::is_less (element_list *a, element_list *b)
1025{
1026  // was if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) {
1027  if (a->lineno < b->lineno) {
1028    return( TRUE );
1029  } else if (a->lineno > b->lineno) {
1030    return( FALSE );
1031  } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) {
1032    return( a->minh < b->minh );
1033  } else {
1034    return( a->maxv < b->maxv );
1035  }
1036}
1037
1038/*
1039 *  add - adds a datum to the list in the order specified by the
1040 *        region position.
1041 */
1042
1043void list::add (text_glob *in, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal)
1044{
1045  // create a new list element with datum and position fields initialized
1046  element_list *t    = new element_list(in, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
1047  element_list *last;
1048
1049#if 0
1050  fprintf(stderr, "[%s %d,%d,%d,%d] ",
1051	  in->text_string, min_vertical, min_horizontal, max_vertical, max_horizontal);
1052  fflush(stderr);
1053#endif
1054
1055  if (head == NULL) {
1056    head     = t;
1057    tail     = t;
1058    ptr      = t;
1059    t->left  = t;
1060    t->right = t;
1061  } else {
1062    last = tail;
1063
1064    while ((last != head) && (is_less(t, last)))
1065      last = last->left;
1066
1067    if (is_less(t, last)) {
1068      t->right          = last;
1069      last->left->right = t;
1070      t->left           = last->left;
1071      last->left        = t;
1072      // now check for a new head
1073      if (last == head)
1074	head = t;
1075    } else {
1076      // add t beyond last
1077      t->right          = last->right;
1078      t->left           = last;
1079      last->right->left = t;
1080      last->right       = t;
1081      // now check for a new tail
1082      if (last == tail)
1083	tail = t;
1084    }
1085  }
1086}
1087
1088/*
1089 *  sub_move_right - removes the element which is currently pointed to by ptr
1090 *                   from the list and moves ptr to the right.
1091 */
1092
1093void list::sub_move_right (void)
1094{
1095  element_list *t=ptr->right;
1096
1097  if (head == tail) {
1098    head = NULL;
1099    if (tail != NULL)
1100      delete tail;
1101
1102    tail = NULL;
1103    ptr  = NULL;
1104  } else {
1105    if (head == ptr)
1106      head = head->right;
1107    if (tail == ptr)
1108      tail = tail->left;
1109    ptr->left->right = ptr->right;
1110    ptr->right->left = ptr->left;
1111    ptr = t;
1112  }
1113}
1114
1115/*
1116 *  start_from_head - assigns ptr to the head.
1117 */
1118
1119void list::start_from_head (void)
1120{
1121  ptr = head;
1122}
1123
1124/*
1125 *  start_from_tail - assigns ptr to the tail.
1126 */
1127
1128void list::start_from_tail (void)
1129{
1130  ptr = tail;
1131}
1132
1133/*
1134 *  is_empty - returns TRUE if the list has no elements.
1135 */
1136
1137int list::is_empty (void)
1138{
1139  return head == NULL;
1140}
1141
1142/*
1143 *  is_equal_to_tail - returns TRUE if the ptr equals the tail.
1144 */
1145
1146int list::is_equal_to_tail (void)
1147{
1148  return ptr == tail;
1149}
1150
1151/*
1152 *  is_equal_to_head - returns TRUE if the ptr equals the head.
1153 */
1154
1155int list::is_equal_to_head (void)
1156{
1157  return ptr == head;
1158}
1159
1160/*
1161 *  move_left - moves the ptr left.
1162 */
1163
1164void list::move_left (void)
1165{
1166  ptr = ptr->left;
1167}
1168
1169/*
1170 *  move_right - moves the ptr right.
1171 */
1172
1173void list::move_right (void)
1174{
1175  ptr = ptr->right;
1176}
1177
1178/*
1179 *  get_datum - returns the datum referenced via ptr.
1180 */
1181
1182text_glob* list::get_data (void)
1183{
1184  return ptr->datum;
1185}
1186
1187/*
1188 *  move_right_get_data - returns the datum referenced via ptr and moves
1189 *                        ptr right.
1190 */
1191
1192text_glob* list::move_right_get_data (void)
1193{
1194  ptr = ptr->right;
1195  if (ptr == head)
1196    return NULL;
1197  else
1198    return ptr->datum;
1199}
1200
1201/*
1202 *  move_left_get_data - returns the datum referenced via ptr and moves
1203 *                       ptr right.
1204 */
1205
1206text_glob* list::move_left_get_data (void)
1207{
1208  ptr = ptr->left;
1209  if (ptr == tail)
1210    return NULL;
1211  else
1212    return ptr->datum;
1213}
1214
1215/*
1216 *  insert - inserts data after the current position.
1217 */
1218
1219void list::insert (text_glob *in)
1220{
1221  if (is_empty())
1222    fatal("list must not be empty if we are inserting data");
1223  else {
1224    if (ptr == NULL)
1225      ptr = head;
1226
1227    element_list *t = new element_list(in, ptr->lineno, ptr->minv, ptr->minh, ptr->maxv, ptr->maxh);
1228    if (ptr == tail)
1229      tail = t;
1230    ptr->right->left = t;
1231    t->right = ptr->right;
1232    ptr->right = t;
1233    t->left = ptr;
1234  }
1235}
1236
1237/*
1238 *  move_to - moves the current position to the point where data, in, exists.
1239 *            This is an expensive method and should be used sparingly.
1240 */
1241
1242void list::move_to (text_glob *in)
1243{
1244  ptr = head;
1245  while (ptr != tail && ptr->datum != in)
1246    ptr = ptr->right;
1247}
1248
1249/*
1250 *  page class and methods
1251 */
1252
1253class page {
1254public:
1255                              page            (void);
1256  void                        add             (style *s, const string &str,
1257					       int line_number,
1258					       int min_vertical, int min_horizontal,
1259					       int max_vertical, int max_horizontal);
1260  void                        add_tag         (style *s, const string &str,
1261					       int line_number,
1262					       int min_vertical, int min_horizontal,
1263					       int max_vertical, int max_horizontal);
1264  void                        add_and_encode  (style *s, const string &str,
1265					       int line_number,
1266					       int min_vertical, int min_horizontal,
1267					       int max_vertical, int max_horizontal,
1268					       int is_tag);
1269  void                        add_line        (style *s,
1270					       int line_number,
1271					       int x1, int y1, int x2, int y2,
1272					       int thickness);
1273  void                        insert_tag      (const string &str);
1274  void                        dump_page       (void);   // debugging method
1275
1276  // and the data
1277
1278  list                        glyphs;         // position of glyphs and specials on page
1279  char_buffer                 buffer;         // all characters for this page
1280};
1281
1282page::page()
1283{
1284}
1285
1286/*
1287 *  insert_tag - inserts a tag after the current position.
1288 */
1289
1290void page::insert_tag (const string &str)
1291{
1292  if (str.length() > 0) {
1293    text_glob *g=new text_glob();
1294    text_glob *f=glyphs.get_data();
1295    g->text_glob_tag(&f->text_style, buffer.add_string(str), str.length(),
1296		     f->minv, f->minh, f->maxv, f->maxh);
1297    glyphs.insert(g);
1298  }
1299}
1300
1301/*
1302 *  add - add html text to the list of glyphs.
1303 */
1304
1305void page::add (style *s, const string &str,
1306		int line_number,
1307		int min_vertical, int min_horizontal,
1308		int max_vertical, int max_horizontal)
1309{
1310  if (str.length() > 0) {
1311    text_glob *g=new text_glob();
1312    g->text_glob_html(s, buffer.add_string(str), str.length(),
1313		      min_vertical, min_horizontal, max_vertical, max_horizontal);
1314    glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
1315  }
1316}
1317
1318/*
1319 *  add_tag - adds a troff tag, for example: .tl .sp .br
1320 */
1321
1322void page::add_tag (style *s, const string &str,
1323		    int line_number,
1324		    int min_vertical, int min_horizontal,
1325		    int max_vertical, int max_horizontal)
1326{
1327  if (str.length() > 0) {
1328    text_glob *g;
1329
1330    if (strncmp((str+'\0').contents(), "devtag:.auto-image",
1331		strlen("devtag:.auto-image")) == 0) {
1332      g = new text_glob();
1333      g->text_glob_auto_image(s, buffer.add_string(str), str.length(),
1334			      min_vertical, min_horizontal, max_vertical, max_horizontal);
1335    } else {
1336      g = new text_glob();
1337      g->text_glob_tag(s, buffer.add_string(str), str.length(),
1338		       min_vertical, min_horizontal, max_vertical, max_horizontal);
1339    }
1340    glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
1341  }
1342}
1343
1344/*
1345 *  add_line - adds the <line> primitive providing that y1==y2
1346 */
1347
1348void page::add_line (style *s,
1349		     int line_number,
1350		     int x_1, int y_1, int x_2, int y_2,
1351		     int thickness)
1352{
1353  if (y_1 == y_2) {
1354    text_glob *g = new text_glob();
1355    g->text_glob_line(s,
1356		      min(y_1, y_2), min(x_1, x_2),
1357		      max(y_1, y_2), max(x_1, x_2),
1358		      thickness);
1359    glyphs.add(g, line_number,
1360	       min(y_1, y_2), min(x_1, x_2),
1361	       max(y_1, y_2), max(x_1, x_2));
1362  }
1363}
1364
1365/*
1366 *  to_unicode - returns a unicode translation of int, ch.
1367 */
1368
1369static char *to_unicode (unsigned int ch)
1370{
1371  static char buf[30];
1372
1373  sprintf(buf, "&#%u;", ch);
1374  return buf;
1375}
1376
1377/*
1378 *  add_and_encode - adds a special string to the page, it translates the string
1379 *                   into html glyphs. The special string will have come from x X html:
1380 *                   and can contain troff character encodings which appear as
1381 *                   \(char\). A sequence of \\ represents \.
1382 *                   So for example we can write:
1383 *                      "cost = \(Po\)3.00 file = \\foo\\bar"
1384 *                   which is translated into:
1385 *                      "cost = &pound;3.00 file = \foo\bar"
1386 */
1387
1388void page::add_and_encode (style *s, const string &str,
1389			   int line_number,
1390			   int min_vertical, int min_horizontal,
1391			   int max_vertical, int max_horizontal,
1392			   int is_tag)
1393{
1394  string html_string;
1395  char *html_glyph;
1396  int i=0;
1397
1398  if (s->f == NULL)
1399    return;
1400  while (i < str.length()) {
1401    if ((i+1<str.length()) && (str.substring(i, 2) == string("\\("))) {
1402      // start of escape
1403      i += 2; // move over \(
1404      int a = i;
1405      while ((i+1<str.length()) && (str.substring(i, 2) != string("\\)"))) {
1406	i++;
1407      }
1408      int n = i;
1409      if ((i+1<str.length()) && (str.substring(i, 2) == string("\\)")))
1410	i++;
1411      else
1412	n = -1;
1413      if (n > 0) {
1414	string troff_charname = str.substring(a, n-a);
1415	html_glyph = get_html_translation(s->f, troff_charname);
1416	if (html_glyph)
1417	  html_string += html_glyph;
1418	else {
1419	  int idx=s->f->name_to_index((troff_charname + '\0').contents());
1420
1421	  if (s->f->contains(idx) && (idx != 0))
1422	    html_string += s->f->get_code(idx);
1423	}
1424      }
1425    } else
1426      html_string += str[i];
1427    i++;
1428  }
1429  if (html_string.length() > 0) {
1430    text_glob *g=new text_glob();
1431    if (is_tag)
1432      g->text_glob_tag(s, buffer.add_string(html_string),
1433		       html_string.length(),
1434		       min_vertical, min_horizontal,
1435		       max_vertical, max_horizontal);
1436    else
1437      g->text_glob_special(s, buffer.add_string(html_string),
1438			   html_string.length(),
1439			   min_vertical, min_horizontal,
1440			   max_vertical, max_horizontal);
1441    glyphs.add(g, line_number, min_vertical,
1442	       min_horizontal, max_vertical, max_horizontal);
1443  }
1444}
1445
1446/*
1447 *  dump_page - dump the page contents for debugging purposes.
1448 */
1449
1450void page::dump_page(void)
1451{
1452#if defined(DEBUG_TABLES)
1453  text_glob *old_pos = glyphs.get_data();
1454  text_glob *g;
1455
1456  printf("\n<!--\n");
1457  printf("\n\ndebugging start\n");
1458  glyphs.start_from_head();
1459  do {
1460    g = glyphs.get_data();
1461    if (g->is_tab_ts()) {
1462      printf("\n\n");
1463      if (g->get_table() != NULL)
1464	g->get_table()->dump_table();
1465    }
1466    printf("%s ", g->text_string);
1467    if (g->is_tab_te())
1468      printf("\n\n");
1469    glyphs.move_right();
1470  } while (! glyphs.is_equal_to_head());
1471  glyphs.move_to(old_pos);
1472  printf("\ndebugging end\n\n");
1473  printf("\n-->\n");
1474  fflush(stdout);
1475#endif
1476}
1477
1478/*
1479 *  font classes and methods
1480 */
1481
1482class html_font : public font {
1483  html_font(const char *);
1484public:
1485  int encoding_index;
1486  char *encoding;
1487  char *reencoded_name;
1488  ~html_font();
1489  static html_font *load_html_font(const char *);
1490};
1491
1492html_font *html_font::load_html_font(const char *s)
1493{
1494  html_font *f = new html_font(s);
1495  if (!f->load()) {
1496    delete f;
1497    return 0;
1498  }
1499  return f;
1500}
1501
1502html_font::html_font(const char *nm)
1503: font(nm)
1504{
1505}
1506
1507html_font::~html_font()
1508{
1509}
1510
1511/*
1512 *  a simple class to contain the header to this document
1513 */
1514
1515class title_desc {
1516public:
1517          title_desc ();
1518         ~title_desc ();
1519
1520  int     has_been_written;
1521  int     has_been_found;
1522  int     with_h1;
1523  string  text;
1524};
1525
1526
1527title_desc::title_desc ()
1528  : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE)
1529{
1530}
1531
1532title_desc::~title_desc ()
1533{
1534}
1535
1536class header_desc {
1537public:
1538                            header_desc ();
1539                           ~header_desc ();
1540
1541  int                       no_of_level_one_headings; // how many .SH or .NH 1 have we found?
1542  int                       no_of_headings;           // how many headings have we found?
1543  char_buffer               headings;                 // all the headings used in the document
1544  list                      headers;                  // list of headers built from .NH and .SH
1545  list                      header_filename;          // in which file is this header?
1546  int                       header_level;             // current header level
1547  int                       written_header;           // have we written the header yet?
1548  string                    header_buffer;            // current header text
1549
1550  void                      write_headings (FILE *f, int force);
1551};
1552
1553header_desc::header_desc ()
1554  :   no_of_level_one_headings(0), no_of_headings(0),
1555      header_level(2), written_header(0)
1556{
1557}
1558
1559header_desc::~header_desc ()
1560{
1561}
1562
1563/*
1564 *  write_headings - emits a list of links for the headings in this document
1565 */
1566
1567void header_desc::write_headings (FILE *f, int force)
1568{
1569  text_glob *g;
1570
1571  if (auto_links || force) {
1572    if (! headers.is_empty()) {
1573      int h=1;
1574
1575      headers.start_from_head();
1576      header_filename.start_from_head();
1577      do {
1578	g = headers.get_data();
1579	fputs("<a href=\"", f);
1580	if (multiple_files && (! header_filename.is_empty())) {
1581	  text_glob *fn = header_filename.get_data();
1582	  fputs(fn->text_string, f);
1583	}
1584	fputs("#", f);
1585	if (simple_anchors) {
1586	  string buffer(ANCHOR_TEMPLATE);
1587
1588	  buffer += as_string(h);
1589	  buffer += '\0';
1590	  fputs(buffer.contents(), f);
1591	} else
1592	  fputs(g->text_string, f);
1593	h++;
1594	fputs("\">", f);
1595	fputs(g->text_string, f);
1596        fputs("</a><br>\n", f);
1597	headers.move_right();
1598	if (multiple_files && (! header_filename.is_empty()))
1599	  header_filename.move_right();
1600      } while (! headers.is_equal_to_head());
1601      fputs("\n", f);
1602    }
1603  }
1604}
1605
1606struct assert_pos {
1607  assert_pos *next;
1608  const char *val;
1609  const char *id;
1610};
1611
1612class assert_state {
1613public:
1614        assert_state ();
1615        ~assert_state ();
1616
1617  void  addx (const char *c, const char *i, const char *v,
1618	      const char *f, const char *l);
1619  void  addy (const char *c, const char *i, const char *v,
1620	      const char *f, const char *l);
1621  void  build(const char *c, const char *v,
1622	      const char *f, const char *l);
1623  void  check_br (int br);
1624  void  check_ce (int ce);
1625  void  check_fi (int fi);
1626  void  check_sp (int sp);
1627  void  reset    (void);
1628
1629private:
1630  int check_br_flag;
1631  int check_ce_flag;
1632  int check_fi_flag;
1633  int check_sp_flag;
1634  const char *val_br;
1635  const char *val_ce;
1636  const char *val_fi;
1637  const char *val_sp;
1638  const char *file_br;
1639  const char *file_ce;
1640  const char *file_fi;
1641  const char *file_sp;
1642  const char *line_br;
1643  const char *line_ce;
1644  const char *line_fi;
1645  const char *line_sp;
1646
1647  assert_pos *xhead;
1648  assert_pos *yhead;
1649
1650  void add (assert_pos **h,
1651	    const char *c, const char *i, const char *v,
1652	    const char *f, const char *l);
1653  void compare(assert_pos *t,
1654	       const char *v, const char *f, const char *l);
1655  void close (const char *c);
1656  void set (const char *c, const char *v,
1657	    const char *f, const char *l);
1658  void check_value (const char *s, int v, const char *name,
1659		    const char *f, const char *l, int *flag);
1660  int check_value_error (int c, int v, const char *s,
1661			 const char *name,
1662			 const char *f, const char *l, int flag);
1663};
1664
1665assert_state::assert_state ()
1666{
1667  reset();
1668  val_br   = NULL;
1669  val_ce   = NULL;
1670  val_fi   = NULL;
1671  val_sp   = NULL;
1672  file_br  = NULL;
1673  file_ce  = NULL;
1674  file_fi  = NULL;
1675  file_sp  = NULL;
1676  line_br  = NULL;
1677  line_ce  = NULL;
1678  line_fi  = NULL;
1679  line_sp  = NULL;
1680  xhead    = NULL;
1681  yhead    = NULL;
1682}
1683
1684assert_state::~assert_state ()
1685{
1686  assert_pos *t;
1687
1688  while (xhead != NULL) {
1689    t = xhead;
1690    xhead = xhead->next;
1691    a_delete (char *)t->val;
1692    a_delete (char *)t->id;
1693    delete t;
1694  }
1695  while (yhead != NULL) {
1696    t = yhead;
1697    yhead = yhead->next;
1698    a_delete (char *)t->val;
1699    a_delete (char *)t->id;
1700    delete t;
1701  }
1702}
1703
1704void assert_state::reset (void)
1705{
1706  check_br_flag = 0;
1707  check_ce_flag = 0;
1708  check_fi_flag = 0;
1709  check_sp_flag = 0;
1710}
1711
1712void assert_state::add (assert_pos **h,
1713			const char *c, const char *i, const char *v,
1714			const char *f, const char *l)
1715{
1716  assert_pos *t = *h;
1717
1718  while (t != NULL) {
1719    if (strcmp(t->id, i) == 0)
1720      break;
1721    t = t->next;
1722  }
1723  if (t != NULL && v != NULL && (v[0] != '='))
1724    compare(t, v, f, l);
1725  else {
1726    if (t == NULL) {
1727      t = new assert_pos;
1728      t->next = *h;
1729      (*h) = t;
1730    }
1731    if (v == NULL || v[0] != '=') {
1732      if (f == NULL)
1733	f = "stdin";
1734      if (l == NULL)
1735	l = "<none>";
1736      if (v == NULL)
1737	v = "no value at all";
1738      fprintf(stderr, "%s:%s:error in assert format of id=%s expecting value to be prefixed with an `=' got %s\n",
1739	      f, l, i, v);
1740    }
1741    t->id = i;
1742    t->val = v;
1743    a_delete (char *)c;
1744    a_delete (char *)f;
1745    a_delete (char *)l;
1746  }
1747}
1748
1749void assert_state::addx (const char *c, const char *i, const char *v,
1750			 const char *f, const char *l)
1751{
1752  add(&xhead, c, i, v, f, l);
1753}
1754
1755void assert_state::addy (const char *c, const char *i, const char *v,
1756			 const char *f, const char *l)
1757{
1758  add(&yhead, c, i, v, f, l);
1759}
1760
1761void assert_state::compare(assert_pos *t,
1762			   const char *v, const char *f, const char *l)
1763{
1764  const char *s=t->val;
1765
1766  while ((*v) == '=')
1767    v++;
1768  while ((*s) == '=')
1769    s++;
1770
1771  if (strcmp(v, s) != 0) {
1772    if (f == NULL)
1773      f = "stdin";
1774    if (l == NULL)
1775      l = "<none>";
1776    fprintf(stderr, "%s:%s: grohtml assertion failed at id%s expecting %s and was given %s\n",
1777	    f, l, t->id, s, v);
1778  }
1779}
1780
1781void assert_state::close (const char *c)
1782{
1783  if (strcmp(c, "sp") == 0)
1784    check_sp_flag = 0;
1785  else if (strcmp(c, "br") == 0)
1786    check_br_flag = 0;
1787  else if (strcmp(c, "fi") == 0)
1788    check_fi_flag = 0;
1789  else if (strcmp(c, "nf") == 0)
1790    check_fi_flag = 0;
1791  else if (strcmp(c, "ce") == 0)
1792    check_ce_flag = 0;
1793  else
1794    fprintf(stderr, "internal error: unrecognised tag in grohtml (%s)\n", c);
1795}
1796
1797const char *replace_negate_str (const char *before, char *after)
1798{
1799  if (before != NULL)
1800    a_delete (char *)before;
1801
1802  if (strlen(after) > 0) {
1803    int d = atoi(after);
1804
1805    if (d < 0 || d > 1) {
1806      fprintf(stderr, "expecting nf/fi value to be 0 or 1 not %d\n", d);
1807      d = 0;
1808    }
1809    if (d == 0)
1810      after[0] = '1';
1811    else
1812      after[0] = '0';
1813    after[1] = (char)0;
1814  }
1815  return after;
1816}
1817
1818const char *replace_str (const char *before, const char *after)
1819{
1820  if (before != NULL)
1821    a_delete (char *)before;
1822  return after;
1823}
1824
1825void assert_state::set (const char *c, const char *v,
1826			const char *f, const char *l)
1827{
1828  if (l == NULL)
1829    l = "<none>";
1830  if (f == NULL)
1831    f = "stdin";
1832
1833  // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v);
1834  if (strcmp(c, "sp") == 0) {
1835    check_sp_flag = 1;
1836    val_sp = replace_str(val_sp, strsave(v));
1837    file_sp = replace_str(file_sp, strsave(f));
1838    line_sp = replace_str(line_sp, strsave(l));
1839  } else if (strcmp(c, "br") == 0) {
1840    check_br_flag = 1;
1841    val_br = replace_str(val_br, strsave(v));
1842    file_br = replace_str(file_br, strsave(f));
1843    line_br = replace_str(line_br, strsave(l));
1844  } else if (strcmp(c, "fi") == 0) {
1845    check_fi_flag = 1;
1846    val_fi = replace_str(val_fi, strsave(v));
1847    file_fi = replace_str(file_fi, strsave(f));
1848    line_fi = replace_str(line_fi, strsave(l));
1849  } else if (strcmp(c, "nf") == 0) {
1850    check_fi_flag = 1;
1851    val_fi = replace_negate_str(val_fi, strsave(v));
1852    file_fi = replace_str(file_fi, strsave(f));
1853    line_fi = replace_str(line_fi, strsave(l));
1854  } else if (strcmp(c, "ce") == 0) {
1855    check_ce_flag = 1;
1856    val_ce = replace_str(val_ce, strsave(v));
1857    file_ce = replace_str(file_ce, strsave(f));
1858    line_ce = replace_str(line_ce, strsave(l));
1859  }
1860}
1861
1862/*
1863 *  build - builds the troff state assertion.
1864 *          see tmac/www.tmac for cmd examples.
1865 */
1866
1867void assert_state::build (const char *c, const char *v,
1868			  const char *f, const char *l)
1869{
1870  if (c[0] == '{')
1871    set(&c[1], v, f, l);
1872  if (c[0] == '}')
1873    close(&c[1]);
1874}
1875
1876int assert_state::check_value_error (int c, int v, const char *s,
1877				     const char *name,
1878				     const char *f, const char *l, int flag)
1879{
1880  if (! c) {
1881    if (f == NULL)
1882      f = "stdin";
1883    if (l == NULL)
1884      l = "<none>";
1885    fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed, expected %s to be %s but found it to contain %d\n",
1886	    f, l, name, s, v);
1887    return 0;
1888  }
1889  return flag;
1890}
1891
1892void assert_state::check_value (const char *s, int v, const char *name,
1893				const char *f, const char *l, int *flag)
1894{
1895  if (strncmp(s, "<=", 2) == 0)
1896    *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag);
1897  else if (strncmp(s, ">=", 2) == 0)
1898    *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag);
1899  else if (strncmp(s, "==", 2) == 0)
1900    *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag);
1901  else if (strncmp(s, "!=", 2) == 0)
1902    *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag);
1903  else if (strncmp(s, "<", 1) == 0)
1904    *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag);
1905  else if (strncmp(s, ">", 1) == 0)
1906    *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag);
1907  else if (strncmp(s, "=", 1) == 0)
1908    *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag);
1909  else
1910    *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag);
1911}
1912
1913void assert_state::check_sp (int sp)
1914{
1915  if (check_sp_flag)
1916    check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag);
1917}
1918
1919void assert_state::check_fi (int fi)
1920{
1921  if (check_fi_flag)
1922    check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag);
1923}
1924
1925void assert_state::check_br (int br)
1926{
1927  if (check_br_flag)
1928    check_value(val_br, br, "br", file_br, line_br, &check_br_flag);
1929}
1930
1931void assert_state::check_ce (int ce)
1932{
1933  if (check_ce_flag)
1934    check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag);
1935}
1936
1937class html_printer : public printer {
1938  files                file_list;
1939  simple_output        html;
1940  int                  res;
1941  int                  space_char_index;
1942  int                  space_width;
1943  int                  no_of_printed_pages;
1944  int                  paper_length;
1945  string               sbuf;
1946  int                  sbuf_start_hpos;
1947  int                  sbuf_vpos;
1948  int                  sbuf_end_hpos;
1949  int                  sbuf_prev_hpos;
1950  int                  sbuf_kern;
1951  style                sbuf_style;
1952  int                  last_sbuf_length;
1953  int                  overstrike_detected;
1954  style                output_style;
1955  int                  output_hpos;
1956  int                  output_vpos;
1957  int                  output_vpos_max;
1958  int                  output_draw_point_size;
1959  int                  line_thickness;
1960  int                  output_line_thickness;
1961  unsigned char        output_space_code;
1962  char                *inside_font_style;
1963  int                  page_number;
1964  title_desc           title;
1965  header_desc          header;
1966  int                  header_indent;
1967  int                  supress_sub_sup;
1968  int                  cutoff_heading;
1969  page                *page_contents;
1970  html_text           *current_paragraph;
1971  html_indent         *indent;
1972  html_table          *table;
1973  int                  end_center;
1974  int                  end_tempindent;
1975  TAG_ALIGNMENT        next_tag;
1976  int                  fill_on;
1977  int                  max_linelength;
1978  int                  linelength;
1979  int                  pageoffset;
1980  int                  troff_indent;
1981  int                  device_indent;
1982  int                  temp_indent;
1983  int                  pointsize;
1984  int                  vertical_spacing;
1985  int                  line_number;
1986  color               *background;
1987  int                  seen_indent;
1988  int                  next_indent;
1989  int                  seen_pageoffset;
1990  int                  next_pageoffset;
1991  int                  seen_linelength;
1992  int                  next_linelength;
1993  int                  seen_center;
1994  int                  next_center;
1995  int                  seen_space;
1996  int                  seen_break;
1997  int                  current_column;
1998  int                  row_space;
1999  assert_state         as;
2000
2001  void  flush_sbuf                    ();
2002  void  set_style                     (const style &);
2003  void  set_space_code                (unsigned char c);
2004  void  do_exec                       (char *, const environment *);
2005  void  do_import                     (char *, const environment *);
2006  void  do_def                        (char *, const environment *);
2007  void  do_mdef                       (char *, const environment *);
2008  void  do_file                       (char *, const environment *);
2009  void  set_line_thickness            (const environment *);
2010  void  terminate_current_font        (void);
2011  void  flush_font                    (void);
2012  void  add_to_sbuf                   (int index, const string &s);
2013  void  write_title                   (int in_head);
2014  int   sbuf_continuation             (int index, const char *name, const environment *env, int w);
2015  void  flush_page                    (void);
2016  void  troff_tag                     (text_glob *g);
2017  void  flush_globs                   (void);
2018  void  emit_line                     (text_glob *g);
2019  void  emit_raw                      (text_glob *g);
2020  void  emit_html                     (text_glob *g);
2021  void  determine_space               (text_glob *g);
2022  void  start_font                    (const char *name);
2023  void  end_font                      (const char *name);
2024  int   is_font_courier               (font *f);
2025  int   is_line_start                 (int nf);
2026  int   is_courier_until_eol          (void);
2027  void  start_size                    (int from, int to);
2028  void  do_font                       (text_glob *g);
2029  void  do_center                     (char *arg);
2030  void  do_check_center               (void);
2031  void  do_break                      (void);
2032  void  do_space                      (char *arg);
2033  void  do_eol                        (void);
2034  void  do_eol_ce                     (void);
2035  void  do_title                      (void);
2036  void  do_fill                       (char *arg);
2037  void  do_heading                    (char *arg);
2038  void  write_header                  (void);
2039  void  determine_header_level        (int level);
2040  void  do_linelength                 (char *arg);
2041  void  do_pageoffset                 (char *arg);
2042  void  do_indentation                (char *arg);
2043  void  do_tempindent                 (char *arg);
2044  void  do_indentedparagraph          (void);
2045  void  do_verticalspacing            (char *arg);
2046  void  do_pointsize                  (char *arg);
2047  void  do_centered_image             (void);
2048  void  do_left_image                 (void);
2049  void  do_right_image                (void);
2050  void  do_auto_image                 (text_glob *g, const char *filename);
2051  void  do_links                      (void);
2052  void  do_flush                      (void);
2053  void  do_job_name                   (char *name);
2054  void  do_head                       (char *name);
2055  void  insert_split_file             (void);
2056  int   is_in_middle                  (int left, int right);
2057  void  do_sup_or_sub                 (text_glob *g);
2058  int   start_subscript               (text_glob *g);
2059  int   end_subscript                 (text_glob *g);
2060  int   start_superscript             (text_glob *g);
2061  int   end_superscript               (text_glob *g);
2062  void  outstanding_eol               (int n);
2063  int   is_bold                       (font *f);
2064  font *make_bold                     (font *f);
2065  int   overstrike                    (int index, const char *name, const environment *env, int w);
2066  void  do_body                       (void);
2067  int   next_horiz_pos                (text_glob *g, int nf);
2068  void  lookahead_for_tables          (void);
2069  void  insert_tab_te                 (void);
2070  text_glob *insert_tab_ts            (text_glob *where);
2071  void insert_tab0_foreach_tab        (void);
2072  void insert_tab_0                   (text_glob *where);
2073  void do_indent                      (int in, int pageoff, int linelen);
2074  void shutdown_table                 (void);
2075  void do_tab_ts                      (text_glob *g);
2076  void do_tab_te                      (void);
2077  void do_col                         (char *s);
2078  void do_tab                         (char *s);
2079  void do_tab0                        (void);
2080  int  calc_nf                        (text_glob *g, int nf);
2081  void calc_po_in                     (text_glob *g, int nf);
2082  void remove_tabs                    (void);
2083  void remove_courier_tabs            (void);
2084  void update_min_max                 (colType type_of_col, int *minimum, int *maximum, text_glob *g);
2085  void add_table_end                  (const char *);
2086  void do_file_components             (void);
2087  void write_navigation               (const string &top, const string &prev,
2088				       const string &next, const string &current);
2089  void emit_link                      (const string &to, const char *name);
2090  int  get_troff_indent               (void);
2091  void restore_troff_indent           (void);
2092  void handle_assertion               (int minv, int minh, int maxv, int maxh, const char *s);
2093  void handle_state_assertion         (text_glob *g);
2094  void do_end_para                    (text_glob *g);
2095  int  round_width                    (int x);
2096  void handle_tag_within_title        (text_glob *g);
2097  void writeHeadMetaStyle             (void);
2098  // ADD HERE
2099
2100public:
2101  html_printer          ();
2102  ~html_printer         ();
2103  void set_char         (int i, font *f, const environment *env, int w, const char *name);
2104  void set_numbered_char(int num, const environment *env, int *widthp);
2105  int set_char_and_width(const char *nm, const environment *env,
2106			 int *widthp, font **f);
2107  void draw             (int code, int *p, int np, const environment *env);
2108  void begin_page       (int);
2109  void end_page         (int);
2110  void special          (char *arg, const environment *env, char type);
2111  void devtag           (char *arg, const environment *env, char type);
2112  font *make_font       (const char *);
2113  void end_of_line      ();
2114};
2115
2116printer *make_printer()
2117{
2118  return new html_printer;
2119}
2120
2121static void usage(FILE *stream);
2122
2123void html_printer::set_style(const style &sty)
2124{
2125  const char *fontname = sty.f->get_name();
2126  if (fontname == NULL)
2127    fatal("no internalname specified for font");
2128
2129#if 0
2130  change_font(fontname, (font::res/(72*font::sizescale))*sty.point_size);
2131#endif
2132}
2133
2134/*
2135 *  is_bold - returns TRUE if font, f, is bold.
2136 */
2137
2138int html_printer::is_bold (font *f)
2139{
2140  const char *fontname = f->get_name();
2141  return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0);
2142}
2143
2144/*
2145 *  make_bold - if a bold font of, f, exists then return it.
2146 */
2147
2148font *html_printer::make_bold (font *f)
2149{
2150  const char *fontname = f->get_name();
2151
2152  if (strcmp(fontname, "B") == 0)
2153    return f;
2154  if (strcmp(fontname, "I") == 0)
2155    return font::load_font("BI");
2156  if (strcmp(fontname, "BI") == 0)
2157    return f;
2158  return NULL;
2159}
2160
2161void html_printer::end_of_line()
2162{
2163  flush_sbuf();
2164  line_number++;
2165}
2166
2167/*
2168 *  emit_line - writes out a horizontal rule.
2169 */
2170
2171void html_printer::emit_line (text_glob *)
2172{
2173  // --fixme-- needs to know the length in percentage
2174  html.put_string("<hr>");
2175}
2176
2177/*
2178 *  restore_troff_indent - is called when we have temporarily shutdown
2179 *                         indentation (typically done when we have
2180 *                         centered an image).
2181 */
2182
2183void html_printer::restore_troff_indent (void)
2184{
2185  troff_indent = next_indent;
2186  if (troff_indent > 0) {
2187    /*
2188     *  force device indentation
2189     */
2190    device_indent = 0;
2191    do_indent(get_troff_indent(), pageoffset, linelength);
2192  }
2193}
2194
2195/*
2196 *  emit_raw - writes the raw html information directly to the device.
2197 */
2198
2199void html_printer::emit_raw (text_glob *g)
2200{
2201  do_font(g);
2202  if (next_tag == INLINE) {
2203    determine_space(g);
2204    current_paragraph->do_emittext(g->text_string, g->text_length);
2205  } else {
2206    int space = current_paragraph->retrieve_para_space() || seen_space;
2207
2208    current_paragraph->done_para();
2209    shutdown_table();
2210    switch (next_tag) {
2211
2212    case CENTERED:
2213      current_paragraph->do_para("align=center", space);
2214      break;
2215    case LEFT:
2216      current_paragraph->do_para(&html, "align=left", get_troff_indent(), pageoffset, linelength, space);
2217      break;
2218    case RIGHT:
2219      current_paragraph->do_para(&html, "align=right", get_troff_indent(), pageoffset, linelength, space);
2220      break;
2221    default:
2222      fatal("unknown enumeration");
2223    }
2224    current_paragraph->do_emittext(g->text_string, g->text_length);
2225    current_paragraph->done_para();
2226    next_tag        = INLINE;
2227    supress_sub_sup = TRUE;
2228    seen_space      = FALSE;
2229    restore_troff_indent();
2230  }
2231}
2232
2233/*
2234 *  handle_tag_within_title - handle a limited number of tags within
2235 *                            the context of a table. Those tags which
2236 *                            set values rather than generate spaces
2237 *                            and paragraphs.
2238 */
2239
2240void html_printer::handle_tag_within_title (text_glob *g)
2241{
2242  if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll()
2243      || g->is_fi() || g->is_nf())
2244    troff_tag(g);
2245}
2246
2247/*
2248 *  do_center - handle the .ce commands from troff.
2249 */
2250
2251void html_printer::do_center (char *arg)
2252{
2253  next_center = atoi(arg);
2254  seen_center = TRUE;
2255}
2256
2257/*
2258 *  do_centered_image - set a flag such that the next devtag is
2259 *                      placed inside a centered paragraph.
2260 */
2261
2262void html_printer::do_centered_image (void)
2263{
2264  next_tag = CENTERED;
2265}
2266
2267/*
2268 *  do_right_image - set a flag such that the next devtag is
2269 *                   placed inside a right aligned paragraph.
2270 */
2271
2272void html_printer::do_right_image (void)
2273{
2274  next_tag = RIGHT;
2275}
2276
2277/*
2278 *  do_left_image - set a flag such that the next devtag is
2279 *                  placed inside a left aligned paragraph.
2280 */
2281
2282void html_printer::do_left_image (void)
2283{
2284  next_tag = LEFT;
2285}
2286
2287/*
2288 *  exists - returns TRUE if filename exists.
2289 */
2290
2291static int exists (const char *filename)
2292{
2293  FILE *fp = fopen(filename, "r");
2294
2295  if (fp == 0) {
2296    return( FALSE );
2297  } else {
2298    fclose(fp);
2299    return( TRUE );
2300  }
2301}
2302
2303/*
2304 *  generate_img_src - returns a html image tag for the filename
2305 *                     providing that the image exists.
2306 */
2307
2308static string &generate_img_src (const char *filename)
2309{
2310  string *s = new string("");
2311
2312  while (filename && (filename[0] == ' ')) {
2313    filename++;
2314  }
2315  if (exists(filename))
2316    *s += string("<img src=\"") + filename + "\" "
2317	  + "alt=\"Image " + filename + "\">";
2318  return *s;
2319}
2320
2321/*
2322 *  do_auto_image - tests whether the image, indicated by filename,
2323 *                  is present, if so then it emits an html image tag.
2324 *                  An image tag may be passed through from pic, eqn
2325 *                  but the corresponding image might not be created.
2326 *                  Consider .EQ delim $$ .EN  or an empty .PS .PE.
2327 */
2328
2329void html_printer::do_auto_image (text_glob *g, const char *filename)
2330{
2331  string buffer = generate_img_src(filename);
2332
2333  if (! buffer.empty()) {
2334    /*
2335     *  utilize emit_raw by creating a new text_glob.
2336     */
2337    text_glob h = *g;
2338
2339    h.text_string = buffer.contents();
2340    h.text_length = buffer.length();
2341    emit_raw(&h);
2342  } else
2343    next_tag = INLINE;
2344}
2345
2346/*
2347 *  outstanding_eol - call do_eol, n, times.
2348 */
2349
2350void html_printer::outstanding_eol (int n)
2351{
2352  while (n > 0) {
2353    do_eol();
2354    n--;
2355  }
2356}
2357
2358/*
2359 *  do_title - handle the .tl commands from troff.
2360 */
2361
2362void html_printer::do_title (void)
2363{
2364  text_glob    *t;
2365  int           removed_from_head;
2366
2367  if (page_number == 1) {
2368    int found_title_start  = FALSE;
2369    if (! page_contents->glyphs.is_empty()) {
2370      page_contents->glyphs.sub_move_right();       /* move onto next word */
2371      do {
2372	t = page_contents->glyphs.get_data();
2373	removed_from_head = FALSE;
2374	if (t->is_auto_img()) {
2375	  string img = generate_img_src((char *)(t->text_string + 20));
2376
2377	  if (! img.empty()) {
2378	    if (found_title_start)
2379	      title.text += " ";
2380	    found_title_start = TRUE;
2381	    title.has_been_found = TRUE;
2382	    title.text += img;
2383	  }
2384	  page_contents->glyphs.sub_move_right(); 	  /* move onto next word */
2385	  removed_from_head = ((!page_contents->glyphs.is_empty()) &&
2386			       (page_contents->glyphs.is_equal_to_head()));
2387	} else if (t->is_eo_tl()) {
2388	  /* end of title found
2389	   */
2390	  title.has_been_found = TRUE;
2391	  return;
2392	} else if (t->is_a_tag()) {
2393	  handle_tag_within_title(t);
2394	  page_contents->glyphs.sub_move_right(); 	  /* move onto next word */
2395	  removed_from_head = ((!page_contents->glyphs.is_empty()) &&
2396			       (page_contents->glyphs.is_equal_to_head()));
2397	} else if (found_title_start) {
2398	  title.text += " " + string(t->text_string, t->text_length);
2399	  page_contents->glyphs.sub_move_right(); 	  /* move onto next word */
2400	  removed_from_head = ((!page_contents->glyphs.is_empty()) &&
2401			       (page_contents->glyphs.is_equal_to_head()));
2402	} else {
2403	  title.text += string(t->text_string, t->text_length);
2404	  found_title_start    = TRUE;
2405	  title.has_been_found = TRUE;
2406	  page_contents->glyphs.sub_move_right(); 	  /* move onto next word */
2407	  removed_from_head = ((!page_contents->glyphs.is_empty()) &&
2408			       (page_contents->glyphs.is_equal_to_head()));
2409	}
2410      } while ((! page_contents->glyphs.is_equal_to_head()) ||
2411	       (removed_from_head));
2412    }
2413  }
2414}
2415
2416void html_printer::write_header (void)
2417{
2418  if (! header.header_buffer.empty()) {
2419    int space = current_paragraph->retrieve_para_space() || seen_space;
2420
2421    if (header.header_level > 7) {
2422      header.header_level = 7;
2423    }
2424
2425    // firstly we must terminate any font and type faces
2426    current_paragraph->done_para();
2427    supress_sub_sup = TRUE;
2428
2429    if (cutoff_heading+2 > header.header_level) {
2430      // now we save the header so we can issue a list of links
2431      header.no_of_headings++;
2432      style st;
2433
2434      text_glob *h=new text_glob();
2435      h->text_glob_html(&st,
2436			header.headings.add_string(header.header_buffer),
2437			header.header_buffer.length(),
2438			header.no_of_headings, header.header_level,
2439			header.no_of_headings, header.header_level);
2440
2441      header.headers.add(h,
2442			 header.no_of_headings,
2443			 header.no_of_headings, header.no_of_headings,
2444			 header.no_of_headings, header.no_of_headings);   // and add this header to the header list
2445
2446      // lastly we generate a tag
2447
2448      html.nl().nl().put_string("<a name=\"");
2449      if (simple_anchors) {
2450	string buffer(ANCHOR_TEMPLATE);
2451
2452	buffer += as_string(header.no_of_headings);
2453	buffer += '\0';
2454	html.put_string(buffer.contents());
2455      } else {
2456	html.put_string(header.header_buffer);
2457      }
2458      html.put_string("\"></a>").nl();
2459    }
2460
2461    if (manufacture_headings) {
2462      // line break before a header
2463      if (!current_paragraph->emitted_text())
2464	current_paragraph->do_space();
2465      // user wants manufactured headings which look better than <Hn></Hn>
2466      if (header.header_level<4) {
2467	html.put_string("<b><font size=\"+1\">");
2468	html.put_string(header.header_buffer);
2469	html.put_string("</font></b>").nl();
2470      }
2471      else {
2472	html.put_string("<b>");
2473	html.put_string(header.header_buffer);
2474	html.put_string("</b>").nl();
2475      }
2476    }
2477    else {
2478      // and now we issue the real header
2479      html.put_string("<h");
2480      html.put_number(header.header_level);
2481      html.put_string(">");
2482      html.put_string(header.header_buffer);
2483      html.put_string("</h");
2484      html.put_number(header.header_level);
2485      html.put_string(">").nl();
2486    }
2487
2488    /* and now we save the file name in which this header will occur */
2489
2490    style st;   // fake style to enable us to use the list data structure
2491
2492    text_glob *h=new text_glob();
2493    h->text_glob_html(&st,
2494		      header.headings.add_string(file_list.file_name()),
2495		      file_list.file_name().length(),
2496		      header.no_of_headings, header.header_level,
2497		      header.no_of_headings, header.header_level);
2498
2499    header.header_filename.add(h,
2500			       header.no_of_headings,
2501			       header.no_of_headings, header.no_of_headings,
2502			       header.no_of_headings, header.no_of_headings);
2503
2504    current_paragraph->do_para(&html, "", get_troff_indent(), pageoffset, linelength, space);
2505  }
2506}
2507
2508void html_printer::determine_header_level (int level)
2509{
2510  if (level == 0) {
2511    int i;
2512
2513    for (i=0; ((i<header.header_buffer.length())
2514	       && ((header.header_buffer[i] == '.')
2515		   || is_digit(header.header_buffer[i]))) ; i++) {
2516      if (header.header_buffer[i] == '.') {
2517	level++;
2518      }
2519    }
2520  }
2521  header.header_level = level+1;
2522  if (header.header_level >= 2 && header.header_level <= split_level) {
2523    header.no_of_level_one_headings++;
2524    insert_split_file();
2525  }
2526}
2527
2528/*
2529 *  do_heading - handle the .SH and .NH and equivalent commands from troff.
2530 */
2531
2532void html_printer::do_heading (char *arg)
2533{
2534  text_glob *g;
2535  int  level=atoi(arg);
2536  int  horiz;
2537
2538  header.header_buffer.clear();
2539  page_contents->glyphs.move_right();
2540  if (! page_contents->glyphs.is_equal_to_head()) {
2541    g = page_contents->glyphs.get_data();
2542    horiz = g->minh;
2543    do {
2544      if (g->is_auto_img()) {
2545	string img=generate_img_src((char *)(g->text_string + 20));
2546
2547	if (! img.empty()) {
2548	  simple_anchors = TRUE;  // we cannot use full heading anchors with images
2549	  if (horiz < g->minh)
2550	    header.header_buffer += " ";
2551
2552	  header.header_buffer += img;
2553	}
2554      }
2555      else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll())
2556	troff_tag(g);
2557      else if (g->is_fi())
2558	fill_on = 1;
2559      else if (g->is_nf())
2560	fill_on = 0;
2561      else if (! (g->is_a_line() || g->is_a_tag())) {
2562	/*
2563	 *  we ignore the other tag commands when constructing a heading
2564	 */
2565	if (horiz < g->minh)
2566	  header.header_buffer += " ";
2567
2568	horiz = g->maxh;
2569	header.header_buffer += string(g->text_string, g->text_length);
2570      }
2571      page_contents->glyphs.move_right();
2572      g = page_contents->glyphs.get_data();
2573    } while ((! page_contents->glyphs.is_equal_to_head()) &&
2574	     (! g->is_eo_h()));
2575  }
2576
2577  determine_header_level(level);
2578  write_header();
2579
2580  // finally set the output to neutral for after the header
2581  g = page_contents->glyphs.get_data();
2582  page_contents->glyphs.move_left();     // so that next time we use old g
2583}
2584
2585/*
2586 *  is_courier_until_eol - returns TRUE if we can see a whole line which is courier
2587 */
2588
2589int html_printer::is_courier_until_eol (void)
2590{
2591  text_glob *orig = page_contents->glyphs.get_data();
2592  int result      = TRUE;
2593  text_glob *g;
2594
2595  if (! page_contents->glyphs.is_equal_to_tail()) {
2596    page_contents->glyphs.move_right();
2597    do {
2598      g = page_contents->glyphs.get_data();
2599      if (! g->is_a_tag() && (! is_font_courier(g->text_style.f)))
2600	result = FALSE;
2601      page_contents->glyphs.move_right();
2602    } while (result &&
2603	     (! page_contents->glyphs.is_equal_to_head()) &&
2604	     (! g->is_fi()) && (! g->is_eol()));
2605
2606    /*
2607     *  now restore our previous position.
2608     */
2609    while (page_contents->glyphs.get_data() != orig)
2610      page_contents->glyphs.move_left();
2611  }
2612  return result;
2613}
2614
2615/*
2616 *  do_linelength - handle the .ll command from troff.
2617 */
2618
2619void html_printer::do_linelength (char *arg)
2620{
2621  if (max_linelength == -1)
2622    max_linelength = atoi(arg);
2623
2624  next_linelength = atoi(arg);
2625  seen_linelength = TRUE;
2626}
2627
2628/*
2629 *  do_pageoffset - handle the .po command from troff.
2630 */
2631
2632void html_printer::do_pageoffset (char *arg)
2633{
2634  next_pageoffset = atoi(arg);
2635  seen_pageoffset = TRUE;
2636}
2637
2638/*
2639 *  get_troff_indent - returns the indent value.
2640 */
2641
2642int html_printer::get_troff_indent (void)
2643{
2644  if (end_tempindent > 0)
2645    return temp_indent;
2646  else
2647    return troff_indent;
2648}
2649
2650/*
2651 *  do_indentation - handle the .in command from troff.
2652 */
2653
2654void html_printer::do_indentation (char *arg)
2655{
2656  next_indent = atoi(arg);
2657  seen_indent = TRUE;
2658}
2659
2660/*
2661 *  do_tempindent - handle the .ti command from troff.
2662 */
2663
2664void html_printer::do_tempindent (char *arg)
2665{
2666  if (fill_on) {
2667    /*
2668     *  we set the end_tempindent to 2 as the first .br
2669     *  activates the .ti and the second terminates it.
2670     */
2671    end_tempindent = 2;
2672    temp_indent = atoi(arg);
2673  }
2674}
2675
2676/*
2677 *  shutdown_table - shuts down the current table.
2678 */
2679
2680void html_printer::shutdown_table (void)
2681{
2682  if (table != NULL) {
2683    current_paragraph->done_para();
2684    table->emit_finish_table();
2685    // dont delete this table as it will be deleted when we destroy the text_glob
2686    table = NULL;
2687  }
2688}
2689
2690/*
2691 *  do_indent - remember the indent parameters and if
2692 *              indent is > pageoff and indent has changed
2693 *              then we start a html table to implement the indentation.
2694 */
2695
2696void html_printer::do_indent (int in, int pageoff, int linelen)
2697{
2698  if ((device_indent != -1) &&
2699      (pageoffset+device_indent != in+pageoff)) {
2700
2701    int space = current_paragraph->retrieve_para_space() || seen_space;
2702    current_paragraph->done_para();
2703
2704    device_indent = in;
2705    pageoffset  = pageoff;
2706    if (linelen <= max_linelength)
2707      linelength  = linelen;
2708
2709    current_paragraph->do_para(&html, "", device_indent,
2710			       pageoffset, max_linelength, space);
2711  }
2712}
2713
2714/*
2715 *  do_verticalspacing - handle the .vs command from troff.
2716 */
2717
2718void html_printer::do_verticalspacing (char *arg)
2719{
2720  vertical_spacing = atoi(arg);
2721}
2722
2723/*
2724 *  do_pointsize - handle the .ps command from troff.
2725 */
2726
2727void html_printer::do_pointsize (char *arg)
2728{
2729  /*
2730   *  firstly check to see whether this point size is really associated with a .tl tag
2731   */
2732
2733  if (! page_contents->glyphs.is_empty()) {
2734    text_glob *g = page_contents->glyphs.get_data();
2735    text_glob *t = page_contents->glyphs.get_data();
2736
2737    while (t->is_a_tag() && (! page_contents->glyphs.is_equal_to_head())) {
2738      if (t->is_tl()) {
2739	/*
2740	 *  found title therefore ignore this .ps tag
2741	 */
2742	while (t != g) {
2743	  page_contents->glyphs.move_left();
2744	  t = page_contents->glyphs.get_data();
2745	}
2746	return;
2747      }
2748      page_contents->glyphs.move_right();
2749      t = page_contents->glyphs.get_data();
2750    }
2751    /*
2752     *  move back to original position
2753     */
2754    while (t != g) {
2755      page_contents->glyphs.move_left();
2756      t = page_contents->glyphs.get_data();
2757    }
2758    /*
2759     *  collect legal pointsize
2760     */
2761    pointsize = atoi(arg);
2762  }
2763}
2764
2765/*
2766 *  do_fill - records whether troff has requested that text be filled.
2767 */
2768
2769void html_printer::do_fill (char *arg)
2770{
2771  int on = atoi(arg);
2772
2773  output_hpos = get_troff_indent()+pageoffset;
2774  supress_sub_sup = TRUE;
2775
2776  if (fill_on != on) {
2777    if (on)
2778      current_paragraph->do_para("", seen_space);
2779    fill_on = on;
2780  }
2781}
2782
2783/*
2784 *  do_eol - handle the end of line
2785 */
2786
2787void html_printer::do_eol (void)
2788{
2789  if (! fill_on) {
2790    if (current_paragraph->ever_emitted_text()) {
2791      current_paragraph->do_newline();
2792      current_paragraph->do_break();
2793    }
2794  }
2795  output_hpos = get_troff_indent()+pageoffset;
2796}
2797
2798/*
2799 *  do_check_center - checks to see whether we have seen a `.ce' tag
2800 *                    during the previous line.
2801 */
2802
2803void html_printer::do_check_center(void)
2804{
2805  if (seen_center) {
2806    seen_center = FALSE;
2807    if (next_center > 0) {
2808      if (end_center == 0) {
2809	int space = current_paragraph->retrieve_para_space() || seen_space;
2810	current_paragraph->done_para();
2811	supress_sub_sup = TRUE;
2812	current_paragraph->do_para("align=center", space);
2813      } else
2814	if (strcmp("align=center",
2815		   current_paragraph->get_alignment()) != 0) {
2816	  /*
2817	   *  different alignment, so shutdown paragraph and open
2818	   *  a new one.
2819	   */
2820	  int space = current_paragraph->retrieve_para_space() || seen_space;
2821	  current_paragraph->done_para();
2822	  supress_sub_sup = TRUE;
2823	  current_paragraph->do_para("align=center", space);
2824	} else
2825	  /*
2826	   *  same alignment, if we have emitted text then issue a break.
2827	   */
2828	  if (current_paragraph->emitted_text())
2829	    current_paragraph->do_break();
2830    } else
2831      /*
2832       *  next_center == 0
2833       */
2834      if (end_center > 0) {
2835	seen_space = seen_space || current_paragraph->retrieve_para_space();
2836	current_paragraph->done_para();
2837	supress_sub_sup = TRUE;
2838	current_paragraph->do_para("", seen_space);
2839      }
2840    end_center = next_center;
2841  }
2842}
2843
2844/*
2845 *  do_eol_ce - handle end of line specifically for a .ce
2846 */
2847
2848void html_printer::do_eol_ce (void)
2849{
2850  if (end_center > 0) {
2851    if (end_center > 1)
2852      if (current_paragraph->emitted_text())
2853	current_paragraph->do_break();
2854
2855    end_center--;
2856    if (end_center == 0) {
2857      current_paragraph->done_para();
2858      supress_sub_sup = TRUE;
2859    }
2860  }
2861}
2862
2863/*
2864 *  do_flush - flushes all output and tags.
2865 */
2866
2867void html_printer::do_flush (void)
2868{
2869  current_paragraph->done_para();
2870}
2871
2872/*
2873 *  do_links - moves onto a new temporary file and sets auto_links to FALSE.
2874 */
2875
2876void html_printer::do_links (void)
2877{
2878  html.end_line();                      // flush line
2879  auto_links = FALSE;   /* from now on only emit under user request */
2880  file_list.add_new_file(xtmpfile());
2881  file_list.set_links_required();
2882  html.set_file(file_list.get_file());
2883}
2884
2885/*
2886 *  insert_split_file -
2887 */
2888
2889void html_printer::insert_split_file (void)
2890{
2891  if (multiple_files) {
2892    current_paragraph->done_para();       // flush paragraph
2893    html.end_line();                      // flush line
2894    html.set_file(file_list.get_file());  // flush current file
2895    file_list.add_new_file(xtmpfile());
2896    string split_file = job_name;
2897
2898    split_file += string("-");
2899    split_file += as_string(header.no_of_level_one_headings);
2900    split_file += string(".html");
2901    split_file += '\0';
2902
2903    file_list.set_file_name(split_file);
2904    html.set_file(file_list.get_file());
2905  }
2906}
2907
2908/*
2909 *  do_job_name - assigns the job_name to name.
2910 */
2911
2912void html_printer::do_job_name (char *name)
2913{
2914  if (! multiple_files) {
2915    multiple_files = TRUE;
2916    while (name != NULL && (*name != (char)0) && (*name == ' '))
2917      name++;
2918    job_name = name;
2919  }
2920}
2921
2922/*
2923 *  do_head - adds a string to head_info which is to be included into
2924 *            the <head> </head> section of the html document.
2925 */
2926
2927void html_printer::do_head (char *name)
2928{
2929  head_info += string(name);
2930  head_info += '\n';
2931}
2932
2933/*
2934 *  do_break - handles the ".br" request and also
2935 *             undoes an outstanding ".ti" command
2936 *             and calls indent if the indentation
2937 *             related registers have changed.
2938 */
2939
2940void html_printer::do_break (void)
2941{
2942  int seen_temp_indent = FALSE;
2943
2944  current_paragraph->do_break();
2945  if (end_tempindent > 0) {
2946    end_tempindent--;
2947    if (end_tempindent > 0)
2948      seen_temp_indent = TRUE;
2949  }
2950  if (seen_indent || seen_pageoffset || seen_linelength || seen_temp_indent) {
2951    if (seen_indent && (! seen_temp_indent))
2952      troff_indent = next_indent;
2953    if (! seen_pageoffset)
2954      next_pageoffset = pageoffset;
2955    if (! seen_linelength)
2956      next_linelength = linelength;
2957    do_indent(get_troff_indent(), next_pageoffset, next_linelength);
2958  }
2959  seen_indent     = seen_temp_indent;
2960  seen_linelength = FALSE;
2961  seen_pageoffset = FALSE;
2962  do_check_center();
2963  output_hpos     = get_troff_indent()+pageoffset;
2964  supress_sub_sup = TRUE;
2965}
2966
2967void html_printer::do_space (char *arg)
2968{
2969  int n = atoi(arg);
2970
2971  seen_space = atoi(arg);
2972  as.check_sp(seen_space);
2973#if 0
2974  if (n>0 && table)
2975    table->set_space(TRUE);
2976#endif
2977
2978  while (n>0) {
2979    current_paragraph->do_space();
2980    n--;
2981  }
2982  supress_sub_sup = TRUE;
2983}
2984
2985/*
2986 *  do_tab_ts - start a table, which will have already been defined.
2987 */
2988
2989void html_printer::do_tab_ts (text_glob *g)
2990{
2991  html_table *t = g->get_table();
2992
2993  if (t != NULL) {
2994    current_column = 0;
2995    current_paragraph->done_pre();
2996    current_paragraph->done_para();
2997    current_paragraph->remove_para_space();
2998
2999#if defined(DEBUG_TABLES)
3000    html.simple_comment("TABS");
3001#endif
3002
3003    t->set_linelength(max_linelength);
3004    t->add_indent(pageoffset);
3005#if 0
3006    t->emit_table_header(seen_space);
3007#else
3008    t->emit_table_header(FALSE);
3009    row_space = current_paragraph->retrieve_para_space() || seen_space;
3010    seen_space = FALSE;
3011#endif
3012  }
3013
3014  table = t;
3015}
3016
3017/*
3018 *  do_tab_te - finish a table.
3019 */
3020
3021void html_printer::do_tab_te (void)
3022{
3023  if (table) {
3024    current_paragraph->done_para();
3025    current_paragraph->remove_para_space();
3026    table->emit_finish_table();
3027  }
3028
3029  table = NULL;
3030  restore_troff_indent();
3031}
3032
3033/*
3034 *  do_tab - handle the "devtag:tab" tag
3035 */
3036
3037void html_printer::do_tab (char *s)
3038{
3039  if (table) {
3040    while (isspace(*s))
3041      s++;
3042    s++;
3043    int col = table->find_column(atoi(s) + pageoffset + get_troff_indent());
3044    if (col > 0) {
3045      current_paragraph->done_para();
3046      table->emit_col(col);
3047    }
3048  }
3049}
3050
3051/*
3052 *  do_tab0 - handle the "devtag:tab0" tag
3053 */
3054
3055void html_printer::do_tab0 (void)
3056{
3057  if (table) {
3058    int col = table->find_column(pageoffset+get_troff_indent());
3059    if (col > 0) {
3060      current_paragraph->done_para();
3061      table->emit_col(col);
3062    }
3063  }
3064}
3065
3066/*
3067 *  do_col - start column, s.
3068 */
3069
3070void html_printer::do_col (char *s)
3071{
3072  if (table) {
3073    if (atoi(s) < current_column)
3074      row_space = seen_space;
3075
3076    current_column = atoi(s);
3077    current_paragraph->done_para();
3078    table->emit_col(current_column);
3079    current_paragraph->do_para("", row_space);
3080  }
3081}
3082
3083/*
3084 *  troff_tag - processes the troff tag and manipulates the troff
3085 *              state machine.
3086 */
3087
3088void html_printer::troff_tag (text_glob *g)
3089{
3090  /*
3091   *  firstly skip over devtag:
3092   */
3093  char *t=(char *)g->text_string+strlen("devtag:");
3094
3095  if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) {
3096    do_end_para(g);
3097  } else if (g->is_eol()) {
3098    do_eol();
3099  } else if (g->is_eol_ce()) {
3100    do_eol_ce();
3101  } else if (strncmp(t, ".sp", 3) == 0) {
3102    char *a = (char *)t+3;
3103    do_space(a);
3104  } else if (strncmp(t, ".br", 3) == 0) {
3105    seen_break = 1;
3106    as.check_br(1);
3107    do_break();
3108  } else if (strcmp(t, ".centered-image") == 0) {
3109    do_centered_image();
3110  } else if (strcmp(t, ".right-image") == 0) {
3111    do_right_image();
3112  } else if (strcmp(t, ".left-image") == 0) {
3113    do_left_image();
3114  } else if (strncmp(t, ".auto-image", 11) == 0) {
3115    char *a = (char *)t+11;
3116    do_auto_image(g, a);
3117  } else if (strncmp(t, ".ce", 3) == 0) {
3118    char *a = (char *)t+3;
3119    supress_sub_sup = TRUE;
3120    do_center(a);
3121  } else if (g->is_tl()) {
3122    supress_sub_sup = TRUE;
3123    title.with_h1 = TRUE;
3124    do_title();
3125  } else if (strncmp(t, ".html-tl", 8) == 0) {
3126    supress_sub_sup = TRUE;
3127    title.with_h1 = FALSE;
3128    do_title();
3129  } else if (strncmp(t, ".fi", 3) == 0) {
3130    char *a = (char *)t+3;
3131    do_fill(a);
3132  } else if ((strncmp(t, ".SH", 3) == 0) || (strncmp(t, ".NH", 3) == 0)) {
3133    char *a = (char *)t+3;
3134    do_heading(a);
3135  } else if (strncmp(t, ".ll", 3) == 0) {
3136    char *a = (char *)t+3;
3137    do_linelength(a);
3138  } else if (strncmp(t, ".po", 3) == 0) {
3139    char *a = (char *)t+3;
3140    do_pageoffset(a);
3141  } else if (strncmp(t, ".in", 3) == 0) {
3142    char *a = (char *)t+3;
3143    do_indentation(a);
3144  } else if (strncmp(t, ".ti", 3) == 0) {
3145    char *a = (char *)t+3;
3146    do_tempindent(a);
3147  } else if (strncmp(t, ".vs", 3) == 0) {
3148    char *a = (char *)t+3;
3149    do_verticalspacing(a);
3150  } else if (strncmp(t, ".ps", 3) == 0) {
3151    char *a = (char *)t+3;
3152    do_pointsize(a);
3153  } else if (strcmp(t, ".links") == 0) {
3154    do_links();
3155  } else if (strncmp(t, ".job-name", 9) == 0) {
3156    char *a = (char *)t+9;
3157    do_job_name(a);
3158  } else if (strncmp(t, ".head", 5) == 0) {
3159    char *a = (char *)t+5;
3160    do_head(a);
3161  } else if (strcmp(t, ".no-auto-rule") == 0) {
3162    auto_rule = FALSE;
3163  } else if (strcmp(t, ".tab-ts") == 0) {
3164    do_tab_ts(g);
3165  } else if (strcmp(t, ".tab-te") == 0) {
3166    do_tab_te();
3167  } else if (strncmp(t, ".col ", 5) == 0) {
3168    char *a = (char *)t+4;
3169    do_col(a);
3170  } else if (strncmp(t, "tab ", 4) == 0) {
3171    char *a = (char *)t+3;
3172    do_tab(a);
3173  } else if (strncmp(t, "tab0", 4) == 0) {
3174    do_tab0();
3175  }
3176}
3177
3178/*
3179 *  is_in_middle - returns TRUE if the positions left..right are in the center of the page.
3180 */
3181
3182int html_printer::is_in_middle (int left, int right)
3183{
3184  return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right))
3185	  <= CENTER_TOLERANCE );
3186}
3187
3188/*
3189 *  flush_globs - runs through the text glob list and emits html.
3190 */
3191
3192void html_printer::flush_globs (void)
3193{
3194  text_glob *g;
3195
3196  if (! page_contents->glyphs.is_empty()) {
3197    page_contents->glyphs.start_from_head();
3198    do {
3199      g = page_contents->glyphs.get_data();
3200#if 0
3201      fprintf(stderr, "[%s:%d:%d:%d:%d]",
3202	      g->text_string, g->minv, g->minh, g->maxv, g->maxh) ;
3203      fflush(stderr);
3204#endif
3205
3206      handle_state_assertion(g);
3207
3208      if (strcmp(g->text_string, "XXXXXXX") == 0)
3209	stop();
3210
3211      if (g->is_a_tag())
3212	troff_tag(g);
3213      else if (g->is_a_line())
3214	emit_line(g);
3215      else {
3216	as.check_sp(seen_space);
3217	as.check_br(seen_break);
3218	seen_break = 0;
3219	seen_space = 0;
3220	emit_html(g);
3221      }
3222
3223      as.check_fi(fill_on);
3224      as.check_ce(end_center);
3225      /*
3226       *  after processing the title (and removing it) the glyph list might be empty
3227       */
3228      if (! page_contents->glyphs.is_empty()) {
3229	page_contents->glyphs.move_right();
3230      }
3231    } while (! page_contents->glyphs.is_equal_to_head());
3232  }
3233}
3234
3235/*
3236 *  calc_nf - calculates the _no_ format flag, given the
3237 *            text glob, g.
3238 */
3239
3240int html_printer::calc_nf (text_glob *g, int nf)
3241{
3242  if (g != NULL) {
3243    if (g->is_fi()) {
3244      as.check_fi(TRUE);
3245      return FALSE;
3246    }
3247    if (g->is_nf()) {
3248      as.check_fi(FALSE);
3249      return TRUE;
3250    }
3251  }
3252  as.check_fi(! nf);
3253  return nf;
3254}
3255
3256/*
3257 *  calc_po_in - calculates the, in, po, registers
3258 */
3259
3260void html_printer::calc_po_in (text_glob *g, int nf)
3261{
3262  if (g->is_in())
3263    troff_indent = g->get_arg();
3264  else if (g->is_po())
3265    pageoffset = g->get_arg();
3266  else if (g->is_ti()) {
3267    temp_indent = g->get_arg();
3268    end_tempindent = 2;
3269  } else if (g->is_br() || (nf && g->is_eol())) {
3270    if (end_tempindent > 0)
3271      end_tempindent--;
3272  }
3273}
3274
3275/*
3276 *  next_horiz_pos - returns the next horiz position.
3277 *                   -1 is returned if it doesn't exist.
3278 */
3279
3280int html_printer::next_horiz_pos (text_glob *g, int nf)
3281{
3282  int next = -1;
3283
3284  if ((g != NULL) && (g->is_br() || (nf && g->is_eol())))
3285    if (! page_contents->glyphs.is_empty()) {
3286      page_contents->glyphs.move_right_get_data();
3287      if (g == NULL) {
3288	page_contents->glyphs.start_from_head();
3289	as.reset();
3290      }
3291      else {
3292	next = g->minh;
3293	page_contents->glyphs.move_left();
3294      }
3295    }
3296  return next;
3297}
3298
3299/*
3300 *  insert_tab_ts - inserts a tab-ts before, where.
3301 */
3302
3303text_glob *html_printer::insert_tab_ts (text_glob *where)
3304{
3305  text_glob *start_of_table;
3306  text_glob *old_pos = page_contents->glyphs.get_data();
3307
3308  page_contents->glyphs.move_to(where);
3309  page_contents->glyphs.move_left();
3310  page_contents->insert_tag(string("devtag:.tab-ts"));  // tab table start
3311  page_contents->glyphs.move_right();
3312  start_of_table = page_contents->glyphs.get_data();
3313  page_contents->glyphs.move_to(old_pos);
3314  return start_of_table;
3315}
3316
3317/*
3318 *  insert_tab_te - inserts a tab-te before the current position
3319 *                  (it skips backwards over .sp/.br)
3320 */
3321
3322void html_printer::insert_tab_te (void)
3323{
3324  text_glob *g = page_contents->glyphs.get_data();
3325  page_contents->dump_page();
3326
3327  while (page_contents->glyphs.get_data()->is_a_tag())
3328    page_contents->glyphs.move_left();
3329
3330  page_contents->insert_tag(string("devtag:.tab-te"));  // tab table end
3331  while (g != page_contents->glyphs.get_data())
3332    page_contents->glyphs.move_right();
3333  page_contents->dump_page();
3334}
3335
3336/*
3337 *  insert_tab_0 - inserts a tab0 before, where.
3338 */
3339
3340void html_printer::insert_tab_0 (text_glob *where)
3341{
3342  text_glob *old_pos = page_contents->glyphs.get_data();
3343
3344  page_contents->glyphs.move_to(where);
3345  page_contents->glyphs.move_left();
3346  page_contents->insert_tag(string("devtag:tab0"));  // tab0 start of line
3347  page_contents->glyphs.move_right();
3348  page_contents->glyphs.move_to(old_pos);
3349}
3350
3351/*
3352 *  remove_tabs - removes the tabs tags on this line.
3353 */
3354
3355void html_printer::remove_tabs (void)
3356{
3357  text_glob *orig = page_contents->glyphs.get_data();
3358  text_glob *g;
3359
3360  if (! page_contents->glyphs.is_equal_to_tail()) {
3361    do {
3362      g = page_contents->glyphs.get_data();
3363      if (g->is_tab()) {
3364	page_contents->glyphs.sub_move_right();
3365	if (g == orig)
3366	  orig = page_contents->glyphs.get_data();
3367      }	else
3368	page_contents->glyphs.move_right();
3369    } while ((! page_contents->glyphs.is_equal_to_head()) &&
3370	     (! g->is_eol()));
3371
3372    /*
3373     *  now restore our previous position.
3374     */
3375    while (page_contents->glyphs.get_data() != orig)
3376      page_contents->glyphs.move_left();
3377  }
3378}
3379
3380void html_printer::remove_courier_tabs (void)
3381{
3382  text_glob  *g;
3383  int line_start = TRUE;
3384  int nf         = FALSE;
3385
3386  if (! page_contents->glyphs.is_empty()) {
3387    page_contents->glyphs.start_from_head();
3388    as.reset();
3389    line_start = TRUE;
3390    do {
3391      g = page_contents->glyphs.get_data();
3392      handle_state_assertion(g);
3393      nf = calc_nf(g, nf);
3394
3395      if (line_start) {
3396	if (line_start && nf && is_courier_until_eol()) {
3397	  remove_tabs();
3398	  g = page_contents->glyphs.get_data();
3399	}
3400      }
3401
3402      // line_start = g->is_br() || g->is_nf() || g->is_fi() || (nf && g->is_eol());
3403      line_start = g->is_br() || (nf && g->is_eol());
3404      page_contents->glyphs.move_right();
3405    } while (! page_contents->glyphs.is_equal_to_head());
3406  }
3407}
3408
3409void html_printer::insert_tab0_foreach_tab (void)
3410{
3411  text_glob  *start_of_line  = NULL;
3412  text_glob  *g              = NULL;
3413  int seen_tab               = FALSE;
3414  int seen_col               = FALSE;
3415  int nf                     = FALSE;
3416
3417  if (! page_contents->glyphs.is_empty()) {
3418    page_contents->glyphs.start_from_head();
3419    as.reset();
3420    start_of_line = page_contents->glyphs.get_data();
3421    do {
3422      g = page_contents->glyphs.get_data();
3423      handle_state_assertion(g);
3424      nf = calc_nf(g, nf);
3425
3426      if (g->is_tab())
3427	seen_tab = TRUE;
3428
3429      if (g->is_col())
3430	seen_col = TRUE;
3431
3432      if (g->is_br() || (nf && g->is_eol())) {
3433	do {
3434	  page_contents->glyphs.move_right();
3435	  g = page_contents->glyphs.get_data();
3436	  handle_state_assertion(g);
3437	  nf = calc_nf(g, nf);
3438	  if (page_contents->glyphs.is_equal_to_head()) {
3439	    if (seen_tab && !seen_col)
3440	      insert_tab_0(start_of_line);
3441	    return;
3442	  }
3443	} while (g->is_br() || (nf && g->is_eol()) || g->is_ta());
3444	// printf("\nstart_of_line is: %s\n", g->text_string);
3445	if (seen_tab && !seen_col) {
3446	  insert_tab_0(start_of_line);
3447	  page_contents->glyphs.move_to(g);
3448	}
3449
3450	seen_tab = FALSE;
3451	seen_col = FALSE;
3452	start_of_line = g;
3453      }
3454      page_contents->glyphs.move_right();
3455    } while (! page_contents->glyphs.is_equal_to_head());
3456    if (seen_tab && !seen_col)
3457      insert_tab_0(start_of_line);
3458
3459  }
3460}
3461
3462/*
3463 *  update_min_max - updates the extent of a column, given the left and right
3464 *                   extents of a glyph, g.
3465 */
3466
3467void html_printer::update_min_max (colType type_of_col, int *minimum, int *maximum, text_glob *g)
3468{
3469  switch (type_of_col) {
3470
3471  case tab_tag:
3472    break;
3473  case tab0_tag:
3474    *minimum = g->minh;
3475    break;
3476  case col_tag:
3477    *minimum = g->minh;
3478    *maximum = g->maxh;
3479    break;
3480  default:
3481    break;
3482  }
3483}
3484
3485/*
3486 *  add_table_end - moves left one glyph, adds a table end tag and adds a
3487 *                  debugging string.
3488 */
3489
3490void html_printer::add_table_end (const char *
3491#if defined(DEBUG_TABLES)
3492  debug_string
3493#endif
3494)
3495{
3496  page_contents->glyphs.move_left();
3497  insert_tab_te();
3498#if defined(DEBUG_TABLES)
3499  page_contents->insert_tag(string(debug_string));
3500#endif
3501}
3502
3503/*
3504 *  lookahead_for_tables - checks for .col tags and inserts table
3505 *                         start/end tags
3506 */
3507
3508void html_printer::lookahead_for_tables (void)
3509{
3510  text_glob  *g;
3511  text_glob  *start_of_line  = NULL;
3512  text_glob  *start_of_table = NULL;
3513  text_glob  *last           = NULL;
3514  colType     type_of_col    = none;
3515  int         left           = 0;
3516  int         found_col      = FALSE;
3517  int         seen_text      = FALSE;
3518  int         ncol           = 0;
3519  int         colmin         = 0;		// pacify compiler
3520  int         colmax         = 0;		// pacify compiler
3521  html_table *tbl            = new html_table(&html, -1);
3522  const char *tab_defs       = NULL;
3523  char        align          = 'L';
3524  int         nf             = FALSE;
3525  int         old_pageoffset = pageoffset;
3526
3527  remove_courier_tabs();
3528  page_contents->dump_page();
3529  insert_tab0_foreach_tab();
3530  page_contents->dump_page();
3531  if (! page_contents->glyphs.is_empty()) {
3532    page_contents->glyphs.start_from_head();
3533    as.reset();
3534    g = page_contents->glyphs.get_data();
3535    if (g->is_br()) {
3536      g = page_contents->glyphs.move_right_get_data();
3537      handle_state_assertion(g);
3538      if (page_contents->glyphs.is_equal_to_head()) {
3539	if (tbl != NULL) {
3540	  delete tbl;
3541	  tbl = NULL;
3542	}
3543	return;
3544      }
3545
3546      start_of_line = g;
3547      seen_text = FALSE;
3548      ncol = 0;
3549      left = next_horiz_pos(g, nf);
3550      if (found_col)
3551	last = g;
3552      found_col = FALSE;
3553    }
3554
3555    do {
3556#if defined(DEBUG_TABLES)
3557      fprintf(stderr, " [") ;
3558      fprintf(stderr, g->text_string) ;
3559      fprintf(stderr, "] ") ;
3560      fflush(stderr);
3561      if (strcmp(g->text_string, "XXXXXXX") == 0)
3562	stop();
3563#endif
3564
3565      nf = calc_nf(g, nf);
3566      calc_po_in(g, nf);
3567      if (g->is_col()) {
3568	if (type_of_col == tab_tag && start_of_table != NULL) {
3569	  page_contents->glyphs.move_left();
3570	  insert_tab_te();
3571	  start_of_table->remember_table(tbl);
3572	  tbl = new html_table(&html, -1);
3573	  page_contents->insert_tag(string("*** TAB -> COL ***"));
3574	  if (tab_defs != NULL)
3575	    tbl->tab_stops->init(tab_defs);
3576	  start_of_table = NULL;
3577	  last = NULL;
3578	}
3579	type_of_col = col_tag;
3580	found_col = TRUE;
3581	ncol = g->get_arg();
3582	align = 'L';
3583	colmin = 0;
3584	colmax = 0;
3585      } else if (g->is_tab()) {
3586	type_of_col = tab_tag;
3587	colmin = g->get_tab_args(&align);
3588	align = 'L'; // for now as 'C' and 'R' are broken
3589	ncol = tbl->find_tab_column(colmin);
3590	colmin += pageoffset + get_troff_indent();
3591	colmax = tbl->get_tab_pos(ncol+1);
3592	if (colmax > 0)
3593	  colmax += pageoffset + get_troff_indent();
3594      } else if (g->is_tab0()) {
3595	if (type_of_col == col_tag && start_of_table != NULL) {
3596	  page_contents->glyphs.move_left();
3597	  insert_tab_te();
3598	  start_of_table->remember_table(tbl);
3599	  tbl = new html_table(&html, -1);
3600	  page_contents->insert_tag(string("*** COL -> TAB ***"));
3601	  start_of_table = NULL;
3602	  last = NULL;
3603	}
3604	if (tab_defs != NULL)
3605	  tbl->tab_stops->init(tab_defs);
3606
3607	type_of_col = tab0_tag;
3608	ncol = 1;
3609	colmin = 0;
3610	colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent();
3611      } else if (! g->is_a_tag())
3612	update_min_max(type_of_col, &colmin, &colmax, g);
3613
3614      if ((! g->is_a_tag()) || g->is_tab())
3615	seen_text = TRUE;
3616
3617      if ((g->is_col() || g->is_tab() || g->is_tab0())
3618	  && (start_of_line != NULL) && (start_of_table == NULL)) {
3619	start_of_table = insert_tab_ts(start_of_line);
3620	start_of_line = NULL;
3621	seen_text = FALSE;
3622      } else if (g->is_ce() && (start_of_table != NULL)) {
3623	add_table_end("*** CE ***");
3624	start_of_table->remember_table(tbl);
3625 	tbl = new html_table(&html, -1);
3626	start_of_table = NULL;
3627	last = NULL;
3628      } else if (g->is_ta()) {
3629	tab_defs = g->text_string;
3630
3631	if (type_of_col == col_tag)
3632	  tbl->tab_stops->check_init(tab_defs);
3633
3634	if (!tbl->tab_stops->compatible(tab_defs)) {
3635	  if (start_of_table != NULL) {
3636	    add_table_end("*** TABS ***");
3637	    start_of_table->remember_table(tbl);
3638	    tbl = new html_table(&html, -1);
3639	    start_of_table = NULL;
3640	    type_of_col = none;
3641	    last = NULL;
3642	  }
3643	  tbl->tab_stops->init(tab_defs);
3644	}
3645      }
3646
3647      if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != NULL)) {
3648	// we are in a table and have a glyph
3649	if ((ncol == 0) || (! tbl->add_column(ncol, colmin, colmax, align))) {
3650	  if (ncol == 0)
3651	    add_table_end("*** NCOL == 0 ***");
3652	  else
3653	    add_table_end("*** CROSSED COLS ***");
3654
3655	  start_of_table->remember_table(tbl);
3656	  tbl = new html_table(&html, -1);
3657	  start_of_table = NULL;
3658	  type_of_col = none;
3659	  last = NULL;
3660	}
3661      }
3662
3663      /*
3664       *  move onto next glob, check whether we are starting a new line
3665       */
3666      g = page_contents->glyphs.move_right_get_data();
3667      handle_state_assertion(g);
3668
3669      if (g == NULL) {
3670	if (found_col) {
3671	  page_contents->glyphs.start_from_head();
3672	  as.reset();
3673	  last = g;
3674	  found_col = FALSE;
3675	}
3676      } else if (g->is_br() || (nf && g->is_eol())) {
3677	do {
3678	  g = page_contents->glyphs.move_right_get_data();
3679	  handle_state_assertion(g);
3680	  nf = calc_nf(g, nf);
3681	} while ((g != NULL) && (g->is_br() || (nf && g->is_eol())));
3682	start_of_line = g;
3683	seen_text = FALSE;
3684	ncol = 0;
3685	left = next_horiz_pos(g, nf);
3686	if (found_col)
3687	  last = g;
3688	found_col = FALSE;
3689      }
3690    } while ((g != NULL) && (! page_contents->glyphs.is_equal_to_head()));
3691
3692#if defined(DEBUG_TABLES)
3693    fprintf(stderr, "finished scanning for tables\n");
3694#endif
3695
3696    page_contents->glyphs.start_from_head();
3697    if (start_of_table != NULL) {
3698      if (last != NULL)
3699	while (last != page_contents->glyphs.get_data())
3700	  page_contents->glyphs.move_left();
3701
3702      insert_tab_te();
3703      start_of_table->remember_table(tbl);
3704      tbl = NULL;
3705      page_contents->insert_tag(string("*** LAST ***"));
3706    }
3707  }
3708  if (tbl != NULL) {
3709    delete tbl;
3710    tbl = NULL;
3711  }
3712
3713  // and reset the registers
3714  pageoffset = old_pageoffset;
3715  troff_indent = 0;
3716  temp_indent = 0;
3717  end_tempindent = 0;
3718}
3719
3720void html_printer::flush_page (void)
3721{
3722  supress_sub_sup = TRUE;
3723  flush_sbuf();
3724  page_contents->dump_page();
3725  lookahead_for_tables();
3726  page_contents->dump_page();
3727
3728  flush_globs();
3729  current_paragraph->done_para();
3730
3731  // move onto a new page
3732  delete page_contents;
3733#if defined(DEBUG_TABLES)
3734  fprintf(stderr, "\n\n*** flushed page ***\n\n");
3735
3736  html.simple_comment("new page called");
3737#endif
3738  page_contents = new page;
3739}
3740
3741/*
3742 *  determine_space - works out whether we need to write a space.
3743 *                    If last glyph is ajoining then no space emitted.
3744 */
3745
3746void html_printer::determine_space (text_glob *g)
3747{
3748  if (current_paragraph->is_in_pre()) {
3749    /*
3750     *  .nf has been specified
3751     */
3752    while (output_hpos < g->minh) {
3753      output_hpos += space_width;
3754      current_paragraph->emit_space();
3755    }
3756  } else {
3757    if ((output_vpos != g->minv) || (output_hpos < g->minh)) {
3758      current_paragraph->emit_space();
3759    }
3760  }
3761}
3762
3763/*
3764 *  is_line_start - returns TRUE if we are at the start of a line.
3765 */
3766
3767int html_printer::is_line_start (int nf)
3768{
3769  int line_start  = FALSE;
3770  int result      = TRUE;
3771  text_glob *orig = page_contents->glyphs.get_data();
3772  text_glob *g;
3773
3774  if (! page_contents->glyphs.is_equal_to_head()) {
3775    do {
3776      page_contents->glyphs.move_left();
3777      g = page_contents->glyphs.get_data();
3778      result = g->is_a_tag();
3779      if (g->is_fi())
3780	nf = FALSE;
3781      else if (g->is_nf())
3782	nf = TRUE;
3783      line_start = g->is_col() || g->is_br() || (nf && g->is_eol());
3784    } while ((!line_start) && (result));
3785    /*
3786     *  now restore our previous position.
3787     */
3788    while (page_contents->glyphs.get_data() != orig)
3789      page_contents->glyphs.move_right();
3790  }
3791  return result;
3792}
3793
3794/*
3795 *  is_font_courier - returns TRUE if the font, f, is courier.
3796 */
3797
3798int html_printer::is_font_courier (font *f)
3799{
3800  if (f != 0) {
3801    const char *fontname = f->get_name();
3802
3803    return( (fontname != 0) && (fontname[0] == 'C') );
3804  }
3805  return FALSE;
3806}
3807
3808/*
3809 *  end_font - shuts down the font corresponding to fontname.
3810 */
3811
3812void html_printer::end_font (const char *fontname)
3813{
3814  if (strcmp(fontname, "B") == 0) {
3815    current_paragraph->done_bold();
3816  } else if (strcmp(fontname, "I") == 0) {
3817    current_paragraph->done_italic();
3818  } else if (strcmp(fontname, "BI") == 0) {
3819    current_paragraph->done_bold();
3820    current_paragraph->done_italic();
3821  } else if (strcmp(fontname, "CR") == 0) {
3822    current_paragraph->done_tt();
3823  } else if (strcmp(fontname, "CI") == 0) {
3824    current_paragraph->done_italic();
3825    current_paragraph->done_tt();
3826  } else if (strcmp(fontname, "CB") == 0) {
3827    current_paragraph->done_bold();
3828    current_paragraph->done_tt();
3829  } else if (strcmp(fontname, "CBI") == 0) {
3830    current_paragraph->done_bold();
3831    current_paragraph->done_italic();
3832    current_paragraph->done_tt();
3833  }
3834}
3835
3836/*
3837 *  start_font - starts the font corresponding to name.
3838 */
3839
3840void html_printer::start_font (const char *fontname)
3841{
3842  if (strcmp(fontname, "R") == 0) {
3843    current_paragraph->done_bold();
3844    current_paragraph->done_italic();
3845    current_paragraph->done_tt();
3846  } else if (strcmp(fontname, "B") == 0) {
3847    current_paragraph->do_bold();
3848  } else if (strcmp(fontname, "I") == 0) {
3849    current_paragraph->do_italic();
3850  } else if (strcmp(fontname, "BI") == 0) {
3851    current_paragraph->do_bold();
3852    current_paragraph->do_italic();
3853  } else if (strcmp(fontname, "CR") == 0) {
3854    if ((! fill_on) && (is_courier_until_eol()) &&
3855	is_line_start(! fill_on)) {
3856      current_paragraph->do_pre();
3857    }
3858    current_paragraph->do_tt();
3859  } else if (strcmp(fontname, "CI") == 0) {
3860    if ((! fill_on) && (is_courier_until_eol()) &&
3861	is_line_start(! fill_on)) {
3862      current_paragraph->do_pre();
3863    }
3864    current_paragraph->do_tt();
3865    current_paragraph->do_italic();
3866  } else if (strcmp(fontname, "CB") == 0) {
3867    if ((! fill_on) && (is_courier_until_eol()) &&
3868	is_line_start(! fill_on)) {
3869      current_paragraph->do_pre();
3870    }
3871    current_paragraph->do_tt();
3872    current_paragraph->do_bold();
3873  } else if (strcmp(fontname, "CBI") == 0) {
3874    if ((! fill_on) && (is_courier_until_eol()) &&
3875	is_line_start(! fill_on)) {
3876      current_paragraph->do_pre();
3877    }
3878    current_paragraph->do_tt();
3879    current_paragraph->do_italic();
3880    current_paragraph->do_bold();
3881  }
3882}
3883
3884/*
3885 *  start_size - from is old font size, to is the new font size.
3886 *               The html increase <big> and <small> decrease alters the
3887 *               font size by 20%. We try and map these onto glyph sizes.
3888 */
3889
3890void html_printer::start_size (int from, int to)
3891{
3892  if (from < to) {
3893    while (from < to) {
3894      current_paragraph->do_big();
3895      from += SIZE_INCREMENT;
3896    }
3897  } else if (from > to) {
3898    while (from > to) {
3899      current_paragraph->do_small();
3900      from -= SIZE_INCREMENT;
3901    }
3902  }
3903}
3904
3905/*
3906 *  do_font - checks to see whether we need to alter the html font.
3907 */
3908
3909void html_printer::do_font (text_glob *g)
3910{
3911  /*
3912   *  check if the output_style.point_size has not been set yet
3913   *  this allow users to place .ps at the top of their troff files
3914   *  and grohtml can then treat the .ps value as the base font size (3)
3915   */
3916  if (output_style.point_size == -1) {
3917    output_style.point_size = pointsize;
3918  }
3919
3920  if (g->text_style.f != output_style.f) {
3921    if (output_style.f != 0) {
3922      end_font(output_style.f->get_name());
3923    }
3924    output_style.f = g->text_style.f;
3925    if (output_style.f != 0) {
3926      start_font(output_style.f->get_name());
3927    }
3928  }
3929  if (output_style.point_size != g->text_style.point_size) {
3930    do_sup_or_sub(g);
3931    if ((output_style.point_size > 0) &&
3932	(g->text_style.point_size > 0)) {
3933      start_size(output_style.point_size, g->text_style.point_size);
3934    }
3935    if (g->text_style.point_size > 0) {
3936      output_style.point_size = g->text_style.point_size;
3937    }
3938  }
3939  if (output_style.col != g->text_style.col) {
3940    current_paragraph->done_color();
3941    output_style.col = g->text_style.col;
3942    current_paragraph->do_color(&output_style.col);
3943  }
3944}
3945
3946/*
3947 *  start_subscript - returns TRUE if, g, looks like a subscript start.
3948 */
3949
3950int html_printer::start_subscript (text_glob *g)
3951{
3952  int r        = font::res;
3953  int height   = output_style.point_size*r/72;
3954
3955  return( (output_style.point_size != 0) &&
3956	  (output_vpos < g->minv) &&
3957	  (output_vpos-height > g->maxv) &&
3958	  (output_style.point_size > g->text_style.point_size) );
3959}
3960
3961/*
3962 *  start_superscript - returns TRUE if, g, looks like a superscript start.
3963 */
3964
3965int html_printer::start_superscript (text_glob *g)
3966{
3967  int r        = font::res;
3968  int height   = output_style.point_size*r/72;
3969
3970  return( (output_style.point_size != 0) &&
3971	  (output_vpos > g->minv) &&
3972	  (output_vpos-height < g->maxv) &&
3973	  (output_style.point_size > g->text_style.point_size) );
3974}
3975
3976/*
3977 *  end_subscript - returns TRUE if, g, looks like the end of a subscript.
3978 */
3979
3980int html_printer::end_subscript (text_glob *g)
3981{
3982  int r        = font::res;
3983  int height   = output_style.point_size*r/72;
3984
3985  return( (output_style.point_size != 0) &&
3986	  (g->minv < output_vpos) &&
3987	  (output_vpos-height > g->maxv) &&
3988	  (output_style.point_size < g->text_style.point_size) );
3989}
3990
3991/*
3992 *  end_superscript - returns TRUE if, g, looks like the end of a superscript.
3993 */
3994
3995int html_printer::end_superscript (text_glob *g)
3996{
3997  int r        = font::res;
3998  int height   = output_style.point_size*r/72;
3999
4000  return( (output_style.point_size != 0) &&
4001	  (g->minv > output_vpos) &&
4002	  (output_vpos-height < g->maxv) &&
4003	  (output_style.point_size < g->text_style.point_size) );
4004}
4005
4006/*
4007 *  do_sup_or_sub - checks to see whether the next glyph is a subscript/superscript
4008 *                  start/end and it calls the services of html-text to issue the
4009 *                  appropriate tags.
4010 */
4011
4012void html_printer::do_sup_or_sub (text_glob *g)
4013{
4014  if (! supress_sub_sup) {
4015    if (start_subscript(g)) {
4016      current_paragraph->do_sub();
4017    } else if (start_superscript(g)) {
4018      current_paragraph->do_sup();
4019    } else if (end_subscript(g)) {
4020      current_paragraph->done_sub();
4021    } else if (end_superscript(g)) {
4022      current_paragraph->done_sup();
4023    }
4024  }
4025}
4026
4027/*
4028 *  do_end_para - writes out the html text after shutting down the
4029 *                current paragraph.
4030 */
4031
4032void html_printer::do_end_para (text_glob *g)
4033{
4034  do_font(g);
4035  current_paragraph->done_para();
4036  current_paragraph->remove_para_space();
4037  html.put_string(g->text_string+9);
4038  output_vpos     = g->minv;
4039  output_hpos     = g->maxh;
4040  output_vpos_max = g->maxv;
4041  supress_sub_sup = FALSE;
4042}
4043
4044/*
4045 *  emit_html - write out the html text
4046 */
4047
4048void html_printer::emit_html (text_glob *g)
4049{
4050  do_font(g);
4051  determine_space(g);
4052  current_paragraph->do_emittext(g->text_string, g->text_length);
4053  output_vpos     = g->minv;
4054  output_hpos     = g->maxh;
4055  output_vpos_max = g->maxv;
4056  supress_sub_sup = FALSE;
4057}
4058
4059/*
4060 *  flush_sbuf - flushes the current sbuf into the list of glyphs.
4061 */
4062
4063void html_printer::flush_sbuf()
4064{
4065  if (sbuf.length() > 0) {
4066    int r=font::res;   // resolution of the device
4067    set_style(sbuf_style);
4068
4069    if (overstrike_detected && (! is_bold(sbuf_style.f))) {
4070      font *bold_font = make_bold(sbuf_style.f);
4071      if (bold_font != NULL)
4072	sbuf_style.f = bold_font;
4073    }
4074
4075    page_contents->add(&sbuf_style, sbuf,
4076		       line_number,
4077		       sbuf_vpos-sbuf_style.point_size*r/72, sbuf_start_hpos,
4078		       sbuf_vpos                           , sbuf_end_hpos);
4079
4080    output_hpos = sbuf_end_hpos;
4081    output_vpos = sbuf_vpos;
4082    last_sbuf_length = 0;
4083    sbuf_prev_hpos = sbuf_end_hpos;
4084    overstrike_detected = FALSE;
4085    sbuf.clear();
4086  }
4087}
4088
4089void html_printer::set_line_thickness(const environment *env)
4090{
4091  line_thickness = env->size;
4092}
4093
4094void html_printer::draw(int code, int *p, int np, const environment *env)
4095{
4096  switch (code) {
4097
4098  case 'l':
4099# if 0
4100    if (np == 2) {
4101      page_contents->add_line(&sbuf_style,
4102			      line_number,
4103			      env->hpos, env->vpos, env->hpos+p[0], env->vpos+p[1], line_thickness);
4104    } else {
4105      error("2 arguments required for line");
4106    }
4107# endif
4108    break;
4109  case 't':
4110    {
4111      if (np == 0) {
4112	line_thickness = -1;
4113      } else {
4114	// troff gratuitously adds an extra 0
4115	if (np != 1 && np != 2) {
4116	  error("0 or 1 argument required for thickness");
4117	  break;
4118	}
4119	line_thickness = p[0];
4120      }
4121      break;
4122    }
4123
4124  case 'P':
4125    break;
4126  case 'p':
4127    break;
4128  case 'E':
4129    break;
4130  case 'e':
4131    break;
4132  case 'C':
4133    break;
4134  case 'c':
4135    break;
4136  case 'a':
4137    break;
4138  case '~':
4139    break;
4140  case 'f':
4141    break;
4142  case 'F':
4143    // fill with color env->fill
4144    if (background != NULL)
4145      delete background;
4146    background = new color;
4147    *background = *env->fill;
4148    break;
4149
4150  default:
4151    error("unrecognised drawing command `%1'", char(code));
4152    break;
4153  }
4154}
4155
4156html_printer::html_printer()
4157: html(0, MAX_LINE_LENGTH),
4158  no_of_printed_pages(0),
4159  last_sbuf_length(0),
4160  overstrike_detected(FALSE),
4161  output_hpos(-1),
4162  output_vpos(-1),
4163  output_vpos_max(-1),
4164  line_thickness(-1),
4165  inside_font_style(0),
4166  page_number(0),
4167  header_indent(-1),
4168  supress_sub_sup(TRUE),
4169  cutoff_heading(100),
4170  indent(NULL),
4171  table(NULL),
4172  end_center(0),
4173  end_tempindent(0),
4174  next_tag(INLINE),
4175  fill_on(TRUE),
4176  max_linelength(-1),
4177  linelength(0),
4178  pageoffset(0),
4179  troff_indent(0),
4180  device_indent(0),
4181  temp_indent(0),
4182  pointsize(base_point_size),
4183  line_number(0),
4184  background(default_background),
4185  seen_indent(FALSE),
4186  next_indent(0),
4187  seen_pageoffset(FALSE),
4188  next_pageoffset(0),
4189  seen_linelength(FALSE),
4190  next_linelength(0),
4191  seen_center(FALSE),
4192  next_center(0),
4193  seen_space(0),
4194  seen_break(0),
4195  current_column(0),
4196  row_space(FALSE)
4197{
4198  file_list.add_new_file(xtmpfile());
4199  html.set_file(file_list.get_file());
4200  if (font::hor != 24)
4201    fatal("horizontal resolution must be 24");
4202  if (font::vert != 40)
4203    fatal("vertical resolution must be 40");
4204#if 0
4205  // should be sorted html..
4206  if (font::res % (font::sizescale*72) != 0)
4207    fatal("res must be a multiple of 72*sizescale");
4208#endif
4209  int r = font::res;
4210  int point = 0;
4211  while (r % 10 == 0) {
4212    r /= 10;
4213    point++;
4214  }
4215  res               = r;
4216  html.set_fixed_point(point);
4217  space_char_index  = font::name_to_index("space");
4218  space_width       = font::hor;
4219  paper_length      = font::paperlength;
4220  linelength        = font::res*13/2;
4221  if (paper_length == 0)
4222    paper_length    = 11*font::res;
4223
4224  page_contents = new page();
4225}
4226
4227/*
4228 *  add_to_sbuf - adds character code or name to the sbuf.
4229 */
4230
4231void html_printer::add_to_sbuf (int idx, const string &s)
4232{
4233  if (sbuf_style.f == NULL)
4234    return;
4235
4236  char *html_glyph = NULL;
4237  unsigned int code = sbuf_style.f->get_code(idx);
4238
4239  if (s.empty()) {
4240    if (sbuf_style.f->contains(idx))
4241      html_glyph = (char *)sbuf_style.f->get_special_device_encoding(idx);
4242    else
4243      html_glyph = NULL;
4244
4245    if ((html_glyph == NULL) && (code >= UNICODE_DESC_START))
4246      html_glyph = to_unicode(code);
4247  } else
4248    html_glyph = get_html_translation(sbuf_style.f, s);
4249
4250  last_sbuf_length = sbuf.length();
4251  if (html_glyph == NULL)
4252    sbuf += ((char)code);
4253  else
4254    sbuf += html_glyph;
4255}
4256
4257int html_printer::sbuf_continuation (int idx, const char *name,
4258				     const environment *env, int w)
4259{
4260  /*
4261   *  lets see whether the glyph is closer to the end of sbuf
4262   */
4263  if ((sbuf_end_hpos == env->hpos)
4264      || ((sbuf_prev_hpos < sbuf_end_hpos)
4265	  && (env->hpos < sbuf_end_hpos)
4266	  && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) {
4267    add_to_sbuf(idx, name);
4268    sbuf_prev_hpos = sbuf_end_hpos;
4269    sbuf_end_hpos += w + sbuf_kern;
4270    return TRUE;
4271  } else {
4272    if ((env->hpos >= sbuf_end_hpos) &&
4273	((sbuf_kern == 0) || (sbuf_end_hpos - sbuf_kern != env->hpos))) {
4274      /*
4275       *  lets see whether a space is needed or not
4276       */
4277
4278      if (env->hpos-sbuf_end_hpos < space_width) {
4279	add_to_sbuf(idx, name);
4280	sbuf_prev_hpos = sbuf_end_hpos;
4281	sbuf_end_hpos = env->hpos + w;
4282	return TRUE;
4283      }
4284    }
4285  }
4286  return FALSE ;
4287}
4288
4289/*
4290 *  get_html_translation - given the position of the character and its name
4291 *                         return the device encoding for such character.
4292 */
4293
4294char *get_html_translation (font *f, const string &name)
4295{
4296  int idx;
4297
4298  if ((f == 0) || name.empty())
4299    return NULL;
4300  else {
4301    idx = f->name_to_index((char *)(name + '\0').contents());
4302    if (idx == 0) {
4303      error("character `%s' not found", (name + '\0').contents());
4304      return NULL;
4305    } else
4306      if (f->contains(idx))
4307	return (char *)f->get_special_device_encoding(idx);
4308      else
4309	return NULL;
4310  }
4311}
4312
4313/*
4314 *  overstrike - returns TRUE if the glyph (i, name) is going to overstrike
4315 *               a previous glyph in sbuf.
4316 *               If TRUE the font is changed to bold and the previous sbuf
4317 *               is flushed.
4318 */
4319
4320int html_printer::overstrike(int idx, const char *name, const environment *env, int w)
4321{
4322  if ((env->hpos < sbuf_end_hpos)
4323      || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos))) {
4324    /*
4325     *  at this point we have detected an overlap
4326     */
4327    if (overstrike_detected) {
4328      /* already detected, remove previous glyph and use this glyph */
4329      sbuf.set_length(last_sbuf_length);
4330      add_to_sbuf(idx, name);
4331      sbuf_end_hpos = env->hpos + w;
4332      return TRUE;
4333    } else {
4334      /* first time we have detected an overstrike in the sbuf */
4335      sbuf.set_length(last_sbuf_length); /* remove previous glyph */
4336      if (! is_bold(sbuf_style.f))
4337	flush_sbuf();
4338      overstrike_detected = TRUE;
4339      add_to_sbuf(idx, name);
4340      sbuf_end_hpos = env->hpos + w;
4341      return TRUE;
4342    }
4343  }
4344  return FALSE ;
4345}
4346
4347/*
4348 *  set_char - adds a character into the sbuf if it is a continuation
4349 *             with the previous word otherwise flush the current sbuf
4350 *             and add character anew.
4351 */
4352
4353void html_printer::set_char(int i, font *f, const environment *env,
4354			    int w, const char *name)
4355{
4356  style sty(f, env->size, env->height, env->slant, env->fontno, *env->col);
4357  if (sty.slant != 0) {
4358    if (sty.slant > 80 || sty.slant < -80) {
4359      error("silly slant `%1' degrees", sty.slant);
4360      sty.slant = 0;
4361    }
4362  }
4363  if (((! sbuf.empty()) && (sty == sbuf_style) && (sbuf_vpos == env->vpos))
4364      && (sbuf_continuation(i, name, env, w) || overstrike(i, name, env, w)))
4365    return;
4366
4367  flush_sbuf();
4368  if (sbuf_style.f == NULL)
4369    sbuf_style = sty;
4370  add_to_sbuf(i, name);
4371  sbuf_end_hpos = env->hpos + w;
4372  sbuf_start_hpos = env->hpos;
4373  sbuf_prev_hpos = env->hpos;
4374  sbuf_vpos = env->vpos;
4375  sbuf_style = sty;
4376  sbuf_kern = 0;
4377}
4378
4379/*
4380 *  set_numbered_char - handle numbered characters.
4381 *                      Negative values are interpreted as unbreakable spaces;
4382 *                      the value (taken positive) gives the width.
4383 */
4384
4385void html_printer::set_numbered_char(int num, const environment *env,
4386				     int *widthp)
4387{
4388  int nbsp_width = 0;
4389  if (num < 0) {
4390    nbsp_width = -num;
4391    num = 160;		// &nbsp;
4392  }
4393  int i = font::number_to_index(num);
4394  int fn = env->fontno;
4395  if (fn < 0 || fn >= nfonts) {
4396    error("bad font position `%1'", fn);
4397    return;
4398  }
4399  font *f = font_table[fn];
4400  if (f == 0) {
4401    error("no font mounted at `%1'", fn);
4402    return;
4403  }
4404  if (!f->contains(i)) {
4405    error("font `%1' does not contain numbered character %2",
4406	  f->get_name(),
4407	  num);
4408    return;
4409  }
4410  int w;
4411  if (nbsp_width)
4412    w = nbsp_width;
4413  else
4414    w = f->get_width(i, env->size);
4415  w = round_width(w);
4416  if (widthp)
4417    *widthp = w;
4418  set_char(i, f, env, w, 0);
4419}
4420
4421int html_printer::set_char_and_width(const char *nm, const environment *env,
4422				     int *widthp, font **f)
4423{
4424  int i = font::name_to_index(nm);
4425  int fn = env->fontno;
4426  if (fn < 0 || fn >= nfonts) {
4427    error("bad font position `%1'", fn);
4428    return -1;
4429  }
4430  *f = font_table[fn];
4431  if (*f == 0) {
4432    error("no font mounted at `%1'", fn);
4433    return -1;
4434  }
4435  if (!(*f)->contains(i)) {
4436    if (nm[0] != '\0' && nm[1] == '\0')
4437      error("font `%1' does not contain ascii character `%2'",
4438	    (*f)->get_name(),
4439	    nm[0]);
4440    else
4441      error("font `%1' does not contain special character `%2'",
4442	    (*f)->get_name(),
4443	    nm);
4444    return -1;
4445  }
4446  int w = (*f)->get_width(i, env->size);
4447  w = round_width(w);
4448  if (widthp)
4449    *widthp = w;
4450  return i;
4451}
4452
4453/*
4454 *  write_title - writes the title to this document
4455 */
4456
4457void html_printer::write_title (int in_head)
4458{
4459  if (title.has_been_found) {
4460    if (in_head) {
4461      html.put_string("<title>");
4462      html.put_string(title.text);
4463      html.put_string("</title>").nl().nl();
4464    } else {
4465      title.has_been_written = TRUE;
4466      if (title.with_h1) {
4467	html.put_string("<h1 align=center>");
4468	html.put_string(title.text);
4469	html.put_string("</h1>").nl().nl();
4470      }
4471    }
4472  } else if (in_head) {
4473    // place empty title tags to help conform to `tidy'
4474    html.put_string("<title></title>").nl();
4475  }
4476}
4477
4478/*
4479 *  write_rule - emits a html rule tag, if the auto_rule boolean is true.
4480 */
4481
4482static void write_rule (void)
4483{
4484  if (auto_rule)
4485    fputs("<hr>\n", stdout);
4486}
4487
4488void html_printer::begin_page(int n)
4489{
4490  page_number            =  n;
4491#if defined(DEBUGGING)
4492  html.begin_comment("Page: ").put_string(i_to_a(page_number)).end_comment();;
4493#endif
4494  no_of_printed_pages++;
4495
4496  output_style.f         =  0;
4497  output_style.point_size= -1;
4498  output_space_code      = 32;
4499  output_draw_point_size = -1;
4500  output_line_thickness  = -1;
4501  output_hpos            = -1;
4502  output_vpos            = -1;
4503  output_vpos_max        = -1;
4504  current_paragraph      = new html_text(&html);
4505  do_indent(get_troff_indent(), pageoffset, linelength);
4506  current_paragraph->do_para("", FALSE);
4507}
4508
4509void html_printer::end_page(int)
4510{
4511  flush_sbuf();
4512  flush_page();
4513}
4514
4515font *html_printer::make_font(const char *nm)
4516{
4517  return html_font::load_html_font(nm);
4518}
4519
4520void html_printer::do_body (void)
4521{
4522  if (background == NULL)
4523    fputs("<body>\n\n", stdout);
4524  else {
4525    unsigned int r, g, b;
4526    char buf[6+1];
4527
4528    background->get_rgb(&r, &g, &b);
4529    // we have to scale 0..0xFFFF to 0..0xFF
4530    sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
4531
4532    fputs("<body bgcolor=\"#", stdout);
4533    fputs(buf, stdout);
4534    fputs("\">\n\n", stdout);
4535  }
4536}
4537
4538/*
4539 *  emit_link - generates: <a href="to">name</a>
4540 */
4541
4542void html_printer::emit_link (const string &to, const char *name)
4543{
4544  fputs("<a href=\"", stdout);
4545  fputs(to.contents(), stdout);
4546  fputs("\">", stdout);
4547  fputs(name, stdout);
4548  fputs("</a>", stdout);
4549}
4550
4551/*
4552 *  write_navigation - writes out the links which navigate between
4553 *                     file fragments.
4554 */
4555
4556void html_printer::write_navigation (const string &top, const string &prev,
4557				     const string &next, const string &current)
4558{
4559  int need_bar = FALSE;
4560
4561  if (multiple_files) {
4562    write_rule();
4563    fputs("[ ", stdout);
4564    if ((strcmp(prev.contents(), "") != 0) && prev != top && prev != current) {
4565      emit_link(prev, "prev");
4566      need_bar = TRUE;
4567    }
4568    if ((strcmp(next.contents(), "") != 0) && next != top && next != current) {
4569      if (need_bar)
4570	fputs(" | ", stdout);
4571      emit_link(next, "next");
4572      need_bar = TRUE;
4573    }
4574    if (top != "<standard input>" && (strcmp(top.contents(), "") != 0) && top != current) {
4575      if (need_bar)
4576	fputs(" | ", stdout);
4577      emit_link(top, "top");
4578    }
4579    fputs(" ]\n", stdout);
4580    write_rule();
4581  }
4582}
4583
4584/*
4585 *  do_file_components - scan the file list copying each temporary
4586 *                       file in turn.  This is used twofold:
4587 *
4588 *                       firstly to emit section heading links,
4589 *                       between file fragments if required and
4590 *                       secondly to generate jobname file fragments
4591 *                       if required.
4592 */
4593
4594void html_printer::do_file_components (void)
4595{
4596  int fragment_no = 1;
4597  string top;
4598  string prev;
4599  string next;
4600  string current;
4601
4602  file_list.start_of_list();
4603  top = string(job_name);
4604  top += string(".html");
4605  top += '\0';
4606  next = file_list.next_file_name();
4607  next += '\0';
4608  current = next;
4609  while (file_list.get_file() != 0) {
4610    if (fseek(file_list.get_file(), 0L, 0) < 0)
4611      fatal("fseek on temporary file failed");
4612    html.copy_file(file_list.get_file());
4613    fclose(file_list.get_file());
4614
4615    file_list.move_next();
4616    if (file_list.is_new_output_file()) {
4617      if (fragment_no > 1)
4618	write_navigation(top, prev, next, current);
4619      prev = current;
4620      current = next;
4621      next = file_list.next_file_name();
4622      next += '\0';
4623      string split_file = file_list.file_name();
4624      split_file += '\0';
4625      fflush(stdout);
4626      freopen(split_file.contents(), "w", stdout);
4627      fragment_no++;
4628      writeHeadMetaStyle();
4629      write_navigation(top, prev, next, current);
4630    }
4631    if (file_list.are_links_required())
4632      header.write_headings(stdout, TRUE);
4633  }
4634  if (fragment_no > 1)
4635    write_navigation(top, prev, next, current);
4636  else
4637    write_rule();
4638}
4639
4640/*
4641 *  writeHeadMetaStyle - emits the <head> <meta> and <style> tags and
4642 *                       related information.
4643 */
4644
4645void html_printer::writeHeadMetaStyle (void)
4646{
4647  fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n", stdout);
4648  fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout);
4649
4650  fputs("<html>\n", stdout);
4651  fputs("<head>\n", stdout);
4652  fputs("<meta name=\"generator\" "
4653	      "content=\"groff -Thtml, see www.gnu.org\">\n", stdout);
4654  fputs("<meta http-equiv=\"Content-Type\" "
4655	      "content=\"text/html; charset=US-ASCII\">\n", stdout);
4656  fputs("<meta name=\"Content-Style\" content=\"text/css\">\n", stdout);
4657
4658  fputs("<style type=\"text/css\">\n", stdout);
4659  fputs("       p     { margin-top: 0; margin-bottom: 0; }\n", stdout);
4660  fputs("       pre   { margin-top: 0; margin-bottom: 0; }\n", stdout);
4661  fputs("       table { margin-top: 0; margin-bottom: 0; }\n", stdout);
4662  fputs("</style>\n", stdout);
4663}
4664
4665html_printer::~html_printer()
4666{
4667#ifdef LONG_FOR_TIME_T
4668  long t;
4669#else
4670  time_t t;
4671#endif
4672
4673  current_paragraph->flush_text();
4674  html.end_line();
4675  html.set_file(stdout);
4676  html.begin_comment("Creator     : ")
4677    .put_string("groff ")
4678    .put_string("version ")
4679    .put_string(Version_string)
4680    .end_comment();
4681
4682  t = time(0);
4683  html.begin_comment("CreationDate: ")
4684    .put_string(ctime(&t), strlen(ctime(&t))-1)
4685    .end_comment();
4686
4687  writeHeadMetaStyle();
4688
4689  write_title(TRUE);
4690  head_info += '\0';
4691  fputs(head_info.contents(), stdout);
4692  fputs("</head>\n", stdout);
4693  do_body();
4694
4695  write_title(FALSE);
4696  header.write_headings(stdout, FALSE);
4697  write_rule();
4698#if defined(DEBUGGING)
4699  html.begin_comment("Total number of pages: ").put_string(i_to_a(no_of_printed_pages)).end_comment();
4700#endif
4701  html.end_line();
4702  html.end_line();
4703
4704  if (multiple_files) {
4705    fputs("</body>\n", stdout);
4706    fputs("</html>\n", stdout);
4707    do_file_components();
4708  } else {
4709    do_file_components();
4710    fputs("</body>\n", stdout);
4711    fputs("</html>\n", stdout);
4712  }
4713}
4714
4715/*
4716 *  get_str - returns a dupicate of string, s. The duplicate
4717 *            string is terminated at the next ',' or ']'.
4718 */
4719
4720static char *get_str (const char *s, char **n)
4721{
4722  int i=0;
4723  char *v;
4724
4725  while ((s[i] != (char)0) && (s[i] != ',') && (s[i] != ']'))
4726    i++;
4727  if (i>0) {
4728    v = new char[i+1];
4729    memcpy(v, s, i+1);
4730    v[i] = (char)0;
4731    if (s[i] == ',')
4732      (*n) = (char *)&s[i+1];
4733    else
4734      (*n) = (char *)&s[i];
4735    return v;
4736  }
4737  if (s[i] == ',')
4738    (*n) = (char *)&s[1];
4739  else
4740    (*n) = (char *)s;
4741  return NULL;
4742}
4743
4744/*
4745 *  make_val - creates a string from if s is NULL.
4746 */
4747
4748char *make_val (char *s, int v, char *id, char *f, char *l)
4749{
4750  if (s == NULL) {
4751    char buf[30];
4752
4753    sprintf(buf, "%d", v);
4754    return strsave(buf);
4755  }
4756  else {
4757    /*
4758     *  check that value, s, is the same as, v.
4759     */
4760    char *t = s;
4761
4762    while (*t == '=')
4763      t++;
4764    if (atoi(t) != v) {
4765      if (f == NULL)
4766	f = (char *)"stdin";
4767      if (l == NULL)
4768	l = (char *)"<none>";
4769      fprintf(stderr, "%s:%s: grohtml assertion failed at id%s expecting %d and was given %s\n",
4770	      f, l, id, v, s);
4771    }
4772    return s;
4773  }
4774}
4775
4776/*
4777 *  handle_assertion - handles the assertions created via .www:ASSERT
4778 *                     in www.tmac. See www.tmac for examples.
4779 *                     This method should be called as we are
4780 *                     parsing the ditroff input. It checks the x, y
4781 *                     position assertions. It does _not_ check the
4782 *                     troff state assertions as these are unknown at this
4783 *                     point.
4784 */
4785
4786void html_printer::handle_assertion (int minv, int minh, int maxv, int maxh, const char *s)
4787{
4788  char *n;
4789  char *cmd = get_str(s, &n);
4790  char *id  = get_str(n, &n);
4791  char *val = get_str(n, &n);
4792  char *file= get_str(n, &n);
4793  char *line= get_str(n, &n);
4794
4795  if (strcmp(cmd, "assertion:[x") == 0)
4796    as.addx(cmd, id, make_val(val, minh, id, file, line), file, line);
4797  else if (strcmp(cmd, "assertion:[y") == 0)
4798    as.addy(cmd, id, make_val(val, minv, id, file, line), file, line);
4799  else
4800    if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0)
4801      page_contents->add_tag(&sbuf_style, string(s),
4802			     line_number, minv, minh, maxv, maxh);
4803}
4804
4805/*
4806 *  build_state_assertion - builds the troff state assertions.
4807 */
4808
4809void html_printer::handle_state_assertion (text_glob *g)
4810{
4811  if (g != NULL && g->is_a_tag() &&
4812      (strncmp(g->text_string, "assertion:[", 11) == 0)) {
4813    char *n   = (char *)&g->text_string[11];
4814    char *cmd = get_str(n, &n);
4815    char *val = get_str(n, &n);
4816    (void)get_str(n, &n);	// unused
4817    char *file= get_str(n, &n);
4818    char *line= get_str(n, &n);
4819
4820    as.build(cmd, val, file, line);
4821  }
4822}
4823
4824/*
4825 *  special - handle all x X requests from troff. For post-html they
4826 *            allow users to pass raw html commands, turn auto linked
4827 *            headings off/on etc.
4828 */
4829
4830void html_printer::special(char *s, const environment *env, char type)
4831{
4832  if (type != 'p')
4833    return;
4834  if (s != 0) {
4835    flush_sbuf();
4836    if (env->fontno >= 0) {
4837      style sty(get_font_from_index(env->fontno), env->size, env->height,
4838		env->slant, env->fontno, *env->col);
4839      sbuf_style = sty;
4840    }
4841
4842    if (strncmp(s, "html:", 5) == 0) {
4843      int r=font::res;   /* resolution of the device */
4844      font *f=sbuf_style.f;
4845
4846      if (f == NULL) {
4847	int found=FALSE;
4848
4849	f = font::load_font("TR", &found);
4850      }
4851
4852      /*
4853       *  need to pass rest of string through to html output during flush
4854       */
4855      page_contents->add_and_encode(&sbuf_style, string(&s[5]),
4856				    line_number,
4857				    env->vpos-env->size*r/72, env->hpos,
4858				    env->vpos               , env->hpos,
4859				    FALSE);
4860
4861      /*
4862       * assume that the html command has no width, if it does then
4863       * hopefully troff will have fudged this in a macro by
4864       * requesting that the formatting move right by the appropriate
4865       * amount.
4866       */
4867    } else if (strncmp(s, "html</p>:", 9) == 0) {
4868      int r=font::res;   /* resolution of the device */
4869      font *f=sbuf_style.f;
4870
4871      if (f == NULL) {
4872	int found=FALSE;
4873
4874	f = font::load_font("TR", &found);
4875      }
4876
4877      /*
4878       *  need to pass all of string through to html output during flush
4879       */
4880      page_contents->add_and_encode(&sbuf_style, string(s),
4881				    line_number,
4882				    env->vpos-env->size*r/72, env->hpos,
4883				    env->vpos               , env->hpos,
4884				    TRUE);
4885
4886      /*
4887       * assume that the html command has no width, if it does then
4888       * hopefully troff will have fudged this in a macro by
4889       * requesting that the formatting move right by the appropriate
4890       * amount.
4891       */
4892    } else if (strncmp(s, "index:", 6) == 0) {
4893      cutoff_heading = atoi(&s[6]);
4894    } else if (strncmp(s, "assertion:[", 11) == 0) {
4895      int r=font::res;   /* resolution of the device */
4896
4897      handle_assertion(env->vpos-env->size*r/72, env->hpos,
4898		       env->vpos, env->hpos, s);
4899    }
4900  }
4901}
4902
4903/*
4904 *  devtag - handles device troff tags sent from the `troff'.
4905 *           These include the troff state machine tags:
4906 *           .br, .sp, .in, .tl, .ll etc
4907 *
4908 *           (see man 5 grohtml_tags).
4909 */
4910
4911void html_printer::devtag (char *s, const environment *env, char type)
4912{
4913  if (type != 'p')
4914    return;
4915
4916  if (s != 0) {
4917    flush_sbuf();
4918    if (env->fontno >= 0) {
4919      style sty(get_font_from_index(env->fontno), env->size, env->height,
4920		env->slant, env->fontno, *env->col);
4921      sbuf_style = sty;
4922    }
4923
4924    if (strncmp(s, "devtag:", strlen("devtag:")) == 0) {
4925      int r=font::res;   /* resolution of the device */
4926
4927      page_contents->add_tag(&sbuf_style, string(s),
4928			     line_number,
4929			     env->vpos-env->size*r/72, env->hpos,
4930			     env->vpos               , env->hpos);
4931    }
4932  }
4933}
4934
4935
4936/*
4937 *  taken from number.cpp in src/roff/troff, [hunits::hunits(units x)]
4938 */
4939
4940int html_printer::round_width(int x)
4941{
4942  int r = font::hor;
4943  int n;
4944
4945  // don't depend on the rounding direction for division of negative integers
4946  if (r == 1)
4947    n = x;
4948  else
4949    n = (x < 0
4950	 ? -((-x + r/2 - 1)/r)
4951	 : (x + r/2 - 1)/r);
4952  return n * r;
4953}
4954
4955int main(int argc, char **argv)
4956{
4957  program_name = argv[0];
4958  static char stderr_buf[BUFSIZ];
4959  setbuf(stderr, stderr_buf);
4960  int c;
4961  static const struct option long_options[] = {
4962    { "help", no_argument, 0, CHAR_MAX + 1 },
4963    { "version", no_argument, 0, 'v' },
4964    { NULL, 0, 0, 0 }
4965  };
4966  while ((c = getopt_long(argc, argv, "a:bdD:F:g:hi:I:j:lno:prs:S:v",
4967			  long_options, NULL))
4968	 != EOF)
4969    switch(c) {
4970    case 'a':
4971      /* text antialiasing bits - handled by pre-html */
4972      break;
4973    case 'b':
4974      // set background color to white
4975      default_background = new color;
4976      default_background->set_gray(color::MAX_COLOR_VAL);
4977      break;
4978    case 'd':
4979      /* handled by pre-html */
4980      break;
4981    case 'D':
4982      /* handled by pre-html */
4983      break;
4984    case 'F':
4985      font::command_line_font_dir(optarg);
4986      break;
4987    case 'g':
4988      /* graphic antialiasing bits - handled by pre-html */
4989      break;
4990    case 'h':
4991      /* do not use the Hn headings of html, but manufacture our own */
4992      manufacture_headings = TRUE;
4993      break;
4994    case 'i':
4995      /* handled by pre-html */
4996      break;
4997    case 'I':
4998      /* handled by pre-html */
4999      break;
5000    case 'j':
5001      multiple_files = TRUE;
5002      job_name = optarg;
5003      break;
5004    case 'l':
5005      auto_links = FALSE;
5006      break;
5007    case 'n':
5008      simple_anchors = TRUE;
5009      break;
5010    case 'o':
5011      /* handled by pre-html */
5012      break;
5013    case 'p':
5014      /* handled by pre-html */
5015      break;
5016    case 'r':
5017      auto_rule = FALSE;
5018      break;
5019    case 's':
5020      base_point_size = atoi(optarg);
5021      break;
5022    case 'S':
5023      split_level = atoi(optarg) + 1;
5024      break;
5025    case 'v':
5026      printf("GNU post-grohtml (groff) version %s\n", Version_string);
5027      exit(0);
5028      break;
5029    case CHAR_MAX + 1: // --help
5030      usage(stdout);
5031      exit(0);
5032      break;
5033    case '?':
5034      usage(stderr);
5035      exit(1);
5036      break;
5037    default:
5038      assert(0);
5039    }
5040  if (optind >= argc) {
5041    do_file("-");
5042  } else {
5043    for (int i = optind; i < argc; i++)
5044      do_file(argv[i]);
5045  }
5046  return 0;
5047}
5048
5049static void usage(FILE *stream)
5050{
5051  fprintf(stream, "usage: %s [-vblnh] [-D dir] [-I image_stem] [-F dir] [files ...]\n",
5052	  program_name);
5053}
5054