2 /* Copyright (C) 2000-2018 Free Software Foundation, Inc.
3 * Written by Gaius Mulley (gaius@glam.ac.uk).
5 * This file is part of groff.
7 * groff is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free
9 * Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 * You should have received a copy of the GNU General Public License along
18 * with groff; see the file COPYING. If not, write to the Free Software
19 * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
33 #include "stringclass.h"
36 #include "searchpath.h"
42 #include <sys/types.h>
48 # include <sys/wait.h>
50 #else /* not _POSIX_VERSION */
52 #endif /* not _POSIX_VERSION */
62 /* Establish some definitions to facilitate discrimination between
63 differing runtime environments. */
65 #undef MAY_FORK_CHILD_PROCESS
66 #undef MAY_SPAWN_ASYNCHRONOUS_CHILD
68 #if defined(__MSDOS__) || defined(_WIN32)
70 // Most MS-DOS and Win32 environments will be missing the 'fork' capability
71 // (some like Cygwin have it, but it is best avoided).
73 # define MAY_FORK_CHILD_PROCESS 0
75 // On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'.
76 # include <process.h> // for 'spawn...'
77 # include <fcntl.h> // for attributes of pipes
79 # if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
81 // These Win32 implementations allow parent and 'spawn...'ed child to
82 // multitask asynchronously.
84 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
88 // Others may adopt MS-DOS behaviour where parent must sleep,
89 // from 'spawn...' until child terminates.
91 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
93 # endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
95 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
96 /* When we are building a DEBUGGING version we need to tell pre-grohtml
97 where to put intermediate files (the DEBUGGING version will preserve
100 On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will
101 probably not have this on all disk drives, so default to using
102 'c:/temp' instead. (Note that user may choose to override this by
103 supplying a definition such as
105 -DDEBUG_FILE_DIR=d:/path/to/debug/files
107 in the CPPFLAGS to 'make'.) */
109 # define DEBUG_FILE_DIR c:/temp
112 #else /* not __MSDOS__ or _WIN32 */
114 // For non-Microsoft environments assume Unix conventions,
115 // so 'fork' is required and child processes are asynchronous.
116 # define MAY_FORK_CHILD_PROCESS 1
117 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
119 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
120 /* For a DEBUGGING version, on the Unix host, we can also usually rely
121 on being able to use '/tmp' for temporary file storage. (Note that,
122 as in the __MSDOS__ or _WIN32 case above, the user may override this
125 -DDEBUG_FILE_DIR=/path/to/debug/files
129 # define DEBUG_FILE_DIR /tmp
132 #endif /* not __MSDOS__ or _WIN32 */
135 // For a DEBUGGING version, we need some additional macros,
136 // to direct the captured debug mode output to appropriately named files
137 // in the specified DEBUG_FILE_DIR.
139 # define DEBUG_TEXT(text) #text
140 # define DEBUG_NAME(text) DEBUG_TEXT(text)
141 # define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
144 extern "C" const char *Version_string;
146 #include "pre-html.h"
147 #include "pushback.h"
148 #include "html-strings.h"
150 #define DEFAULT_LINE_LENGTH 7 // inches wide
151 #define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
152 #define IMAGE_BOARDER_PIXELS 0
153 #define INLINE_LEADER_CHAR '\\'
155 // Don't use colour names here! Otherwise there is a dependency on
156 // a file called 'rgb.txt' which maps names to colours.
157 #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
158 #define MIN_ALPHA_BITS 0
159 #define MAX_ALPHA_BITS 4
161 #define PAGE_TEMPLATE_SHORT "pg"
162 #define PAGE_TEMPLATE_LONG "-page-"
163 #define PS_TEMPLATE_SHORT "ps"
164 #define PS_TEMPLATE_LONG "-ps-"
165 #define REGION_TEMPLATE_SHORT "rg"
166 #define REGION_TEMPLATE_LONG "-regions-"
172 # define FALSE (1==0)
176 CENTERED, LEFT, RIGHT, INLINE
179 typedef enum {xhtml, html4} html_dialect;
181 static int postscriptRes = -1; // postscript resolution,
183 static int stdoutfd = 1; // output file descriptor -
184 // normally 1 but might move
186 static char *psFileName = NULL; // name of postscript file
187 static char *psPageName = NULL; // name of file containing
188 // postscript current page
189 static char *regionFileName = NULL; // name of file containing all
191 static char *imagePageName = NULL; // name of bitmap image containing
193 static const char *image_device = "pnmraw";
194 static int image_res = DEFAULT_IMAGE_RES;
195 static int vertical_offset = 0;
196 static char *image_template = NULL; // image template filename
197 static char *macroset_template= NULL; // image template passed to troff
199 static int troff_arg = 0; // troff arg index
200 static char *image_dir = NULL; // user specified image directory
201 static int textAlphaBits = MAX_ALPHA_BITS;
202 static int graphicAlphaBits = MAX_ALPHA_BITS;
203 static char *antiAlias = NULL; // antialias arguments we pass to gs
204 static int show_progress = FALSE; // should we display page numbers as
205 // they are processed?
206 static int currentPageNo = -1; // current image page number
207 #if defined(DEBUGGING)
208 static int debug = FALSE;
209 static char *troffFileName = NULL; // output of pre-html output which
210 // is sent to troff -Tps
211 static char *htmlFileName = NULL; // output of pre-html output which
212 // is sent to troff -Thtml
214 static int eqn_flag = FALSE; // must we preprocess via eqn?
216 static char *linebuf = NULL; // for scanning devps/DESC
217 static int linebufsize = 0;
218 static const char *image_gen = NULL; // the 'gs' program
220 const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
221 static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
222 static html_dialect dialect = html4;
226 * Images are generated via postscript, gs, and the pnm utilities.
228 #define IMAGE_DEVICE "-Tps"
231 static int do_file(const char *filename);
235 * sys_fatal - Write a fatal error message.
236 * Taken from src/roff/groff/pipeline.c.
239 void sys_fatal(const char *s)
241 fatal("%1: %2", s, strerror(errno));
245 * get_line - Copy a line (w/o newline) from a file to the
246 * global line buffer.
249 int get_line(FILE *f)
254 linebuf = new char[128];
258 // skip leading whitespace
263 if (c != ' ' && c != '\t') {
272 if (i + 1 >= linebufsize) {
273 char *old_linebuf = linebuf;
274 linebuf = new char[linebufsize * 2];
275 memcpy(linebuf, old_linebuf, linebufsize);
276 a_delete old_linebuf;
290 * get_resolution - Return the postscript resolution from devps/DESC.
293 static unsigned int get_resolution(void)
298 f = font_path.open_file("devps/DESC", &pathp);
301 fatal("can't open devps/DESC");
302 while (get_line(f)) {
303 int n = sscanf(linebuf, "res %u", &res);
309 fatal("can't find 'res' keyword in devps/DESC");
314 * html_system - A wrapper for system().
317 void html_system(const char *s, int redirect_stdout)
319 #if defined(DEBUGGING)
321 fprintf(stderr, "executing: ");
322 fwrite(s, sizeof(char), strlen(s), stderr);
327 // Redirect standard error to the null device. This is more
328 // portable than using "2> /dev/null", since it doesn't require a
330 int save_stderr = dup(2);
331 int save_stdout = dup(1);
332 int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
333 if (save_stderr > 2 && fdnull > 2)
335 if (redirect_stdout && save_stdout > 1 && fdnull > 1)
339 int status = system(s);
340 dup2(save_stderr, 2);
342 dup2(save_stdout, 1);
344 fprintf(stderr, "Calling '%s' failed\n", s);
346 fprintf(stderr, "Calling '%s' returned status %d\n", s, status);
353 * make_message - Create a string via malloc and place the result of the
354 * va args into string. Finally the new string is returned.
355 * Taken from man page of printf(3).
358 char *make_message(const char *fmt, ...)
360 /* Guess we need no more than 100 bytes. */
365 if ((p = (char *)malloc(size)) == NULL)
368 /* Try to print in the allocated space. */
370 n = vsnprintf(p, size, fmt, ap);
372 /* If that worked, return the string. */
373 if (n > -1 && n < size - 1) { /* glibc 2.1 and pre-ANSI C 99 */
381 /* Else try again with more space. */
383 size *= 2; /* twice the old size */
384 if ((np = (char *)realloc(p, size)) == NULL) {
385 free(p); /* realloc failed, free old, p. */
388 p = np; /* use realloc'ed, p */
393 * the class and methods for retaining ascii text
406 * char_block - Constructor. Set the, used, and, next, fields to zero.
409 char_block::char_block()
412 for (int i = 0; i < SIZE; i++)
420 int read_file(FILE *fp);
421 int do_html(int argc, char *argv[]);
422 int do_image(int argc, char *argv[]);
423 void emit_troff_output(int device_format_selector);
424 void write_upto_newline(char_block **t, int *i, int is_html);
425 int can_see(char_block **t, int *i, const char *string);
426 int skip_spaces(char_block **t, int *i);
427 void skip_until_newline(char_block **t, int *i);
431 int run_output_filter(int device_format_selector, int argc, char *argv[]);
435 * char_buffer - Constructor.
438 char_buffer::char_buffer()
444 * char_buffer - Destructor. Throw away the whole buffer list.
447 char_buffer::~char_buffer()
449 while (head != NULL) {
450 char_block *temp = head;
457 * read_file - Read in a complete file, fp, placing the contents inside
461 int char_buffer::read_file(FILE *fp)
466 tail = new char_block;
470 if (tail->used == char_block::SIZE) {
471 tail->next = new char_block;
475 // at this point we have a tail which is ready for the next SIZE
477 n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used, fp);
482 tail->used += n * sizeof(char);
488 * writeNbytes - Write n bytes to stdout.
491 static void writeNbytes(const char *s, int l)
497 r = write(stdoutfd, s, l - n);
506 * writeString - Write a string to stdout.
509 static void writeString(const char *s)
511 writeNbytes(s, strlen(s));
515 * makeFileName - Create the image filename template
516 * and the macroset image template.
519 static void makeFileName(void)
521 if ((image_dir != NULL) && (strchr(image_dir, '%') != NULL)) {
522 error("cannot use a '%%' within the image directory name");
526 if ((image_template != NULL) && (strchr(image_template, '%') != NULL)) {
527 error("cannot use a '%%' within the image template");
531 if (image_dir == NULL)
532 image_dir = (char *)"";
533 else if (strlen(image_dir) > 0
534 && image_dir[strlen(image_dir) - 1] != '/') {
535 image_dir = make_message("%s/", image_dir);
536 if (image_dir == NULL)
537 sys_fatal("make_message");
540 if (image_template == NULL)
541 macroset_template = make_message("%sgrohtml-%d", image_dir,
544 macroset_template = make_message("%s%s", image_dir, image_template);
546 if (macroset_template == NULL)
547 sys_fatal("make_message");
550 (char *)malloc(strlen("-%d") + strlen(macroset_template) + 1);
551 if (image_template == NULL)
553 strcpy(image_template, macroset_template);
554 strcat(image_template, "-%d");
558 * setupAntiAlias - Set up the antialias string, used when we call gs.
561 static void setupAntiAlias(void)
563 if (textAlphaBits == 0 && graphicAlphaBits == 0)
564 antiAlias = make_message(" ");
565 else if (textAlphaBits == 0)
566 antiAlias = make_message("-dGraphicsAlphaBits=%d ", graphicAlphaBits);
567 else if (graphicAlphaBits == 0)
568 antiAlias = make_message("-dTextAlphaBits=%d ", textAlphaBits);
570 antiAlias = make_message("-dTextAlphaBits=%d -dGraphicsAlphaBits=%d ",
571 textAlphaBits, graphicAlphaBits);
575 * checkImageDir - Check whether the image directory is available.
578 static void checkImageDir(void)
580 if (image_dir != NULL && strcmp(image_dir, "") != 0)
581 if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST)) {
582 error("cannot create directory '%1'", image_dir);
588 * write_end_image - End the image. Write out the image extents if we
592 static void write_end_image(int is_html)
595 * if we are producing html then these
596 * emit image name and enable output
598 * we are producing images
599 * in which case these generate image
602 writeString("\\O[4]\\O[2]");
604 writeString("\\O[1]");
606 writeString("\\O[0]");
610 * write_start_image - Write troff code which will:
612 * (i) disable html output for the following image
613 * (ii) reset the max/min x/y registers during postscript
617 static void write_start_image(IMAGE_ALIGNMENT pos, int is_html)
619 writeString("\\O[5");
635 writeString(image_template);
636 writeString(".png]");
638 writeString("\\O[0]\\O[3]");
640 // reset min/max registers
641 writeString("\\O[1]\\O[3]");
645 * write_upto_newline - Write the contents of the buffer until a newline
646 * is seen. Check for HTML_IMAGE_INLINE_BEGIN and
647 * HTML_IMAGE_INLINE_END; process them if they are
651 void char_buffer::write_upto_newline(char_block **t, int *i, int is_html)
656 while (j < (*t)->used
657 && (*t)->buffer[j] != '\n'
658 && (*t)->buffer[j] != INLINE_LEADER_CHAR)
661 && (*t)->buffer[j] == '\n')
663 writeNbytes((*t)->buffer + (*i), j - (*i));
664 if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) {
665 if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN))
666 write_start_image(INLINE, is_html);
667 else if (can_see(t, &j, HTML_IMAGE_INLINE_END))
668 write_end_image(is_html);
670 if (j < (*t)->used) {
673 writeNbytes((*t)->buffer + (*i), j - (*i));
677 if (j == (*t)->used) {
680 if (*t && (*t)->buffer[j - 1] != '\n')
681 write_upto_newline(t, i, is_html);
690 * can_see - Return TRUE if we can see string in t->buffer[i] onwards.
693 int char_buffer::can_see(char_block **t, int *i, const char *str)
701 while (k < s->used && j < l && s->buffer[k] == str[j]) {
710 else if (k < s->used && s->buffer[k] != str[j])
719 * skip_spaces - Return TRUE if we have not run out of data.
720 * Consume spaces also.
723 int char_buffer::skip_spaces(char_block **t, int *i)
729 while (k < s->used && isspace(s->buffer[k]))
744 * skip_until_newline - Skip all characters until a newline is seen.
745 * The newline is not consumed.
748 void char_buffer::skip_until_newline(char_block **t, int *i)
753 while (j < (*t)->used && (*t)->buffer[j] != '\n')
755 if (j == (*t)->used) {
758 skip_until_newline(t, i);
766 #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
767 #define HTML_OUTPUT_FILTER 0
768 #define IMAGE_OUTPUT_FILTER 1
769 #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
770 #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
771 #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
774 * emit_troff_output - Write formatted buffer content to the troff
775 * post-processor data pipeline.
778 void char_buffer::emit_troff_output(int device_format_selector)
780 // Handle output for BOTH html and image device formats
781 // if 'device_format_selector' is passed as
783 // HTML_FORMAT(HTML_OUTPUT_FILTER)
784 // Buffer data is written to the output stream
785 // with template image names translated to actual image names.
787 // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
788 // Buffer data is written to the output stream
789 // with no translation, for image file creation in the post-processor.
792 char_block *element = head;
794 while (element != NULL)
795 write_upto_newline(&element, &idx, device_format_selector);
798 if (close(stdoutfd) < 0)
801 // now we grab fd=1 so that the next pipe cannot use fd=1
803 if (dup(2) != stdoutfd)
804 sys_fatal ("dup failed to use fd=1");
810 * The image class remembers the position of all images in the
811 * postscript file and assigns names for each image.
825 imageItem(int x1, int y1, int x2, int y2,
826 int page, int res, int max_width, char *name);
831 * imageItem - Constructor.
834 imageItem::imageItem(int x1, int y1, int x2, int y2,
835 int page, int res, int max_width, char *name)
849 * imageItem - Destructor.
852 imageItem::~imageItem()
859 * imageList - A class containing a list of imageItems.
870 void add(int x1, int y1, int x2, int y2,
871 int page, int res, int maxx, char *name);
872 void createImages(void);
873 int createPage(int pageno);
874 void createImage(imageItem *i);
875 int getMaxX(int pageno);
879 * imageList - Constructor.
882 imageList::imageList()
883 : head(0), tail(0), count(0)
888 * imageList - Destructor.
891 imageList::~imageList()
893 while (head != NULL) {
901 * createPage - Create one image of, page pageno, from the postscript file.
904 int imageList::createPage(int pageno)
908 if (currentPageNo == pageno)
911 if (currentPageNo >= 1) {
913 * We need to unlink the files which change each time a new page is
914 * processed. The final unlink is done by xtmpfile when pre-grohtml
917 unlink(imagePageName);
922 fprintf(stderr, "[%d] ", pageno);
926 #if defined(DEBUGGING)
928 fprintf(stderr, "creating page %d\n", pageno);
931 s = make_message("psselect -q -p%d %s %s\n",
932 pageno, psFileName, psPageName);
935 sys_fatal("make_message");
938 s = make_message("echo showpage | "
939 "%s%s -q -dBATCH -dSAFER "
940 "-dDEVICEHEIGHTPOINTS=792 "
941 "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
942 "-sDEVICE=%s -r%d %s "
943 "-sOutputFile=%s %s -\n",
946 (getMaxX(pageno) * image_res) / postscriptRes,
953 sys_fatal("make_message");
956 currentPageNo = pageno;
961 * min - Return the minimum of two numbers.
964 int min(int x, int y)
973 * max - Return the maximum of two numbers.
976 int max(int x, int y)
985 * getMaxX - Return the largest right-hand position for any image
989 int imageList::getMaxX(int pageno)
992 int x = postscriptRes * DEFAULT_LINE_LENGTH;
995 if (h->pageNo == pageno)
1003 * createImage - Generate a minimal png file from the set of page images.
1006 void imageList::createImage(imageItem *i)
1010 int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes
1011 - IMAGE_BOARDER_PIXELS,
1013 int y1 = max(image_res * vertical_offset / 72
1014 + min(i->Y1, i->Y2) * image_res / postscriptRes
1015 - IMAGE_BOARDER_PIXELS,
1017 int x2 = max(i->X1, i->X2) * image_res / postscriptRes
1018 + IMAGE_BOARDER_PIXELS;
1019 int y2 = image_res * vertical_offset / 72
1020 + max(i->Y1, i->Y2) * image_res / postscriptRes
1021 + 1 + IMAGE_BOARDER_PIXELS;
1022 if (createPage(i->pageNo) == 0) {
1023 s = make_message("pnmcut%s %d %d %d %d < %s "
1024 "| pnmcrop -quiet | pnmtopng%s %s > %s\n",
1026 x1, y1, x2 - x1 + 1, y2 - y1 + 1,
1032 sys_fatal("make_message");
1038 fprintf(stderr, "failed to generate image of page %d\n", i->pageNo);
1041 #if defined(DEBUGGING)
1045 fprintf(stderr, "ignoring image as x1 coord is -1\n");
1053 * add - Add an image description to the imageList.
1056 void imageList::add(int x1, int y1, int x2, int y2,
1057 int page, int res, int maxx, char *name)
1059 imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name);
1072 * createImages - For each image descriptor on the imageList,
1073 * create the actual image.
1076 void imageList::createImages(void)
1078 imageItem *h = head;
1086 static imageList listOfImages; // List of images defined by the region file.
1089 * generateImages - Parse the region file and generate images
1090 * from the postscript file. The region file
1091 * contains the x1,y1--x2,y2 extents of each
1095 static void generateImages(char *region_file_name)
1097 pushBackBuffer *f=new pushBackBuffer(region_file_name);
1099 while (f->putPB(f->getPB()) != eof) {
1100 if (f->isString("grohtml-info:page")) {
1101 int page = f->readInt();
1102 int x1 = f->readInt();
1103 int y1 = f->readInt();
1104 int x2 = f->readInt();
1105 int y2 = f->readInt();
1106 int maxx = f->readInt();
1107 char *name = f->readString();
1108 int res = postscriptRes;
1109 listOfImages.add(x1, y1, x2, y2, page, res, maxx, name);
1110 while (f->putPB(f->getPB()) != '\n'
1111 && f->putPB(f->getPB()) != eof)
1113 if (f->putPB(f->getPB()) == '\n')
1117 /* Write any error messages out to the user. */
1118 fputc(f->getPB(), stderr);
1122 listOfImages.createImages();
1123 if (show_progress) {
1124 fprintf(stderr, "done\n");
1131 * set_redirection - Set up I/O Redirection for handle, was, to refer to
1132 * stream on handle, willbe.
1135 static void set_redirection(int was, int willbe)
1137 // Nothing to do if 'was' and 'willbe' already have same handle.
1138 if (was != willbe) {
1139 // Otherwise attempt the specified redirection.
1140 if (dup2 (willbe, was) < 0) {
1141 // Redirection failed, so issue diagnostic and bail out.
1142 fprintf(stderr, "failed to replace fd=%d with %d\n", was, willbe);
1143 if (willbe == STDOUT_FILENO)
1145 "likely that stdout should be opened before %d\n", was);
1149 // When redirection has been successfully completed assume redundant
1150 // handle 'willbe' is no longer required, so close it.
1151 if (close(willbe) < 0)
1152 // Issue diagnostic if 'close' fails.
1158 * save_and_redirect - Get duplicate handle for stream, was, then
1159 * redirect, was, to refer to, willbe.
1162 static int save_and_redirect(int was, int willbe)
1165 // No redirection specified so don't do anything but silently bailing out.
1168 // Proceeding with redirection so first save and verify our duplicate
1169 // handle for 'was'.
1170 int saved = dup(was);
1172 fprintf(stderr, "unable to get duplicate handle for %d\n", was);
1176 // Duplicate handle safely established so complete redirection.
1177 set_redirection(was, willbe);
1179 // Finally return the saved duplicate descriptor for the
1180 // original 'was' stream.
1185 * alterDeviceTo - If, toImage, is set
1186 * the argument list is altered to include
1187 * IMAGE_DEVICE and we invoke groff rather than troff.
1189 * set -Thtml and groff.
1192 static void alterDeviceTo(int argc, char *argv[], int toImage)
1198 if ((strcmp(argv[i], "-Thtml") == 0) ||
1199 (strcmp(argv[i], "-Txhtml") == 0))
1200 argv[i] = (char *)IMAGE_DEVICE;
1203 argv[troff_arg] = (char *)"groff"; /* rather than troff */
1207 if (strcmp(argv[i], IMAGE_DEVICE) == 0) {
1208 if (dialect == xhtml)
1209 argv[i] = (char *)"-Txhtml";
1211 argv[i] = (char *)"-Thtml";
1215 argv[troff_arg] = (char *)"groff"; /* use groff -Z */
1220 * addArg - Append newarg onto the command list for groff.
1223 char **addArg(int argc, char *argv[], char *newarg)
1225 char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1228 if (new_argv == NULL)
1229 sys_fatal("malloc");
1232 new_argv[i] = argv[i];
1235 new_argv[i] = newarg;
1237 new_argv[i + 1] = argv[i];
1241 new_argv[argc] = NULL;
1246 * addRegDef - Append a defined register or string onto the command
1250 char **addRegDef(int argc, char *argv[], const char *numReg)
1252 char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1255 if (new_argv == NULL)
1256 sys_fatal("malloc");
1259 new_argv[i] = argv[i];
1262 new_argv[argc] = strsave(numReg);
1264 new_argv[argc] = NULL;
1269 * dump_args - Display the argument list.
1272 void dump_args(int argc, char *argv[])
1274 fprintf(stderr, " %d arguments:", argc);
1275 for (int i = 0; i < argc; i++)
1276 fprintf(stderr, " %s", argv[i]);
1277 fprintf(stderr, "\n");
1281 * print_args - print arguments as if they were issued on the command line.
1284 #if defined(DEBUGGING)
1286 void print_args(int argc, char *argv[])
1289 fprintf(stderr, "executing: ");
1290 for (int i = 0; i < argc; i++)
1291 fprintf(stderr, "%s ", argv[i]);
1292 fprintf(stderr, "\n");
1298 void print_args(int, char **)
1304 int char_buffer::run_output_filter(int filter, int argc, char **argv)
1310 print_args(argc, argv);
1311 if (pipe(pipedes) < 0)
1314 #if MAY_FORK_CHILD_PROCESS
1315 // This is the Unix process model. To invoke our post-processor,
1316 // we must 'fork' the current process.
1318 if ((child_pid = fork()) < 0)
1321 else if (child_pid == 0) {
1322 // This is the child process fork. We redirect its 'stdin' stream
1323 // to read data emerging from our pipe. There is no point in saving,
1324 // since we won't be able to restore later!
1326 set_redirection(STDIN_FILENO, pipedes[0]);
1328 // The parent process will be writing this data, so we should release
1329 // the child's writeable handle on the pipe, since we have no use for it.
1331 if (close(pipedes[1]) < 0)
1334 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1336 if (filter == IMAGE_OUTPUT_FILTER) {
1337 // with BOTH 'stdout' AND 'stderr' diverted to files.
1339 set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
1340 set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM);
1343 // Now we are ready to launch the output filter.
1345 execvp(argv[0], argv);
1347 // If we get to here then the 'exec...' request for the output filter
1348 // failed. Diagnose it and bail out.
1350 error("couldn't exec %1: %2", argv[0], strerror(errno), ((char *)0));
1351 fflush(stderr); // just in case error() didn't
1356 // This is the parent process fork. We will be writing data to the
1357 // filter pipeline, and the child will be reading it. We have no further
1358 // use for our read handle on the pipe, and should close it.
1360 if (close(pipedes[0]) < 0)
1363 // Now we redirect the 'stdout' stream to the inlet end of the pipe,
1364 // and push out the appropiately formatted data to the filter.
1366 pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]);
1367 emit_troff_output(DEVICE_FORMAT(filter));
1369 // After emitting all the data we close our connection to the inlet
1370 // end of the pipe so the child process will detect end of data.
1372 set_redirection(STDOUT_FILENO, pipedes[1]);
1374 // Finally, we must wait for the child process to complete.
1376 if (WAIT(&status, child_pid, _WAIT_CHILD) != child_pid)
1380 #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1382 // We do not have 'fork', (or we prefer not to use it),
1383 // but asynchronous processes are allowed, passing data through pipes.
1384 // This should be ok for most Win32 systems and is preferred to 'fork'
1385 // for starting child processes under Cygwin.
1387 // Before we start the post-processor we bind its inherited 'stdin'
1388 // stream to the readable end of our pipe, saving our own 'stdin' stream
1391 pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]);
1393 // for the Win32 model,
1394 // we need special provision for saving BOTH 'stdout' and 'stderr'.
1396 int saved_stdout = dup(STDOUT_FILENO);
1397 int saved_stderr = STDERR_FILENO;
1399 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1401 if (filter == IMAGE_OUTPUT_FILTER) {
1402 // with BOTH 'stdout' AND 'stderr' diverted to files while saving a
1403 // duplicate handle for 'stderr'.
1405 set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
1406 saved_stderr = save_and_redirect(STDERR_FILENO, REGION_OUTPUT_STREAM);
1409 // We then use an asynchronous spawn request to start the post-processor.
1411 if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
1412 // Should the spawn request fail we issue a diagnostic and bail out.
1414 error("cannot spawn %1: %2", argv[0], strerror(errno), ((char *)0));
1418 // Once the post-processor has been started we revert our 'stdin'
1419 // to its original saved source, which also closes the readable handle
1422 set_redirection(STDIN_FILENO, pipedes[0]);
1424 // if we redirected 'stderr', for use by the image post-processor,
1425 // then we also need to reinstate its original assignment.
1427 if (filter == IMAGE_OUTPUT_FILTER)
1428 set_redirection(STDERR_FILENO, saved_stderr);
1430 // Now we redirect the 'stdout' stream to the inlet end of the pipe,
1431 // and push out the appropiately formatted data to the filter.
1433 set_redirection(STDOUT_FILENO, pipedes[1]);
1434 emit_troff_output(DEVICE_FORMAT(filter));
1436 // After emitting all the data we close our connection to the inlet
1437 // end of the pipe so the child process will detect end of data.
1439 set_redirection(STDOUT_FILENO, saved_stdout);
1441 // And finally, we must wait for the child process to complete.
1443 if (WAIT(&status, child_pid, _WAIT_CHILD) != child_pid)
1446 #else /* can't do asynchronous pipes! */
1448 // TODO: code to support an MS-DOS style process model
1451 #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1457 * do_html - Set the troff number htmlflip and
1458 * write out the buffer to troff -Thtml.
1461 int char_buffer::do_html(int argc, char *argv[])
1465 alterDeviceTo(argc, argv, 0);
1466 argv += troff_arg; // skip all arguments up to groff
1468 argv = addArg(argc, argv, (char *)"-Z");
1471 s = (char *)"-dwww-image-template=";
1472 s += macroset_template; // do not combine these statements,
1473 // otherwise they will not work
1474 s += '\0'; // the trailing '\0' is ignored
1475 argv = addRegDef(argc, argv, s.contents());
1478 if (dialect == xhtml) {
1479 argv = addRegDef(argc, argv, "-rxhtml=1");
1482 argv = addRegDef(argc, argv, "-e");
1487 #if defined(DEBUGGING)
1488 # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
1489 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1491 int saved_stdout = save_and_redirect(STDOUT_FILENO, HTML_DEBUG_STREAM);
1492 emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER));
1493 set_redirection(STDOUT_FILENO, saved_stdout);
1497 return run_output_filter(HTML_OUTPUT_FILTER, argc, argv);
1501 * do_image - Write out the buffer to troff -Tps.
1504 int char_buffer::do_image(int argc, char *argv[])
1508 alterDeviceTo(argc, argv, 1);
1509 argv += troff_arg; // skip all arguments up to troff/groff
1511 argv = addRegDef(argc, argv, "-rps4html=1");
1514 s = "-dwww-image-template=";
1515 s += macroset_template;
1517 argv = addRegDef(argc, argv, s.contents());
1520 // override local settings and produce a page size letter postscript file
1521 argv = addRegDef(argc, argv, "-P-pletter");
1524 if (dialect == xhtml) {
1526 argv = addRegDef(argc, argv, "-rxhtml=1");
1529 argv = addRegDef(argc, argv, "-e");
1533 #if defined(DEBUGGING)
1534 # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
1535 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1537 int saved_stdout = save_and_redirect(STDOUT_FILENO, IMAGE_DEBUG_STREAM);
1538 emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER));
1539 set_redirection(STDOUT_FILENO, saved_stdout);
1543 return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
1546 static char_buffer inputFile;
1549 * usage - Emit usage arguments.
1552 static void usage(FILE *stream)
1556 "This program is not intended to be called stand-alone;\n"
1557 "it is part of the groff pipeline to produce HTML output.\n"
1559 "If there is ever the need to call it manually (e.g., for\n"
1560 "debugging purposes), add command-line option '-V' while calling\n"
1561 "the 'groff' program to see which arguments are passed to it.\n"
1566 * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1567 * and -P-I. Return the argument index of the first
1571 static int scanArguments(int argc, char **argv)
1573 const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
1574 if (!command_prefix)
1575 command_prefix = PROG_PREFIX;
1576 char *troff_name = new char[strlen(command_prefix) + strlen("troff") + 1];
1577 strcpy(troff_name, command_prefix);
1578 strcat(troff_name, "troff");
1580 static const struct option long_options[] = {
1581 { "help", no_argument, 0, CHAR_MAX + 1 },
1582 { "version", no_argument, 0, 'v' },
1585 while ((c = getopt_long(argc, argv, "+a:bdD:eF:g:hi:I:j:lno:prs:S:vVx:y",
1586 long_options, NULL))
1590 textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1592 if (textAlphaBits == 3) {
1593 error("cannot use 3 bits of antialiasing information");
1598 // handled by post-grohtml (set background color to white)
1601 #if defined(DEBUGGING)
1612 font_path.command_line_dir(optarg);
1615 graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1617 if (graphicAlphaBits == 3) {
1618 error("cannot use 3 bits of antialiasing information");
1623 // handled by post-grohtml
1626 image_res = atoi(optarg);
1629 image_template = optarg;
1632 // handled by post-grohtml (set job name for multiple file output)
1635 // handled by post-grohtml (no automatic section links)
1638 // handled by post-grohtml (generate simple heading anchors)
1641 vertical_offset = atoi(optarg);
1644 show_progress = TRUE;
1647 // handled by post-grohtml (no header and footer lines)
1650 // handled by post-grohtml (use font size n as the html base font size)
1653 // handled by post-grohtml (set file split level)
1656 printf("GNU pre-grohtml (groff) version %s\n", Version_string);
1659 // handled by post-grohtml (create validator button)
1663 if (strcmp(optarg, "x") == 0)
1665 else if (strcmp(optarg, "4") == 0)
1668 printf("unsupported html dialect %s (defaulting to html4)\n", optarg);
1671 // handled by post-grohtml (create groff signature)
1673 case CHAR_MAX + 1: // --help
1687 if (strcmp(argv[i], troff_name) == 0)
1689 else if (argv[i][0] != '-')
1693 a_delete troff_name;
1699 * makeTempFiles - Name the temporary files.
1702 static int makeTempFiles(void)
1704 #if defined(DEBUGGING)
1705 psFileName = DEBUG_FILE("prehtml-ps");
1706 regionFileName = DEBUG_FILE("prehtml-region");
1707 imagePageName = DEBUG_FILE("prehtml-page");
1708 psPageName = DEBUG_FILE("prehtml-psn");
1709 troffFileName = DEBUG_FILE("prehtml-troff");
1710 htmlFileName = DEBUG_FILE("prehtml-html");
1711 #else /* not DEBUGGING */
1714 /* psPageName contains a single page of postscript */
1715 f = xtmpfile(&psPageName,
1716 PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
1719 sys_fatal("xtmpfile");
1724 /* imagePageName contains a bitmap image of the single postscript page */
1725 f = xtmpfile(&imagePageName,
1726 PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT,
1729 sys_fatal("xtmpfile");
1734 /* psFileName contains a postscript file of the complete document */
1735 f = xtmpfile(&psFileName,
1736 PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
1739 sys_fatal("xtmpfile");
1744 /* regionFileName contains a list of the images and their boxed coordinates */
1745 f = xtmpfile(®ionFileName,
1746 REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT,
1749 sys_fatal("xtmpfile");
1754 #endif /* not DEBUGGING */
1758 int main(int argc, char **argv)
1760 program_name = argv[0];
1767 fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc);
1768 for (i = 0; i < argc; i++)
1769 fprintf(stderr, "%2d: %s\n", i, argv[i]);
1770 if ((dump = fopen(DEBUG_FILE("pre-html-data"), "wb")) != NULL) {
1771 while((i = fgetc(stdin)) >= 0)
1776 #endif /* CAPTURE_MODE */
1778 if (!font::load_desc())
1779 fatal("cannot find devhtml/DESC exiting");
1780 image_gen = font::image_generator;
1781 if (image_gen == NULL || (strcmp(image_gen, "") == 0))
1782 fatal("devhtml/DESC must set the image_generator field, exiting");
1783 postscriptRes = get_resolution();
1784 i = scanArguments(argc, argv);
1789 if (argv[i][0] != '-') {
1790 /* found source file */
1791 ok = do_file(argv[i]);
1801 if (makeTempFiles())
1803 ok = inputFile.do_image(argc, argv);
1805 generateImages(regionFileName);
1806 ok = inputFile.do_html(argc, argv);
1811 static int do_file(const char *filename)
1815 current_filename = filename;
1816 if (strcmp(filename, "-") == 0)
1819 fp = fopen(filename, "r");
1821 error("can't open '%1': %2", filename, strerror(errno));
1826 if (inputFile.read_file(fp)) {
1832 current_filename = NULL;