summaryrefslogtreecommitdiffstats
path: root/src/preproc/html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
commitd318611dd6f23fcfedd50e9b9e24620b102ba96a (patch)
tree8b9eef82ca40fdd5a8deeabf07572074c236095d /src/preproc/html
parentInitial commit. (diff)
downloadgroff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.tar.xz
groff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.zip
Adding upstream version 1.23.0.upstream/1.23.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/preproc/html')
-rw-r--r--src/preproc/html/html.am32
-rw-r--r--src/preproc/html/pre-html.cpp1819
-rw-r--r--src/preproc/html/pre-html.h38
-rw-r--r--src/preproc/html/pushback.cpp336
-rw-r--r--src/preproc/html/pushback.h52
5 files changed, 2277 insertions, 0 deletions
diff --git a/src/preproc/html/html.am b/src/preproc/html/html.am
new file mode 100644
index 0000000..ac5ca9d
--- /dev/null
+++ b/src/preproc/html/html.am
@@ -0,0 +1,32 @@
+# 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 += pre-grohtml
+pre_grohtml_LDADD = libgroff.a lib/libgnu.a $(LIBM)
+pre_grohtml_SOURCES = \
+ src/preproc/html/pre-html.cpp \
+ src/preproc/html/pushback.cpp \
+ src/preproc/html/pre-html.h \
+ src/preproc/html/pushback.h
+src/preproc/html/pre-html.$(OBJEXT): defs.h
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/html/pre-html.cpp b/src/preproc/html/pre-html.cpp
new file mode 100644
index 0000000..fecfb05
--- /dev/null
+++ b/src/preproc/html/pre-html.cpp
@@ -0,0 +1,1819 @@
+/* Copyright (C) 2000-2021 Free Software Foundation, Inc.
+ * Written by Gaius Mulley (gaius@glam.ac.uk).
+ *
+ * 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 groff; see the file COPYING. If not, write to the Free
+ * Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define PREHTMLC
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "posix.h"
+#include "defs.h"
+#include "searchpath.h"
+#include "paper.h"
+#include "device.h"
+#include "font.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef _POSIX_VERSION
+# include <sys/wait.h>
+# define PID_T pid_t
+#else /* not _POSIX_VERSION */
+# define PID_T int
+#endif /* not _POSIX_VERSION */
+
+#include <stdarg.h>
+
+#include "nonposix.h"
+
+#if 0
+# define DEBUGGING
+#endif
+
+/* Establish some definitions to facilitate discrimination between
+ differing runtime environments. */
+
+#undef MAY_FORK_CHILD_PROCESS
+#undef MAY_SPAWN_ASYNCHRONOUS_CHILD
+
+#if defined(__MSDOS__) || defined(_WIN32)
+
+// Most MS-DOS and Win32 environments will be missing the 'fork'
+// capability (some, like Cygwin, have it, but it is better avoided).
+
+# define MAY_FORK_CHILD_PROCESS 0
+
+// On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'.
+# include <process.h> // for 'spawn...'
+# include <fcntl.h> // for attributes of pipes
+
+# if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
+
+// These Win32 implementations allow parent and 'spawn...'ed child to
+// multitask asynchronously.
+
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
+
+# else
+
+// Others may adopt MS-DOS behaviour where parent must sleep,
+// from 'spawn...' until child terminates.
+
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
+
+# endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
+
+# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
+/* When we are building a DEBUGGING version we need to tell pre-grohtml
+ where to put intermediate files (the DEBUGGING version will preserve
+ these on exit).
+
+ On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will
+ probably not have this on all disk drives, so default to using
+ 'c:/temp' instead. (Note that user may choose to override this by
+ supplying a definition such as
+
+ -DDEBUG_FILE_DIR=d:/path/to/debug/files
+
+ in the CPPFLAGS to 'make'.) */
+
+# define DEBUG_FILE_DIR c:/temp
+# endif
+
+#else /* not __MSDOS__ or _WIN32 */
+
+// For non-Microsoft environments assume Unix conventions,
+// so 'fork' is required and child processes are asynchronous.
+# define MAY_FORK_CHILD_PROCESS 1
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
+
+# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
+/* For a DEBUGGING version, on the Unix host, we can also usually rely
+ on being able to use '/tmp' for temporary file storage. (Note that,
+ as in the __MSDOS__ or _WIN32 case above, the user may override this
+ by defining
+
+ -DDEBUG_FILE_DIR=/path/to/debug/files
+
+ in the CPPFLAGS.) */
+
+# define DEBUG_FILE_DIR /tmp
+# endif
+
+#endif /* not __MSDOS__ or _WIN32 */
+
+#ifdef DEBUGGING
+// For a DEBUGGING version, we need some additional macros,
+// to direct the captured debugging mode output to appropriately named
+// files in the specified DEBUG_FILE_DIR.
+
+# define DEBUG_TEXT(text) #text
+# define DEBUG_NAME(text) DEBUG_TEXT(text)
+# define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
+#endif
+
+extern "C" const char *Version_string;
+
+#include "pre-html.h"
+#include "pushback.h"
+#include "html-strings.h"
+
+#define DEFAULT_LINE_LENGTH 7 // inches wide
+#define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
+#define IMAGE_BORDER_PIXELS 0
+#define INLINE_LEADER_CHAR '\\'
+
+// Don't use colour names here! Otherwise there is a dependency on
+// a file called 'rgb.txt' which maps names to colours.
+#define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
+#define MIN_ALPHA_BITS 0
+#define MAX_ALPHA_BITS 4
+
+#define PAGE_TEMPLATE_SHORT "pg"
+#define PAGE_TEMPLATE_LONG "-page-"
+#define PS_TEMPLATE_SHORT "ps"
+#define PS_TEMPLATE_LONG "-ps-"
+#define REGION_TEMPLATE_SHORT "rg"
+#define REGION_TEMPLATE_LONG "-regions-"
+
+typedef enum {
+ CENTERED, LEFT, RIGHT, INLINE
+} IMAGE_ALIGNMENT;
+
+typedef enum {xhtml, html4} html_dialect;
+
+static int postscriptRes = -1; // PostScript resolution,
+ // dots per inch
+static int stdoutfd = 1; // output file descriptor -
+ // normally 1 but might move
+ // -1 means closed
+static char *psFileName = 0 /* nullptr */; // PostScript file name
+static char *psPageName = 0 /* nullptr */; // name of file
+ // containing current
+ // PostScript page
+static char *regionFileName = 0 /* nullptr */; // name of file
+ // containing all image
+ // regions
+static char *imagePageName = 0 /* nullptr */; // name of bitmap image
+ // file containing
+ // current page
+static const char *image_device = "pnmraw";
+static int image_res = DEFAULT_IMAGE_RES;
+static int vertical_offset = 0;
+static char *image_template = 0 /* nullptr */; // image file name
+ // template
+static char *macroset_template= 0 /* nullptr */; // image file
+ // name template
+ // passed to
+ // troff by -D
+static int troff_arg = 0; // troff arg index
+static char *image_dir = 0 /* nullptr */; // user-specified image
+ // directory
+static int textAlphaBits = MAX_ALPHA_BITS;
+static int graphicAlphaBits = MAX_ALPHA_BITS;
+static char *antiAlias = 0 /* nullptr */; // anti-alias arguments
+ // to be passed to gs
+static bool want_progress_report = false; // display page numbers
+ // as they are processed
+static int currentPageNo = -1; // current image page number
+#if defined(DEBUGGING)
+static bool debugging = false;
+static char *troffFileName = 0 /* nullptr */; // pre-html output sent
+ // to troff -Tps
+static char *htmlFileName = 0 /* nullptr */; // pre-html output sent
+ // to troff -Thtml
+#endif
+static bool need_eqn = false; // must we preprocess via eqn?
+
+static char *linebuf = 0 /* nullptr */; // for scanning devps/DESC
+static int linebufsize = 0;
+static const char *image_gen = 0 /* nullptr */; // the 'gs' program
+
+static const char devhtml_desc[] = "devhtml/DESC";
+static const char devps_desc[] = "devps/DESC";
+
+const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
+static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
+static html_dialect dialect = html4;
+
+
+/*
+ * Images are generated via PostScript, gs, and the pnm utilities.
+ */
+#define IMAGE_DEVICE "-Tps"
+
+
+/*
+ * sys_fatal - Write a fatal error message.
+ * Taken from src/roff/groff/pipeline.c.
+ */
+
+void sys_fatal(const char *s)
+{
+ fatal("%1: %2", s, strerror(errno));
+}
+
+/*
+ * get_line - Copy a line (w/o newline) from a file to the
+ * global line buffer.
+ */
+
+int get_line(FILE *f)
+{
+ if (f == 0)
+ return 0;
+ if (linebuf == 0) {
+ linebuf = new char[128];
+ linebufsize = 128;
+ }
+ int i = 0;
+ // skip leading whitespace
+ for (;;) {
+ int c = getc(f);
+ if (c == EOF)
+ return 0;
+ if (c != ' ' && c != '\t') {
+ ungetc(c, f);
+ break;
+ }
+ }
+ for (;;) {
+ int c = getc(f);
+ if (c == EOF)
+ break;
+ if (i + 1 >= linebufsize) {
+ char *old_linebuf = linebuf;
+ linebuf = new char[linebufsize * 2];
+ memcpy(linebuf, old_linebuf, linebufsize);
+ delete[] old_linebuf;
+ linebufsize *= 2;
+ }
+ linebuf[i++] = c;
+ if (c == '\n') {
+ i--;
+ break;
+ }
+ }
+ linebuf[i] = '\0';
+ return 1;
+}
+
+/*
+ * get_resolution - Return the PostScript device resolution.
+ */
+
+static unsigned int get_resolution(void)
+{
+ char *pathp;
+ FILE *f;
+ unsigned int res = 0;
+ f = font_path.open_file(devps_desc, &pathp);
+ if (0 == f)
+ fatal("cannot open file '%1'", devps_desc);
+ free(pathp);
+ // XXX: We should break out of this loop if we hit a "charset" line.
+ // "This line and everything following it in the file are ignored."
+ // (groff_font(5))
+ while (get_line(f))
+ (void) sscanf(linebuf, "res %u", &res);
+ fclose(f);
+ return res;
+}
+
+
+/*
+ * get_image_generator - Return the declared program from the HTML
+ * device description.
+ */
+
+static char *get_image_generator(void)
+{
+ char *pathp;
+ FILE *f;
+ char *generator = 0;
+ const char keyword[] = "image_generator";
+ const size_t keyword_len = strlen(keyword);
+ f = font_path.open_file(devhtml_desc, &pathp);
+ if (0 == f)
+ fatal("cannot open file '%1'", devhtml_desc);
+ free(pathp);
+ // XXX: We should break out of this loop if we hit a "charset" line.
+ // "This line and everything following it in the file are ignored."
+ // (groff_font(5))
+ while (get_line(f)) {
+ char *cursor = linebuf;
+ size_t limit = strlen(linebuf);
+ char *end = linebuf + limit;
+ if (0 == (strncmp(linebuf, keyword, keyword_len))) {
+ cursor += keyword_len;
+ // At least one space or tab is required.
+ if(!(' ' == *cursor) || ('\t' == *cursor))
+ continue;
+ cursor++;
+ while((cursor < end) && ((' ' == *cursor) || ('\t' == *cursor)))
+ cursor++;
+ if (cursor == end)
+ continue;
+ generator = cursor;
+ }
+ }
+ fclose(f);
+ return generator;
+}
+
+/*
+ * html_system - A wrapper for system().
+ */
+
+void html_system(const char *s, int redirect_stdout)
+{
+#if defined(DEBUGGING)
+ if (debugging) {
+ fprintf(stderr, "executing: ");
+ fwrite(s, sizeof(char), strlen(s), stderr);
+ fflush(stderr);
+ }
+#endif
+ {
+ int saved_stdout = dup(1);
+ int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
+ if (redirect_stdout && saved_stdout > 1 && fdnull > 1)
+ dup2(fdnull, 1);
+ if (fdnull >= 0)
+ close(fdnull);
+ int status = system(s);
+ if (redirect_stdout)
+ dup2(saved_stdout, 1);
+ if (status == -1)
+ fprintf(stderr, "Calling '%s' failed\n", s);
+ else if (status)
+ fprintf(stderr, "Calling '%s' returned status %d\n", s, status);
+ close(saved_stdout);
+ }
+}
+
+/*
+ * make_string - Create a string via `malloc()`, place the variadic
+ * arguments as formatted by `fmt` into it, and return
+ * it. Adapted from Linux man-pages' printf(3) example.
+ * We never return a null pointer, instead treating
+ * failure as invariably fatal.
+ */
+
+char *make_string(const char *fmt, ...)
+{
+ size_t size = 0;
+ char *p = 0 /* nullptr */;
+ va_list ap;
+ va_start(ap, fmt);
+ int n = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ sys_fatal("vsnprintf");
+ size = static_cast<size_t>(n) + 1 /* '\0' */;
+ p = static_cast<char *>(malloc(size));
+ if (0 /* nullptr */ == p)
+ sys_fatal("vsnprintf");
+ va_start(ap, fmt);
+ n = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ sys_fatal("vsnprintf");
+ assert(p != 0 /* nullptr */);
+ return p;
+}
+
+/*
+ * classes and methods for retaining ascii text
+ */
+
+struct char_block {
+ enum { SIZE = 256 };
+ char buffer[SIZE];
+ int used;
+ char_block *next;
+
+ char_block();
+};
+
+char_block::char_block()
+: used(0), next(0)
+{
+ for (int i = 0; i < SIZE; i++)
+ buffer[i] = 0;
+}
+
+class char_buffer {
+public:
+ char_buffer();
+ ~char_buffer();
+ void read_file(FILE *fp);
+ int do_html(int argc, char *argv[]);
+ int do_image(int argc, char *argv[]);
+ void emit_troff_output(int device_format_selector);
+ void write_upto_newline(char_block **t, int *i, int is_html);
+ bool can_see(char_block **t, int *i, const char *string);
+ void skip_until_newline(char_block **t, int *i);
+private:
+ char_block *head;
+ char_block *tail;
+ int run_output_filter(int device_format_selector, int argc,
+ char *argv[]);
+};
+
+char_buffer::char_buffer()
+: head(0), tail(0)
+{
+}
+
+char_buffer::~char_buffer()
+{
+ while (head != 0 /* nullptr */) {
+ char_block *temp = head;
+ head = head->next;
+ delete temp;
+ }
+}
+
+/*
+ * read_file - Read file `fp` into char_blocks.
+ */
+
+void char_buffer::read_file(FILE *fp)
+{
+ int n;
+ while (!feof(fp)) {
+ if (0 /* nullptr */ == tail) {
+ tail = new char_block;
+ head = tail;
+ }
+ else {
+ if (tail->used == char_block::SIZE) {
+ tail->next = new char_block;
+ tail = tail->next;
+ }
+ }
+ // We now have a tail ready for the next `SIZE` bytes of the file.
+ n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used,
+ fp);
+ if ((n < 0) || ((0 == n) && !feof(fp)))
+ sys_fatal("fread");
+ tail->used += n * sizeof(char);
+ }
+}
+
+/*
+ * writeNbytes - Write n bytes to stdout.
+ */
+
+static void writeNbytes(const char *s, int l)
+{
+ int n = 0;
+ int r;
+
+ while (n < l) {
+ r = write(stdoutfd, s, l - n);
+ if (r < 0)
+ sys_fatal("write");
+ n += r;
+ s += r;
+ }
+}
+
+/*
+ * writeString - Write a string to stdout.
+ */
+
+static void writeString(const char *s)
+{
+ writeNbytes(s, strlen(s));
+}
+
+/*
+ * makeFileName - Create the image filename template
+ * and the macroset image template.
+ */
+
+static void makeFileName(void)
+{
+ if ((image_dir != 0 /* nullptr */)
+ && (strchr(image_dir, '%') != 0 /* nullptr */))
+ fatal("'%%' is prohibited within the image directory name");
+ if ((image_template != 0 /* nullptr */)
+ && (strchr(image_template, '%') != 0 /* nullptr */))
+ fatal("'%%' is prohibited within the image template");
+ if (0 /* nullptr */ == image_dir)
+ image_dir = (char *)"";
+ else if (strlen(image_dir) > 0
+ && image_dir[strlen(image_dir) - 1] != '/')
+ image_dir = make_string("%s/", image_dir);
+ if (0 /* nullptr */ == image_template)
+ macroset_template = make_string("%sgrohtml-%d-", image_dir,
+ int(getpid()));
+ else
+ macroset_template = make_string("%s%s-", image_dir,
+ image_template);
+ size_t mtlen = strlen(macroset_template);
+ image_template = (char *)malloc(strlen("%d") + mtlen + 1);
+ if (0 /* nullptr */ == image_template)
+ sys_fatal("malloc");
+ char *s = strcpy(image_template, macroset_template);
+ s += mtlen;
+ // Keep this format string synced with troff:suppress_node::tprint().
+ strcpy(s, "%d");
+}
+
+/*
+ * setupAntiAlias - Set up the antialias string, used when we call gs.
+ */
+
+static void setupAntiAlias(void)
+{
+ if (textAlphaBits == 0 && graphicAlphaBits == 0)
+ antiAlias = make_string(" ");
+ else if (textAlphaBits == 0)
+ antiAlias = make_string("-dGraphicsAlphaBits=%d ",
+ graphicAlphaBits);
+ else if (graphicAlphaBits == 0)
+ antiAlias = make_string("-dTextAlphaBits=%d ", textAlphaBits);
+ else
+ antiAlias = make_string("-dTextAlphaBits=%d"
+ " -dGraphicsAlphaBits=%d ", textAlphaBits,
+ graphicAlphaBits);
+}
+
+/*
+ * checkImageDir - Check whether the image directory is available.
+ */
+
+static void checkImageDir(void)
+{
+ if (image_dir != 0 /* nullptr */ && strcmp(image_dir, "") != 0)
+ if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST))
+ fatal("cannot create directory '%1': %2", image_dir,
+ strerror(errno));
+}
+
+/*
+ * write_end_image - End the image. Write out the image extents if we
+ * are using -Tps.
+ */
+
+static void write_end_image(int is_html)
+{
+ /*
+ * if we are producing html then these
+ * emit image name and enable output
+ * else
+ * we are producing images
+ * in which case these generate image
+ * boundaries
+ */
+ writeString("\\O[4]\\O[2]");
+ if (is_html)
+ writeString("\\O[1]");
+ else
+ writeString("\\O[0]");
+}
+
+/*
+ * write_start_image - Write troff code which will:
+ *
+ * (i) disable html output for the following image
+ * (ii) reset the max/min x/y registers during
+ * Postscript Rendering.
+ */
+
+static void write_start_image(IMAGE_ALIGNMENT pos, int is_html)
+{
+ writeString("\\O[5");
+ switch (pos) {
+ case INLINE:
+ writeString("i");
+ break;
+ case LEFT:
+ writeString("l");
+ break;
+ case RIGHT:
+ writeString("r");
+ break;
+ case CENTERED:
+ default:
+ writeString("c");
+ break;
+ }
+ writeString(image_template);
+ writeString(".png]");
+ if (is_html)
+ writeString("\\O[0]\\O[3]");
+ else
+ // reset min/max registers
+ writeString("\\O[1]\\O[3]");
+}
+
+/*
+ * write_upto_newline - Write the contents of the buffer until a
+ * newline is seen. Check for
+ * HTML_IMAGE_INLINE_BEGIN and
+ * HTML_IMAGE_INLINE_END; process them if they are
+ * present.
+ */
+
+void char_buffer::write_upto_newline(char_block **t, int *i,
+ int is_html)
+{
+ int j = *i;
+
+ if (*t) {
+ while (j < (*t)->used
+ && (*t)->buffer[j] != '\n'
+ && (*t)->buffer[j] != INLINE_LEADER_CHAR)
+ j++;
+ if (j < (*t)->used
+ && (*t)->buffer[j] == '\n')
+ j++;
+ writeNbytes((*t)->buffer + (*i), j - (*i));
+ if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) {
+ if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN))
+ write_start_image(INLINE, is_html);
+ else if (can_see(t, &j, HTML_IMAGE_INLINE_END))
+ write_end_image(is_html);
+ else {
+ if (j < (*t)->used) {
+ *i = j;
+ j++;
+ writeNbytes((*t)->buffer + (*i), j - (*i));
+ }
+ }
+ }
+ if (j == (*t)->used) {
+ *i = 0;
+ *t = (*t)->next;
+ if (*t && (*t)->buffer[j - 1] != '\n')
+ write_upto_newline(t, i, is_html);
+ }
+ else
+ // newline was seen
+ *i = j;
+ }
+}
+
+/*
+ * can_see - Return true if we can see string in t->buffer[i] onwards.
+ */
+
+bool char_buffer::can_see(char_block **t, int *i, const char *str)
+{
+ int j = 0;
+ int l = strlen(str);
+ int k = *i;
+ char_block *s = *t;
+
+ while (s) {
+ while (k < s->used && j < l && s->buffer[k] == str[j]) {
+ j++;
+ k++;
+ }
+ if (j == l) {
+ *i = k;
+ *t = s;
+ return true;
+ }
+ else if (k < s->used && s->buffer[k] != str[j])
+ return false;
+ s = s->next;
+ k = 0;
+ }
+ return false;
+}
+
+/*
+ * skip_until_newline - Skip all characters until a newline is seen.
+ * The newline is not consumed.
+ */
+
+void char_buffer::skip_until_newline(char_block **t, int *i)
+{
+ int j = *i;
+
+ if (*t) {
+ while (j < (*t)->used && (*t)->buffer[j] != '\n')
+ j++;
+ if (j == (*t)->used) {
+ *i = 0;
+ *t = (*t)->next;
+ skip_until_newline(t, i);
+ }
+ else
+ // newline was seen
+ *i = j;
+ }
+}
+
+#define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
+#define HTML_OUTPUT_FILTER 0
+#define IMAGE_OUTPUT_FILTER 1
+#define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
+#define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
+#define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
+
+/*
+ * emit_troff_output - Write formatted buffer content to the troff
+ * post-processor data pipeline.
+ */
+
+void char_buffer::emit_troff_output(int device_format_selector)
+{
+ // Handle output for BOTH html and image device formats
+ // if 'device_format_selector' is passed as
+ //
+ // HTML_FORMAT(HTML_OUTPUT_FILTER)
+ // Buffer data is written to the output stream
+ // with template image names translated to actual image names.
+ //
+ // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
+ // Buffer data is written to the output stream
+ // with no translation, for image file creation in the
+ // post-processor.
+
+ int idx = 0;
+ char_block *element = head;
+
+ while (element != 0 /* nullptr */)
+ write_upto_newline(&element, &idx, device_format_selector);
+
+#if 0
+ if (close(stdoutfd) < 0)
+ sys_fatal ("close");
+
+ // now we grab fd=1 so that the next pipe cannot use fd=1
+ if (stdoutfd == 1) {
+ if (dup(2) != stdoutfd)
+ sys_fatal ("dup failed to use fd=1");
+ }
+#endif /* 0 */
+}
+
+/*
+ * The image class remembers the position of all images in the
+ * PostScript file and assigns names for each image.
+ */
+
+struct imageItem {
+ imageItem *next;
+ int X1;
+ int Y1;
+ int X2;
+ int Y2;
+ char *imageName;
+ int resolution;
+ int maxx;
+ int pageNo;
+
+ imageItem(int x1, int y1, int x2, int y2,
+ int page, int res, int max_width, char *name);
+ ~imageItem();
+};
+
+/*
+ * imageItem - Constructor.
+ */
+
+imageItem::imageItem(int x1, int y1, int x2, int y2,
+ int page, int res, int max_width, char *name)
+{
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ pageNo = page;
+ resolution = res;
+ maxx = max_width;
+ imageName = name;
+ next = 0 /* nullptr */;
+}
+
+/*
+ * imageItem - Destructor.
+ */
+
+imageItem::~imageItem()
+{
+ if (imageName)
+ free(imageName);
+}
+
+/*
+ * imageList - A class containing a list of imageItems.
+ */
+
+class imageList {
+private:
+ imageItem *head;
+ imageItem *tail;
+ int count;
+public:
+ imageList();
+ ~imageList();
+ void add(int x1, int y1, int x2, int y2,
+ int page, int res, int maxx, char *name);
+ void createImages(void);
+ int createPage(int pageno);
+ void createImage(imageItem *i);
+ int getMaxX(int pageno);
+};
+
+/*
+ * imageList - Constructor.
+ */
+
+imageList::imageList()
+: head(0), tail(0), count(0)
+{
+}
+
+/*
+ * imageList - Destructor.
+ */
+
+imageList::~imageList()
+{
+ while (head != 0 /* nullptr */) {
+ imageItem *i = head;
+ head = head->next;
+ delete i;
+ }
+}
+
+/*
+ * createPage - Create image of page `pageno` from PostScript file.
+ */
+
+int imageList::createPage(int pageno)
+{
+ char *s;
+
+ if (currentPageNo == pageno)
+ return 0;
+
+ if (currentPageNo >= 1) {
+ /*
+ * We need to unlink the files which change each time a new page is
+ * processed. The final unlink is done by xtmpfile when
+ * pre-grohtml exits.
+ */
+ unlink(imagePageName);
+ unlink(psPageName);
+ }
+
+ if (want_progress_report) {
+ fprintf(stderr, "[%d] ", pageno);
+ fflush(stderr);
+ }
+
+#if defined(DEBUGGING)
+ if (debugging)
+ fprintf(stderr, "creating page %d\n", pageno);
+#endif
+
+ s = make_string("psselect -q -p%d %s %s\n",
+ pageno, psFileName, psPageName);
+ html_system(s, 1);
+ assert(strlen(image_gen) > 0);
+ s = make_string("echo showpage | "
+ "%s%s -q -dBATCH -dSAFER "
+ "-dDEVICEHEIGHTPOINTS=792 "
+ "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
+ "-sDEVICE=%s -r%d %s "
+ "-sOutputFile=%s %s -\n",
+ image_gen,
+ EXE_EXT,
+ (getMaxX(pageno) * image_res) / postscriptRes,
+ image_device,
+ image_res,
+ antiAlias,
+ imagePageName,
+ psPageName);
+ html_system(s, 1);
+ free(s);
+ currentPageNo = pageno;
+ return 0;
+}
+
+/*
+ * min - Return the minimum of two numbers.
+ */
+
+int min(int x, int y)
+{
+ if (x < y)
+ return x;
+ else
+ return y;
+}
+
+/*
+ * max - Return the maximum of two numbers.
+ */
+
+int max(int x, int y)
+{
+ if (x > y)
+ return x;
+ else
+ return y;
+}
+
+/*
+ * getMaxX - Return the largest right-hand position for any image
+ * on `pageno`.
+ */
+
+int imageList::getMaxX(int pageno)
+{
+ imageItem *h = head;
+ int x = postscriptRes * DEFAULT_LINE_LENGTH;
+
+ while (h != 0 /* nullptr */) {
+ if (h->pageNo == pageno)
+ x = max(h->X2, x);
+ h = h->next;
+ }
+ return x;
+}
+
+/*
+ * createImage - Generate a minimal PNG file from the set of page
+ * images.
+ */
+
+void imageList::createImage(imageItem *i)
+{
+ if (i->X1 != -1) {
+ char *s;
+ int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes
+ - IMAGE_BORDER_PIXELS,
+ 0);
+ int y1 = max(image_res * vertical_offset / 72
+ + min(i->Y1, i->Y2) * image_res / postscriptRes
+ - IMAGE_BORDER_PIXELS,
+ 0);
+ int x2 = max(i->X1, i->X2) * image_res / postscriptRes
+ + IMAGE_BORDER_PIXELS;
+ int y2 = image_res * vertical_offset / 72
+ + max(i->Y1, i->Y2) * image_res / postscriptRes
+ + 1 + IMAGE_BORDER_PIXELS;
+ if (createPage(i->pageNo) == 0) {
+ s = make_string("pnmcut%s %d %d %d %d < %s "
+ "| pnmcrop%s -quiet | pnmtopng%s -quiet %s"
+ "> %s\n",
+ EXE_EXT,
+ x1, y1, x2 - x1 + 1, y2 - y1 + 1,
+ imagePageName,
+ EXE_EXT,
+ EXE_EXT,
+ TRANSPARENT,
+ i->imageName);
+ html_system(s, 0);
+ free(s);
+ }
+ else {
+ fprintf(stderr, "failed to generate image of page %d\n",
+ i->pageNo);
+ fflush(stderr);
+ }
+#if defined(DEBUGGING)
+ }
+ else {
+ if (debugging) {
+ fprintf(stderr, "ignoring image as x1 coord is -1\n");
+ fflush(stderr);
+ }
+#endif
+ }
+}
+
+/*
+ * add - Add an image description to the imageList.
+ */
+
+void imageList::add(int x1, int y1, int x2, int y2,
+ int page, int res, int maxx, char *name)
+{
+ imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name);
+
+ if (0 /* nullptr */ == head) {
+ head = i;
+ tail = i;
+ }
+ else {
+ tail->next = i;
+ tail = i;
+ }
+}
+
+/*
+ * createImages - For each image descriptor on the imageList,
+ * create the actual image.
+ */
+
+void imageList::createImages(void)
+{
+ imageItem *h = head;
+
+ while (h != 0 /* nullptr */) {
+ createImage(h);
+ h = h->next;
+ }
+}
+
+static imageList listOfImages; // list of images defined by region file
+
+/*
+ * generateImages - Parse the region file and generate images from the
+ * PostScript file. The region file contains the
+ * x1,y1--x2,y2 extents of each image.
+ */
+
+static void generateImages(char *region_file_name)
+{
+ pushBackBuffer *f=new pushBackBuffer(region_file_name);
+
+ while (f->putPB(f->getPB()) != eof) {
+ if (f->isString("grohtml-info:page")) {
+ int page = f->readInt();
+ int x1 = f->readInt();
+ int y1 = f->readInt();
+ int x2 = f->readInt();
+ int y2 = f->readInt();
+ int maxx = f->readInt();
+ char *name = f->readString();
+ int res = postscriptRes;
+ listOfImages.add(x1, y1, x2, y2, page, res, maxx, name);
+ while (f->putPB(f->getPB()) != '\n'
+ && f->putPB(f->getPB()) != eof)
+ (void)f->getPB();
+ if (f->putPB(f->getPB()) == '\n')
+ (void)f->getPB();
+ }
+ else {
+ /* Write any error messages out to the user. */
+ fputc(f->getPB(), stderr);
+ }
+ }
+ fflush(stderr);
+
+ listOfImages.createImages();
+ if (want_progress_report) {
+ fprintf(stderr, "done\n");
+ fflush(stderr);
+ }
+ delete f;
+}
+
+/*
+ * set_redirection - Redirect file descriptor `was` to file descriptor
+ * `willbe`.
+ */
+
+static void set_redirection(int was, int willbe)
+{
+ // Nothing to do if 'was' and 'willbe' already have same handle.
+ if (was != willbe) {
+ // Otherwise attempt the specified redirection.
+ if (dup2(willbe, was) < 0) {
+ // Redirection failed, so issue diagnostic and bail out.
+ fprintf(stderr, "failed to replace fd=%d with %d\n", was, willbe);
+ if (willbe == STDOUT_FILENO)
+ fprintf(stderr,
+ "likely that stdout should be opened before %d\n", was);
+ sys_fatal("dup2");
+ }
+
+ // When redirection has been successfully completed assume redundant
+ // handle 'willbe' is no longer required, so close it.
+ if (close(willbe) < 0)
+ // Issue diagnostic if 'close' fails.
+ sys_fatal("close");
+ }
+}
+
+/*
+ * save_and_redirect - Duplicate file descriptor for `was` on file
+ * descriptor `willbe`.
+ */
+
+static int save_and_redirect(int was, int willbe)
+{
+ if (was == willbe)
+ // No redirection specified; silently bail out.
+ return (was);
+
+ // Proceeding with redirection so first save and verify our duplicate
+ // handle for 'was'.
+ int saved = dup(was);
+ if (saved < 0) {
+ fprintf(stderr, "unable to get duplicate handle for %d\n", was);
+ sys_fatal("dup");
+ }
+
+ // Duplicate handle safely established so complete redirection.
+ set_redirection(was, willbe);
+
+ // Finally return the saved duplicate descriptor for the original
+ // 'was' descriptor.
+ return saved;
+}
+
+/*
+ * alterDeviceTo - If toImage is set
+ * the argument list is altered to include
+ * IMAGE_DEVICE; we invoke groff rather than troff.
+ * Else
+ * set -Thtml and groff.
+ */
+
+static void alterDeviceTo(int argc, char *argv[], int toImage)
+{
+ int i = 0;
+
+ if (toImage) {
+ while (i < argc) {
+ if ((strcmp(argv[i], "-Thtml") == 0) ||
+ (strcmp(argv[i], "-Txhtml") == 0))
+ argv[i] = (char *)IMAGE_DEVICE;
+ i++;
+ }
+ argv[troff_arg] = (char *)"groff"; /* rather than troff */
+ }
+ else {
+ while (i < argc) {
+ if (strcmp(argv[i], IMAGE_DEVICE) == 0) {
+ if (dialect == xhtml)
+ argv[i] = (char *)"-Txhtml";
+ else
+ argv[i] = (char *)"-Thtml";
+ }
+ i++;
+ }
+ argv[troff_arg] = (char *)"groff"; /* use groff -Z */
+ }
+}
+
+/*
+ * addArg - Append newarg onto the command list for groff.
+ */
+
+char **addArg(int argc, char *argv[], char *newarg)
+{
+ char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
+ int i = 0;
+
+ if (0 /* nullptr */ == new_argv)
+ sys_fatal("malloc");
+
+ if (argc > 0) {
+ new_argv[i] = argv[i];
+ i++;
+ }
+ new_argv[i] = newarg;
+ while (i < argc) {
+ new_argv[i + 1] = argv[i];
+ i++;
+ }
+ argc++;
+ new_argv[argc] = 0 /* nullptr */;
+ return new_argv;
+}
+
+/*
+ * addRegDef - Append a defined register or string onto the command
+ * list for troff.
+ */
+
+char **addRegDef(int argc, char *argv[], const char *numReg)
+{
+ char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
+ int i = 0;
+
+ if (0 /* nullptr */ == new_argv)
+ sys_fatal("malloc");
+
+ while (i < argc) {
+ new_argv[i] = argv[i];
+ i++;
+ }
+ new_argv[argc] = strsave(numReg);
+ argc++;
+ new_argv[argc] = 0 /* nullptr */;
+ return new_argv;
+}
+
+/*
+ * dump_args - Display the argument list.
+ */
+
+void dump_args(int argc, char *argv[])
+{
+ fprintf(stderr, " %d arguments:", argc);
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, " %s", argv[i]);
+ fprintf(stderr, "\n");
+}
+
+/*
+ * print_args - Print arguments as if issued on the command line.
+ */
+
+#if defined(DEBUGGING)
+
+void print_args(int argc, char *argv[])
+{
+ if (debugging) {
+ fprintf(stderr, "executing: ");
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, "%s ", argv[i]);
+ fprintf(stderr, "\n");
+ }
+}
+
+#else
+
+void print_args(int, char **)
+{
+}
+
+#endif
+
+int char_buffer::run_output_filter(int filter, int argc, char **argv)
+{
+ int pipedes[2];
+ PID_T child_pid;
+ int wstatus;
+
+ print_args(argc, argv);
+ if (pipe(pipedes) < 0)
+ sys_fatal("pipe");
+
+#if MAY_FORK_CHILD_PROCESS
+ // This is the Unix process model. To invoke our post-processor,
+ // we must 'fork' the current process.
+
+ if ((child_pid = fork()) < 0)
+ sys_fatal("fork");
+
+ else if (child_pid == 0) {
+ // This is the child process. We redirect its input file descriptor
+ // to read data emerging from our pipe. There is no point in
+ // saving, since we won't be able to restore later!
+
+ set_redirection(STDIN_FILENO, pipedes[0]);
+
+ // The parent process will be writing this data; release the child's
+ // writeable handle on the pipe since we have no use for it.
+
+ if (close(pipedes[1]) < 0)
+ sys_fatal("close");
+
+ // The IMAGE_OUTPUT_FILTER needs special output redirection...
+
+ if (filter == IMAGE_OUTPUT_FILTER) {
+ // ...with BOTH 'stdout' AND 'stderr' diverted to files, the
+ // latter so that `generateImages()` can scrape "grohtml-info"
+ // from it.
+
+ set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
+ set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM);
+ }
+
+ // Now we are ready to launch the output filter.
+
+ execvp(argv[0], argv); // does not return unless it fails
+ fatal("cannot execute '%1': %2", argv[0], strerror(errno));
+ }
+
+ else {
+ // This is the parent process. We write data to the filter pipeline
+ // where the child will read it. We have no need to read from the
+ // input side ourselves, so close it.
+
+ if (close(pipedes[0]) < 0)
+ sys_fatal("close");
+
+ // Now redirect the standard output file descriptor to the inlet end
+ // of the pipe, and push the formatted data to the filter.
+
+ pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]);
+ emit_troff_output(DEVICE_FORMAT(filter));
+
+ // After emitting all the data we close our connection to the inlet
+ // end of the pipe so the child process will detect end of data.
+
+ set_redirection(STDOUT_FILENO, pipedes[1]);
+
+ // Finally, we must wait for the child process to complete.
+
+ if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
+ sys_fatal("wait");
+ }
+
+#elif MAY_SPAWN_ASYNCHRONOUS_CHILD
+
+ // We do not have `fork` (or we prefer not to use it), but
+ // asynchronous processes are allowed, passing data through pipes.
+ // This should be okay for most Win32 systems and is preferred to
+ // `fork` for starting child processes under Cygwin.
+
+ // Before we start the post-processor we bind its inherited standard
+ // input file descriptor to the readable end of our pipe, saving our
+ // own standard input file descriptor in `pipedes[0]`.
+
+ pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]);
+
+ // For the Win32 model,
+ // we need special provision for saving BOTH 'stdout' and 'stderr'.
+
+ int saved_stdout = dup(STDOUT_FILENO);
+ int saved_stderr = STDERR_FILENO;
+
+ // The IMAGE_OUTPUT_FILTER needs special output redirection...
+
+ if (filter == IMAGE_OUTPUT_FILTER) {
+ // with BOTH 'stdout' AND 'stderr' diverted to files while saving a
+ // duplicate handle for 'stderr'.
+
+ set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
+ saved_stderr = save_and_redirect(STDERR_FILENO,
+ REGION_OUTPUT_STREAM);
+ }
+
+ // Use an asynchronous spawn request to start the post-processor.
+
+ if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
+ fatal("cannot spawn %1: %2", argv[0], strerror(errno));
+ }
+
+ // Once the post-processor has been started we revert our 'stdin'
+ // to its original saved source, which also closes the readable handle
+ // for the pipe.
+
+ set_redirection(STDIN_FILENO, pipedes[0]);
+
+ // if we redirected 'stderr', for use by the image post-processor,
+ // then we also need to reinstate its original assignment.
+
+ if (filter == IMAGE_OUTPUT_FILTER)
+ set_redirection(STDERR_FILENO, saved_stderr);
+
+ // Now we redirect the standard output to the inlet end of the pipe,
+ // and push out the appropriately formatted data to the filter.
+
+ set_redirection(STDOUT_FILENO, pipedes[1]);
+ emit_troff_output(DEVICE_FORMAT(filter));
+
+ // After emitting all the data we close our connection to the inlet
+ // end of the pipe so the child process will detect end of data.
+
+ set_redirection(STDOUT_FILENO, saved_stdout);
+
+ // And finally, we must wait for the child process to complete.
+
+ if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
+ sys_fatal("wait");
+
+#else /* can't do asynchronous pipes! */
+
+ // TODO: code to support an MS-DOS style process model should go here
+ fatal("output filtering not supported on this platform");
+
+#endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
+
+ return wstatus;
+}
+
+/*
+ * do_html - Set the troff number htmlflip and
+ * write out the buffer to troff -Thtml.
+ */
+
+int char_buffer::do_html(int argc, char *argv[])
+{
+ string s;
+
+ alterDeviceTo(argc, argv, 0);
+ argv += troff_arg; // skip all arguments up to groff
+ argc -= troff_arg;
+ argv = addArg(argc, argv, (char *)"-Z");
+ argc++;
+
+ s = (char *)"-dwww-image-template=";
+ s += macroset_template; // Do not combine these statements,
+ // otherwise they will not work.
+ s += '\0'; // The trailing '\0' is ignored.
+ argv = addRegDef(argc, argv, s.contents());
+ argc++;
+
+ if (dialect == xhtml) {
+ argv = addRegDef(argc, argv, "-rxhtml=1");
+ argc++;
+ if (need_eqn) {
+ argv = addRegDef(argc, argv, "-e");
+ argc++;
+ }
+ }
+
+#if defined(DEBUGGING)
+# define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
+ // slight security risk: only enabled if defined(DEBUGGING)
+ if (debugging) {
+ int saved_stdout = save_and_redirect(STDOUT_FILENO,
+ HTML_DEBUG_STREAM);
+ emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER));
+ set_redirection(STDOUT_FILENO, saved_stdout);
+ }
+#endif
+
+ return run_output_filter(HTML_OUTPUT_FILTER, argc, argv);
+}
+
+/*
+ * do_image - Write out the buffer to troff -Tps.
+ */
+
+int char_buffer::do_image(int argc, char *argv[])
+{
+ string s;
+
+ alterDeviceTo(argc, argv, 1);
+ argv += troff_arg; // skip all arguments up to troff/groff
+ argc -= troff_arg;
+ argv = addRegDef(argc, argv, "-rps4html=1");
+ argc++;
+
+ s = "-dwww-image-template=";
+ s += macroset_template;
+ s += '\0';
+ argv = addRegDef(argc, argv, s.contents());
+ argc++;
+
+ // Override local settings and produce a letter-size PostScript page
+ // file.
+ argv = addRegDef(argc, argv, "-P-pletter");
+ argc++;
+
+ if (dialect == xhtml) {
+ if (need_eqn) {
+ argv = addRegDef(argc, argv, "-rxhtml=1");
+ argc++;
+ }
+ argv = addRegDef(argc, argv, "-e");
+ argc++;
+ }
+
+#if defined(DEBUGGING)
+# define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
+ // slight security risk: only enabled if defined(DEBUGGING)
+ if (debugging) {
+ int saved_stdout = save_and_redirect(STDOUT_FILENO,
+ IMAGE_DEBUG_STREAM);
+ emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER));
+ set_redirection(STDOUT_FILENO, saved_stdout);
+ }
+#endif
+
+ return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
+}
+
+static char_buffer inputFile;
+
+/*
+ * usage - Emit usage message.
+ */
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-epV] [-a anti-aliasing-text-bits] [-D image-directory]"
+" [-F font-directory] [-g anti-aliasing-graphics-bits] [-i resolution]"
+" [-I image-stem] [-o image-vertical-offset] [-x html-dialect]"
+" troff-command troff-argument ...\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Prepare a troff(1) document for HTML formatting.\n"
+"\n"
+"This program is not intended to be executed standalone; it is\n"
+"normally part of a groff pipeline. If your need to run it manually\n"
+"(e.g., for debugging purposes), give the 'groff' program the\n"
+"command-line option '-V' to inspect the arguments with which\n",
+ stream);
+ fprintf(stream,
+"'%s' is called. See the grohtml(1) manual page.\n",
+ program_name);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+/*
+ * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
+ * and -P-I. Return the argument index of the first
+ * non-option.
+ */
+
+static int scanArguments(int argc, char **argv)
+{
+ const char *cmdprefix = getenv("GROFF_COMMAND_PREFIX");
+ if (!cmdprefix)
+ cmdprefix = PROG_PREFIX;
+ size_t pfxlen = strlen(cmdprefix);
+ char *troff_name = new char[pfxlen + strlen("troff") + 1];
+ char *s = strcpy(troff_name, cmdprefix);
+ s += pfxlen;
+ strcpy(s, "troff");
+ int c, i;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { 0 /* nullptr */, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv,
+ "+a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options,
+ 0 /* nullptr */))
+ != EOF)
+ switch(c) {
+ case 'a':
+ textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
+ MAX_ALPHA_BITS);
+ if (textAlphaBits == 3)
+ fatal("cannot use 3 bits of antialiasing information");
+ break;
+ case 'b':
+ // handled by post-grohtml (set background color to white)
+ break;
+ case 'C':
+ // handled by post-grohtml (don't write Creator HTML comment)
+ break;
+ case 'd':
+#if defined(DEBUGGING)
+ debugging = true;
+#endif
+ break;
+ case 'D':
+ image_dir = optarg;
+ break;
+ case 'e':
+ need_eqn = true;
+ break;
+ case 'F':
+ font_path.command_line_dir(optarg);
+ break;
+ case 'g':
+ graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
+ MAX_ALPHA_BITS);
+ if (graphicAlphaBits == 3)
+ fatal("cannot use 3 bits of antialiasing information");
+ break;
+ case 'G':
+ // handled by post-grohtml (don't write CreationDate HTML comment)
+ break;
+ case 'h':
+ // handled by post-grohtml (write headings with font size changes)
+ break;
+ case 'i':
+ image_res = atoi(optarg);
+ break;
+ case 'I':
+ image_template = optarg;
+ break;
+ case 'j':
+ // handled by post-grohtml (set job name for multiple file output)
+ break;
+ case 'l':
+ // handled by post-grohtml (no automatic section links)
+ break;
+ case 'n':
+ // handled by post-grohtml (generate simple heading anchors)
+ break;
+ case 'o':
+ vertical_offset = atoi(optarg);
+ break;
+ case 'p':
+ want_progress_report = true;
+ break;
+ case 'r':
+ // handled by post-grohtml (no header and footer lines)
+ break;
+ case 's':
+ // handled by post-grohtml (use font size n as the HTML base size)
+ break;
+ case 'S':
+ // handled by post-grohtml (set file split level)
+ break;
+ case 'v':
+ printf("GNU pre-grohtml (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ case 'V':
+ // handled by post-grohtml (create validator button)
+ break;
+ case 'x':
+ // html dialect
+ if (strcmp(optarg, "x") == 0)
+ dialect = xhtml;
+ else if (strcmp(optarg, "4") == 0)
+ dialect = html4;
+ else
+ warning("unsupported HTML dialect: '%1'", optarg);
+ break;
+ case 'y':
+ // handled by post-grohtml (create groff signature)
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ break;
+ }
+
+ i = optind;
+ while (i < argc) {
+ if (strcmp(argv[i], troff_name) == 0)
+ troff_arg = i;
+ else if (argv[i][0] != '-')
+ return i;
+ i++;
+ }
+ delete[] troff_name;
+
+ return argc;
+}
+
+/*
+ * makeTempFiles - Name the temporary files.
+ */
+
+static void makeTempFiles(void)
+{
+#if defined(DEBUGGING)
+ psFileName = DEBUG_FILE("prehtml-ps");
+ regionFileName = DEBUG_FILE("prehtml-region");
+ imagePageName = DEBUG_FILE("prehtml-page");
+ psPageName = DEBUG_FILE("prehtml-psn");
+ troffFileName = DEBUG_FILE("prehtml-troff");
+ htmlFileName = DEBUG_FILE("prehtml-html");
+#else /* not DEBUGGING */
+ FILE *f;
+
+ // psPageName contains a single page of PostScript.
+ f = xtmpfile(&psPageName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // imagePageName contains a bitmap image of a single PostScript page.
+ f = xtmpfile(&imagePageName, PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT,
+ true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // psFileName contains a PostScript file of the complete document.
+ f = xtmpfile(&psFileName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // regionFileName contains a list of the images and their boxed
+ // coordinates.
+ f = xtmpfile(&regionFileName,
+ REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+#endif /* not DEBUGGING */
+}
+
+static bool do_file(const char *filename)
+{
+ FILE *fp;
+
+ current_filename = filename;
+ if (strcmp(filename, "-") == 0)
+ fp = stdin;
+ else {
+ fp = fopen(filename, "r");
+ if (0 /* nullptr*/ == fp) {
+ error("unable to open '%1': %2", filename, strerror(errno));
+ return false;
+ }
+ }
+ inputFile.read_file(fp);
+ if (fp != stdin)
+ if (fclose(fp) != 0)
+ sys_fatal("fclose");
+ current_filename = 0 /* nullptr */;
+ return true;
+}
+
+static void cleanup(void)
+{
+ free(const_cast<char *>(image_gen));
+}
+
+int main(int argc, char **argv)
+{
+#ifdef CAPTURE_MODE
+ fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc);
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, "%2d: %s\n", i, argv[i]);
+ FILE *dump = fopen(DEBUG_FILE("pre-html-data"), "wb");
+ if (dump != 0 /* nullptr */) {
+ while((int ch = fgetc(stdin)) >= 0)
+ fputc(ch, dump);
+ fclose(dump);
+ }
+ exit(EXIT_FAILURE);
+#endif /* CAPTURE_MODE */
+ program_name = argv[0];
+ if (atexit(&cleanup) != 0)
+ sys_fatal("atexit");
+ int operand_index = scanArguments(argc, argv);
+ image_gen = strsave(get_image_generator());
+ if (0 == image_gen)
+ fatal("'image_generator' directive not found in file '%1'",
+ devhtml_desc);
+ postscriptRes = get_resolution();
+ if (postscriptRes < 1) // TODO: what's a more sane minimum value?
+ fatal("'res' directive missing or invalid in file '%1'",
+ devps_desc);
+ setupAntiAlias();
+ checkImageDir();
+ makeFileName();
+ bool have_file_operand = false;
+ while (operand_index < argc) {
+ if (argv[operand_index][0] != '-') {
+ if(!do_file(argv[operand_index]))
+ exit(EXIT_FAILURE);
+ have_file_operand = true;
+ }
+ operand_index++;
+ }
+
+ if (!have_file_operand)
+ do_file("-");
+ makeTempFiles();
+ int wstatus = inputFile.do_image(argc, argv);
+ if (wstatus == 0) {
+ generateImages(regionFileName);
+ wstatus = inputFile.do_html(argc, argv);
+ }
+ else
+ if (WEXITSTATUS(wstatus) != 0)
+ // XXX: This is a crappy suggestion. See Savannah #62673.
+ fatal("'%1' exited with status %2; re-run with a different output"
+ " driver to see diagnostic messages", argv[0],
+ WEXITSTATUS(wstatus));
+ exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/html/pre-html.h b/src/preproc/html/pre-html.h
new file mode 100644
index 0000000..f257854
--- /dev/null
+++ b/src/preproc/html/pre-html.h
@@ -0,0 +1,38 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ * Written by Gaius Mulley (gaius@glam.ac.uk).
+ *
+ * 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 groff; see the file COPYING. If not, write to the Free Software
+ * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * defines functions implemented within pre-html.cpp
+ */
+
+#if !defined(PREHTMLH)
+# define PREHTMLH
+# if defined(PREHTMLC)
+# define EXTERN
+# else
+# define EXTERN extern
+# endif
+
+
+extern void sys_fatal (const char *s);
+
+#undef EXTERN
+#endif
diff --git a/src/preproc/html/pushback.cpp b/src/preproc/html/pushback.cpp
new file mode 100644
index 0000000..100ac0b
--- /dev/null
+++ b/src/preproc/html/pushback.cpp
@@ -0,0 +1,336 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk).
+
+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 "lib.h"
+
+#include <signal.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "posix.h"
+#include "nonposix.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "pushback.h"
+#include "pre-html.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+# define ERROR(X) (void)(fprintf(stderr, "%s:%d error %s\n", __FILE__, __LINE__, X) && \
+ (fflush(stderr)) && localexit(1))
+
+
+#define MAXPUSHBACKSTACK 4096 /* maximum number of character that can be pushed back */
+
+
+/*
+ * constructor for pushBackBuffer
+ */
+
+pushBackBuffer::pushBackBuffer (char *filename)
+{
+ charStack = (char *)malloc(MAXPUSHBACKSTACK);
+ if (charStack == 0) {
+ sys_fatal("malloc");
+ }
+ stackPtr = 0; /* index to push back stack */
+ verbose = 0;
+ eofFound = FALSE;
+ lineNo = 1;
+ if (strcmp(filename, "") != 0) {
+ stdIn = dup(0);
+ if (stdIn < 0) {
+ sys_fatal("dup stdin");
+ }
+ close(0);
+ if (open(filename, O_RDONLY) != 0) {
+ sys_fatal("when trying to open file");
+ } else {
+ fileName = filename;
+ }
+ }
+}
+
+pushBackBuffer::~pushBackBuffer ()
+{
+ if (charStack != 0) {
+ free(charStack);
+ }
+ close(0);
+ /* restore stdin in file descriptor 0 */
+ if (dup(stdIn) < 0) {
+ sys_fatal("restore stdin");
+ }
+ close(stdIn);
+}
+
+/*
+ * localexit - wraps exit with a return code to aid the ERROR macro.
+ */
+
+int localexit (int i)
+{
+ exit(i);
+ return( 1 );
+}
+
+/*
+ * getPB - returns a character, possibly a pushed back character.
+ */
+
+char pushBackBuffer::getPB (void)
+{
+ if (stackPtr>0) {
+ stackPtr--;
+ return( charStack[stackPtr] );
+ } else {
+ char ch;
+
+ if (read(0, &ch, 1) == 1) {
+ if (verbose) {
+ printf("%c", ch);
+ }
+ if (ch == '\n') {
+ lineNo++;
+ }
+ return( ch );
+ } else {
+ eofFound = TRUE;
+ return( eof );
+ }
+ }
+}
+
+/*
+ * putPB - pushes a character onto the push back stack.
+ * The same character is returned.
+ */
+
+char pushBackBuffer::putPB (char ch)
+{
+ if (stackPtr<MAXPUSHBACKSTACK) {
+ charStack[stackPtr] = ch ;
+ stackPtr++;
+ } else {
+ ERROR("max push back stack exceeded, increase MAXPUSHBACKSTACK constant");
+ }
+ return( ch );
+}
+
+/*
+ * isWhite - returns TRUE if a white character is found. This character is NOT consumed.
+ */
+
+static int isWhite (char ch)
+{
+ return( (ch==' ') || (ch == '\t') || (ch == '\n') );
+}
+
+/*
+ * skipToNewline - skips characters until a newline is seen.
+ */
+
+void pushBackBuffer::skipToNewline (void)
+{
+ while ((putPB(getPB()) != '\n') && (! eofFound)) {
+ getPB();
+ }
+}
+
+/*
+ * skipUntilToken - skips until a token is seen
+ */
+
+void pushBackBuffer::skipUntilToken (void)
+{
+ char ch;
+
+ while ((isWhite(putPB(getPB())) || (putPB(getPB()) == '#')) && (! eofFound)) {
+ ch = getPB();
+ if (ch == '#') {
+ skipToNewline();
+ }
+ }
+}
+
+/*
+ * isString - returns TRUE if the string, s, matches the pushed back string.
+ * if TRUE is returned then this string is consumed, otherwise it is
+ * left alone.
+ */
+
+int pushBackBuffer::isString (const char *s)
+{
+ int length=strlen(s);
+ int i=0;
+
+ while ((i<length) && (putPB(getPB())==s[i])) {
+ if (getPB() != s[i]) {
+ ERROR("assert failed");
+ }
+ i++;
+ }
+ if (i==length) {
+ return( TRUE );
+ } else {
+ i--;
+ while (i>=0) {
+ if (putPB(s[i]) != s[i]) {
+ ERROR("assert failed");
+ }
+ i--;
+ }
+ }
+ return( FALSE );
+}
+
+/*
+ * isDigit - returns TRUE if the character, ch, is a digit.
+ */
+
+static int isDigit (char ch)
+{
+ return( ((ch>='0') && (ch<='9')) );
+}
+
+/*
+ * isHexDigit - returns TRUE if the character, ch, is a hex digit.
+ */
+
+#if 0
+static int isHexDigit (char ch)
+{
+ return( (isDigit(ch)) || ((ch>='a') && (ch<='f')) );
+}
+#endif
+
+/*
+ * readInt - returns an integer from the input stream.
+ */
+
+int pushBackBuffer::readInt (void)
+{
+ int c =0;
+ int i =0;
+ int s =1;
+ char ch=getPB();
+
+ while (isWhite(ch)) {
+ ch=getPB();
+ }
+ // now read integer
+
+ if (ch == '-') {
+ s = -1;
+ ch = getPB();
+ }
+ while (isDigit(ch)) {
+ i *= 10;
+ if ((ch>='0') && (ch<='9')) {
+ i += (int)(ch-'0');
+ }
+ ch = getPB();
+ c++;
+ }
+ if (ch != putPB(ch)) {
+ ERROR("assert failed");
+ }
+ return( i*s );
+}
+
+/*
+ * convertToFloat - converts integers, a and b into a.b
+ */
+
+static double convertToFloat (int a, int b)
+{
+ int c=10;
+ double f;
+
+ while (b>c) {
+ c *= 10;
+ }
+ f = ((double)a) + (((double)b)/((double)c));
+ return( f );
+}
+
+/*
+ * readNumber - returns a float representing the word just read.
+ */
+
+double pushBackBuffer::readNumber (void)
+{
+ int i;
+ char ch;
+
+ i = readInt();
+ if ((ch = getPB()) == '.') {
+ return convertToFloat(i, readInt());
+ }
+ putPB(ch);
+ return (double)i;
+}
+
+/*
+ * readString - reads a string terminated by white space
+ * and returns a malloced area of memory containing
+ * a copy of the characters.
+ */
+
+char *pushBackBuffer::readString (void)
+{
+ char buffer[MAXPUSHBACKSTACK];
+ char *str = 0;
+ int i=0;
+ char ch=getPB();
+
+ while (isWhite(ch)) {
+ ch=getPB();
+ }
+ while ((i < MAXPUSHBACKSTACK) && (! isWhite(ch)) && (! eofFound)) {
+ buffer[i] = ch;
+ i++;
+ ch = getPB();
+ }
+ if (i < MAXPUSHBACKSTACK) {
+ buffer[i] = (char)0;
+ str = (char *)malloc(strlen(buffer)+1);
+ strcpy(str, buffer);
+ }
+ return( str );
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/html/pushback.h b/src/preproc/html/pushback.h
new file mode 100644
index 0000000..263a6c5
--- /dev/null
+++ b/src/preproc/html/pushback.h
@@ -0,0 +1,52 @@
+// -*- C -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk).
+
+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/>. */
+
+
+#define eof (char)-1
+
+
+/*
+ * defines the class and methods implemented within pushback.cpp
+ */
+
+class pushBackBuffer
+{
+ private:
+ char *charStack;
+ int stackPtr; /* index to push back stack */
+ int verbose;
+ int eofFound;
+ char *fileName;
+ int lineNo;
+ int stdIn;
+
+ public:
+ pushBackBuffer (char *);
+ ~ pushBackBuffer ();
+ char getPB (void);
+ char putPB (char ch);
+ void skipUntilToken (void);
+ void skipToNewline (void);
+ double readNumber (void);
+ int readInt (void);
+ char *readString (void);
+ int isString (const char *string);
+};
+
+