summaryrefslogtreecommitdiffstats
path: root/src/devices/grohtml
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/grohtml')
-rw-r--r--src/devices/grohtml/grohtml.1.man731
-rw-r--r--src/devices/grohtml/grohtml.am40
-rw-r--r--src/devices/grohtml/html-table.cpp848
-rw-r--r--src/devices/grohtml/html-table.h133
-rw-r--r--src/devices/grohtml/html-text.cpp1056
-rw-r--r--src/devices/grohtml/html-text.h138
-rw-r--r--src/devices/grohtml/html.h97
-rw-r--r--src/devices/grohtml/output.cpp363
-rw-r--r--src/devices/grohtml/post-html.cpp5684
9 files changed, 9090 insertions, 0 deletions
diff --git a/src/devices/grohtml/grohtml.1.man b/src/devices/grohtml/grohtml.1.man
new file mode 100644
index 0000000..2243b47
--- /dev/null
+++ b/src/devices/grohtml/grohtml.1.man
@@ -0,0 +1,731 @@
+.TH grohtml @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grohtml, post\-grohtml, pre\-grohtml \-
+.I groff
+output driver for HTML
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1999-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grohtml_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pre\-grohtml
+.RB [ \-epV ]
+.RB [ \-a
+.IR anti-aliasing-text-bits ]
+.RB [ \-D
+.IR image-directory ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-g
+.IR anti-aliasing-graphic-bits ]
+.RB [ \-i
+.IR resolution ]
+.RB [ \-I
+.IR image-stem ]
+.RB [ \-o
+.IR image-vertical-offset ]
+.RB [ \-x
+.IR html-dialect ]
+.I troff-command
+.I troff-argument
+\&.\|.\|.
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-v
+.
+.SY pre\-grohtml
+.B \-\-version
+.YS
+.
+.
+.SY post\-grohtml
+.RB [ \-bCGhlnrVy ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-j
+.IR output-stem ]
+.RB [ \-s
+.IR base-point-size ]
+.RB [ \-S
+.IR heading-level ]
+.RB [ \-x
+.IR html-dialect ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY post\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY post\-grohtml
+.B \-v
+.
+.SY post\-grohtml
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+system's HTML support consists of a preprocessor,
+.IR \%pre\-grohtml ,
+and an output driver,
+.IR \%post\-grohtml ;
+together,
+they translate
+.MR roff @MAN7EXT@
+documents to HTML.
+.
+Because a preprocessor is (uniquely) required for this output driver,
+users should invoke
+.I \%grohtml
+via the
+.MR groff @MAN1EXT@
+command with the
+.B \-Thtml
+or
+.B \-Txhtml
+options.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR \%grohtml .
+.
+If no operands are given,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I \%grohtml
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+.I \%grohtml
+invokes
+.I groff
+twice.
+.
+In the first pass,
+the preprocessor
+.I \%pre\-grohtml
+renders
+pictures,
+equations,
+and tables as images in PostScript format using the
+.B ps
+output device.
+.
+In the second pass,
+the output driver
+.I \%post\-grohtml
+translates the output of
+.MR @g@troff @MAN1EXT@
+to HTML.
+.
+.
+.P
+.I \%grohtml
+writes output encoded in \%UTF-8 and has built-in HTML entities for all
+non-composite Unicode characters.
+.
+In spite of this,
+.I groff
+may issue warnings about unknown special characters if they can't be
+found during the first pass.
+.
+Such warnings can be safely ignored unless the special characters
+appear inside a table or equation.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I \%grohtml
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.B T
+and
+.B C
+having members in each style.
+.
+.
+.RS
+.TP
+.B TR
+Times roman
+.
+.TQ
+.B TI
+Times italic
+.
+.TQ
+.B TB
+Times bold
+.
+.TQ
+.B TBI
+Times bold-italic
+.
+.TQ
+.B CR
+Courier roman
+.
+.TQ
+.B CI
+Courier italic
+.
+.TQ
+.B CB
+Courier bold
+.
+.TQ
+.B CBI
+Courier bold-italic
+.RE
+.
+.
+.P
+A special font,
+.BR S ,
+is also provided to accommodate
+.I roff
+documents that expect it to always be available.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+The font description files used with
+.I \%grohtml
+expose the same glyph repertoire in their
+.B charset
+sections.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS Dependencies
+.\" ====================================================================
+.
+.I \%pre\-grohtml
+generates an image whenever an
+.I @g@eqn
+equation,
+.I @g@tbl
+table,
+or
+.I @g@pic
+picture is encountered in the input.
+.
+.I \%grohtml
+therefore may run several commands as part of its operation.
+.
+These include the \%Netpbm tools
+.IR \%pnmcrop ,
+.IR \%pnmcut ,
+and
+.IR \%pnmtopng ;
+\%Ghostscript
+.RI ( gs );
+and the \%PSUtils tool
+.IR \%psselect .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-a \~anti-aliasing-text-bits
+Number of bits of antialiasing information to be used by text when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dTextAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-b
+Initialize the background color to white.
+.
+.
+.TP
+.B \-C
+Suppress output of \[lq]CreationDate:\[rq] HTML comment.
+.
+.
+.TP
+.BI \-D \~image-directory
+Instruct
+.I \%grohtml
+to place all image files into directory
+.IR image-directory .
+.
+.
+.TP
+.B \-e
+Direct
+.I @g@eqn
+to produce MathML.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.BI \-F \~font-directory
+Prepend directory
+.RI font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR html .
+.
+.
+.TP
+.BI \-g \~anti-aliasing-graphic-bits
+Number of bits of antialiasing information to be used by graphics when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dGraphicAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-G
+Suppress output of \[lq]Creator:\[rq] HTML comment.
+.
+.
+.TP
+.B \-h
+Generate section headings by using HTML
+.B B
+elements and increasing the font size,
+rather than HTML
+.B H
+elements.
+.
+.
+.TP
+.BI \-i \~resolution
+Set the image resolution in pixels per inch;
+the default
+.RB is\~ 100 .
+.
+.
+.TP
+.BI \-I \~image-stem
+Determine the image file name stem.
+.
+If omitted,
+.I \%grohtml
+uses
+.IR \%grohtml\- XXXXX
+(where
+.I XXXXX
+is the process ID).
+.
+A dash is appended to the stem to separate it from the following image
+number.
+.
+.
+.TP
+.BI \-j \~output-stem
+Instruct
+.I \%grohtml
+to split the HTML output into multiple files.
+.
+Output is written to a new file at each section heading
+(but see option
+.B \-S
+below)
+named
+.IR output-stem\- n .html .
+.
+.
+.TP
+.B \-l
+Turn off the production of automatic section links at the top of the
+document.
+.
+.
+.TP
+.B \-n
+Generate simple heading anchors whenever a section/number heading is
+found.
+.
+Without the option the anchor value is the textual heading.
+.
+This can cause problems when a heading contains a \[lq]?\[rq] on older
+versions of some browsers.
+.
+This feature is automatically enabled if a heading contains an image.
+.
+.
+.TP
+.BI \-o \~image-vertical-offset
+Specify the vertical offset of images in points.
+.
+.
+.TP
+.B \-p
+Display page rendering progress to the standard error stream.
+.
+.I \%grohtml
+displays a page number only when an image is required.
+.
+.
+.TP
+.B \-r
+Turn off the automatic header and footer line
+(HTML rule).
+.
+.
+.TP
+.BI \-s \~base-type-size
+Set the document's base type size in points.
+.
+When this size is used in the source,
+it corresponds to the HTML base type size.
+.
+Every increase of two points in the source will produce a
+.RB \[lq] big \[rq]
+element,
+and conversely when a decrease of two points is seen,
+a
+.RB \[lq] small \[rq]
+element is emitted.
+.
+.
+.TP
+.BI \-S \~heading-level
+When splitting HTML output
+(see option
+.B \-j
+above),
+split at each nested heading level defined by
+.IR heading-level ,
+or higher).
+.
+The default is
+.BR 1 .
+.
+.
+.TP
+.B \-V
+Create an XHTML or HTML validator button at the bottom of each page of
+the document.
+.
+.
+.TP
+.BI \-x \~html-dialect
+Select HTML dialect.
+.
+Currently,
+.I html-dialect
+should be either the
+.RB digit\~ 4
+or the
+.RB letter\~ x ,
+which indicates whether
+.I \%grohtml
+should generate HTML\~4 or XHTML,
+respectively.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.B \-y
+Produce a right-aligned
+.I groff
+signature at the end of the document
+(only if
+.B \-V
+is also specified).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to search for
+.IR devhtml ,
+.IR grohtml 's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in an HTML comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devhtml/\:DESC
+describes the
+.B html
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devhtml/ F
+describes the font known
+.RI as\~ F
+on device
+.BR html .
+.
+.
+.TP
+.I @MACRODIR@/\:html\:.tmac
+defines font mappings,
+special characters,
+and colors for use with the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:html\-end\:.tmac
+finalizes setup of the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc\-end
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.P
+.I \%grohtml
+uses temporary files.
+.
+See
+.MR groff @MAN1EXT@
+for details about where such files are created.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.I \%grohtml
+is still beta code.
+.
+.
+.PP
+.I \%grohtml
+does not truly support hyphenation,
+but you can fool it into hyphenating long input lines,
+which can appear in HTML output with a hyphenated word followed by a
+space but no line break.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.\" IR afmtodit (@MAN1EXT@),
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.\" IR psbb (1), \" XXX: what is this?
+.\" IR groff_out (@MAN5EXT@),
+.\" IR groff_char (@MAN7EXT@),
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grohtml_1_man_C]
+.do rr *groff_grohtml_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grohtml/grohtml.am b/src/devices/grohtml/grohtml.am
new file mode 100644
index 0000000..a87cad2
--- /dev/null
+++ b/src/devices/grohtml/grohtml.am
@@ -0,0 +1,40 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += post-grohtml
+post_grohtml_SOURCES = \
+ src/devices/grohtml/post-html.cpp \
+ src/devices/grohtml/html-table.cpp \
+ src/devices/grohtml/html-text.cpp \
+ src/devices/grohtml/output.cpp \
+ src/devices/grohtml/html.h \
+ src/devices/grohtml/html-text.h \
+ src/devices/grohtml/html-table.h
+
+post_grohtml_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grohtml/grohtml.1
+EXTRA_DIST += src/devices/grohtml/grohtml.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grohtml/html-table.cpp b/src/devices/grohtml/html-table.cpp
new file mode 100644
index 0000000..3d7dfcd
--- /dev/null
+++ b/src/devices/grohtml/html-table.cpp
@@ -0,0 +1,848 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html-table.h"
+#include "ctype.h"
+#include "html.h"
+#include "html-text.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+extern html_dialect dialect;
+
+
+tabs::tabs ()
+ : tab(NULL)
+{
+}
+
+tabs::~tabs ()
+{
+ delete_list();
+}
+
+/*
+ * delete_list - frees the tab list and sets tab to NULL.
+ */
+
+void tabs::delete_list (void)
+{
+ tab_position *p = tab;
+ tab_position *q;
+
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ delete q;
+ }
+ tab = NULL;
+}
+
+void tabs::clear (void)
+{
+ delete_list();
+}
+
+/*
+ * compatible - returns TRUE if the tab stops in, s, do
+ * not conflict with the current tab stops.
+ * The new tab stops are _not_ placed into
+ * this class.
+ */
+
+int tabs::compatible (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = tab;
+
+ if (last == NULL)
+ return FALSE; // no tab stops defined
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0 && last != NULL) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last->alignment != align || last->position != total)
+ return FALSE;
+
+ last = last->next;
+ }
+ return TRUE;
+}
+
+/*
+ * init - scans the string, s, and initializes the tab stops.
+ */
+
+void tabs::init (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = NULL;
+
+ clear(); // remove any tab stops
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last == NULL) {
+ tab = new tab_position;
+ last = tab;
+ } else {
+ last->next = new tab_position;
+ last = last->next;
+ }
+ last->alignment = align;
+ last->position = total;
+ last->next = NULL;
+ }
+}
+
+/*
+ * check_init - define tab stops using, s, providing none already exist.
+ */
+
+void tabs::check_init (const char *s)
+{
+ if (tab == NULL)
+ init(s);
+}
+
+/*
+ * find_tab - returns the tab number corresponding to the position, pos.
+ */
+
+int tabs::find_tab (int pos)
+{
+ tab_position *p;
+ int i=0;
+
+ for (p = tab; p != NULL; p = p->next) {
+ i++;
+ if (p->position == pos)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int tabs::get_tab_pos (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->position;
+ }
+ return 0;
+}
+
+char tabs::get_tab_align (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->alignment;
+ }
+ return 'L';
+}
+
+/*
+ * dump_tab - display tab positions
+ */
+
+void tabs::dump_tabs (void)
+{
+ int i=1;
+ tab_position *p;
+
+ for (p = tab; p != NULL; p = p->next) {
+ printf("tab %d is %d\n", i, p->position);
+ i++;
+ }
+}
+
+/*
+ * html_table - methods
+ */
+
+html_table::html_table (simple_output *op, int linelen)
+ : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE)
+{
+ tab_stops = new tabs();
+}
+
+html_table::~html_table ()
+{
+ cols *c;
+ if (tab_stops != NULL)
+ delete tab_stops;
+
+ c = columns;
+ while (columns != NULL) {
+ columns = columns->next;
+ delete c;
+ c = columns;
+ }
+}
+
+/*
+ * remove_cols - remove a list of columns as defined by, c.
+ */
+
+void html_table::remove_cols (cols *c)
+{
+ cols *p;
+
+ while (c != NULL) {
+ p = c;
+ c = c->next;
+ delete p;
+ }
+}
+
+/*
+ * set_linelength - sets the line length value in this table.
+ * It also adds an extra blank column to the
+ * table should linelen exceed the last column.
+ */
+
+void html_table::set_linelength (int linelen)
+{
+ cols *p = NULL;
+ cols *c;
+ linelength = linelen;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->right > linelength) {
+ c->right = linelength;
+ remove_cols(c->next);
+ c->next = NULL;
+ return;
+ }
+ p = c;
+ }
+ if (p != NULL && p->right > 0 && linelength > p->right)
+ add_column(p->no+1, p->right, linelength, 'L');
+}
+
+/*
+ * get_effective_linelength -
+ */
+
+int html_table::get_effective_linelength (void)
+{
+ if (columns != NULL)
+ return linelength - columns->left;
+ else
+ return linelength;
+}
+
+/*
+ * add_indent - adds the indent to a table.
+ */
+
+void html_table::add_indent (int indent)
+{
+ if (columns != NULL && columns->left > indent)
+ add_column(0, indent, columns->left, 'L');
+}
+
+/*
+ * emit_table_header - emits the html header for this table.
+ */
+
+void html_table::emit_table_header (int space)
+{
+ if (columns == NULL)
+ return;
+
+ // dump_table();
+
+ last_col = NULL;
+ if (linelength > 0) {
+ out->nl();
+ out->nl();
+
+ out->put_string("<table width=\"100%\"")
+ .put_string(" border=\"0\" rules=\"none\" frame=\"void\"\n")
+ .put_string(" cellspacing=\"0\" cellpadding=\"0\"");
+ out->put_string(">")
+ .nl();
+ if (dialect == xhtml)
+ emit_colspan();
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ }
+}
+
+/*
+ * get_right - returns the right most position of this column.
+ */
+
+int html_table::get_right (cols *c)
+{
+ if (c != NULL && c->right > 0)
+ return c->right;
+ if (c->next != NULL)
+ return c->left;
+ return linelength;
+}
+
+/*
+ * set_space - assigns start_space. Used to determine the
+ * vertical alignment when generating the next table row.
+ */
+
+void html_table::set_space (int space)
+{
+ start_space = space;
+}
+
+/*
+ * emit_colspan - emits a series of colspan entries defining the
+ * table columns.
+ */
+
+void html_table::emit_colspan (void)
+{
+ cols *b = columns;
+ cols *c = columns;
+ int width = 0;
+
+ out->put_string("<colgroup>");
+ while (c != NULL) {
+ if (b != NULL && b != c && is_gap(b))
+ /*
+ * blank column for gap
+ */
+ out->put_string("<col width=\"")
+ .put_number(is_gap(b))
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+
+ width = (get_right(c)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (c->alignment) {
+ case 'C':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+ break;
+ case 'R':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"right\"></col>")
+ .nl();
+ break;
+ default:
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\"></col>")
+ .nl();
+ }
+ b = c;
+ c = c->next;
+ }
+ out->put_string("</colgroup>").nl();
+}
+
+/*
+ * emit_td - writes out a <td> tag with a corresponding width
+ * if the dialect is html4.
+ */
+
+void html_table::emit_td (int percentage, const char *s)
+{
+ if (percentage) {
+ if (dialect == html4) {
+ out->put_string("<td width=\"")
+ .put_number(percentage)
+ .put_string("%\"");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ else {
+ out->put_string("<td");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ }
+}
+
+/*
+ * emit_col - moves onto column, n.
+ */
+
+void html_table::emit_col (int n)
+{
+ cols *c = columns;
+ cols *b = columns;
+ int width = 0;
+
+ // must be a different row
+ if (last_col != NULL && n <= last_col->no)
+ emit_new_row();
+
+ while (c != NULL && c->no < n)
+ c = c->next;
+
+ // can we find column, n?
+ if (c != NULL && c->no == n) {
+ // shutdown previous column
+ if (last_col != NULL)
+ out->put_string("</td>").nl();
+
+ // find previous column
+ if (last_col == NULL)
+ b = columns;
+ else
+ b = last_col;
+
+ // have we a gap?
+ if (last_col != NULL) {
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+
+ // move across to column n
+ while (b != c) {
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ emit_td(width, "></td>");
+ // have we a gap?
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (b->alignment) {
+ case 'C':
+ emit_td(width, " align=center>");
+ break;
+ case 'R':
+ emit_td(width, " align=right>");
+ break;
+ default:
+ emit_td(width);
+ }
+ // remember column, b
+ last_col = b;
+ }
+}
+
+/*
+ * finish_row -
+ */
+
+void html_table::finish_row (void)
+{
+ int n = 0;
+ cols *c;
+
+ if (last_col != NULL) {
+ for (c = last_col->next; c != NULL; c = c->next)
+ n = c->no;
+
+ if (n > 0)
+ emit_col(n);
+#if 1
+ if (last_col != NULL) {
+ out->put_string("</td>");
+ last_col = NULL;
+ }
+#endif
+ out->put_string("</tr>").nl();
+ }
+}
+
+/*
+ * emit_new_row - move to the next row.
+ */
+
+void html_table::emit_new_row (void)
+{
+ finish_row();
+
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (start_space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ start_space = FALSE;
+ last_col = NULL;
+}
+
+void html_table::emit_finish_table (void)
+{
+ finish_row();
+ out->put_string("</table>");
+}
+
+/*
+ * add_column - adds a column. It returns FALSE if hstart..hend
+ * crosses into a different columns.
+ */
+
+int html_table::add_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = get_column(coln);
+
+ if (c == NULL)
+ return insert_column(coln, hstart, hend, align);
+ else
+ return modify_column(c, hstart, hend, align);
+}
+
+/*
+ * get_column - returns the column, coln.
+ */
+
+cols *html_table::get_column (int coln)
+{
+ cols *c = columns;
+
+ while (c != NULL && coln != c->no)
+ c = c->next;
+
+ if (c != NULL && coln == c->no)
+ return c;
+ else
+ return NULL;
+}
+
+/*
+ * insert_column - inserts a column, coln.
+ * It returns TRUE if it does not bump into
+ * another column.
+ */
+
+int html_table::insert_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = columns;
+ cols *l = columns;
+ cols *n = NULL;
+
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+ if (l != NULL && l->no>coln && hend > l->left)
+ return FALSE; // new column bumps into previous one
+
+ l = NULL;
+ c = columns;
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((l != NULL) && (l->next != NULL) &&
+ (l->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ n = new cols;
+ if (l == NULL) {
+ n->next = columns;
+ columns = n;
+ } else {
+ n->next = l->next;
+ l->next = n;
+ }
+ n->left = hstart;
+ n->right = hend;
+ n->no = coln;
+ n->alignment = align;
+ return TRUE;
+}
+
+/*
+ * modify_column - given a column, c, modify the width to
+ * contain hstart..hend.
+ * It returns TRUE if it does not clash with
+ * the next or previous column.
+ */
+
+int html_table::modify_column (cols *c, int hstart, int hend, char align)
+{
+ cols *l = columns;
+
+ while (l != NULL && l->next != c)
+ l = l->next;
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((c->next != NULL) && (c->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ if (c->left > hstart)
+ c->left = hstart;
+
+ if (c->right < hend)
+ c->right = hend;
+
+ c->alignment = align;
+
+ return TRUE;
+}
+
+/*
+ * find_tab_column - finds the column number for position, pos.
+ * It searches through the list tab stops.
+ */
+
+int html_table::find_tab_column (int pos)
+{
+ // remember the first column is reserved for untabbed glyphs
+ return tab_stops->find_tab(pos)+1;
+}
+
+/*
+ * find_column - find the column number for position, pos.
+ * It searches through the list of columns.
+ */
+
+int html_table::find_column (int pos)
+{
+ int p=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->left > pos)
+ return p;
+ p = c->no;
+ }
+ return p;
+}
+
+/*
+ * no_columns - returns the number of table columns (rather than tabs)
+ */
+
+int html_table::no_columns (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ n++;
+ return n;
+}
+
+/*
+ * is_gap - returns the gap between column, c, and the next column.
+ */
+
+int html_table::is_gap (cols *c)
+{
+ if (c == NULL || c->right <= 0 || c->next == NULL)
+ return 0;
+ else
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ return (c->next->left*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->right*100 + get_effective_linelength()/2)
+ / get_effective_linelength();
+}
+
+/*
+ * no_gaps - returns the number of table gaps between the columns
+ */
+
+int html_table::no_gaps (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ if (is_gap(c))
+ n++;
+ return n;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int html_table::get_tab_pos (int n)
+{
+ return tab_stops->get_tab_pos(n);
+}
+
+char html_table::get_tab_align (int n)
+{
+ return tab_stops->get_tab_align(n);
+}
+
+
+void html_table::dump_table (void)
+{
+ if (columns != NULL) {
+ cols *c;
+ for (c = columns; c != NULL; c = c->next) {
+ printf("column %d %d..%d %c\n", c->no, c->left, c->right, c->alignment);
+ }
+ } else
+ tab_stops->dump_tabs();
+}
+
+/*
+ * html_indent - creates an indent with indentation, ind, given
+ * a line length of linelength.
+ */
+
+html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength)
+{
+ table = new html_table(op, linelength);
+
+ table->add_column(1, ind+pageoffset, linelength, 'L');
+ table->add_indent(pageoffset);
+ in = ind;
+ pg = pageoffset;
+ ll = linelength;
+}
+
+html_indent::~html_indent (void)
+{
+ end();
+ delete table;
+}
+
+void html_indent::begin (int space)
+{
+ if (in + pg == 0) {
+ if (space) {
+ table->out->put_string(" style=\"margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ table->out->put_string("\"");
+ }
+ }
+ else {
+ //
+ // we use exactly the same mechanism for calculating
+ // indentation as html_table::emit_col
+ //
+ table->out->put_string(" style=\"margin-left:")
+ .put_number(((in + pg) * 100 + ll/2) / ll -
+ (ll/2)/ll)
+ .put_string("%;");
+
+ if (space) {
+ table->out->put_string(" margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ }
+ table->out->put_string("\"");
+ }
+}
+
+void html_indent::end (void)
+{
+}
+
+/*
+ * get_reg - collects the registers as supplied during initialization.
+ */
+
+void html_indent::get_reg (int *ind, int *pageoffset, int *linelength)
+{
+ *ind = in;
+ *pageoffset = pg;
+ *linelength = ll;
+}
diff --git a/src/devices/grohtml/html-table.h b/src/devices/grohtml/html-table.h
new file mode 100644
index 0000000..7ed27a9
--- /dev/null
+++ b/src/devices/grohtml/html-table.h
@@ -0,0 +1,133 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.h
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+
+#if !defined(HTML_TABLE_H)
+#define HTML_TABLE_H
+
+typedef struct tab_position {
+ char alignment;
+ int position;
+ struct tab_position *next;
+} tab_position;
+
+
+class tabs {
+public:
+ tabs ();
+ ~tabs ();
+ void clear (void);
+ int compatible (const char *s);
+ void init (const char *s);
+ void check_init (const char *s);
+ int find_tab (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void dump_tabs (void);
+
+private:
+ void delete_list (void);
+ tab_position *tab;
+};
+
+/*
+ * define a column
+ */
+
+typedef struct cols {
+ int left, right;
+ int no;
+ char alignment;
+ struct cols *next;
+} cols;
+
+class html_table {
+public:
+ html_table (simple_output *op, int linelen);
+ ~html_table (void);
+ int add_column (int coln, int hstart, int hend, char align);
+ cols *get_column (int coln);
+ int insert_column (int coln, int hstart, int hend, char align);
+ int modify_column (cols *c, int hstart, int hend, char align);
+ int find_tab_column (int pos);
+ int find_column (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void set_linelength (int linelen);
+ int no_columns (void);
+ int no_gaps (void);
+ int is_gap (cols *c);
+ void dump_table (void);
+ void emit_table_header (int space);
+ void emit_col (int n);
+ void emit_new_row (void);
+ void emit_finish_table (void);
+ int get_right (cols *c);
+ void add_indent (int indent);
+ void finish_row (void);
+ int get_effective_linelength (void);
+ void set_space (int space);
+ void emit_colspan (void);
+ void emit_td (int percentage, const char *s = ">");
+
+ tabs *tab_stops; /* tab stop positions */
+ simple_output *out;
+private:
+ cols *columns; /* column entries */
+ int linelength;
+ cols *last_col; /* last column started */
+ int start_space; /* have we seen a '.sp' tag? */
+
+ void remove_cols (cols *c);
+};
+
+/*
+ * the indentation wrapper.
+ * Builds an indentation from a html-table.
+ * This table is only emitted if the paragraph is emitted.
+ */
+
+class html_indent {
+public:
+ html_indent (simple_output *op, int ind, int pageoffset, int linelength);
+ ~html_indent (void);
+ void begin (int space); // called if we need to use the indent
+ void get_reg (int *ind, int *pageoffset, int *linelength);
+
+ // the indent is shutdown when it is deleted
+
+private:
+ void end (void);
+ int is_used;
+ int pg; // values of the registers as passed via initialization
+ int ll;
+ int in;
+ html_table *table;
+};
+
+#endif
diff --git a/src/devices/grohtml/html-text.cpp b/src/devices/grohtml/html-text.cpp
new file mode 100644
index 0000000..b07cbe7
--- /dev/null
+++ b/src/devices/grohtml/html-text.cpp
@@ -0,0 +1,1056 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp
+ *
+ * html-text.cpp
+ *
+ * provide a troff like state machine interface which
+ * generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#include "html-text.h"
+
+html_text::html_text (simple_output *op, html_dialect d) :
+ stackptr(NULL), lastptr(NULL), out(op), dialect(d),
+ space_emitted(TRUE), current_indentation(-1),
+ pageoffset(-1), linelength(-1), blank_para(TRUE),
+ start_space(FALSE)
+{
+}
+
+html_text::~html_text ()
+{
+ flush_text();
+}
+
+
+#if defined(DEBUGGING)
+static int debugStack = FALSE;
+
+
+/*
+ * turnDebug - flip the debugStack boolean and return the new value.
+ */
+
+static int turnDebug (void)
+{
+ debugStack = 1-debugStack;
+ return debugStack;
+}
+
+/*
+ * dump_stack_element - display an element of the html stack, p.
+ */
+
+void html_text::dump_stack_element (tag_definition *p)
+{
+ fprintf(stderr, " | ");
+ switch (p->type) {
+
+ case P_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<P %s>", (char *)p->arg1); break;
+ } else {
+ fprintf(stderr, "<P %s [TABLE]>", (char *)p->arg1); break;
+ }
+ case I_TAG: fprintf(stderr, "<I>"); break;
+ case B_TAG: fprintf(stderr, "<B>"); break;
+ case SUB_TAG: fprintf(stderr, "<SUB>"); break;
+ case SUP_TAG: fprintf(stderr, "<SUP>"); break;
+ case TT_TAG: fprintf(stderr, "<TT>"); break;
+ case PRE_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<PRE>"); break;
+ } else {
+ fprintf(stderr, "<PRE [TABLE]>"); break;
+ }
+ case SMALL_TAG: fprintf(stderr, "<SMALL>"); break;
+ case BIG_TAG: fprintf(stderr, "<BIG>"); break;
+ case BREAK_TAG: fprintf(stderr, "<BREAK>"); break;
+ case COLOR_TAG: {
+ if (p->col.is_default())
+ fprintf(stderr, "<COLOR (default)>");
+ else {
+ unsigned int r, g, b;
+
+ p->col.get_rgb(&r, &g, &b);
+ fprintf(stderr, "<COLOR %x %x %x>", r/0x101, g/0x101, b/0x101);
+ }
+ break;
+ }
+ default: fprintf(stderr, "unknown tag");
+ }
+ if (p->text_emitted)
+ fprintf(stderr, "[t] ");
+}
+
+/*
+ * dump_stack - debugging function only.
+ */
+
+void html_text::dump_stack (void)
+{
+ if (debugStack) {
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ dump_stack_element(p);
+ p = p->next;
+ }
+ }
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+#else
+void html_text::dump_stack (void) {}
+#endif
+
+
+/*
+ * end_tag - shuts down the tag.
+ */
+
+void html_text::end_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: out->put_string("</i>"); break;
+ case B_TAG: out->put_string("</b>"); break;
+ case P_TAG: if (t->indent == NULL) {
+ out->put_string("</p>");
+ } else {
+ delete t->indent;
+ t->indent = NULL;
+ out->put_string("</p>");
+ }
+ out->enable_newlines(FALSE);
+ blank_para = TRUE; break;
+ case SUB_TAG: out->put_string("</sub>"); break;
+ case SUP_TAG: out->put_string("</sup>"); break;
+ case TT_TAG: out->put_string("</tt>"); break;
+ case PRE_TAG: out->put_string("</pre>"); out->enable_newlines(TRUE);
+ blank_para = TRUE;
+ if (t->indent != NULL)
+ delete t->indent;
+ t->indent = NULL;
+ break;
+ case SMALL_TAG: if (! is_in_pre ())
+ out->put_string("</small>");
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ out->put_string("</big>");
+ break;
+ case COLOR_TAG: if (! is_in_pre ())
+ out->put_string("</font>");
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * issue_tag - writes out an html tag with argument.
+ * space == 0 if no space is requested
+ * space == 1 if a space is requested
+ * space == 2 if tag should not have a space style
+ */
+
+void html_text::issue_tag (const char *tagname, const char *arg,
+ int space)
+{
+ if ((arg == 0) || (strlen(arg) == 0))
+ out->put_string(tagname);
+ else {
+ out->put_string(tagname);
+ out->put_string(" ");
+ out->put_string(arg);
+ }
+ if (space == TRUE) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+#if 0
+ if (space == TRUE || space == FALSE)
+ out->put_string(" valign=\"top\"");
+#endif
+ out->put_string(">");
+}
+
+/*
+ * issue_color_begin - writes out an html color tag.
+ */
+
+void html_text::issue_color_begin (color *c)
+{
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ out->put_string("<font color=\"#");
+ if (c->is_default())
+ sprintf(buf, "000000");
+ else {
+ c->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+ }
+ out->put_string(buf);
+ out->put_string("\">");
+}
+
+/*
+ * start_tag - starts a tag.
+ */
+
+void html_text::start_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: issue_tag("<i", (char *)t->arg1); break;
+ case B_TAG: issue_tag("<b", (char *)t->arg1); break;
+ case P_TAG: if (t->indent != NULL) {
+ out->nl();
+#if defined(DEBUGGING)
+ out->simple_comment("INDENTATION");
+#endif
+ out->put_string("\n<p");
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ } else {
+ out->nl();
+ issue_tag("\n<p", (char *)t->arg1, start_space);
+ }
+
+ out->enable_newlines(TRUE); break;
+ case SUB_TAG: issue_tag("<sub", (char *)t->arg1); break;
+ case SUP_TAG: issue_tag("<sup", (char *)t->arg1); break;
+ case TT_TAG: issue_tag("<tt", (char *)t->arg1); break;
+ case PRE_TAG: out->enable_newlines(TRUE);
+ out->nl(); out->put_string("<pre");
+ if (t->indent == NULL)
+ issue_tag("", (char *)t->arg1, start_space);
+ else {
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ }
+ out->enable_newlines(FALSE); break;
+ case SMALL_TAG: if (! is_in_pre ())
+ issue_tag("<small", (char *)t->arg1);
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ issue_tag("<big", (char *)t->arg1);
+ break;
+ case BREAK_TAG: break;
+ case COLOR_TAG: if (! is_in_pre ())
+ issue_color_begin(&t->col);
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * flush_text - flushes html tags which are outstanding on the html stack.
+ */
+
+void html_text::flush_text (void)
+{
+ int notext=TRUE;
+ tag_definition *p=stackptr;
+
+ while (stackptr != 0) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ delete p;
+ }
+ lastptr = NULL;
+}
+
+/*
+ * is_present - returns TRUE if tag is already present on the stack.
+ */
+
+int html_text::is_present (HTML_TAG t)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (t == p->type)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+/*
+ * uses_indent - returns TRUE if the current paragraph is using a
+ * html table to effect an indent.
+ */
+
+int html_text::uses_indent (void)
+{
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ if (p->indent != NULL)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+extern void stop();
+
+/*
+ * do_push - places, tag_definition, p, onto the stack
+ */
+
+void html_text::do_push (tag_definition *p)
+{
+ HTML_TAG t = p->type;
+
+#if defined(DEBUGGING)
+ if (t == PRE_TAG)
+ stop();
+ debugStack = TRUE;
+ fprintf(stderr, "\nentering do_push (");
+ dump_stack_element(p);
+ fprintf(stderr, ")\n");
+ dump_stack();
+ fprintf(stderr, ")\n");
+ fflush(stderr);
+#endif
+
+ /*
+ * if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack.
+ */
+
+ if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) {
+ /*
+ * store, p, at the end
+ */
+ lastptr->next = p;
+ lastptr = p;
+ p->next = NULL;
+ } else {
+ p->next = stackptr;
+ if (stackptr == NULL)
+ lastptr = p;
+ stackptr = p;
+ }
+
+#if defined(DEBUGGING)
+ dump_stack();
+ fprintf(stderr, "exiting do_push\n");
+#endif
+}
+
+/*
+ * push_para - adds a new entry onto the html paragraph stack.
+ */
+
+void html_text::push_para (HTML_TAG t, void *arg, html_indent *in)
+{
+ tag_definition *p= new tag_definition;
+
+ p->type = t;
+ p->arg1 = arg;
+ p->text_emitted = FALSE;
+ p->indent = in;
+
+ if (t == PRE_TAG && is_present(PRE_TAG))
+ fatal("cannot have multiple PRE_TAGs");
+
+ do_push(p);
+}
+
+void html_text::push_para (HTML_TAG t)
+{
+ push_para(t, (void *)"", NULL);
+}
+
+void html_text::push_para (color *c)
+{
+ tag_definition *p = new tag_definition;
+
+ p->type = COLOR_TAG;
+ p->arg1 = NULL;
+ p->col = *c;
+ p->text_emitted = FALSE;
+ p->indent = NULL;
+
+ do_push(p);
+}
+
+/*
+ * do_italic - changes to italic
+ */
+
+void html_text::do_italic (void)
+{
+ if (! is_present(I_TAG))
+ push_para(I_TAG);
+}
+
+/*
+ * do_bold - changes to bold.
+ */
+
+void html_text::do_bold (void)
+{
+ if (! is_present(B_TAG))
+ push_para(B_TAG);
+}
+
+/*
+ * do_tt - changes to teletype.
+ */
+
+void html_text::do_tt (void)
+{
+ if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG)))
+ push_para(TT_TAG);
+}
+
+/*
+ * do_pre - changes to preformated text.
+ */
+
+void html_text::do_pre (void)
+{
+ done_tt();
+ if (is_present(P_TAG)) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ (void)done_para();
+ if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, i);
+ start_space = space;
+ } else if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, NULL);
+ dump_stack();
+}
+
+/*
+ * is_in_pre - returns TRUE if we are currently within a preformatted
+ * <pre> block.
+ */
+
+int html_text::is_in_pre (void)
+{
+ return is_present(PRE_TAG);
+}
+
+/*
+ * do_color - initiates a new color tag.
+ */
+
+void html_text::do_color (color *c)
+{
+ shutdown(COLOR_TAG); // shutdown a previous color tag, if present
+ push_para(c);
+}
+
+/*
+ * done_color - shutdown an outstanding color tag, if it exists.
+ */
+
+void html_text::done_color (void)
+{
+ shutdown(COLOR_TAG);
+}
+
+/*
+ * shutdown - shuts down an html tag.
+ */
+
+char *html_text::shutdown (HTML_TAG t)
+{
+ char *arg=NULL;
+
+ if (is_present(t)) {
+ tag_definition *p =stackptr;
+ tag_definition *temp =NULL;
+ int notext =TRUE;
+
+ dump_stack();
+ while ((stackptr != NULL) && (stackptr->type != t)) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+
+ /*
+ * pop tag
+ */
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+
+ /*
+ * push tag onto temp stack
+ */
+ p->next = temp;
+ temp = p;
+ }
+
+ /*
+ * and examine stackptr
+ */
+ if ((stackptr != NULL) && (stackptr->type == t)) {
+ if (stackptr->text_emitted) {
+ end_tag(stackptr);
+ }
+ if (t == P_TAG) {
+ arg = (char *)stackptr->arg1;
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ if (p->indent != NULL)
+ delete p->indent;
+ delete p;
+ }
+
+ /*
+ * and restore unaffected tags
+ */
+ while (temp != NULL) {
+ if (temp->type == COLOR_TAG)
+ push_para(&temp->col);
+ else
+ push_para(temp->type, temp->arg1, temp->indent);
+ p = temp;
+ temp = temp->next;
+ delete p;
+ }
+ }
+ return arg;
+}
+
+/*
+ * done_bold - shuts downs a bold tag.
+ */
+
+void html_text::done_bold (void)
+{
+ shutdown(B_TAG);
+}
+
+/*
+ * done_italic - shuts downs an italic tag.
+ */
+
+void html_text::done_italic (void)
+{
+ shutdown(I_TAG);
+}
+
+/*
+ * done_sup - shuts downs a sup tag.
+ */
+
+void html_text::done_sup (void)
+{
+ shutdown(SUP_TAG);
+}
+
+/*
+ * done_sub - shuts downs a sub tag.
+ */
+
+void html_text::done_sub (void)
+{
+ shutdown(SUB_TAG);
+}
+
+/*
+ * done_tt - shuts downs a tt tag.
+ */
+
+void html_text::done_tt (void)
+{
+ shutdown(TT_TAG);
+}
+
+/*
+ * done_pre - shuts downs a pre tag.
+ */
+
+void html_text::done_pre (void)
+{
+ shutdown(PRE_TAG);
+}
+
+/*
+ * done_small - shuts downs a small tag.
+ */
+
+void html_text::done_small (void)
+{
+ shutdown(SMALL_TAG);
+}
+
+/*
+ * done_big - shuts downs a big tag.
+ */
+
+void html_text::done_big (void)
+{
+ shutdown(BIG_TAG);
+}
+
+/*
+ * check_emit_text - ensures that all previous tags have been emitted (in order)
+ * before the text is written.
+ */
+
+void html_text::check_emit_text (tag_definition *t)
+{
+ if ((t != NULL) && (! t->text_emitted)) {
+ check_emit_text(t->next);
+ t->text_emitted = TRUE;
+ start_tag(t);
+ }
+}
+
+/*
+ * do_emittext - tells the class that text was written during the current tag.
+ */
+
+void html_text::do_emittext (const char *s, int length)
+{
+ if ((! is_present(P_TAG)) && (! is_present(PRE_TAG)))
+ do_para("", FALSE);
+
+ if (is_present(BREAK_TAG)) {
+ int text = remove_break();
+ check_emit_text(stackptr);
+ if (text) {
+ if (is_present(PRE_TAG))
+ out->nl();
+ else if (dialect == xhtml)
+ out->put_string("<br/>").nl();
+ else
+ out->put_string("<br>").nl();
+ }
+ } else
+ check_emit_text(stackptr);
+
+ out->put_string(s, length);
+ space_emitted = FALSE;
+ blank_para = FALSE;
+}
+
+/*
+ * do_para - starts a new paragraph
+ */
+
+void html_text::do_para (const char *arg, html_indent *in, int space)
+{
+ if (! is_present(P_TAG)) {
+ if (is_present(PRE_TAG)) {
+ html_indent *i = remove_indent(PRE_TAG);
+ done_pre();
+ if ((arg == NULL || (strcmp(arg, "") == 0)) &&
+ (i == in || in == NULL))
+ in = i;
+ else
+ delete i;
+ }
+ remove_sub_sup();
+ push_para(P_TAG, (void *)arg, in);
+ start_space = space;
+ }
+}
+
+void html_text::do_para (const char *arg, int space)
+{
+ do_para(arg, NULL, space);
+}
+
+void html_text::do_para (simple_output *op, const char *arg1,
+ int indentation_value, int page_offset,
+ int line_length, int space)
+{
+ html_indent *ind;
+
+ if (indentation_value == 0)
+ ind = NULL;
+ else
+ ind = new html_indent(op, indentation_value, page_offset, line_length);
+ do_para(arg1, ind, space);
+}
+
+/*
+ * done_para - shuts down a paragraph tag.
+ */
+
+char *html_text::done_para (void)
+{
+ char *result;
+ space_emitted = TRUE;
+ result = shutdown(P_TAG);
+ start_space = FALSE;
+ return result;
+}
+
+/*
+ * remove_indent - returns the indent associated with, tag.
+ * The indent associated with tag is set to NULL.
+ */
+
+html_indent *html_text::remove_indent (HTML_TAG tag)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (tag == p->type) {
+ html_indent *i = p->indent;
+ p->indent = NULL;
+ return i;
+ }
+ p = p->next;
+ }
+ return NULL;
+}
+
+/*
+ * remove_para_space - removes the leading space to a paragraph
+ * (effectively this trims off a leading '.sp' tag).
+ */
+
+void html_text::remove_para_space (void)
+{
+ start_space = FALSE;
+}
+
+/*
+ * do_space - issues an end of paragraph
+ */
+
+void html_text::do_space (void)
+{
+ if (is_in_pre()) {
+ do_emittext("", 0);
+ out->force_nl();
+ space_emitted = TRUE;
+ } else {
+ html_indent *i = remove_indent(P_TAG);
+
+ do_para(done_para(), i, TRUE);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * do_break - issue a break tag.
+ */
+
+void html_text::do_break (void)
+{
+ if (! is_present(PRE_TAG))
+ if (emitted_text())
+ if (! is_present(BREAK_TAG))
+ push_para(BREAK_TAG);
+
+ space_emitted = TRUE;
+}
+
+/*
+ * do_newline - issue a newline providing that we are inside a <pre> tag.
+ */
+
+void html_text::do_newline (void)
+{
+ if (is_present(PRE_TAG)) {
+ do_emittext("\n", 1);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * emitted_text - returns FALSE if white space has just been written.
+ */
+
+int html_text::emitted_text (void)
+{
+ return !space_emitted;
+}
+
+/*
+ * ever_emitted_text - returns TRUE if we have ever emitted text in this
+ * paragraph.
+ */
+
+int html_text::ever_emitted_text (void)
+{
+ return !blank_para;
+}
+
+/*
+ * starts_with_space - returns TRUE if we started this paragraph with a .sp
+ */
+
+int html_text::starts_with_space (void)
+{
+ return start_space;
+}
+
+/*
+ * retrieve_para_space - returns TRUE, if the paragraph starts with
+ * a space and text has not yet been emitted.
+ * If TRUE is returned, then the, start_space,
+ * variable is set to FALSE.
+ */
+
+int html_text::retrieve_para_space (void)
+{
+ if (start_space && blank_para) {
+ start_space = FALSE;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/*
+ * emit_space - writes a space providing that text was written beforehand.
+ */
+
+void html_text::emit_space (void)
+{
+ if (is_present(PRE_TAG))
+ do_emittext(" ", 1);
+ else
+ out->space_or_newline();
+
+ space_emitted = TRUE;
+}
+
+/*
+ * remove_def - removes a definition, t, from the stack.
+ */
+
+void html_text::remove_def (tag_definition *t)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+
+ while ((p != 0) && (p != t)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p == t)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ } else if (l == 0) {
+ error("stack list pointers are wrong");
+ } else {
+ l->next = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+}
+
+/*
+ * remove_tag - removes a tag from the stack.
+ */
+
+void html_text::remove_tag (HTML_TAG tag)
+{
+ tag_definition *p = stackptr;
+
+ while ((p != 0) && (p->type != tag)) {
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == tag))
+ remove_def(p);
+}
+
+/*
+ * remove_sub_sup - removes a sub or sup tag, should either exist
+ * on the stack.
+ */
+
+void html_text::remove_sub_sup (void)
+{
+ if (is_present(SUB_TAG)) {
+ remove_tag(SUB_TAG);
+ }
+ if (is_present(SUP_TAG)) {
+ remove_tag(SUP_TAG);
+ }
+ if (is_present(PRE_TAG)) {
+ remove_tag(PRE_TAG);
+ }
+}
+
+/*
+ * remove_break - break tags are not balanced thus remove it once it has been emitted.
+ * It returns TRUE if text was emitted before the <br> was issued.
+ */
+
+int html_text::remove_break (void)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+ tag_definition *q = 0;
+
+ while ((p != 0) && (p->type != BREAK_TAG)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == BREAK_TAG)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ q = stackptr;
+ } else if (l == 0)
+ error("stack list pointers are wrong");
+ else {
+ l->next = p->next;
+ q = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+ /*
+ * now determine whether text was issued before <br>
+ */
+ while (q != 0) {
+ if (q->text_emitted)
+ return TRUE;
+ else
+ q = q->next;
+ }
+ return FALSE;
+}
+
+/*
+ * remove_para_align - removes a paragraph which has a text
+ * argument. If the paragraph has no text
+ * argument then it is left alone.
+ */
+
+void html_text::remove_para_align (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ done_para();
+ do_para("", i, space);
+ return;
+ }
+ p = p->next;
+ }
+ }
+}
+
+/*
+ * get_alignment - returns the alignment for the paragraph.
+ * If no alignment was given then we return "".
+ */
+
+char *html_text::get_alignment (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL)
+ return (char *)p->arg1;
+ p = p->next;
+ }
+ }
+ return (char *)"";
+}
+
+/*
+ * do_small - potentially inserts a <small> tag into the html stream.
+ * However we check for a <big> tag, if present then we terminate it.
+ * Otherwise a <small> tag is inserted.
+ */
+
+void html_text::do_small (void)
+{
+ if (is_present(BIG_TAG))
+ done_big();
+ else
+ push_para(SMALL_TAG);
+}
+
+/*
+ * do_big - is the mirror image of do_small.
+ */
+
+void html_text::do_big (void)
+{
+ if (is_present(SMALL_TAG))
+ done_small();
+ else
+ push_para(BIG_TAG);
+}
+
+/*
+ * do_sup - save a superscript tag on the stack of tags.
+ */
+
+void html_text::do_sup (void)
+{
+ push_para(SUP_TAG);
+}
+
+/*
+ * do_sub - save a subscript tag on the stack of tags.
+ */
+
+void html_text::do_sub (void)
+{
+ push_para(SUB_TAG);
+}
diff --git a/src/devices/grohtml/html-text.h b/src/devices/grohtml/html-text.h
new file mode 100644
index 0000000..ee58601
--- /dev/null
+++ b/src/devices/grohtml/html-text.h
@@ -0,0 +1,138 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.h
+ *
+ * html-text.h
+ *
+ * provides a state machine interface which generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+#include "html-table.h"
+
+#define STYLE_VERTICAL_SPACE "1em"
+
+/*
+ * supported html dialects.
+ */
+
+typedef enum {xhtml, html4} html_dialect;
+
+/*
+ * html tags
+ */
+
+typedef enum {I_TAG, B_TAG, P_TAG, SUB_TAG, SUP_TAG, TT_TAG,
+ PRE_TAG, SMALL_TAG, BIG_TAG, BREAK_TAG,
+ COLOR_TAG} HTML_TAG;
+
+typedef struct tag_definition {
+ HTML_TAG type;
+ void *arg1;
+ int text_emitted;
+ color col;
+ html_indent *indent;
+ tag_definition *next;
+} tag_definition ;
+
+/*
+ * the state of the current paragraph.
+ * It allows post-html.cpp to request font changes, paragraph start/end
+ * and emits balanced tags with a small amount of peephole optimization.
+ */
+
+class html_text {
+public:
+ html_text (simple_output *op, html_dialect d);
+ ~html_text (void);
+ void flush_text (void);
+ void do_emittext (const char *s, int length);
+ void do_italic (void);
+ void do_bold (void);
+ void do_roman (void);
+ void do_tt (void);
+ void do_pre (void);
+ void do_small (void);
+ void do_big (void);
+ void do_para (const char *arg, int space); // used for no indentation
+ void do_para (simple_output *op, const char *arg1,
+ int indentation, int pageoffset, int linelength,
+ int space);
+ void do_sup (void);
+ void do_sub (void);
+ void do_space (void);
+ void do_break (void);
+ void do_newline (void);
+ void do_table (const char *arg);
+ void done_bold (void);
+ void done_italic (void);
+ char *done_para (void);
+ void done_sup (void);
+ void done_sub (void);
+ void done_tt (void);
+ void done_pre (void);
+ void done_small (void);
+ void done_big (void);
+ void do_color (color *c);
+ void done_color (void);
+ int emitted_text (void);
+ int ever_emitted_text (void);
+ int starts_with_space (void);
+ int retrieve_para_space (void);
+ void emit_space (void);
+ int is_in_pre (void);
+ int uses_indent (void);
+ void remove_tag (HTML_TAG tag);
+ void remove_sub_sup (void);
+ void remove_para_align (void);
+ void remove_para_space (void);
+ char *get_alignment (void);
+
+private:
+ tag_definition *stackptr; /* the current paragraph state */
+ tag_definition *lastptr; /* the end of the stack */
+ simple_output *out;
+ html_dialect dialect; /* which dialect of html? */
+ int space_emitted; /* just emitted a space? */
+ int current_indentation; /* current .in value */
+ int pageoffset; /* .po value */
+ int linelength; /* current line length */
+ int blank_para; /* have we ever written text? */
+ int start_space; /* does para start with a .sp */
+ html_indent *indent; /* our indent class */
+
+ int is_present (HTML_TAG t);
+ void end_tag (tag_definition *t);
+ void start_tag (tag_definition *t);
+ void do_para (const char *arg, html_indent *in, int space);
+ void push_para (HTML_TAG t);
+ void push_para (HTML_TAG t, void *arg, html_indent *in);
+ void push_para (color *c);
+ void do_push (tag_definition *p);
+ char *shutdown (HTML_TAG t);
+ void check_emit_text (tag_definition *t);
+ int remove_break (void);
+ void issue_tag (const char *tagname, const char *arg, int space=2);
+ void issue_color_begin (color *c);
+ void remove_def (tag_definition *t);
+ html_indent *remove_indent (HTML_TAG tag);
+ void dump_stack_element (tag_definition *p);
+ void dump_stack (void);
+};
diff --git a/src/devices/grohtml/html.h b/src/devices/grohtml/html.h
new file mode 100644
index 0000000..4828646
--- /dev/null
+++ b/src/devices/grohtml/html.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#if !defined(HTML_H)
+# define HTML_H
+
+const int INT_HEXDIGITS = 16; // enough for 64-bit ints
+
+/*
+ * class and structure needed to buffer words
+ */
+
+struct word {
+ char *s;
+ word *next;
+
+ word (const char *w, int n);
+ ~word ();
+};
+
+class word_list {
+public:
+ word_list ();
+ int flush (FILE *f);
+ void add_word (const char *s, int n);
+ int get_length (void);
+
+private:
+ int length;
+ word *head;
+ word *tail;
+};
+
+class simple_output {
+public:
+ simple_output(FILE *, int max_line_length);
+ simple_output &put_string(const char *, int);
+ simple_output &put_string(const char *s);
+ simple_output &put_string(const string &s);
+ simple_output &put_troffps_char (const char *s);
+ simple_output &put_translated_string(const char *s);
+ simple_output &put_number(int);
+ simple_output &put_float(double);
+ simple_output &put_symbol(const char *);
+ simple_output &put_literal_symbol(const char *);
+ simple_output &set_fixed_point(int);
+ simple_output &simple_comment(const char *);
+ simple_output &begin_comment(const char *);
+ simple_output &comment_arg(const char *);
+ simple_output &end_comment();
+ simple_output &set_file(FILE *);
+ simple_output &include_file(FILE *);
+ simple_output &copy_file(FILE *);
+ simple_output &end_line();
+ simple_output &put_raw_char(char);
+ simple_output &special(const char *);
+ simple_output &enable_newlines(int);
+ simple_output &check_newline(int n);
+ simple_output &nl(void);
+ simple_output &force_nl(void);
+ simple_output &space_or_newline (void);
+ simple_output &begin_tag (void);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int max_line_length; // not including newline
+ int col;
+ int fixed_point;
+ int newlines; // can we issue newlines automatically?
+ word_list last_word;
+
+ void flush_last_word (void);
+ int check_space (const char *s, int n);
+};
+
+inline FILE *simple_output::get_file()
+{
+ return fp;
+}
+
+#endif
diff --git a/src/devices/grohtml/output.cpp b/src/devices/grohtml/output.cpp
new file mode 100644
index 0000000..0ffeb58
--- /dev/null
+++ b/src/devices/grohtml/output.cpp
@@ -0,0 +1,363 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote output.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ *
+ * output.cpp
+ *
+ * provide the simple low level output routines needed by html.cpp
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include <time.h>
+#include "html.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#if defined(DEBUGGING)
+# define FPUTC(X,Y) do { fputc((X),(Y)); fputc((X), stderr); fflush(stderr); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); fputs((X), stderr); fflush(stderr); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); putc((X), stderr); fflush(stderr); } while (0)
+#else
+# define FPUTC(X,Y) do { fputc((X),(Y)); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); } while (0)
+#endif
+
+
+/*
+ * word - initialise a word and set next to NULL
+ */
+
+word::word (const char *w, int n)
+ : next(0)
+{
+ s = new char[n+1];
+ strncpy(s, w, n);
+ s[n] = (char)0;
+}
+
+/*
+ * destroy word and the string copy.
+ */
+
+word::~word ()
+{
+ delete[] s;
+}
+
+/*
+ * word_list - create an empty word list.
+ */
+
+word_list::word_list ()
+ : length(0), head(0), tail(0)
+{
+}
+
+/*
+ * flush - flush a word list to a FILE, f, and return the
+ * length of the buffered string.
+ */
+
+int word_list::flush (FILE *f)
+{
+ word *t;
+ int len=length;
+
+ while (head != 0) {
+ t = head;
+ head = head->next;
+ FPUTS(t->s, f);
+ delete t;
+ }
+ head = 0;
+ tail = 0;
+ length = 0;
+#if defined(DEBUGGING)
+ fflush(f); // just for testing
+#endif
+ return( len );
+}
+
+/*
+ * add_word - adds a word to the outstanding word list.
+ */
+
+void word_list::add_word (const char *s, int n)
+{
+ if (head == 0) {
+ head = new word(s, n);
+ tail = head;
+ } else {
+ tail->next = new word(s, n);
+ tail = tail->next;
+ }
+ length += n;
+}
+
+/*
+ * get_length - returns the number of characters buffered
+ */
+
+int word_list::get_length (void)
+{
+ return( length );
+}
+
+/*
+ * the classes and methods for simple_output manipulation
+ */
+
+simple_output::simple_output(FILE *f, int n)
+: fp(f), max_line_length(n), col(0), fixed_point(0), newlines(0)
+{
+}
+
+simple_output &simple_output::set_file(FILE *f)
+{
+ if (fp)
+ fflush(fp);
+ fp = f;
+ return *this;
+}
+
+simple_output &simple_output::copy_file(FILE *infp)
+{
+ int c;
+ while ((c = getc(infp)) != EOF)
+ PUTC(c, fp);
+ return *this;
+}
+
+simple_output &simple_output::end_line()
+{
+ flush_last_word();
+ if (col != 0) {
+ PUTC('\n', fp);
+ col = 0;
+ }
+ return *this;
+}
+
+simple_output &simple_output::special(const char *)
+{
+ return *this;
+}
+
+simple_output &simple_output::simple_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ FPUTS("<!-- ", fp);
+ FPUTS(s, fp);
+ FPUTS(" -->\n", fp);
+ col = 0;
+ return *this;
+}
+
+simple_output &simple_output::begin_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ col = 0;
+ put_string("<!--");
+ space_or_newline();
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::end_comment()
+{
+ flush_last_word();
+ space_or_newline();
+ put_string("-->").nl();
+ return *this;
+}
+
+/*
+ * check_newline - checks to see whether we are able to issue
+ * a newline and that one is needed.
+ */
+
+simple_output &simple_output::check_newline(int n)
+{
+ if ((col + n + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ col = last_word.flush(fp);
+ }
+ return *this;
+}
+
+/*
+ * space_or_newline - will emit a newline or a space later on
+ * depending upon the current column.
+ */
+
+simple_output &simple_output::space_or_newline (void)
+{
+ if ((col + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ if (last_word.get_length() > 0) {
+ col = last_word.flush(fp);
+ } else {
+ col = 0;
+ }
+ } else {
+ if (last_word.get_length() != 0) {
+ if (col > 0) {
+ FPUTC(' ', fp);
+ col++;
+ }
+ col += last_word.flush(fp);
+ }
+ }
+ return *this;
+}
+
+/*
+ * force_nl - forces a newline.
+ */
+
+simple_output &simple_output::force_nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+/*
+ * nl - writes a newline providing that we
+ * are not in the first column.
+ */
+
+simple_output &simple_output::nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+simple_output &simple_output::set_fixed_point(int n)
+{
+ assert(n >= 0 && n <= 10);
+ fixed_point = n;
+ return *this;
+}
+
+simple_output &simple_output::put_raw_char(char c)
+{
+ col += last_word.flush(fp);
+ PUTC(c, fp);
+ col++;
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s, int n)
+{
+ last_word.add_word(s, n);
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s)
+{
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::put_string(const string &s)
+{
+ last_word.add_word(s.contents(), s.length());
+ return *this;
+}
+
+simple_output &simple_output::put_number(int n)
+{
+ char buf[1 + INT_DIGITS + 1];
+ sprintf(buf, "%d", n);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::put_float(double d)
+{
+ char buf[128];
+
+ sprintf(buf, "%.4f", d);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::enable_newlines (int auto_newlines)
+{
+ check_newline(0);
+ newlines = auto_newlines;
+ check_newline(0);
+ return *this;
+}
+
+/*
+ * flush_last_word - flushes the last word and adjusts the
+ * col position. It will insert a newline
+ * before the last word if allowed and if
+ * necessary.
+ */
+
+void simple_output::flush_last_word (void)
+{
+ int len=last_word.get_length();
+
+ if (len > 0) {
+ if (newlines) {
+ if (col + len + 1 > max_line_length) {
+ FPUTS("\n", fp);
+ col = 0;
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ }
+ len += last_word.flush(fp);
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ col += last_word.flush(fp);
+ }
+ }
+}
diff --git a/src/devices/grohtml/post-html.cpp b/src/devices/grohtml/post-html.cpp
new file mode 100644
index 0000000..4e02b5c
--- /dev/null
+++ b/src/devices/grohtml/post-html.cpp
@@ -0,0 +1,5684 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html.h"
+#include "html-text.h"
+#include "html-table.h"
+#include "curtime.h"
+
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+extern "C" const char *Version_string;
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+#define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */
+#define SIZE_INCREMENT 2 /* font size increment <big> = +2 */
+#define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */
+#define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */
+#define UNICODE_DESC_START 0x80 /* all character entities above this are */
+ /* either encoded by their glyph names or if */
+ /* there is no name then we use &#nnn; */
+typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT;
+typedef enum {col_tag, tab_tag, tab0_tag, none} colType;
+
+#undef DEBUG_TABLES
+// #define DEBUG_TABLES
+
+/*
+ * prototypes
+ */
+
+const char *get_html_translation (font *f, const string &name);
+static const char *get_html_entity(unsigned int code);
+int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single);
+
+
+static int auto_links = TRUE; /* by default we enable automatic links at */
+ /* top of the document. */
+static int auto_rule = TRUE; /* by default we enable an automatic rule */
+ /* at the top and bottom of the document */
+static int simple_anchors = FALSE; /* default to anchors with heading text */
+static int manufacture_headings = FALSE; /* default is to use the Hn html headings, */
+ /* rather than manufacture our own. */
+static int do_write_creator_comment = TRUE; /* write Creator HTML comment */
+static int do_write_date_comment = TRUE; /* write CreationDate HTML comment */
+static color *default_background = 0; /* has user requested initial bg color? */
+static string job_name; /* if set then the output is split into */
+ /* multiple files with 'job_name'-%d.html */
+static int multiple_files = FALSE; /* must we the output be divided into */
+ /* multiple html files, one for each */
+ /* heading? */
+static int base_point_size = 0; /* which troff font size maps onto html */
+ /* size 3? */
+static int split_level = 2; /* what heading level to split at? */
+static string head_info; /* user supplied information to be placed */
+ /* into <head> </head> */
+static int valid_flag = FALSE; /* has user requested a valid flag at the */
+ /* end of each page? */
+static int groff_sig = FALSE; /* "This document was produced using" */
+html_dialect dialect = html4; /* which html dialect should grohtml output */
+
+
+/*
+ * start with a few favorites
+ */
+
+void stop () {}
+
+static int min (int a, int b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+static int max (int a, int b)
+{
+ if (a > b)
+ return a;
+ else
+ return b;
+}
+
+/*
+ * is_intersection - returns TRUE if range a1..a2 intersects with
+ * b1..b2
+ */
+
+static int is_intersection (int a1, int a2, int b1, int b2)
+{
+ // easier to prove NOT outside limits
+ return ! ((a1 > b2) || (a2 < b1));
+}
+
+/*
+ * is_digit - returns TRUE if character, ch, is a digit.
+ */
+
+static int is_digit (char ch)
+{
+ return (ch >= '0') && (ch <= '9');
+}
+
+/*
+ * the classes and methods for maintaining a list of files.
+ */
+
+struct file {
+ FILE *fp;
+ file *next;
+ int new_output_file;
+ int require_links;
+ string output_file_name;
+
+ file (FILE *f);
+};
+
+/*
+ * file - initialize all fields to null pointers
+ */
+
+file::file (FILE *f)
+ : fp(f), next(0), new_output_file(FALSE),
+ require_links(FALSE), output_file_name("")
+{
+}
+
+class files {
+public:
+ files ();
+ FILE *get_file (void);
+ void start_of_list (void);
+ void move_next (void);
+ void add_new_file (FILE *f);
+ void set_file_name (string name);
+ void set_links_required (void);
+ int are_links_required (void);
+ int is_new_output_file (void);
+ string file_name (void);
+ string next_file_name (void);
+private:
+ file *head;
+ file *tail;
+ file *ptr;
+};
+
+/*
+ * files - create an empty list of files.
+ */
+
+files::files ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * get_file - returns the FILE associated with ptr.
+ */
+
+FILE *files::get_file (void)
+{
+ if (ptr)
+ return ptr->fp;
+ else
+ return 0;
+}
+
+/*
+ * start_of_list - reset the ptr to the start of the list.
+ */
+
+void files::start_of_list (void)
+{
+ ptr = head;
+}
+
+/*
+ * move_next - moves the ptr to the next element on the list.
+ */
+
+void files::move_next (void)
+{
+ if (ptr != 0)
+ ptr = ptr->next;
+}
+
+/*
+ * add_new_file - adds a new file, f, to the list.
+ */
+
+void files::add_new_file (FILE *f)
+{
+ if (0 /* nullptr */ == head) {
+ head = new file(f);
+ tail = head;
+ } else {
+ tail->next = new file(f);
+ tail = tail->next;
+ }
+ ptr = tail;
+}
+
+/*
+ * set_file_name - sets the final file name to contain the html
+ * data to name.
+ */
+
+void files::set_file_name (string name)
+{
+ if (ptr != 0) {
+ ptr->output_file_name = name;
+ ptr->new_output_file = TRUE;
+ }
+}
+
+/*
+ * set_links_required - issue links when processing this component
+ * of the file.
+ */
+
+void files::set_links_required (void)
+{
+ if (ptr != 0)
+ ptr->require_links = TRUE;
+}
+
+/*
+ * are_links_required - returns TRUE if this section of the file
+ * requires that links should be issued.
+ */
+
+int files::are_links_required (void)
+{
+ if (ptr != 0)
+ return ptr->require_links;
+ return FALSE;
+}
+
+/*
+ * is_new_output_file - returns TRUE if this component of the file
+ * is the start of a new output file.
+ */
+
+int files::is_new_output_file (void)
+{
+ if (ptr != 0)
+ return ptr->new_output_file;
+ return FALSE;
+}
+
+/*
+ * file_name - returns the name of the file.
+ */
+
+string files::file_name (void)
+{
+ if (ptr != 0)
+ return ptr->output_file_name;
+ return string("");
+}
+
+/*
+ * next_file_name - returns the name of the next file.
+ */
+
+string files::next_file_name (void)
+{
+ if (ptr != 0 && ptr->next != 0)
+ return ptr->next->output_file_name;
+ return string("");
+}
+
+/*
+ * the class and methods for styles
+ */
+
+struct style {
+ font *f;
+ int point_size;
+ int font_no;
+ int height;
+ int slant;
+ color col;
+ style ();
+ style (font *, int, int, int, int, color);
+ int operator == (const style &) const;
+ int operator != (const style &) const;
+};
+
+style::style()
+ : f(0), point_size(-1)
+{
+}
+
+style::style(font *p, int sz, int h, int sl, int no, color c)
+ : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c)
+{
+}
+
+int style::operator==(const style &s) const
+{
+ return (f == s.f && point_size == s.point_size
+ && height == s.height && slant == s.slant && col == s.col);
+}
+
+int style::operator!=(const style &s) const
+{
+ return !(*this == s);
+}
+
+/*
+ * the class and methods for retaining ascii text
+ */
+
+struct char_block {
+ enum { SIZE = 256 };
+ char *buffer;
+ int used;
+ char_block *next;
+
+ char_block();
+ char_block(int length);
+ ~char_block();
+};
+
+char_block::char_block()
+: buffer(0), used(0), next(0)
+{
+}
+
+char_block::char_block(int length)
+: used(0), next(0)
+{
+ buffer = new char[max(length, char_block::SIZE)];
+ if (0 /* nullptr */ == buffer)
+ fatal("out of memory error");
+}
+
+char_block::~char_block()
+{
+ if (buffer != 0)
+ delete[] buffer;
+}
+
+class char_buffer {
+public:
+ char_buffer();
+ ~char_buffer();
+ char *add_string(const char *, unsigned int);
+ char *add_string(const string &);
+private:
+ char_block *head;
+ char_block *tail;
+};
+
+char_buffer::char_buffer()
+: head(0), tail(0)
+{
+}
+
+char_buffer::~char_buffer()
+{
+ while (head != 0) {
+ char_block *temp = head;
+ head = head->next;
+ delete temp;
+ }
+}
+
+char *char_buffer::add_string (const char *s, unsigned int length)
+{
+ int i = 0;
+ unsigned int old_used;
+
+ if (0 /* nullptr */ == s|| length == 0)
+ return 0;
+
+ if (0 /* nullptr */ == tail) {
+ tail = new char_block(length+1);
+ head = tail;
+ } else {
+ if (tail->used + length+1 > char_block::SIZE) {
+ tail->next = new char_block(length+1);
+ tail = tail->next;
+ }
+ }
+
+ old_used = tail->used;
+ do {
+ tail->buffer[tail->used] = s[i];
+ tail->used++;
+ i++;
+ length--;
+ } while (length>0);
+
+ // add terminating nul character
+
+ tail->buffer[tail->used] = '\0';
+ tail->used++;
+
+ // and return start of new string
+
+ return &tail->buffer[old_used];
+}
+
+char *char_buffer::add_string (const string &s)
+{
+ return add_string(s.contents(), s.length());
+}
+
+/*
+ * the classes and methods for maintaining glyph positions.
+ */
+
+class text_glob {
+public:
+ void text_glob_html (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness);
+ void text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+
+ text_glob (void);
+ ~text_glob (void);
+ int is_a_line (void);
+ int is_a_tag (void);
+ int is_eol (void);
+ int is_auto_img (void);
+ int is_br (void);
+ int is_in (void);
+ int is_po (void);
+ int is_ti (void);
+ int is_ll (void);
+ int is_ce (void);
+ int is_tl (void);
+ int is_eo_tl (void);
+ int is_eol_ce (void);
+ int is_col (void);
+ int is_tab (void);
+ int is_tab0 (void);
+ int is_ta (void);
+ int is_tab_ts (void);
+ int is_tab_te (void);
+ int is_nf (void);
+ int is_fi (void);
+ int is_eo_h (void);
+ int get_arg (void);
+ int get_tab_args (char *align);
+
+ void remember_table (html_table *t);
+ html_table *get_table (void);
+
+ style text_style;
+ const char *text_string;
+ unsigned int text_length;
+ int minv, minh, maxv, maxh;
+ int is_tag; // is this a .br, .sp, .tl etc
+ int is_img_auto; // image created by eqn delim
+ int is_special; // text has come via 'x X html:'
+ int is_line; // is the command a <line>?
+ int thickness; // the thickness of a line
+ html_table *tab; // table description
+
+private:
+ text_glob (style *s, const char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line , int thickness);
+};
+
+text_glob::text_glob (style *s, const char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line_flag, int line_thickness)
+ : text_style(*s), text_string(str), text_length(length),
+ minv(min_vertical), minh(min_horizontal), maxv(max_vertical),
+ maxh(max_horizontal), is_tag(is_troff_command),
+ is_img_auto(is_auto_image), is_special(is_special_command),
+ is_line(is_a_line_flag), thickness(line_thickness), tab(0)
+{
+}
+
+text_glob::text_glob ()
+ : text_string(0), text_length(0), minv(-1), minh(-1), maxv(-1),
+ maxh(-1), is_tag(FALSE), is_special(FALSE), is_line(FALSE),
+ thickness(0), tab(0)
+{
+}
+
+text_glob::~text_glob ()
+{
+ if (tab != 0)
+ delete tab;
+}
+
+/*
+ * text_glob_html - used to place html text into the glob buffer.
+ */
+
+void text_glob::text_glob_html (style *s, char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_html - used to place html specials into the glob buffer.
+ * This text is essentially html commands coming
+ * through from the macro sets, with special
+ * designated sequences of characters translated into
+ * html. See add_and_encode.
+ */
+
+void text_glob::text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, TRUE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_line - record horizontal draw line commands.
+ */
+
+void text_glob::text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness_value)
+{
+ text_glob *g = new text_glob(s, "", 0,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, TRUE,
+ thickness_value);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_auto_image - record the presence of a .auto-image tag
+ * command. Used to mark that an image has been
+ * created automatically by a preprocessor and
+ * (pre-grohtml/troff) combination. Under some
+ * circumstances images may not be created.
+ * (consider .EQ
+ * delim $$
+ * .EN
+ * .TS
+ * tab(!), center;
+ * l!l.
+ * $1 over x$!recripical of x
+ * .TE
+ * the first auto-image marker is created via
+ * .EQ/.EN pair and no image is created. The
+ * second auto-image marker occurs at $1 over
+ * x$ Currently this image will not be created
+ * as the whole of the table is created as an
+ * image. (Once html tables are handled by
+ * grohtml this will change. Shortly this will
+ * be the case).
+ */
+
+void text_glob::text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical,
+ int min_horizontal,
+ int max_vertical,
+ int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, TRUE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_tag - records a troff tag.
+ */
+
+void text_glob::text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * is_a_line - returns TRUE if glob should be converted into an <hr>
+ */
+
+int text_glob::is_a_line (void)
+{
+ return is_line;
+}
+
+/*
+ * is_a_tag - returns TRUE if glob contains a troff directive.
+ */
+
+int text_glob::is_a_tag (void)
+{
+ return is_tag;
+}
+
+/*
+ * is_eol - returns TRUE if glob contains the tag eol
+ */
+
+int text_glob::is_eol (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eol") == 0);
+}
+
+/*
+ * is_eol_ce - returns TRUE if glob contains the tag eol.ce
+ */
+
+int text_glob::is_eol_ce (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0);
+}
+
+/*
+ * is_tl - returns TRUE if glob contains the tag .tl
+ */
+
+int text_glob::is_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tl") == 0);
+}
+
+/*
+ * is_eo_tl - returns TRUE if glob contains the tag eo.tl
+ */
+
+int text_glob::is_eo_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0);
+}
+
+/*
+ * is_nf - returns TRUE if glob contains the tag .fi 0
+ */
+
+int text_glob::is_nf (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 0);
+}
+
+/*
+ * is_fi - returns TRUE if glob contains the tag .fi 1
+ */
+
+int text_glob::is_fi (void)
+{
+ return (is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 1));
+}
+
+/*
+ * is_eo_h - returns TRUE if glob contains the tag .eo.h
+ */
+
+int text_glob::is_eo_h (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0);
+}
+
+/*
+ * is_ce - returns TRUE if glob contains the tag .ce
+ */
+
+int text_glob::is_ce (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ce",
+ strlen("devtag:.ce")) == 0);
+}
+
+/*
+ * is_in - returns TRUE if glob contains the tag .in
+ */
+
+int text_glob::is_in (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.in ",
+ strlen("devtag:.in ")) == 0);
+}
+
+/*
+ * is_po - returns TRUE if glob contains the tag .po
+ */
+
+int text_glob::is_po (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.po ",
+ strlen("devtag:.po ")) == 0);
+}
+
+/*
+ * is_ti - returns TRUE if glob contains the tag .ti
+ */
+
+int text_glob::is_ti (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ti ",
+ strlen("devtag:.ti ")) == 0);
+}
+
+/*
+ * is_ll - returns TRUE if glob contains the tag .ll
+ */
+
+int text_glob::is_ll (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ll ",
+ strlen("devtag:.ll ")) == 0);
+}
+
+/*
+ * is_col - returns TRUE if glob contains the tag .col
+ */
+
+int text_glob::is_col (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.col",
+ strlen("devtag:.col")) == 0);
+}
+
+/*
+ * is_tab_ts - returns TRUE if glob contains the tag .tab_ts
+ */
+
+int text_glob::is_tab_ts (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0);
+}
+
+/*
+ * is_tab_te - returns TRUE if glob contains the tag .tab_te
+ */
+
+int text_glob::is_tab_te (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0);
+}
+
+/*
+ * is_ta - returns TRUE if glob contains the tag .ta
+ */
+
+int text_glob::is_ta (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ta ",
+ strlen("devtag:.ta ")) == 0);
+}
+
+/*
+ * is_tab - returns TRUE if glob contains the tag tab
+ */
+
+int text_glob::is_tab (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab ",
+ strlen("devtag:tab ")) == 0);
+}
+
+/*
+ * is_tab0 - returns TRUE if glob contains the tag tab0
+ */
+
+int text_glob::is_tab0 (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab0",
+ strlen("devtag:tab0")) == 0);
+}
+
+/*
+ * is_auto_img - returns TRUE if the glob contains an automatically
+ * generated image.
+ */
+
+int text_glob::is_auto_img (void)
+{
+ return is_img_auto;
+}
+
+/*
+ * is_br - returns TRUE if the glob is a tag containing a .br
+ * or an implied .br. Note that we do not include .nf or .fi
+ * as grohtml will place a .br after these commands if they
+ * should break the line.
+ */
+
+int text_glob::is_br (void)
+{
+ return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) ||
+ (strncmp("devtag:.sp", text_string,
+ strlen("devtag:.sp")) == 0));
+}
+
+int text_glob::get_arg (void)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * get_tab_args - returns the tab position and alignment of the tab tag
+ */
+
+int text_glob::get_tab_args (char *align)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ // firstly the alignment C|R|L
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ *align = *p;
+ // now the int value
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * remember_table - saves table, t, in the text_glob.
+ */
+
+void text_glob::remember_table (html_table *t)
+{
+ if (tab != 0)
+ delete tab;
+ tab = t;
+}
+
+/*
+ * get_table - returns the stored table description.
+ */
+
+html_table *text_glob::get_table (void)
+{
+ return tab;
+}
+
+/*
+ * the class and methods used to construct ordered double linked
+ * lists. In a previous implementation we used templates via
+ * #include "ordered-list.h", but this does assume that all C++
+ * compilers can handle this feature. Pragmatically it is safer to
+ * assume this is not the case.
+ */
+
+struct element_list {
+ element_list *right;
+ element_list *left;
+ text_glob *datum;
+ int lineno;
+ int minv, minh, maxv, maxh;
+
+ element_list (text_glob *d,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ element_list ();
+ ~element_list ();
+};
+
+element_list::element_list ()
+ : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1),
+ maxv(-1), maxh(-1)
+{
+}
+
+/*
+ * element_list - create a list element assigning the datum and region
+ * parameters.
+ */
+
+element_list::element_list (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+ : right(0), left(0), datum(in), lineno(line_number),
+ minv(min_vertical), minh(min_horizontal),
+ maxv(max_vertical), maxh(max_horizontal)
+{
+}
+
+element_list::~element_list ()
+{
+ if (datum != 0)
+ delete datum;
+}
+
+class list {
+public:
+ list ();
+ ~list ();
+ int is_less (element_list *a, element_list *b);
+ void add (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void sub_move_right (void);
+ void move_right (void);
+ void move_left (void);
+ int is_empty (void);
+ int is_equal_to_tail (void);
+ int is_equal_to_head (void);
+ void start_from_head (void);
+ void start_from_tail (void);
+ void insert (text_glob *in);
+ void move_to (text_glob *in);
+ text_glob *move_right_get_data (void);
+ text_glob *move_left_get_data (void);
+ text_glob *get_data (void);
+private:
+ element_list *head;
+ element_list *tail;
+ element_list *ptr;
+};
+
+/*
+ * list - construct an empty list.
+ */
+
+list::list ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * ~list - destroy a complete list.
+ */
+
+list::~list()
+{
+ element_list *temp=head;
+
+ do {
+ temp = head;
+ if (temp != 0) {
+ head = head->right;
+ delete temp;
+ }
+ } while ((head != 0) && (head != tail));
+}
+
+/*
+ * is_less - returns TRUE if a is left of b if on the same line or
+ * if a is higher up the page than b.
+ */
+
+int list::is_less (element_list *a, element_list *b)
+{
+ // was:
+ // if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) {
+ if (a->lineno < b->lineno) {
+ return TRUE;
+ } else if (a->lineno > b->lineno) {
+ return FALSE;
+ } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) {
+ return (a->minh < b->minh);
+ } else {
+ return (a->maxv < b->maxv);
+ }
+}
+
+/*
+ * add - adds a datum to the list in the order specified by the
+ * region position.
+ */
+
+void list::add (text_glob *in, int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ // create a new list element with datum and position fields
+ // initialized
+ element_list *t = new element_list(in, line_number,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ element_list *last;
+
+#if 0
+ fprintf(stderr, "[%s %d,%d,%d,%d] ",
+ in->text_string, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ fflush(stderr);
+#endif
+
+ if (0 /* nullptr */ == head) {
+ head = t;
+ tail = t;
+ ptr = t;
+ t->left = t;
+ t->right = t;
+ } else {
+ last = tail;
+
+ while ((last != head) && (is_less(t, last)))
+ last = last->left;
+
+ if (is_less(t, last)) {
+ t->right = last;
+ last->left->right = t;
+ t->left = last->left;
+ last->left = t;
+ // now check for a new head
+ if (last == head)
+ head = t;
+ } else {
+ // add t beyond last
+ t->right = last->right;
+ t->left = last;
+ last->right->left = t;
+ last->right = t;
+ // now check for a new tail
+ if (last == tail)
+ tail = t;
+ }
+ }
+}
+
+/*
+ * sub_move_right - removes the element which is currently pointed to
+ * by ptr from the list and moves ptr to the right.
+ */
+
+void list::sub_move_right (void)
+{
+ element_list *t=ptr->right;
+
+ if (head == tail) {
+ head = 0;
+ if (tail != 0)
+ delete tail;
+
+ tail = 0;
+ ptr = 0;
+ } else {
+ if (head == ptr)
+ head = head->right;
+ if (tail == ptr)
+ tail = tail->left;
+ ptr->left->right = ptr->right;
+ ptr->right->left = ptr->left;
+ ptr = t;
+ }
+}
+
+/*
+ * start_from_head - assigns ptr to the head.
+ */
+
+void list::start_from_head (void)
+{
+ ptr = head;
+}
+
+/*
+ * start_from_tail - assigns ptr to the tail.
+ */
+
+void list::start_from_tail (void)
+{
+ ptr = tail;
+}
+
+/*
+ * is_empty - returns TRUE if the list has no elements.
+ */
+
+int list::is_empty (void)
+{
+ return 0 /* nullptr */ == head;
+}
+
+/*
+ * is_equal_to_tail - returns TRUE if the ptr equals the tail.
+ */
+
+int list::is_equal_to_tail (void)
+{
+ return ptr == tail;
+}
+
+/*
+ * is_equal_to_head - returns TRUE if the ptr equals the head.
+ */
+
+int list::is_equal_to_head (void)
+{
+ return ptr == head;
+}
+
+/*
+ * move_left - moves the ptr left.
+ */
+
+void list::move_left (void)
+{
+ ptr = ptr->left;
+}
+
+/*
+ * move_right - moves the ptr right.
+ */
+
+void list::move_right (void)
+{
+ ptr = ptr->right;
+}
+
+/*
+ * get_datum - returns the datum referenced via ptr.
+ */
+
+text_glob* list::get_data (void)
+{
+ return ptr->datum;
+}
+
+/*
+ * move_right_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_right_get_data (void)
+{
+ ptr = ptr->right;
+ if (ptr == head)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * move_left_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_left_get_data (void)
+{
+ ptr = ptr->left;
+ if (ptr == tail)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * insert - inserts data after the current position.
+ */
+
+void list::insert (text_glob *in)
+{
+ if (is_empty())
+ fatal("list must not be empty if we are inserting data");
+ else {
+ if (0 /* nullptr */ == ptr)
+ ptr = head;
+
+ element_list *t = new element_list(in, ptr->lineno,
+ ptr->minv, ptr->minh,
+ ptr->maxv, ptr->maxh);
+ if (ptr == tail)
+ tail = t;
+ ptr->right->left = t;
+ t->right = ptr->right;
+ ptr->right = t;
+ t->left = ptr;
+ }
+}
+
+/*
+ * move_to - moves the current position to the point where data, in,
+ * exists. This is an expensive method and should be used
+ * sparingly.
+ */
+
+void list::move_to (text_glob *in)
+{
+ ptr = head;
+ while (ptr != tail && ptr->datum != in)
+ ptr = ptr->right;
+}
+
+/*
+ * page class and methods
+ */
+
+class page {
+public:
+ page (void);
+ void add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag);
+ void add_line (style *s,
+ int line_number,
+ int x1, int y1, int x2, int y2,
+ int thickness);
+ void insert_tag (const string &str);
+ void dump_page (void); // debugging method
+
+ // and the data
+
+ list glyphs; // position of glyphs and specials on page
+ char_buffer buffer; // all characters for this page
+};
+
+page::page()
+{
+}
+
+/*
+ * insert_tag - inserts a tag after the current position.
+ */
+
+void page::insert_tag (const string &str)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ text_glob *f=glyphs.get_data();
+ g->text_glob_tag(&f->text_style, buffer.add_string(str),
+ str.length(), f->minv, f->minh, f->maxv, f->maxh);
+ glyphs.insert(g);
+ }
+}
+
+/*
+ * add - add html text to the list of glyphs.
+ */
+
+void page::add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ g->text_glob_html(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_tag - adds a troff tag, for example: .tl .sp .br
+ */
+
+void page::add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g;
+
+ if (strncmp((str+'\0').contents(), "devtag:.auto-image",
+ strlen("devtag:.auto-image")) == 0) {
+ g = new text_glob();
+ g->text_glob_auto_image(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ } else {
+ g = new text_glob();
+ g->text_glob_tag(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_line - adds the <line> primitive providing that y1==y2
+ */
+
+void page::add_line (style *s,
+ int line_number,
+ int x_1, int y_1, int x_2, int y_2,
+ int thickness)
+{
+ if (y_1 == y_2) {
+ text_glob *g = new text_glob();
+ g->text_glob_line(s,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2),
+ thickness);
+ glyphs.add(g, line_number,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2));
+ }
+}
+
+/*
+ * to_unicode - returns a unicode translation of int, ch.
+ */
+
+static char *to_unicode (unsigned int ch)
+{
+ static char buf[30];
+
+ sprintf(buf, "&#%u;", ch);
+ return buf;
+}
+
+/*
+ * add_and_encode - adds a special string to the page, it translates
+ * the string into html glyphs. The special string
+ * will have come from x X html: and can contain troff
+ * character encodings which appear as \[char]. A
+ * sequence of \\ represents \.
+ * So for example we can write:
+ * "cost = \[Po]3.00 file = \\foo\\bar"
+ * which is translated into:
+ * "cost = &pound;3.00 file = \foo\bar"
+ */
+
+void page::add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag)
+{
+ string html_string;
+ const char *html_glyph;
+ int i = 0;
+ const int len = str.length();
+
+ if (0 /* nullptr */ == s->f)
+ return;
+ while (i < len) {
+ if ((i + 1 < len) && (str.substring(i, 2) == string("\\["))) {
+ // start of escape
+ i += 2; // move over \[
+ int a = i;
+ while ((i < len) && (str[i] != ']'))
+ i++;
+ if (i > 0) {
+ string troff_charname = str.substring(a, i - a);
+ html_glyph = get_html_translation(s->f, troff_charname);
+ if (html_glyph)
+ html_string += html_glyph;
+ else {
+ glyph *g = name_to_glyph((troff_charname + '\0').contents());
+ if (s->f->contains(g))
+ html_string += s->f->get_code(g);
+ }
+ }
+ }
+ else
+ html_string += str[i];
+ i++;
+ }
+ if (html_string.length() > 0) {
+ text_glob *g=new text_glob();
+ if (is_tag)
+ g->text_glob_tag(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ else
+ g->text_glob_special(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical,
+ min_horizontal, max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * dump_page - dump the page contents for debugging purposes.
+ */
+
+void page::dump_page(void)
+{
+#if defined(DEBUG_TABLES)
+ text_glob *old_pos = glyphs.get_data();
+ text_glob *g;
+
+ printf("\n<!--\n");
+ printf("\n\ndebugging start\n");
+ glyphs.start_from_head();
+ do {
+ g = glyphs.get_data();
+ if (g->is_tab_ts()) {
+ printf("\n\n");
+ if (g->get_table() != 0)
+ g->get_table()->dump_table();
+ }
+ printf("%s ", g->text_string);
+ if (g->is_tab_te())
+ printf("\n\n");
+ glyphs.move_right();
+ } while (! glyphs.is_equal_to_head());
+ glyphs.move_to(old_pos);
+ printf("\ndebugging end\n\n");
+ printf("\n-->\n");
+ fflush(stdout);
+#endif
+}
+
+/*
+ * font classes and methods
+ */
+
+class html_font : public font {
+ html_font(const char *);
+public:
+ int encoding_index;
+ char *encoding;
+ char *reencoded_name;
+ ~html_font();
+ static html_font *load_html_font(const char *);
+};
+
+html_font *html_font::load_html_font(const char *s)
+{
+ html_font *f = new html_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+html_font::html_font(const char *nm)
+: font(nm)
+{
+}
+
+html_font::~html_font()
+{
+}
+
+/*
+ * a simple class to contain the header to this document
+ */
+
+class title_desc {
+public:
+ title_desc ();
+ ~title_desc ();
+
+ int has_been_written;
+ int has_been_found;
+ int with_h1;
+ string text;
+};
+
+
+title_desc::title_desc ()
+ : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE)
+{
+}
+
+title_desc::~title_desc ()
+{
+}
+
+class header_desc {
+public:
+ header_desc ();
+ ~header_desc ();
+
+ int no_of_level_one_headings; // how many .SH or .NH 1 have we found?
+ int no_of_headings; // how many headings have we found?
+ char_buffer headings; // all the headings used in the document
+ list headers; // list of headers built from .NH and .SH
+ list header_filename; // in which file is this header?
+ int header_level; // current header level
+ int written_header; // have we written the header yet?
+ string header_buffer; // current header text
+
+ void write_headings (FILE *f, int force);
+};
+
+header_desc::header_desc ()
+ : no_of_level_one_headings(0), no_of_headings(0), header_level(2),
+ written_header(0)
+{
+}
+
+header_desc::~header_desc ()
+{
+}
+
+/*
+ * write_headings - emits a list of links for the headings in this
+ * document
+ */
+
+void header_desc::write_headings (FILE *f, int force)
+{
+ text_glob *g;
+
+ if (auto_links || force) {
+ if (! headers.is_empty()) {
+ int h=1;
+
+ headers.start_from_head();
+ header_filename.start_from_head();
+ if (dialect == xhtml)
+ fputs("<p>", f);
+ do {
+ g = headers.get_data();
+ fputs("<a href=\"", f);
+ if (multiple_files && (! header_filename.is_empty())) {
+ text_glob *fn = header_filename.get_data();
+ fputs(fn->text_string, f);
+ }
+ fputs("#", f);
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(h);
+ buffer += '\0';
+ fprintf(f, "%s", buffer.contents());
+ } else
+ fputs(g->text_string, f);
+ h++;
+ fputs("\">", f);
+ fputs(g->text_string, f);
+ fputs("</a>", f);
+ if (dialect == xhtml)
+ fputs("<br/>\n", f);
+ else
+ fputs("<br>\n", f);
+ headers.move_right();
+ if (multiple_files && (! header_filename.is_empty()))
+ header_filename.move_right();
+ } while (! headers.is_equal_to_head());
+ fputs("\n", f);
+ if (dialect == xhtml)
+ fputs("</p>\n", f);
+ }
+ }
+}
+
+struct assert_pos {
+ assert_pos *next;
+ const char *val;
+ const char *id;
+};
+
+class assert_state {
+public:
+ assert_state ();
+ ~assert_state ();
+
+ void addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void build(const char *c, const char *v,
+ const char *f, const char *l);
+ void check_br (int br);
+ void check_ce (int ce);
+ void check_fi (int fi);
+ void check_sp (int sp);
+ void reset (void);
+
+private:
+ int check_br_flag;
+ int check_ce_flag;
+ int check_fi_flag;
+ int check_sp_flag;
+ const char *val_br;
+ const char *val_ce;
+ const char *val_fi;
+ const char *val_sp;
+ const char *file_br;
+ const char *file_ce;
+ const char *file_fi;
+ const char *file_sp;
+ const char *line_br;
+ const char *line_ce;
+ const char *line_fi;
+ const char *line_sp;
+
+ assert_pos *xhead;
+ assert_pos *yhead;
+
+ void add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void compare(assert_pos *t,
+ const char *v, const char *f, const char *l);
+ void close (const char *c);
+ void set (const char *c, const char *v,
+ const char *f, const char *l);
+ void check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag);
+ int check_value_error (int c, int v, const char *s,
+ const char *name,
+ const char *f, const char *l, int flag);
+};
+
+assert_state::assert_state ()
+{
+ reset();
+ val_br = 0;
+ val_ce = 0;
+ val_fi = 0;
+ val_sp = 0;
+ file_br = 0;
+ file_ce = 0;
+ file_fi = 0;
+ file_sp = 0;
+ line_br = 0;
+ line_ce = 0;
+ line_fi = 0;
+ line_sp = 0;
+ xhead = 0;
+ yhead = 0;
+}
+
+assert_state::~assert_state ()
+{
+ assert_pos *t;
+
+ while (xhead != 0) {
+ t = xhead;
+ xhead = xhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+ while (yhead != 0) {
+ t = yhead;
+ yhead = yhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+}
+
+void assert_state::reset (void)
+{
+ check_br_flag = 0;
+ check_ce_flag = 0;
+ check_fi_flag = 0;
+ check_sp_flag = 0;
+}
+
+void assert_state::add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ assert_pos *t = *h;
+
+ while (t != 0) {
+ if (strcmp(t->id, i) == 0)
+ break;
+ t = t->next;
+ }
+ if (t != 0 && v != 0 && (v[0] != '='))
+ compare(t, v, f, l);
+ else {
+ if (0 /* nullptr */ == t) {
+ t = new assert_pos;
+ t->next = *h;
+ (*h) = t;
+ }
+ if (v == 0 || v[0] != '=') {
+ if (0 /* nullptr */ == f)
+ f = strsave("stdin");
+ if (0 /* nullptr */ == l)
+ l = strsave("<none>");
+ if (0 /* nullptr */ == v)
+ v = "no value at all";
+ fprintf(stderr, "%s:%s:%s: error in assertion format of id=%s;"
+ " expected value prefixed with an '=', got %s\n",
+ program_name, f, l, i, v);
+ }
+ t->id = i;
+ t->val = v;
+ delete[] (char *)c;
+ delete[] (char *)f;
+ delete[] (char *)l;
+ }
+}
+
+void assert_state::addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&xhead, c, i, v, f, l);
+}
+
+void assert_state::addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&yhead, c, i, v, f, l);
+}
+
+void assert_state::compare(assert_pos *t,
+ const char *v, const char *f, const char *l)
+{
+ const char *s=t->val;
+
+ while ((*v) == '=')
+ v++;
+ while ((*s) == '=')
+ s++;
+
+ if (strcmp(v, s) != 0) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s: "
+ "expected %s, got %s\n", f, l, t->id, s, v);
+ }
+}
+
+void assert_state::close (const char *c)
+{
+ if (strcmp(c, "sp") == 0)
+ check_sp_flag = 0;
+ else if (strcmp(c, "br") == 0)
+ check_br_flag = 0;
+ else if (strcmp(c, "fi") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "nf") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "ce") == 0)
+ check_ce_flag = 0;
+ else
+ fprintf(stderr, "internal error: unrecognised tag in grohtml "
+ "(%s)\n", c);
+}
+
+const char *replace_negate_str (const char *before, char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+
+ if (strlen(after) > 0) {
+ int d = atoi(after);
+
+ if (d < 0 || d > 1) {
+ fprintf(stderr, "expected nf/fi value of 0 or 1, got %d\n", d);
+ d = 0;
+ }
+ if (d == 0)
+ after[0] = '1';
+ else
+ after[0] = '0';
+ after[1] = (char)0;
+ }
+ return after;
+}
+
+const char *replace_str (const char *before, const char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+ return after;
+}
+
+void assert_state::set (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+
+ // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v);
+ if (strcmp(c, "sp") == 0) {
+ check_sp_flag = 1;
+ val_sp = replace_str(val_sp, strsave(v));
+ file_sp = replace_str(file_sp, strsave(f));
+ line_sp = replace_str(line_sp, strsave(l));
+ } else if (strcmp(c, "br") == 0) {
+ check_br_flag = 1;
+ val_br = replace_str(val_br, strsave(v));
+ file_br = replace_str(file_br, strsave(f));
+ line_br = replace_str(line_br, strsave(l));
+ } else if (strcmp(c, "fi") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "nf") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_negate_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "ce") == 0) {
+ check_ce_flag = 1;
+ val_ce = replace_str(val_ce, strsave(v));
+ file_ce = replace_str(file_ce, strsave(f));
+ line_ce = replace_str(line_ce, strsave(l));
+ }
+}
+
+/*
+ * build - builds the troff state assertion.
+ * see tmac/www.tmac for cmd examples.
+ */
+
+void assert_state::build (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (c[0] == '{')
+ set(&c[1], v, f, l);
+ if (c[0] == '}')
+ close(&c[1]);
+}
+
+int assert_state::check_value_error (int c, int v, const char *s,
+ const char *name, const char *f,
+ const char *l, int flag)
+{
+ if (! c) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed; "
+ "expected %s to be %s, got %d\n", f, l, name, s, v);
+ return 0;
+ }
+ return flag;
+}
+
+void assert_state::check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag)
+{
+ if (strncmp(s, "<=", 2) == 0)
+ *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">=", 2) == 0)
+ *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "==", 2) == 0)
+ *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "!=", 2) == 0)
+ *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "<", 1) == 0)
+ *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">", 1) == 0)
+ *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "=", 1) == 0)
+ *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag);
+ else
+ *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag);
+}
+
+void assert_state::check_sp (int sp)
+{
+ if (check_sp_flag)
+ check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag);
+}
+
+void assert_state::check_fi (int fi)
+{
+ if (check_fi_flag)
+ check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag);
+}
+
+void assert_state::check_br (int br)
+{
+ if (check_br_flag)
+ check_value(val_br, br, "br", file_br, line_br, &check_br_flag);
+}
+
+void assert_state::check_ce (int ce)
+{
+ if (check_ce_flag)
+ check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag);
+}
+
+class html_printer : public printer {
+ files file_list;
+ simple_output html;
+ int res;
+ glyph *space_glyph;
+ int space_width;
+ int no_of_printed_pages;
+ int paper_length;
+ string sbuf;
+ int sbuf_start_hpos;
+ int sbuf_vpos;
+ int sbuf_end_hpos;
+ int sbuf_prev_hpos;
+ int sbuf_kern;
+ style sbuf_style;
+ int last_sbuf_length;
+ int overstrike_detected;
+ style output_style;
+ int output_hpos;
+ int output_vpos;
+ int output_vpos_max;
+ int output_draw_point_size;
+ int line_thickness;
+ int output_line_thickness;
+ unsigned char output_space_code;
+ char *inside_font_style;
+ int page_number;
+ title_desc title;
+ header_desc header;
+ int header_indent;
+ int suppress_sub_sup;
+ int cutoff_heading;
+ page *page_contents;
+ html_text *current_paragraph;
+ html_indent *indent;
+ html_table *table;
+ int end_center;
+ int end_tempindent;
+ TAG_ALIGNMENT next_tag;
+ int fill_on;
+ int max_linelength;
+ int linelength;
+ int pageoffset;
+ int troff_indent;
+ int device_indent;
+ int temp_indent;
+ int pointsize;
+ int vertical_spacing;
+ int line_number;
+ color *background;
+ int seen_indent;
+ int next_indent;
+ int seen_pageoffset;
+ int next_pageoffset;
+ int seen_linelength;
+ int next_linelength;
+ int seen_center;
+ int next_center;
+ int seen_space;
+ int seen_break;
+ int current_column;
+ int row_space;
+ assert_state as;
+
+ void flush_sbuf ();
+ void set_style (const style &);
+ void set_space_code (unsigned char c);
+ void do_exec (char *, const environment *);
+ void do_import (char *, const environment *);
+ void do_def (char *, const environment *);
+ void do_mdef (char *, const environment *);
+ void do_file (char *, const environment *);
+ void set_line_thickness (const environment *);
+ void terminate_current_font (void);
+ void flush_font (void);
+ void add_to_sbuf (glyph *g, const string &s);
+ void write_title (int in_head);
+ int sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w);
+ void flush_page (void);
+ void troff_tag (text_glob *g);
+ void flush_globs (void);
+ void emit_line (text_glob *g);
+ void emit_raw (text_glob *g);
+ void emit_html (text_glob *g);
+ void determine_space (text_glob *g);
+ void start_font (const char *name);
+ void end_font (const char *name);
+ int is_font_courier (font *f);
+ int is_line_start (int nf);
+ int is_courier_until_eol (void);
+ void start_size (int from, int to);
+ void do_font (text_glob *g);
+ void do_center (char *arg);
+ void do_check_center (void);
+ void do_break (void);
+ void do_space (char *arg);
+ void do_eol (void);
+ void do_eol_ce (void);
+ void do_title (void);
+ void do_fill (char *arg);
+ void do_heading (char *arg);
+ void write_header (void);
+ void determine_header_level (int level);
+ void do_linelength (char *arg);
+ void do_pageoffset (char *arg);
+ void do_indentation (char *arg);
+ void do_tempindent (char *arg);
+ void do_indentedparagraph (void);
+ void do_verticalspacing (char *arg);
+ void do_pointsize (char *arg);
+ void do_centered_image (void);
+ void do_left_image (void);
+ void do_right_image (void);
+ void do_auto_image (text_glob *g,
+ const char *filename);
+ void do_links (void);
+ void do_flush (void);
+ void do_job_name (char *name);
+ void do_head (char *name);
+ void insert_split_file (void);
+ int is_in_middle (int left, int right);
+ void do_sup_or_sub (text_glob *g);
+ int start_subscript (text_glob *g);
+ int end_subscript (text_glob *g);
+ int start_superscript (text_glob *g);
+ int end_superscript (text_glob *g);
+ void outstanding_eol (int n);
+ int is_bold (font *f);
+ font *make_bold (font *f);
+ int overstrike (glyph *g, const char *name,
+ const environment *env, int w);
+ void do_body (void);
+ int next_horiz_pos (text_glob *g, int nf);
+ void lookahead_for_tables (void);
+ void insert_tab_te (void);
+ text_glob *insert_tab_ts (text_glob *where);
+ void insert_tab0_foreach_tab (void);
+ void insert_tab_0 (text_glob *where);
+ void do_indent (int in, int pageoff,
+ int linelen);
+ void shutdown_table (void);
+ void do_tab_ts (text_glob *g);
+ void do_tab_te (void);
+ void do_col (char *s);
+ void do_tab (char *s);
+ void do_tab0 (void);
+ int calc_nf (text_glob *g, int nf);
+ void calc_po_in (text_glob *g, int nf);
+ void remove_tabs (void);
+ void remove_courier_tabs (void);
+ void update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g);
+ void add_table_end (const char *);
+ void do_file_components (void);
+ void write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current);
+ void emit_link (const string &to,
+ const char *name);
+ int get_troff_indent (void);
+ void restore_troff_indent (void);
+ void handle_assertion (int minv, int minh,
+ int maxv, int maxh,
+ const char *s);
+ void handle_state_assertion (text_glob *g);
+ void do_end_para (text_glob *g);
+ int round_width (int x);
+ void handle_tag_within_title (text_glob *g);
+ void writeHeadMetaStyle (void);
+ void handle_valid_flag (int needs_para);
+ void do_math (text_glob *g);
+ void write_html_anchor (text_glob *h);
+ void write_xhtml_anchor (text_glob *h);
+ // ADD HERE
+
+public:
+ html_printer ();
+ ~html_printer ();
+ void set_char (glyph *g, font *f, const environment *env,
+ int w, const char *name);
+ void set_numbered_char(int num, const environment *env, int *widthp);
+ glyph *set_char_and_width(const char *nm, const environment *env,
+ int *widthp, font **f);
+ void draw (int code, int *p, int np,
+ const environment *env);
+ void begin_page (int);
+ void end_page (int);
+ void special (char *arg, const environment *env, char type);
+ void devtag (char *arg, const environment *env, char type);
+ font *make_font (const char *);
+ void end_of_line ();
+};
+
+printer *make_printer()
+{
+ return new html_printer;
+}
+
+static void usage(FILE *stream);
+
+void html_printer::set_style(const style &sty)
+{
+ const char *fontname = sty.f->get_name();
+ if (0 /* nullptr */ == fontname)
+ fatal("no internalname specified for font");
+
+#if 0
+ change_font(fontname, (font::res / (72 * font::sizescale))
+ * sty.point_size);
+#endif
+}
+
+/*
+ * is_bold - returns TRUE if font, f, is bold.
+ */
+
+int html_printer::is_bold (font *f)
+{
+ const char *fontname = f->get_name();
+ return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0);
+}
+
+/*
+ * make_bold - if a bold style for f exists, return it.
+ */
+
+font *html_printer::make_bold (font *f)
+{
+ const char *fontname = f->get_name();
+
+ if (strcmp(fontname, "B") == 0)
+ return f;
+ if (strcmp(fontname, "I") == 0)
+ return font::load_font("BI");
+ if (strcmp(fontname, "BI") == 0)
+ return f;
+ return 0;
+}
+
+void html_printer::end_of_line()
+{
+ flush_sbuf();
+ line_number++;
+}
+
+/*
+ * emit_line - writes out a horizontal rule.
+ */
+
+void html_printer::emit_line (text_glob *)
+{
+ // --fixme-- needs to know the length in percentage
+ if (dialect == xhtml)
+ html.put_string("<hr/>");
+ else
+ html.put_string("<hr>");
+}
+
+/*
+ * restore_troff_indent - is called when we have temporarily shutdown
+ * indentation (typically done when we have
+ * centered an image).
+ */
+
+void html_printer::restore_troff_indent (void)
+{
+ troff_indent = next_indent;
+ if (troff_indent > 0) {
+ /*
+ * force device indentation
+ */
+ device_indent = 0;
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ }
+}
+
+/*
+ * emit_raw - writes the raw html information directly to the device.
+ */
+
+void html_printer::emit_raw (text_glob *g)
+{
+ do_font(g);
+ if (next_tag == INLINE) {
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ } else {
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ current_paragraph->done_para();
+ shutdown_table();
+ switch (next_tag) {
+
+ case CENTERED:
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ break;
+ case LEFT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ case RIGHT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ default:
+ fatal("unknown enumeration");
+ }
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ current_paragraph->done_para();
+ next_tag = INLINE;
+ suppress_sub_sup = TRUE;
+ seen_space = FALSE;
+ restore_troff_indent();
+ }
+}
+
+/*
+ * handle_tag_within_title - handle a limited number of tags within
+ * the context of a table. Those tags which
+ * set values rather than generate spaces
+ * and paragraphs.
+ */
+
+void html_printer::handle_tag_within_title (text_glob *g)
+{
+ if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll()
+ || g->is_fi() || g->is_nf())
+ troff_tag(g);
+}
+
+/*
+ * do_center - handle the .ce commands from troff.
+ */
+
+void html_printer::do_center (char *arg)
+{
+ next_center = atoi(arg);
+ seen_center = TRUE;
+}
+
+/*
+ * do_centered_image - set a flag such that the next devtag is
+ * placed inside a centered paragraph.
+ */
+
+void html_printer::do_centered_image (void)
+{
+ next_tag = CENTERED;
+}
+
+/*
+ * do_right_image - set a flag such that the next devtag is
+ * placed inside a right aligned paragraph.
+ */
+
+void html_printer::do_right_image (void)
+{
+ next_tag = RIGHT;
+}
+
+/*
+ * do_left_image - set a flag such that the next devtag is
+ * placed inside a left aligned paragraph.
+ */
+
+void html_printer::do_left_image (void)
+{
+ next_tag = LEFT;
+}
+
+/*
+ * exists - returns TRUE if filename exists.
+ */
+
+static int exists (const char *filename)
+{
+ FILE *fp = fopen(filename, "r");
+
+ if (fp == 0) {
+ return FALSE;
+ } else {
+ fclose(fp);
+ return TRUE;
+ }
+}
+
+/*
+ * generate_img_src - returns a html image tag for the filename
+ * providing that the image exists.
+ */
+
+static string &generate_img_src (const char *filename)
+{
+ string *s = new string("");
+
+ while (filename && (filename[0] == ' ')) {
+ filename++;
+ }
+ if (exists(filename)) {
+ *s += string("<img src=\"") + filename + "\" "
+ + "alt=\"Image " + filename + "\">";
+ if (dialect == xhtml)
+ *s += "</img>";
+ }
+ return *s;
+}
+
+/*
+ * do_auto_image - tests whether the image, indicated by filename,
+ * is present, if so then it emits an html image tag.
+ * An image tag may be passed through from pic, eqn
+ * but the corresponding image might not be created.
+ * Consider .EQ delim $$ .EN or an empty .PS .PE.
+ */
+
+void html_printer::do_auto_image (text_glob *g, const char *filename)
+{
+ string buffer = generate_img_src(filename);
+
+ if (! buffer.empty()) {
+ /*
+ * utilize emit_raw by creating a new text_glob.
+ */
+ text_glob h = *g;
+
+ h.text_string = buffer.contents();
+ h.text_length = buffer.length();
+ emit_raw(&h);
+ } else
+ next_tag = INLINE;
+}
+
+/*
+ * outstanding_eol - call do_eol, n, times.
+ */
+
+void html_printer::outstanding_eol (int n)
+{
+ while (n > 0) {
+ do_eol();
+ n--;
+ }
+}
+
+/*
+ * do_title - handle the .tl commands from troff.
+ */
+
+void html_printer::do_title (void)
+{
+ text_glob *t;
+ int removed_from_head;
+
+ if (page_number == 1) {
+ int found_title_start = FALSE;
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ do {
+ t = page_contents->glyphs.get_data();
+ removed_from_head = FALSE;
+ if (t->is_auto_img()) {
+ string img = generate_img_src((char *)(t->text_string + 20));
+
+ if (! img.empty()) {
+ if (found_title_start)
+ title.text += " ";
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ title.text += img;
+ }
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (t->is_eo_tl()) {
+ // end of title found
+ title.has_been_found = TRUE;
+ return;
+ } else if (t->is_a_tag()) {
+ handle_tag_within_title(t);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (found_title_start) {
+ title.text += " " + string(t->text_string, t->text_length);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else {
+ title.text += string(t->text_string, t->text_length);
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ }
+ } while ((! page_contents->glyphs.is_equal_to_head()) ||
+ (removed_from_head));
+ }
+ }
+}
+
+/*
+ * write_html_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_html_anchor (text_glob *h)
+{
+ if (dialect == html4) {
+ if (h != 0) {
+ html.put_string("<a name=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"></a>").nl();
+ }
+ }
+}
+
+/*
+ * write_xhtml_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_xhtml_anchor (text_glob *h)
+{
+ if (dialect == xhtml) {
+ if (h != 0) {
+ html.put_string(" id=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"");
+ }
+ }
+}
+
+void html_printer::write_header (void)
+{
+ if (! header.header_buffer.empty()) {
+ text_glob *a = 0;
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ if (header.header_level > 7)
+ header.header_level = 7;
+
+ // firstly we must terminate any font and type faces
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+
+ if (cutoff_heading+2 > header.header_level) {
+ // now we save the header so we can issue a list of links
+ header.no_of_headings++;
+ style st;
+
+ a = new text_glob();
+ a->text_glob_html(&st,
+ header.headings
+ .add_string(header.header_buffer),
+ header.header_buffer.length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ // and add this header to the header list
+ header.headers.add(a,
+ header.no_of_headings,
+ header.no_of_headings, header.no_of_headings,
+ header.no_of_headings, header.no_of_headings);
+ }
+
+ html.nl().nl();
+
+ if (manufacture_headings) {
+ // line break before a header
+ if (!current_paragraph->emitted_text())
+ current_paragraph->do_space();
+ // user wants manufactured headings which look better than
+ // <Hn></Hn>
+ if (header.header_level<4) {
+ html.put_string("<b><font size=\"+1\">");
+ html.put_string(header.header_buffer);
+ html.put_string("</font>").nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ else {
+ html.put_string("<b>");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ }
+ else {
+ // and now we issue the real header
+ html.put_string("<h");
+ html.put_number(header.header_level);
+ write_xhtml_anchor(a);
+ html.put_string(">");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</h");
+ html.put_number(header.header_level);
+ html.put_string(">").nl();
+ }
+
+ /* and now we save the file name in which this header will occur */
+
+ style st; // fake style to enable us to use the list data structure
+
+ text_glob *h=new text_glob();
+ h->text_glob_html(&st,
+ header.headings.add_string(file_list.file_name()),
+ file_list.file_name().length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ header.header_filename.add(h,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings);
+
+ current_paragraph->do_para(&html, "", get_troff_indent(),
+ pageoffset, linelength, space);
+ }
+}
+
+void html_printer::determine_header_level (int level)
+{
+ if (level == 0) {
+ int i;
+
+ for (i = 0; ((i<header.header_buffer.length())
+ && ((header.header_buffer[i] == '.')
+ || is_digit(header.header_buffer[i]))) ; i++) {
+ if (header.header_buffer[i] == '.') {
+ level++;
+ }
+ }
+ }
+ header.header_level = level+1;
+ if (header.header_level >= 2 && header.header_level <= split_level) {
+ header.no_of_level_one_headings++;
+ insert_split_file();
+ }
+}
+
+/*
+ * do_heading - handle the .SH and .NH and equivalent commands from
+ * troff.
+ */
+
+void html_printer::do_heading (char *arg)
+{
+ text_glob *g;
+ int level=atoi(arg);
+ int horiz;
+
+ header.header_buffer.clear();
+ page_contents->glyphs.move_right();
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ g = page_contents->glyphs.get_data();
+ horiz = g->minh;
+ do {
+ if (g->is_auto_img()) {
+ string img=generate_img_src((char *)(g->text_string + 20));
+
+ if (! img.empty()) {
+ // we cannot use full heading anchors with images
+ simple_anchors = TRUE;
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ header.header_buffer += img;
+ }
+ }
+ else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce()
+ || g->is_ll())
+ troff_tag(g);
+ else if (g->is_fi())
+ fill_on = 1;
+ else if (g->is_nf())
+ fill_on = 0;
+ else if (! (g->is_a_line() || g->is_a_tag())) {
+ /*
+ * we ignore the other tag commands when constructing a heading
+ */
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ horiz = g->maxh;
+ header.header_buffer += string(g->text_string, g->text_length);
+ }
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eo_h()));
+ }
+
+ determine_header_level(level);
+ write_header();
+
+ /*
+ * finally set the output font to uninitialized, thus forcing
+ * the new paragraph to start a new font block.
+ */
+
+ output_style.f = 0;
+ g = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_left(); // so that next time we use old g
+}
+
+/*
+ * is_courier_until_eol - returns TRUE if we can see a whole line which
+ * is courier
+ */
+
+int html_printer::is_courier_until_eol (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ int result = TRUE;
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ page_contents->glyphs.move_right();
+ do {
+ g = page_contents->glyphs.get_data();
+ if (! g->is_a_tag() && (! is_font_courier(g->text_style.f)))
+ result = FALSE;
+ page_contents->glyphs.move_right();
+ } while (result &&
+ (! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_fi()) && (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+ return result;
+}
+
+/*
+ * do_linelength - handle the .ll command from troff.
+ */
+
+void html_printer::do_linelength (char *arg)
+{
+ if (max_linelength == -1)
+ max_linelength = atoi(arg);
+
+ next_linelength = atoi(arg);
+ seen_linelength = TRUE;
+}
+
+/*
+ * do_pageoffset - handle the .po command from troff.
+ */
+
+void html_printer::do_pageoffset (char *arg)
+{
+ next_pageoffset = atoi(arg);
+ seen_pageoffset = TRUE;
+}
+
+/*
+ * get_troff_indent - returns the indent value.
+ */
+
+int html_printer::get_troff_indent (void)
+{
+ if (end_tempindent > 0)
+ return temp_indent;
+ else
+ return troff_indent;
+}
+
+/*
+ * do_indentation - handle the .in command from troff.
+ */
+
+void html_printer::do_indentation (char *arg)
+{
+ next_indent = atoi(arg);
+ seen_indent = TRUE;
+}
+
+/*
+ * do_tempindent - handle the .ti command from troff.
+ */
+
+void html_printer::do_tempindent (char *arg)
+{
+ if (fill_on) {
+ /*
+ * we set the end_tempindent to 2 as the first .br
+ * activates the .ti and the second terminates it.
+ */
+ end_tempindent = 2;
+ temp_indent = atoi(arg);
+ }
+}
+
+/*
+ * shutdown_table - shuts down the current table.
+ */
+
+void html_printer::shutdown_table (void)
+{
+ if (table != 0) {
+ current_paragraph->done_para();
+ table->emit_finish_table();
+ // don't delete this table as it will be deleted when we destroy the
+ // text_glob
+ table = 0;
+ }
+}
+
+/*
+ * do_indent - remember the indent parameters and if
+ * indent is > pageoff and indent has changed
+ * then we start a html table to implement the indentation.
+ */
+
+void html_printer::do_indent (int in, int pageoff, int linelen)
+{
+ if ((device_indent != -1) &&
+ (pageoffset+device_indent != in+pageoff)) {
+
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+ current_paragraph->done_para();
+
+ device_indent = in;
+ pageoffset = pageoff;
+ if (linelen <= max_linelength)
+ linelength = linelen;
+
+ current_paragraph->do_para(&html, "", device_indent,
+ pageoffset, max_linelength, space);
+ }
+}
+
+/*
+ * do_verticalspacing - handle the .vs command from troff.
+ */
+
+void html_printer::do_verticalspacing (char *arg)
+{
+ vertical_spacing = atoi(arg);
+}
+
+/*
+ * do_pointsize - handle the .ps command from troff.
+ */
+
+void html_printer::do_pointsize (char *arg)
+{
+ /*
+ * firstly check to see whether this point size is really associated
+ * with a .tl tag
+ */
+
+ if (! page_contents->glyphs.is_empty()) {
+ text_glob *g = page_contents->glyphs.get_data();
+ text_glob *t = page_contents->glyphs.get_data();
+
+ while (t->is_a_tag() && (!page_contents->glyphs.is_equal_to_head()))
+ {
+ if (t->is_tl()) {
+ /*
+ * found title therefore ignore this .ps tag
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ return;
+ }
+ page_contents->glyphs.move_right();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * move back to original position
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * collect valid pointsize
+ */
+ pointsize = atoi(arg);
+ }
+}
+
+/*
+ * do_fill - records whether troff has requested that text be filled.
+ */
+
+void html_printer::do_fill (char *arg)
+{
+ int on = atoi(arg);
+
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+
+ if (fill_on != on) {
+ if (on)
+ current_paragraph->do_para("", seen_space);
+ fill_on = on;
+ }
+}
+
+/*
+ * do_eol - handle the end of line
+ */
+
+void html_printer::do_eol (void)
+{
+ if (! fill_on) {
+ if (current_paragraph->ever_emitted_text()) {
+ current_paragraph->do_newline();
+ current_paragraph->do_break();
+ }
+ }
+ output_hpos = get_troff_indent()+pageoffset;
+}
+
+/*
+ * do_check_center - checks to see whether we have seen a '.ce' tag
+ * during the previous line.
+ */
+
+void html_printer::do_check_center(void)
+{
+ if (seen_center) {
+ seen_center = FALSE;
+ if (next_center > 0) {
+ if (end_center == 0) {
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ if ((strcmp("align=\"center\"",
+ current_paragraph->get_alignment()) != 0) &&
+ (strcmp("class=\"center\"",
+ current_paragraph->get_alignment()) != 0)) {
+ /*
+ * different alignment, so shutdown paragraph and open
+ * a new one.
+ */
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ // same alignment; if we have emitted text, issue a break.
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+ } else
+ /*
+ * next_center == 0
+ */
+ if (end_center > 0) {
+ seen_space = seen_space
+ || current_paragraph->retrieve_para_space();
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ current_paragraph->do_para("", seen_space);
+ }
+ end_center = next_center;
+ }
+}
+
+/*
+ * do_eol_ce - handle end of line specifically for a .ce
+ */
+
+void html_printer::do_eol_ce (void)
+{
+ if (end_center > 0) {
+ if (end_center > 1)
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+
+ end_center--;
+ if (end_center == 0) {
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ }
+ }
+}
+
+/*
+ * do_flush - flushes all output and tags.
+ */
+
+void html_printer::do_flush (void)
+{
+ current_paragraph->done_para();
+}
+
+/*
+ * do_links - moves onto a new temporary file and sets auto_links to
+ * false.
+ */
+
+void html_printer::do_links (void)
+{
+ html.end_line(); // flush line
+ auto_links = FALSE; // from now on only emit under user request
+ file_list.add_new_file(xtmpfile());
+ file_list.set_links_required();
+ html.set_file(file_list.get_file());
+}
+
+/*
+ * insert_split_file -
+ */
+
+void html_printer::insert_split_file (void)
+{
+ if (multiple_files) {
+ current_paragraph->done_para(); // flush paragraph
+ html.end_line(); // flush line
+ html.set_file(file_list.get_file()); // flush current file
+ file_list.add_new_file(xtmpfile());
+ string split_file = job_name;
+
+ split_file += string("-");
+ split_file += as_string(header.no_of_level_one_headings);
+ if (dialect == xhtml)
+ split_file += string(".xhtml");
+ else
+ split_file += string(".html");
+ split_file += '\0';
+
+ file_list.set_file_name(split_file);
+ html.set_file(file_list.get_file());
+ }
+}
+
+/*
+ * do_job_name - assigns the job_name to name.
+ */
+
+void html_printer::do_job_name (char *name)
+{
+ if (! multiple_files) {
+ multiple_files = TRUE;
+ while (name != 0 && (*name != (char)0) && (*name == ' '))
+ name++;
+ job_name = name;
+ }
+}
+
+/*
+ * do_head - adds a string to head_info which is to be included into
+ * the <head> </head> section of the html document.
+ */
+
+void html_printer::do_head (char *name)
+{
+ head_info += string(name);
+ head_info += '\n';
+}
+
+/*
+ * do_break - handles the ".br" request and also undoes an outstanding
+ * ".ti" command and calls indent if the indentation related
+ * registers have changed.
+ */
+
+void html_printer::do_break (void)
+{
+ int seen_temp_indent = FALSE;
+
+ current_paragraph->do_break();
+ if (end_tempindent > 0) {
+ end_tempindent--;
+ if (end_tempindent > 0)
+ seen_temp_indent = TRUE;
+ }
+ if (seen_indent || seen_pageoffset || seen_linelength
+ || seen_temp_indent) {
+ if (seen_indent && (! seen_temp_indent))
+ troff_indent = next_indent;
+ if (! seen_pageoffset)
+ next_pageoffset = pageoffset;
+ if (! seen_linelength)
+ next_linelength = linelength;
+ do_indent(get_troff_indent(), next_pageoffset, next_linelength);
+ }
+ seen_indent = seen_temp_indent;
+ seen_linelength = FALSE;
+ seen_pageoffset = FALSE;
+ do_check_center();
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+}
+
+void html_printer::do_space (char *arg)
+{
+ int n = atoi(arg);
+
+ seen_space = atoi(arg);
+ as.check_sp(seen_space);
+#if 0
+ if (n>0 && table)
+ table->set_space(TRUE);
+#endif
+
+ while (n>0) {
+ current_paragraph->do_space();
+ n--;
+ }
+ suppress_sub_sup = TRUE;
+}
+
+/*
+ * do_tab_ts - start a table, which will have already been defined.
+ */
+
+void html_printer::do_tab_ts (text_glob *g)
+{
+ html_table *t = g->get_table();
+
+ if (t != 0) {
+ current_column = 0;
+ current_paragraph->done_pre();
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+
+#if defined(DEBUG_TABLES)
+ html.simple_comment("TABS");
+#endif
+
+ t->set_linelength(max_linelength);
+ t->add_indent(pageoffset);
+#if 0
+ t->emit_table_header(seen_space);
+#else
+ t->emit_table_header(FALSE);
+ row_space = current_paragraph->retrieve_para_space() || seen_space;
+ seen_space = FALSE;
+#endif
+ }
+
+ table = t;
+}
+
+/*
+ * do_tab_te - finish a table.
+ */
+
+void html_printer::do_tab_te (void)
+{
+ if (table) {
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ table->emit_finish_table();
+ }
+
+ table = 0;
+ restore_troff_indent();
+}
+
+/*
+ * do_tab - handle the "devtag:tab" tag
+ */
+
+void html_printer::do_tab (char *s)
+{
+ if (table) {
+ while (isspace(*s))
+ s++;
+ s++;
+ int col = table->find_column(atoi(s) + pageoffset
+ + get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_tab0 - handle the "devtag:tab0" tag
+ */
+
+void html_printer::do_tab0 (void)
+{
+ if (table) {
+ int col = table->find_column(pageoffset+get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_col - start column, s.
+ */
+
+void html_printer::do_col (char *s)
+{
+ if (table) {
+ if (atoi(s) < current_column)
+ row_space = seen_space;
+
+ current_column = atoi(s);
+ current_paragraph->done_para();
+ table->emit_col(current_column);
+ current_paragraph->do_para("", row_space);
+ }
+}
+
+/*
+ * troff_tag - processes the troff tag and manipulates the troff
+ * state machine.
+ */
+
+void html_printer::troff_tag (text_glob *g)
+{
+ /*
+ * firstly skip over devtag:
+ */
+ char *t=(char *)g->text_string+strlen("devtag:");
+ if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) {
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "html<?p>:", strlen("html<?p>:"))
+ == 0) {
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "math<?p>:", strlen("math<?p>:"))
+ == 0) {
+ do_math(g);
+ } else if (g->is_eol()) {
+ do_eol();
+ } else if (g->is_eol_ce()) {
+ do_eol_ce();
+ } else if (strncmp(t, ".sp", 3) == 0) {
+ char *a = (char *)t+3;
+ do_space(a);
+ } else if (strncmp(t, ".br", 3) == 0) {
+ seen_break = 1;
+ as.check_br(1);
+ do_break();
+ } else if (strcmp(t, ".centered-image") == 0) {
+ do_centered_image();
+ } else if (strcmp(t, ".right-image") == 0) {
+ do_right_image();
+ } else if (strcmp(t, ".left-image") == 0) {
+ do_left_image();
+ } else if (strncmp(t, ".auto-image", 11) == 0) {
+ char *a = (char *)t+11;
+ do_auto_image(g, a);
+ } else if (strncmp(t, ".ce", 3) == 0) {
+ char *a = (char *)t+3;
+ suppress_sub_sup = TRUE;
+ do_center(a);
+ } else if (g->is_tl()) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = TRUE;
+ do_title();
+ } else if (strncmp(t, ".html-tl", 8) == 0) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = FALSE;
+ do_title();
+ } else if (strncmp(t, ".fi", 3) == 0) {
+ char *a = (char *)t+3;
+ do_fill(a);
+ } else if ((strncmp(t, ".SH", 3) == 0)
+ || (strncmp(t, ".NH", 3) == 0)) {
+ char *a = (char *)t+3;
+ do_heading(a);
+ } else if (strncmp(t, ".ll", 3) == 0) {
+ char *a = (char *)t+3;
+ do_linelength(a);
+ } else if (strncmp(t, ".po", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pageoffset(a);
+ } else if (strncmp(t, ".in", 3) == 0) {
+ char *a = (char *)t+3;
+ do_indentation(a);
+ } else if (strncmp(t, ".ti", 3) == 0) {
+ char *a = (char *)t+3;
+ do_tempindent(a);
+ } else if (strncmp(t, ".vs", 3) == 0) {
+ char *a = (char *)t+3;
+ do_verticalspacing(a);
+ } else if (strncmp(t, ".ps", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pointsize(a);
+ } else if (strcmp(t, ".links") == 0) {
+ do_links();
+ } else if (strncmp(t, ".job-name", 9) == 0) {
+ char *a = (char *)t+9;
+ do_job_name(a);
+ } else if (strncmp(t, ".head", 5) == 0) {
+ char *a = (char *)t+5;
+ do_head(a);
+ } else if (strcmp(t, ".no-auto-rule") == 0) {
+ auto_rule = FALSE;
+ } else if (strcmp(t, ".tab-ts") == 0) {
+ do_tab_ts(g);
+ } else if (strcmp(t, ".tab-te") == 0) {
+ do_tab_te();
+ } else if (strncmp(t, ".col ", 5) == 0) {
+ char *a = (char *)t+4;
+ do_col(a);
+ } else if (strncmp(t, "tab ", 4) == 0) {
+ char *a = (char *)t+3;
+ do_tab(a);
+ } else if (strncmp(t, "tab0", 4) == 0) {
+ do_tab0();
+ }
+}
+
+/*
+ * do_math - prints out the equation
+ */
+
+void html_printer::do_math (text_glob *g)
+{
+ do_font(g);
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+}
+
+/*
+ * is_in_middle - returns TRUE if the positions left..right are in the
+ * center of the page.
+ */
+
+int html_printer::is_in_middle (int left, int right)
+{
+ return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right))
+ <= CENTER_TOLERANCE );
+}
+
+/*
+ * flush_globs - runs through the text glob list and emits html.
+ */
+
+void html_printer::flush_globs (void)
+{
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ do {
+ g = page_contents->glyphs.get_data();
+#if 0
+ fprintf(stderr, "[%s:%d:%d:%d:%d]",
+ g->text_string, g->minv, g->minh, g->maxv, g->maxh) ;
+ fflush(stderr);
+#endif
+
+ handle_state_assertion(g);
+
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+
+ if (g->is_a_tag())
+ troff_tag(g);
+ else if (g->is_a_line())
+ emit_line(g);
+ else {
+ as.check_sp(seen_space);
+ as.check_br(seen_break);
+ seen_break = 0;
+ seen_space = 0;
+ emit_html(g);
+ }
+
+ as.check_fi(fill_on);
+ as.check_ce(end_center);
+ /*
+ * after processing the title (and removing it) the glyph list
+ * might be empty
+ */
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right();
+ }
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+/*
+ * calc_nf - calculates the _no_ format flag, given the
+ * text glob, g.
+ */
+
+int html_printer::calc_nf (text_glob *g, int nf)
+{
+ if (g != 0) {
+ if (g->is_fi()) {
+ as.check_fi(TRUE);
+ return FALSE;
+ }
+ if (g->is_nf()) {
+ as.check_fi(FALSE);
+ return TRUE;
+ }
+ }
+ as.check_fi(! nf);
+ return nf;
+}
+
+/*
+ * calc_po_in - calculates the, in, po, registers
+ */
+
+void html_printer::calc_po_in (text_glob *g, int nf)
+{
+ if (g->is_in())
+ troff_indent = g->get_arg();
+ else if (g->is_po())
+ pageoffset = g->get_arg();
+ else if (g->is_ti()) {
+ temp_indent = g->get_arg();
+ end_tempindent = 2;
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ if (end_tempindent > 0)
+ end_tempindent--;
+ }
+}
+
+/*
+ * next_horiz_pos - returns the next horiz position.
+ * -1 is returned if it doesn't exist.
+ */
+
+int html_printer::next_horiz_pos (text_glob *g, int nf)
+{
+ int next = -1;
+
+ if ((g != 0) && (g->is_br() || (nf && g->is_eol())))
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right_get_data();
+ if (0 /* nullptr */ == g) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ }
+ else {
+ next = g->minh;
+ page_contents->glyphs.move_left();
+ }
+ }
+ return next;
+}
+
+/*
+ * insert_tab_ts - inserts a tab-ts before, where.
+ */
+
+text_glob *html_printer::insert_tab_ts (text_glob *where)
+{
+ text_glob *start_of_table;
+ text_glob *old_pos = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab table start
+ page_contents->insert_tag(string("devtag:.tab-ts"));
+ page_contents->glyphs.move_right();
+ start_of_table = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(old_pos);
+ return start_of_table;
+}
+
+/*
+ * insert_tab_te - inserts a tab-te before the current position
+ * (it skips backwards over .sp/.br)
+ */
+
+void html_printer::insert_tab_te (void)
+{
+ text_glob *g = page_contents->glyphs.get_data();
+ page_contents->dump_page();
+ while (page_contents->glyphs.get_data()->is_a_tag())
+ page_contents->glyphs.move_left();
+ // tab table end
+ page_contents->insert_tag(string("devtag:.tab-te"));
+ while (g != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_right();
+ page_contents->dump_page();
+}
+
+/*
+ * insert_tab_0 - inserts a tab0 before, where.
+ */
+
+void html_printer::insert_tab_0 (text_glob *where)
+{
+ text_glob *old_pos = page_contents->glyphs.get_data();
+
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab0 start of line
+ page_contents->insert_tag(string("devtag:tab0"));
+ page_contents->glyphs.move_right();
+ page_contents->glyphs.move_to(old_pos);
+}
+
+/*
+ * remove_tabs - removes the tabs tags on this line.
+ */
+
+void html_printer::remove_tabs (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ do {
+ g = page_contents->glyphs.get_data();
+ if (g->is_tab()) {
+ page_contents->glyphs.sub_move_right();
+ if (g == orig)
+ orig = page_contents->glyphs.get_data();
+ } else
+ page_contents->glyphs.move_right();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+}
+
+void html_printer::remove_courier_tabs (void)
+{
+ text_glob *g;
+ int line_start = TRUE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ line_start = TRUE;
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (line_start) {
+ if (line_start && nf && is_courier_until_eol()) {
+ remove_tabs();
+ g = page_contents->glyphs.get_data();
+ }
+ }
+
+ // line_start = g->is_br() || g->is_nf() || g->is_fi()
+ // || (nf && g->is_eol());
+ line_start = g->is_br() || (nf && g->is_eol());
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+void html_printer::insert_tab0_foreach_tab (void)
+{
+ text_glob *start_of_line = 0;
+ text_glob *g = 0;
+ int seen_tab = FALSE;
+ int seen_col = FALSE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ start_of_line = page_contents->glyphs.get_data();
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (g->is_tab())
+ seen_tab = TRUE;
+
+ if (g->is_col())
+ seen_col = TRUE;
+
+ if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+ return;
+ }
+ } while (g->is_br() || (nf && g->is_eol()) || g->is_ta());
+ // printf("\nstart_of_line is: %s\n", g->text_string);
+ if (seen_tab && !seen_col) {
+ insert_tab_0(start_of_line);
+ page_contents->glyphs.move_to(g);
+ }
+
+ seen_tab = FALSE;
+ seen_col = FALSE;
+ start_of_line = g;
+ }
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+
+ }
+}
+
+/*
+ * update_min_max - updates the extent of a column, given the left and
+ * right extents of a glyph, g.
+ */
+
+void html_printer::update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g)
+{
+ switch (type_of_col) {
+
+ case tab_tag:
+ break;
+ case tab0_tag:
+ *minimum = g->minh;
+ break;
+ case col_tag:
+ *minimum = g->minh;
+ *maximum = g->maxh;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * add_table_end - moves left one glyph, adds a table end tag and adds
+ * a debugging string.
+ */
+
+void html_printer::add_table_end (const char *
+#if defined(DEBUG_TABLES)
+ debug_string
+#endif
+)
+{
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+#if defined(DEBUG_TABLES)
+ page_contents->insert_tag(string(debug_string));
+#endif
+}
+
+/*
+ * lookahead_for_tables - checks for .col tags and inserts table
+ * start/end tags
+ */
+
+void html_printer::lookahead_for_tables (void)
+{
+ text_glob *g;
+ text_glob *start_of_line = 0;
+ text_glob *start_of_table = 0;
+ text_glob *last = 0;
+ colType type_of_col = none;
+ int found_col = FALSE;
+ int ncol = 0;
+ int colmin = 0; // pacify compiler
+ int colmax = 0; // pacify compiler
+ html_table *tbl = new html_table(&html, -1);
+ const char *tab_defs = 0;
+ char align = 'L';
+ int nf = FALSE;
+ int old_pageoffset = pageoffset;
+
+ remove_courier_tabs();
+ page_contents->dump_page();
+ insert_tab0_foreach_tab();
+ page_contents->dump_page();
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ g = page_contents->glyphs.get_data();
+ if (g->is_br()) {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+ return;
+ }
+
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+
+ do {
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, " [") ;
+ fprintf(stderr, g->text_string) ;
+ fprintf(stderr, "] ") ;
+ fflush(stderr);
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+#endif
+
+ nf = calc_nf(g, nf);
+ calc_po_in(g, nf);
+ if (g->is_col()) {
+ if (type_of_col == tab_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** TAB -> COL ***"));
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ start_of_table = 0;
+ last = 0;
+ }
+ type_of_col = col_tag;
+ found_col = TRUE;
+ ncol = g->get_arg();
+ align = 'L';
+ colmin = 0;
+ colmax = 0;
+ } else if (g->is_tab()) {
+ type_of_col = tab_tag;
+ colmin = g->get_tab_args(&align);
+ align = 'L'; // for now as 'C' and 'R' are broken
+ ncol = tbl->find_tab_column(colmin);
+ colmin += pageoffset + get_troff_indent();
+ colmax = tbl->get_tab_pos(ncol+1);
+ if (colmax > 0)
+ colmax += pageoffset + get_troff_indent();
+ } else if (g->is_tab0()) {
+ if (type_of_col == col_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** COL -> TAB ***"));
+ start_of_table = 0;
+ last = 0;
+ }
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ type_of_col = tab0_tag;
+ ncol = 1;
+ colmin = 0;
+ colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent();
+ } else if (! g->is_a_tag())
+ update_min_max(type_of_col, &colmin, &colmax, g);
+ if ((g->is_col() || g->is_tab() || g->is_tab0())
+ && (start_of_line != 0)
+ && (0 /* nullptr */ == start_of_table)) {
+ start_of_table = insert_tab_ts(start_of_line);
+ start_of_line = 0;
+ } else if (g->is_ce() && (start_of_table != 0)) {
+ add_table_end("*** CE ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ last = 0;
+ } else if (g->is_ta()) {
+ tab_defs = g->text_string;
+ if (type_of_col == col_tag)
+ tbl->tab_stops->check_init(tab_defs);
+ if (!tbl->tab_stops->compatible(tab_defs)) {
+ if (start_of_table != 0) {
+ add_table_end("*** TABS ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ tbl->tab_stops->init(tab_defs);
+ }
+ }
+ if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != 0)) {
+ // we are in a table and have a glyph
+ if ((ncol == 0)
+ || (! tbl->add_column(ncol, colmin, colmax, align))) {
+ if (ncol == 0)
+ add_table_end("*** NCOL == 0 ***");
+ else
+ add_table_end("*** CROSSED COLS ***");
+
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ }
+ /*
+ * move onto next glob, check whether we are starting a new line
+ */
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (0 /* nullptr */ == g) {
+ if (found_col) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ last = g;
+ found_col = FALSE;
+ }
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ } while ((g != 0) && (g->is_br() || (nf && g->is_eol())));
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+ } while ((g != 0) && (! page_contents->glyphs.is_equal_to_head()));
+
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "finished scanning for tables\n");
+#endif
+
+ page_contents->glyphs.start_from_head();
+ if (start_of_table != 0) {
+ if (last != 0)
+ while (last != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_left();
+
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = 0;
+ page_contents->insert_tag(string("*** LAST ***"));
+ }
+ }
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+
+ // and reset the registers
+ pageoffset = old_pageoffset;
+ troff_indent = 0;
+ temp_indent = 0;
+ end_tempindent = 0;
+}
+
+void html_printer::flush_page (void)
+{
+ suppress_sub_sup = TRUE;
+ flush_sbuf();
+ page_contents->dump_page();
+ lookahead_for_tables();
+ page_contents->dump_page();
+ flush_globs();
+ current_paragraph->done_para();
+ current_paragraph->flush_text();
+ // move onto a new page
+ delete page_contents;
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "\n\n*** flushed page ***\n\n");
+ html.simple_comment("new page called");
+#endif
+ page_contents = new page;
+}
+
+/*
+ * determine_space - works out whether we need to write a space.
+ * If last glyph is adjoining, then emit no space.
+ */
+
+void html_printer::determine_space (text_glob *g)
+{
+ if (current_paragraph->is_in_pre()) {
+ /*
+ * .nf has been specified
+ */
+ while (output_hpos < g->minh) {
+ output_hpos += space_width;
+ current_paragraph->emit_space();
+ }
+ } else {
+ if ((output_vpos != g->minv) || (output_hpos < g->minh)) {
+ current_paragraph->emit_space();
+ }
+ }
+}
+
+/*
+ * is_line_start - returns TRUE if we are at the start of a line.
+ */
+
+int html_printer::is_line_start (int nf)
+{
+ int line_start = FALSE;
+ int result = TRUE;
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ do {
+ page_contents->glyphs.move_left();
+ g = page_contents->glyphs.get_data();
+ result = g->is_a_tag();
+ if (g->is_fi())
+ nf = FALSE;
+ else if (g->is_nf())
+ nf = TRUE;
+ line_start = g->is_col() || g->is_br() || (nf && g->is_eol());
+ } while ((!line_start) && (result));
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_right();
+ }
+ return result;
+}
+
+/*
+ * is_font_courier - returns TRUE if the font, f, is courier.
+ */
+
+int html_printer::is_font_courier (font *f)
+{
+ if (f != 0) {
+ const char *fontname = f->get_name();
+
+ return( (fontname != 0) && (fontname[0] == 'C') );
+ }
+ return FALSE;
+}
+
+/*
+ * end_font - shuts down the font corresponding to fontname.
+ */
+
+void html_printer::end_font (const char *fontname)
+{
+ if (strcmp(fontname, "B") == 0) {
+ current_paragraph->done_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CB") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ }
+}
+
+/*
+ * start_font - starts the font corresponding to name.
+ */
+
+void html_printer::start_font (const char *fontname)
+{
+ if (strcmp(fontname, "R") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "B") == 0) {
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->do_bold();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CB") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ current_paragraph->do_bold();
+ }
+}
+
+/*
+ * start_size - from is old font size, to is the new font size.
+ * The HTML elements <big> and <small> respectively
+ * increase and decrease the font size by 20%. We try and
+ * map these onto glyph sizes.
+ */
+
+void html_printer::start_size (int from, int to)
+{
+ if (from < to) {
+ while (from < to) {
+ current_paragraph->do_big();
+ from += SIZE_INCREMENT;
+ }
+ } else if (from > to) {
+ while (from > to) {
+ current_paragraph->do_small();
+ from -= SIZE_INCREMENT;
+ }
+ }
+}
+
+/*
+ * do_font - checks to see whether we need to alter the html font.
+ */
+
+void html_printer::do_font (text_glob *g)
+{
+ /*
+ * check if the output_style.point_size has not been set yet
+ * this allow users to place .ps at the top of their troff files
+ * and grohtml can then treat the .ps value as the base font size (3)
+ */
+ if (output_style.point_size == -1) {
+ output_style.point_size = pointsize;
+ }
+
+ if (g->text_style.f != output_style.f) {
+ if (output_style.f != 0) {
+ end_font(output_style.f->get_name());
+ }
+ output_style.f = g->text_style.f;
+ if (output_style.f != 0) {
+ start_font(output_style.f->get_name());
+ }
+ }
+ if (output_style.point_size != g->text_style.point_size) {
+ do_sup_or_sub(g);
+ if ((output_style.point_size > 0) &&
+ (g->text_style.point_size > 0)) {
+ start_size(output_style.point_size, g->text_style.point_size);
+ }
+ if (g->text_style.point_size > 0) {
+ output_style.point_size = g->text_style.point_size;
+ }
+ }
+ if (output_style.col != g->text_style.col) {
+ current_paragraph->done_color();
+ output_style.col = g->text_style.col;
+ current_paragraph->do_color(&output_style.col);
+ }
+}
+
+/*
+ * start_subscript - returns TRUE if, g, looks like a subscript start.
+ */
+
+int html_printer::start_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos < g->minv) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * start_superscript - returns TRUE if, g, looks like a superscript
+ * start.
+ */
+
+int html_printer::start_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos > g->minv) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * end_subscript - returns TRUE if, g, looks like the end of a
+ * subscript.
+ */
+
+int html_printer::end_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv < output_vpos) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * end_superscript - returns TRUE if, g, looks like the end of a
+ * superscript.
+ */
+
+int html_printer::end_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv > output_vpos) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * do_sup_or_sub - checks to see whether the next glyph is a
+ * subscript/superscript start/end and it calls the
+ * services of html-text to issue the appropriate tags.
+ */
+
+void html_printer::do_sup_or_sub (text_glob *g)
+{
+ if (! suppress_sub_sup) {
+ if (start_subscript(g)) {
+ current_paragraph->do_sub();
+ } else if (start_superscript(g)) {
+ current_paragraph->do_sup();
+ } else if (end_subscript(g)) {
+ current_paragraph->done_sub();
+ } else if (end_superscript(g)) {
+ current_paragraph->done_sup();
+ }
+ }
+}
+
+/*
+ * do_end_para - writes out the html text after shutting down the
+ * current paragraph.
+ */
+
+void html_printer::do_end_para (text_glob *g)
+{
+ do_font(g);
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ html.put_string(g->text_string+9);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * emit_html - write out the html text
+ */
+
+void html_printer::emit_html (text_glob *g)
+{
+ do_font(g);
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * flush_sbuf - flushes the current sbuf into the list of glyphs.
+ */
+
+void html_printer::flush_sbuf()
+{
+ if (sbuf.length() > 0) {
+ int r=font::res; // resolution of the device
+ set_style(sbuf_style);
+
+ if (overstrike_detected && (! is_bold(sbuf_style.f))) {
+ font *bold_font = make_bold(sbuf_style.f);
+ if (bold_font != 0)
+ sbuf_style.f = bold_font;
+ }
+
+ page_contents->add(&sbuf_style, sbuf, line_number,
+ (sbuf_vpos - (sbuf_style.point_size * r / 72)),
+ sbuf_start_hpos, sbuf_vpos, sbuf_end_hpos);
+ output_hpos = sbuf_end_hpos;
+ output_vpos = sbuf_vpos;
+ last_sbuf_length = 0;
+ sbuf_prev_hpos = sbuf_end_hpos;
+ overstrike_detected = FALSE;
+ sbuf.clear();
+ }
+}
+
+void html_printer::set_line_thickness(const environment *env)
+{
+ line_thickness = env->size;
+}
+
+void html_printer::draw(int code, int *p, int np,
+ const environment *env)
+{
+ switch (code) {
+
+ case 'l':
+# if 0
+ if (np == 2) {
+ page_contents->add_line(&sbuf_style,
+ line_number,
+ env->hpos, env->vpos,
+ (env->hpos + p[0]), (env->vpos + p[1]),
+ line_thickness);
+ } else {
+ error("2 arguments required for line");
+ }
+# endif
+ break;
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ } else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+
+ case 'P':
+ break;
+ case 'p':
+ break;
+ case 'E':
+ break;
+ case 'e':
+ break;
+ case 'C':
+ break;
+ case 'c':
+ break;
+ case 'a':
+ break;
+ case '~':
+ break;
+ case 'f':
+ break;
+ case 'F':
+ // fill with color env->fill
+ if (background != 0)
+ delete background;
+ background = new color;
+ *background = *env->fill;
+ break;
+
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+html_printer::html_printer()
+: html(0, MAX_LINE_LENGTH),
+ no_of_printed_pages(0),
+ last_sbuf_length(0),
+ overstrike_detected(FALSE),
+ output_hpos(-1),
+ output_vpos(-1),
+ output_vpos_max(-1),
+ line_thickness(-1),
+ inside_font_style(0),
+ page_number(0),
+ header_indent(-1),
+ suppress_sub_sup(TRUE),
+ cutoff_heading(100),
+ indent(0),
+ table(0),
+ end_center(0),
+ end_tempindent(0),
+ next_tag(INLINE),
+ fill_on(TRUE),
+ max_linelength(-1),
+ linelength(0),
+ pageoffset(0),
+ troff_indent(0),
+ device_indent(0),
+ temp_indent(0),
+ pointsize(base_point_size),
+ line_number(0),
+ background(default_background),
+ seen_indent(FALSE),
+ next_indent(0),
+ seen_pageoffset(FALSE),
+ next_pageoffset(0),
+ seen_linelength(FALSE),
+ next_linelength(0),
+ seen_center(FALSE),
+ next_center(0),
+ seen_space(0),
+ seen_break(0),
+ current_column(0),
+ row_space(FALSE)
+{
+ file_list.add_new_file(xtmpfile());
+ html.set_file(file_list.get_file());
+ if (font::hor != 24)
+ fatal("horizontal motion quantum must be 24");
+ if (font::vert != 40)
+ fatal("vertical motion quantum must be 40");
+#if 0
+ // should be sorted html..
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("res must be a multiple of 72*sizescale");
+#endif
+ int r = font::res;
+ int point = 0;
+ while (r % 10 == 0) {
+ r /= 10;
+ point++;
+ }
+ res = r;
+ html.set_fixed_point(point);
+ space_glyph = name_to_glyph("space");
+ space_width = font::hor;
+ paper_length = font::paperlength;
+ linelength = font::res*13/2;
+ if (paper_length == 0)
+ paper_length = 11*font::res;
+
+ page_contents = new page();
+}
+
+/*
+ * add_to_sbuf - adds character code or name to the sbuf.
+ */
+
+void html_printer::add_to_sbuf (glyph *g, const string &s)
+{
+ if (0 /* nullptr */ == sbuf_style.f)
+ return;
+
+ const char *html_glyph = 0;
+ unsigned int code = sbuf_style.f->get_code(g);
+
+ if (s.empty()) {
+ if (sbuf_style.f->contains(g))
+ html_glyph = get_html_entity(sbuf_style.f->get_code(g));
+ else
+ html_glyph = 0;
+
+ if ((0 /* nullptr */ == html_glyph) && (code >= UNICODE_DESC_START))
+ html_glyph = to_unicode(code);
+ } else
+ html_glyph = get_html_translation(sbuf_style.f, s);
+
+ last_sbuf_length = sbuf.length();
+ if (0 /* nullptr */ == html_glyph)
+ sbuf += ((char)code);
+ else
+ sbuf += html_glyph;
+}
+
+int html_printer::sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w)
+{
+ /*
+ * lets see whether the glyph is closer to the end of sbuf
+ */
+ if ((sbuf_end_hpos == env->hpos)
+ || ((sbuf_prev_hpos < sbuf_end_hpos)
+ && (env->hpos < sbuf_end_hpos)
+ && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos += w + sbuf_kern;
+ return TRUE;
+ } else {
+ if ((env->hpos >= sbuf_end_hpos)
+ && ((sbuf_kern == 0)
+ || (sbuf_end_hpos - sbuf_kern != env->hpos))) {
+ /*
+ * lets see whether a space is needed or not
+ */
+
+ if (env->hpos-sbuf_end_hpos < space_width) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * get_html_translation - given the position of the character and its
+ * name return the device encoding for such
+ * character.
+ */
+
+const char *get_html_translation (font *f, const string &name)
+{
+ if ((0 /* nullptr */ == f) || name.empty())
+ return 0;
+ else {
+ glyph *g = name_to_glyph((char *)(name + '\0').contents());
+ if (f->contains(g))
+ return get_html_entity(f->get_code(g));
+ else
+ return 0;
+ }
+}
+
+/*
+ * get_html_entity - given a Unicode character's code point, return an
+ * HTML entity that represents the character, if the
+ * character cannot represent itself in all contexts.
+ * the return value, if not a null pointer, is
+ * allocated in a static buffer and is only valid
+ * until the next call of this function.
+ */
+static const char *get_html_entity (unsigned int code)
+{
+ if (code < UNICODE_DESC_START) {
+ switch (code) {
+ case 0x0022: return "&quot;";
+ case 0x0026: return "&amp;";
+ case 0x003C: return "&lt;";
+ case 0x003E: return "&gt;";
+ default: return 0;
+ }
+ } else {
+ switch (code) {
+ case 0x00A0: return "&nbsp;";
+ case 0x00A1: return "&iexcl;";
+ case 0x00A2: return "&cent;";
+ case 0x00A3: return "&pound;";
+ case 0x00A4: return "&curren;";
+ case 0x00A5: return "&yen;";
+ case 0x00A6: return "&brvbar;";
+ case 0x00A7: return "&sect;";
+ case 0x00A8: return "&uml;";
+ case 0x00A9: return "&copy;";
+ case 0x00AA: return "&ordf;";
+ case 0x00AB: return "&laquo;";
+ case 0x00AC: return "&not;";
+ case 0x00AE: return "&reg;";
+ case 0x00AF: return "&macr;";
+ case 0x00B0: return "&deg;";
+ case 0x00B1: return "&plusmn;";
+ case 0x00B2: return "&sup2;";
+ case 0x00B3: return "&sup3;";
+ case 0x00B4: return "&acute;";
+ case 0x00B5: return "&micro;";
+ case 0x00B6: return "&para;";
+ case 0x00B7: return "&middot;";
+ case 0x00B8: return "&cedil;";
+ case 0x00B9: return "&sup1;";
+ case 0x00BA: return "&ordm;";
+ case 0x00BB: return "&raquo;";
+ case 0x00BC: return "&frac14;";
+ case 0x00BD: return "&frac12;";
+ case 0x00BE: return "&frac34;";
+ case 0x00BF: return "&iquest;";
+ case 0x00C0: return "&Agrave;";
+ case 0x00C1: return "&Aacute;";
+ case 0x00C2: return "&Acirc;";
+ case 0x00C3: return "&Atilde;";
+ case 0x00C4: return "&Auml;";
+ case 0x00C5: return "&Aring;";
+ case 0x00C6: return "&AElig;";
+ case 0x00C7: return "&Ccedil;";
+ case 0x00C8: return "&Egrave;";
+ case 0x00C9: return "&Eacute;";
+ case 0x00CA: return "&Ecirc;";
+ case 0x00CB: return "&Euml;";
+ case 0x00CC: return "&Igrave;";
+ case 0x00CD: return "&Iacute;";
+ case 0x00CE: return "&Icirc;";
+ case 0x00CF: return "&Iuml;";
+ case 0x00D0: return "&ETH;";
+ case 0x00D1: return "&Ntilde;";
+ case 0x00D2: return "&Ograve;";
+ case 0x00D3: return "&Oacute;";
+ case 0x00D4: return "&Ocirc;";
+ case 0x00D5: return "&Otilde;";
+ case 0x00D6: return "&Ouml;";
+ case 0x00D7: return "&times;";
+ case 0x00D8: return "&Oslash;";
+ case 0x00D9: return "&Ugrave;";
+ case 0x00DA: return "&Uacute;";
+ case 0x00DB: return "&Ucirc;";
+ case 0x00DC: return "&Uuml;";
+ case 0x00DD: return "&Yacute;";
+ case 0x00DE: return "&THORN;";
+ case 0x00DF: return "&szlig;";
+ case 0x00E0: return "&agrave;";
+ case 0x00E1: return "&aacute;";
+ case 0x00E2: return "&acirc;";
+ case 0x00E3: return "&atilde;";
+ case 0x00E4: return "&auml;";
+ case 0x00E5: return "&aring;";
+ case 0x00E6: return "&aelig;";
+ case 0x00E7: return "&ccedil;";
+ case 0x00E8: return "&egrave;";
+ case 0x00E9: return "&eacute;";
+ case 0x00EA: return "&ecirc;";
+ case 0x00EB: return "&euml;";
+ case 0x00EC: return "&igrave;";
+ case 0x00ED: return "&iacute;";
+ case 0x00EE: return "&icirc;";
+ case 0x00EF: return "&iuml;";
+ case 0x00F0: return "&eth;";
+ case 0x00F1: return "&ntilde;";
+ case 0x00F2: return "&ograve;";
+ case 0x00F3: return "&oacute;";
+ case 0x00F4: return "&ocirc;";
+ case 0x00F5: return "&otilde;";
+ case 0x00F6: return "&ouml;";
+ case 0x00F7: return "&divide;";
+ case 0x00F8: return "&oslash;";
+ case 0x00F9: return "&ugrave;";
+ case 0x00FA: return "&uacute;";
+ case 0x00FB: return "&ucirc;";
+ case 0x00FC: return "&uuml;";
+ case 0x00FD: return "&yacute;";
+ case 0x00FE: return "&thorn;";
+ case 0x00FF: return "&yuml;";
+ case 0x0152: return "&OElig;";
+ case 0x0153: return "&oelig;";
+ case 0x0160: return "&Scaron;";
+ case 0x0161: return "&scaron;";
+ case 0x0178: return "&Yuml;";
+ case 0x0192: return "&fnof;";
+ case 0x0391: return "&Alpha;";
+ case 0x0392: return "&Beta;";
+ case 0x0393: return "&Gamma;";
+ case 0x0394: return "&Delta;";
+ case 0x0395: return "&Epsilon;";
+ case 0x0396: return "&Zeta;";
+ case 0x0397: return "&Eta;";
+ case 0x0398: return "&Theta;";
+ case 0x0399: return "&Iota;";
+ case 0x039A: return "&Kappa;";
+ case 0x039B: return "&Lambda;";
+ case 0x039C: return "&Mu;";
+ case 0x039D: return "&Nu;";
+ case 0x039E: return "&Xi;";
+ case 0x039F: return "&Omicron;";
+ case 0x03A0: return "&Pi;";
+ case 0x03A1: return "&Rho;";
+ case 0x03A3: return "&Sigma;";
+ case 0x03A4: return "&Tau;";
+ case 0x03A5: return "&Upsilon;";
+ case 0x03A6: return "&Phi;";
+ case 0x03A7: return "&Chi;";
+ case 0x03A8: return "&Psi;";
+ case 0x03A9: return "&Omega;";
+ case 0x03B1: return "&alpha;";
+ case 0x03B2: return "&beta;";
+ case 0x03B3: return "&gamma;";
+ case 0x03B4: return "&delta;";
+ case 0x03B5: return "&epsilon;";
+ case 0x03B6: return "&zeta;";
+ case 0x03B7: return "&eta;";
+ case 0x03B8: return "&theta;";
+ case 0x03B9: return "&iota;";
+ case 0x03BA: return "&kappa;";
+ case 0x03BB: return "&lambda;";
+ case 0x03BC: return "&mu;";
+ case 0x03BD: return "&nu;";
+ case 0x03BE: return "&xi;";
+ case 0x03BF: return "&omicron;";
+ case 0x03C0: return "&pi;";
+ case 0x03C1: return "&rho;";
+ case 0x03C2: return "&sigmaf;";
+ case 0x03C3: return "&sigma;";
+ case 0x03C4: return "&tau;";
+ case 0x03C5: return "&upsilon;";
+ case 0x03C6: return "&phi;";
+ case 0x03C7: return "&chi;";
+ case 0x03C8: return "&psi;";
+ case 0x03C9: return "&omega;";
+ case 0x03D1: return "&thetasym;";
+ case 0x03D6: return "&piv;";
+ case 0x2013: return "&ndash;";
+ case 0x2014: return "&mdash;";
+ case 0x2018: return "&lsquo;";
+ case 0x2019: return "&rsquo;";
+ case 0x201A: return "&sbquo;";
+ case 0x201C: return "&ldquo;";
+ case 0x201D: return "&rdquo;";
+ case 0x201E: return "&bdquo;";
+ case 0x2020: return "&dagger;";
+ case 0x2021: return "&Dagger;";
+ case 0x2022: return "&bull;";
+ case 0x2030: return "&permil;";
+ case 0x2032: return "&prime;";
+ case 0x2033: return "&Prime;";
+ case 0x2039: return "&lsaquo;";
+ case 0x203A: return "&rsaquo;";
+ case 0x203E: return "&oline;";
+ case 0x2044: return "&frasl;";
+ case 0x20AC: return "&euro;";
+ case 0x2111: return "&image;";
+ case 0x2118: return "&weierp;";
+ case 0x211C: return "&real;";
+ case 0x2122: return "&trade;";
+ case 0x2135: return "&alefsym;";
+ case 0x2190: return "&larr;";
+ case 0x2191: return "&uarr;";
+ case 0x2192: return "&rarr;";
+ case 0x2193: return "&darr;";
+ case 0x2194: return "&harr;";
+ case 0x21D0: return "&lArr;";
+ case 0x21D1: return "&uArr;";
+ case 0x21D2: return "&rArr;";
+ case 0x21D3: return "&dArr;";
+ case 0x21D4: return "&hArr;";
+ case 0x2200: return "&forall;";
+ case 0x2202: return "&part;";
+ case 0x2203: return "&exist;";
+ case 0x2205: return "&empty;";
+ case 0x2207: return "&nabla;";
+ case 0x2208: return "&isin;";
+ case 0x2209: return "&notin;";
+ case 0x220B: return "&ni;";
+ case 0x220F: return "&prod;";
+ case 0x2211: return "&sum;";
+ case 0x2212: return "&minus;";
+ case 0x2217: return "&lowast;";
+ case 0x221A: return "&radic;";
+ case 0x221D: return "&prop;";
+ case 0x221E: return "&infin;";
+ case 0x2220: return "&ang;";
+ case 0x2227: return "&and;";
+ case 0x2228: return "&or;";
+ case 0x2229: return "&cap;";
+ case 0x222A: return "&cup;";
+ case 0x222B: return "&int;";
+ case 0x2234: return "&there4;";
+ case 0x223C: return "&sim;";
+ case 0x2245: return "&cong;";
+ case 0x2248: return "&asymp;";
+ case 0x2260: return "&ne;";
+ case 0x2261: return "&equiv;";
+ case 0x2264: return "&le;";
+ case 0x2265: return "&ge;";
+ case 0x2282: return "&sub;";
+ case 0x2283: return "&sup;";
+ case 0x2284: return "&nsub;";
+ case 0x2286: return "&sube;";
+ case 0x2287: return "&supe;";
+ case 0x2295: return "&oplus;";
+ case 0x2297: return "&otimes;";
+ case 0x22A5: return "&perp;";
+ case 0x22C5: return "&sdot;";
+ case 0x2308: return "&lceil;";
+ case 0x2309: return "&rceil;";
+ case 0x230A: return "&lfloor;";
+ case 0x230B: return "&rfloor;";
+ case 0x2329: return "&lang;";
+ case 0x232A: return "&rang;";
+ case 0x25CA: return "&loz;";
+ case 0x2660: return "&spades;";
+ case 0x2663: return "&clubs;";
+ case 0x2665: return "&hearts;";
+ case 0x2666: return "&diams;";
+ case 0x27E8: return "&lang;";
+ case 0x27E9: return "&rang;";
+ default: return to_unicode(code);
+ }
+ }
+}
+
+/*
+ * overstrike - returns TRUE if the glyph (i, name) is going to
+ * overstrike a previous glyph in sbuf. If TRUE the font
+ * is changed to bold and the previous sbuf is flushed.
+ */
+
+int html_printer::overstrike(glyph *g, const char *name,
+ const environment *env, int w)
+{
+ if ((env->hpos < sbuf_end_hpos)
+ || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos)))
+ {
+ /*
+ * at this point we have detected an overlap
+ */
+ if (overstrike_detected) {
+ /* already detected, remove previous glyph and use this glyph */
+ sbuf.set_length(last_sbuf_length);
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ } else {
+ /* first time we have detected an overstrike in the sbuf */
+ sbuf.set_length(last_sbuf_length); /* remove previous glyph */
+ if (! is_bold(sbuf_style.f))
+ flush_sbuf();
+ overstrike_detected = TRUE;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * set_char - adds a character into the sbuf if it is a continuation
+ * with the previous word otherwise flush the current sbuf
+ * and add character anew.
+ */
+
+void html_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *name)
+{
+ style sty(f, env->size, env->height, env->slant, env->fontno,
+ *env->col);
+ if (sty.slant != 0) {
+ if (sty.slant > 80 || sty.slant < -80) {
+ error("slant of %1 degrees out of range", sty.slant);
+ sty.slant = 0;
+ }
+ }
+ if (((!sbuf.empty())
+ && (sty == sbuf_style)
+ && (sbuf_vpos == env->vpos))
+ && (sbuf_continuation(g, name, env, w)
+ || overstrike(g, name, env, w)))
+ return;
+
+ flush_sbuf();
+ if (0 /* nullptr */ == sbuf_style.f)
+ sbuf_style = sty;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ sbuf_start_hpos = env->hpos;
+ sbuf_prev_hpos = env->hpos;
+ sbuf_vpos = env->vpos;
+ sbuf_style = sty;
+ sbuf_kern = 0;
+}
+
+/*
+ * set_numbered_char - handle numbered characters. Negative values are
+ * interpreted as unbreakable spaces; the value
+ * (taken positive) gives the width.
+ */
+
+void html_printer::set_numbered_char(int num, const environment *env,
+ int *widthp)
+{
+ int nbsp_width = 0;
+ if (num < 0) {
+ nbsp_width = -num;
+ num = 160; // &nbsp;
+ }
+ glyph *g = number_to_glyph(num);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return;
+ }
+ font *f = font_table[fn];
+ if (f == 0) {
+ error("no font mounted at position %1", fn);
+ return;
+ }
+ if (!f->contains(g)) {
+ error("font '%1' does not contain numbered character %2",
+ f->get_name(),
+ num);
+ return;
+ }
+ int w;
+ if (nbsp_width)
+ w = nbsp_width;
+ else
+ w = f->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ set_char(g, f, env, w, 0);
+}
+
+glyph *html_printer::set_char_and_width(const char *nm,
+ const environment *env,
+ int *widthp, font **f)
+{
+ glyph *g = name_to_glyph(nm);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return UNDEFINED_GLYPH;
+ }
+ *f = font_table[fn];
+ if (*f == 0) {
+ error("no font mounted at position %1", fn);
+ return UNDEFINED_GLYPH;
+ }
+ if (!(*f)->contains(g)) {
+ if (nm[0] != '\0' && nm[1] == '\0')
+ error("font '%1' does not contain ordinary character '%2'",
+ (*f)->get_name(), nm[0]);
+ else
+ error("font '%1' does not contain special character '%2'",
+ (*f)->get_name(), nm);
+ return UNDEFINED_GLYPH;
+ }
+ int w = (*f)->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ return g;
+}
+
+/*
+ * write_title - writes the title to this document
+ */
+
+void html_printer::write_title (int in_head)
+{
+ if (title.has_been_found) {
+ if (in_head) {
+ html.put_string("<title>");
+ html.put_string(title.text);
+ html.put_string("</title>").nl().nl();
+ } else {
+ title.has_been_written = TRUE;
+ if (title.with_h1) {
+ if (dialect == xhtml)
+ html.put_string("<h1>");
+ else
+ html.put_string("<h1 align=\"center\">");
+ html.put_string(title.text);
+ html.put_string("</h1>").nl().nl();
+ }
+ }
+ } else if (in_head) {
+ // place empty title tags to help conform to 'tidy'
+ html.put_string("<title></title>").nl();
+ }
+}
+
+/*
+ * write_rule - emits HTML rule element if the auto_rule is TRUE.
+ */
+
+static void write_rule (void)
+{
+ if (auto_rule) {
+ if (dialect == xhtml)
+ fputs("<hr/>\n", stdout);
+ else
+ fputs("<hr>\n", stdout);
+ }
+}
+
+void html_printer::begin_page(int n)
+{
+ page_number = n;
+#if defined(DEBUGGING)
+ html.begin_comment("Page: ")
+ .put_string(i_to_a(page_number)).end_comment();;
+#endif
+ no_of_printed_pages++;
+
+ output_style.f = 0;
+ output_style.point_size= -1;
+ output_space_code = 32;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ output_hpos = -1;
+ output_vpos = -1;
+ output_vpos_max = -1;
+ current_paragraph = new html_text(&html, dialect);
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ current_paragraph->do_para("", FALSE);
+}
+
+void html_printer::end_page(int)
+{
+ flush_sbuf();
+ flush_page();
+}
+
+font *html_printer::make_font(const char *nm)
+{
+ return html_font::load_html_font(nm);
+}
+
+void html_printer::do_body (void)
+{
+ if (0 /* nullptr */ == background)
+ fputs("<body>\n\n", stdout);
+ else {
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ background->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+
+ fputs("<body bgcolor=\"#", stdout);
+ fputs(buf, stdout);
+ fputs("\">\n\n", stdout);
+ }
+}
+
+/*
+ * emit_link - generates: <a href="to">name</a>
+ */
+
+void html_printer::emit_link (const string &to, const char *name)
+{
+ fputs("<a href=\"", stdout);
+ fputs(to.contents(), stdout);
+ fputs("\">", stdout);
+ fputs(name, stdout);
+ fputs("</a>", stdout);
+}
+
+/*
+ * write_navigation - writes out the links which navigate between
+ * file fragments.
+ */
+
+void html_printer::write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current)
+{
+ int need_bar = FALSE;
+
+ if (multiple_files) {
+ current_paragraph->done_para();
+ write_rule();
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(FALSE);
+ fputs("[ ", stdout);
+ if ((strcmp(prev.contents(), "") != 0)
+ && prev != top
+ && prev != current) {
+ emit_link(prev, "prev");
+ need_bar = TRUE;
+ }
+ if ((strcmp(next.contents(), "") != 0)
+ && next != top
+ && next != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(next, "next");
+ need_bar = TRUE;
+ }
+ if (top != "<standard input>"
+ && (strcmp(top.contents(), "") != 0)
+ && top != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(top, "top");
+ }
+ fputs(" ]\n", stdout);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+}
+
+/*
+ * do_file_components - scan the file list copying each temporary file
+ * in turn. This has twofold use: firstly to emit
+ * section heading links, between file fragments
+ * if required and secondly to generate jobname
+ * file fragments if required.
+ */
+
+void html_printer::do_file_components (void)
+{
+ int fragment_no = 1;
+ string top;
+ string prev;
+ string next;
+ string current;
+
+ file_list.start_of_list();
+ top = string(job_name);
+ if (dialect == xhtml)
+ top += string(".xhtml");
+ else
+ top += string(".html");
+ top += '\0';
+ next = file_list.next_file_name();
+ next += '\0';
+ current = next;
+ while (file_list.get_file() != 0) {
+ if (fseek(file_list.get_file(), 0L, 0) < 0)
+ fatal("fseek on temporary file failed");
+ html.copy_file(file_list.get_file());
+ fclose(file_list.get_file());
+ file_list.move_next();
+ if (file_list.is_new_output_file()) {
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ prev = current;
+ current = next;
+ next = file_list.next_file_name();
+ next += '\0';
+ string split_file = file_list.file_name();
+ split_file += '\0';
+ fflush(stdout);
+ if (!freopen(split_file.contents(), "w", stdout)) {
+ fatal("unable to reopen standard output stream: %1",
+ strerror(errno));
+ }
+ fragment_no++;
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ html.put_string("<title>");
+ html.put_string(split_file.contents());
+ html.put_string("</title>").nl().nl();
+
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ write_navigation(top, prev, next, current);
+ }
+ if (file_list.are_links_required())
+ header.write_headings(stdout, TRUE);
+ }
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ else {
+ assert(current_paragraph != 0);
+ current_paragraph->done_para();
+ write_rule();
+ if (valid_flag) {
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(TRUE);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+ }
+}
+
+/*
+ * writeHeadMetaStyle - emits the <head> <meta> and <style> tags and
+ * related information.
+ */
+
+void html_printer::writeHeadMetaStyle (void)
+{
+ if (dialect == html4) {
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional"
+ "//EN\"\n", stdout);
+ fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout);
+ fputs("<html>\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Thtml, see www.gnu.org\">\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\">\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\">\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ }
+ else {
+ fputs("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n", stdout);
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//"
+ "DTD XHTML 1.1 plus MathML 2.0//EN\"\n", stdout);
+ fputs(" \"http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd\"\n",
+ stdout);
+ fputs(" [<!ENTITY mathml \"http://www.w3.org/1998/Math/"
+ "MathML\">]>\n", stdout);
+
+ fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" "
+ "xml:lang=\"en\">\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Txhtml, see www.gnu.org\"/>\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\"/>\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\"/>\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ fputs(" .center { text-align: center }\n", stdout);
+ fputs(" .right { text-align: right }\n", stdout);
+ }
+ fputs(" p { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" pre { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" table { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" h1 { text-align: center }\n", stdout);
+ fputs("</style>\n", stdout);
+}
+
+html_printer::~html_printer()
+{
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (current_paragraph)
+ current_paragraph->flush_text();
+ html.end_line();
+ html.set_file(stdout);
+
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ write_title(TRUE);
+ head_info += '\0';
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ do_body();
+
+ write_title(FALSE);
+ header.write_headings(stdout, FALSE);
+ write_rule();
+#if defined(DEBUGGING)
+ html.begin_comment("Total number of pages: ")
+ .put_string(i_to_a(no_of_printed_pages)).end_comment();
+#endif
+ html.end_line();
+ html.end_line();
+
+ if (multiple_files) {
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ do_file_components();
+ } else {
+ do_file_components();
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ }
+}
+
+/*
+ * get_str - returns a duplicate of string, s. The duplicate
+ * string is terminated at the next ',' or ']'.
+ */
+
+static char *get_str (const char *s, char **n)
+{
+ int i = 0;
+ char *v;
+
+ while ((s[i] != (char)0) && (s[i] != ',') && (s[i] != ']'))
+ i++;
+ if (i>0) {
+ v = new char[i+1];
+ memcpy(v, s, i+1);
+ v[i] = (char)0;
+ if (s[i] == ',')
+ (*n) = (char *)&s[i+1];
+ else
+ (*n) = (char *)&s[i];
+ return v;
+ }
+ if (s[i] == ',')
+ (*n) = (char *)&s[1];
+ else
+ (*n) = (char *)s;
+ return 0;
+}
+
+/*
+ * make_val - creates a string from if s is a null pointer.
+ */
+
+char *make_val (char *s, int v, char *id, char *f, char *l)
+{
+ if (0 /* nullptr */ == s) {
+ char buf[30];
+
+ sprintf(buf, "%d", v);
+ return strsave(buf);
+ }
+ else {
+ /*
+ * check that value, s, is the same as, v.
+ */
+ char *t = s;
+
+ while (*t == '=')
+ t++;
+ if (atoi(t) != v) {
+ if (0 /* nullptr */ == f)
+ f = (char *)"stdin";
+ if (0 /* nullptr */ == l)
+ l = (char *)"<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s; "
+ "expected %d, got %s\n", f, l, id, v, s);
+ }
+ return s;
+ }
+}
+
+/*
+ * handle_assertion - handles the assertions created via .www:ASSERT
+ * in www.tmac. See www.tmac for examples. This
+ * method should be called as we are parsing the
+ * ditroff input. It checks the x, y position
+ * assertions. It does _not_ check the troff state
+ * assertions as these are unknown at this point.
+ */
+
+void html_printer::handle_assertion (int minv, int minh,
+ int maxv, int maxh, const char *s)
+{
+ char *n;
+ char *cmd = get_str(s, &n);
+ char *id = get_str(n, &n);
+ char *val = get_str(n, &n);
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ if (strcmp(cmd, "assertion:[x") == 0)
+ as.addx(cmd, id, make_val(val, minh, id, file, line), file, line);
+ else if (strcmp(cmd, "assertion:[y") == 0)
+ as.addy(cmd, id, make_val(val, minv, id, file, line), file, line);
+ else
+ if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0)
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number, minv, minh, maxv, maxh);
+}
+
+/*
+ * build_state_assertion - builds the troff state assertions.
+ */
+
+void html_printer::handle_state_assertion (text_glob *g)
+{
+ if (g != 0 && g->is_a_tag()
+ && (strncmp(g->text_string, "assertion:[", 11) == 0)) {
+ char *n = (char *)&g->text_string[11];
+ char *cmd = get_str(n, &n);
+ char *val = get_str(n, &n);
+ (void)get_str(n, &n); // unused
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ as.build(cmd, val, file, line);
+ }
+}
+
+/*
+ * special - handle all x X requests from troff. For post-html they
+ * allow users to pass raw HTML commands, turn auto linked
+ * headings off/on, and so forth.
+ */
+
+void html_printer::special(char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "html:", 5) == 0) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ /*
+ * pass rest of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(&s[5]),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ FALSE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by requesting
+ * that the formatting move right by the appropriate amount.
+ */
+ } else if ((strncmp(s, "html</p>:", 9) == 0) ||
+ (strncmp(s, "html<?p>:", 9) == 0) ||
+ (strncmp(s, "math<?p>:", 9) == 0)) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+ string t;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ if (strncmp(s, "math<?p>:", 9) == 0) {
+ if (strncmp((char *)&s[9], "<math>", 6) == 0) {
+ s[9] = '\0';
+ t = s;
+ t += "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">";
+ t += (char *)&s[15];
+ t += '\0';
+ s = (char *)&t[0];
+ }
+ }
+
+ /*
+ * need to pass all of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ TRUE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by
+ * requesting that the formatting move right by the appropriate
+ * amount.
+ */
+
+ } else if (strncmp(s, "index:", 6) == 0) {
+ cutoff_heading = atoi(&s[6]);
+ } else if (strncmp(s, "assertion:[", 11) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ handle_assertion(env->vpos-env->size*r/72, env->hpos,
+ env->vpos, env->hpos, s);
+ }
+ }
+}
+
+/*
+ * devtag - handles device troff tags sent from the 'troff'.
+ * These include the troff state machine tags:
+ * .br, .sp, .in, .tl, .ll etc
+ *
+ * (see man 5 grohtml_tags).
+ */
+
+void html_printer::devtag (char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "devtag:", strlen("devtag:")) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos);
+ }
+ }
+}
+
+
+/*
+ * taken from number.cpp in src/roff/troff, [hunits::hunits(units x)]
+ */
+
+int html_printer::round_width(int x)
+{
+ int r = font::hor;
+ int n;
+
+ // don't depend on rounding direction for division of negative ints
+ if (r == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + r/2 - 1)/r)
+ : (x + r/2 - 1)/r);
+ return n * r;
+}
+
+/*
+ * handle_valid_flag - emits a valid XHTML 1.1 or HTML 4.01 button,
+ * provided -V was supplied on the command line.
+ */
+
+void html_printer::handle_valid_flag (int needs_para)
+{
+ if (valid_flag) {
+ if (needs_para)
+ fputs("<p>", stdout);
+ if (dialect == xhtml)
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-xhtml11-blue\" "
+ "alt=\"Valid XHTML 1.1 Transitional\" "
+ "height=\"31\" width=\"88\" /></a>\n", stdout);
+ else
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-html401-blue\" "
+ "alt=\"Valid HTML 4.01 Transitional\" "
+ "height=\"31\" width=\"88\"></a>\n", stdout);
+ if (needs_para)
+ fputs("</p>", stdout);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv,
+ "a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'a':
+ /* text antialiasing bits - handled by pre-html */
+ break;
+ case 'b':
+ // set background color to white
+ default_background = new color;
+ default_background->set_gray(color::MAX_COLOR_VAL);
+ break;
+ case 'C':
+ // Don't write CreationDate HTML comments.
+ do_write_date_comment = FALSE;
+ break;
+ case 'd':
+ /* handled by pre-html */
+ break;
+ case 'D':
+ /* handled by pre-html */
+ break;
+ case 'e':
+ /* handled by pre-html */
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'g':
+ /* graphic antialiasing bits - handled by pre-html */
+ break;
+ case 'G':
+ // Don't write Creator HTML comments.
+ do_write_creator_comment = FALSE;
+ break;
+ case 'h':
+ /* do not use the Hn headings of html, but manufacture our own */
+ manufacture_headings = TRUE;
+ break;
+ case 'i':
+ /* handled by pre-html */
+ break;
+ case 'I':
+ /* handled by pre-html */
+ break;
+ case 'j':
+ multiple_files = TRUE;
+ job_name = optarg;
+ break;
+ case 'l':
+ auto_links = FALSE;
+ break;
+ case 'n':
+ simple_anchors = TRUE;
+ break;
+ case 'o':
+ /* handled by pre-html */
+ break;
+ case 'p':
+ /* handled by pre-html */
+ break;
+ case 'r':
+ auto_rule = FALSE;
+ break;
+ case 's':
+ base_point_size = atoi(optarg);
+ break;
+ case 'S':
+ split_level = atoi(optarg) + 1;
+ break;
+ case 'v':
+ printf("GNU post-grohtml (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'V':
+ valid_flag = TRUE;
+ break;
+ case 'x':
+ if (strcmp(optarg, "x") == 0) {
+ dialect = xhtml;
+ simple_anchors = TRUE;
+ } else if (strcmp(optarg, "4") == 0)
+ dialect = html4;
+ else
+ warning("unsupported HTML dialect: '%1'", optarg);
+ break;
+ case 'y':
+ groff_sig = TRUE;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ if (optind >= argc) {
+ do_file("-");
+ } else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bCGhlnrVy] [-F font-directory] [-j output-stem]"
+" [-s base-type-size] [-S heading-level] [-x html-dialect] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: