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