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