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