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