2 * Copyright (c) 2014 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
4 * Copyright (C) 2000 - 2004, 2007, 2008
5 * Free Software Foundation, Inc.
6 * Written by Gaius Mulley (gaius@glam.ac.uk).
8 * This is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2, or (at your option) any later
13 * This is distributed in the hope that it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 * You should have received a copy of the GNU General Public License along
19 * with groff; see the file COPYING. If not, write to the Free Software
20 * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
25 #include "html-config.h"
27 #include <sys/types.h>
30 # include <sys/wait.h>
47 #include "file_case.h"
53 #include "searchpath.h"
54 #include "stringclass.h"
56 #include "html-strings.h"
64 /* Establish some definitions to facilitate discrimination between
65 differing runtime environments. */
67 #undef MAY_FORK_CHILD_PROCESS
68 #undef MAY_SPAWN_ASYNCHRONOUS_CHILD
70 #if defined(__MSDOS__) || defined(_WIN32)
72 // Most MS-DOS and Win32 environments will be missing the `fork' capability
73 // (some like Cygwin have it, but it is best avoided).
75 # define MAY_FORK_CHILD_PROCESS 0
77 // On these systems, we use `spawn...', instead of `fork' ... `exec...'.
78 # include <process.h> // for `spawn...'
79 # include <fcntl.h> // for attributes of pipes
81 # if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
83 // These Win32 implementations allow parent and `spawn...'ed child to
84 // multitask asynchronously.
86 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
90 // Others may adopt MS-DOS behaviour where parent must sleep,
91 // from `spawn...' until child terminates.
93 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
95 # endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
97 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
98 /* When we are building a DEBUGGING version we need to tell pre-grohtml
99 where to put intermediate files (the DEBUGGING version will preserve
102 On a UNIX host, we might simply use `/tmp', but MS-DOS and Win32 will
103 probably not have this on all disk drives, so default to using
104 `c:/temp' instead. (Note that user may choose to override this by
105 supplying a definition such as
107 -DDEBUG_FILE_DIR=d:/path/to/debug/files FIXME what more special to come?
109 in the CPPFLAGS to `make'.) */
111 # define DEBUG_FILE_DIR c:/temp
114 #else /* not __MSDOS__ or _WIN32 */
116 // For non-Microsoft environments assume UNIX conventions,
117 // so `fork' is required and child processes are asynchronous.
118 # define MAY_FORK_CHILD_PROCESS 1
119 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
121 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
122 /* For a DEBUGGING version, on the UNIX host, we can also usually rely
123 on being able to use `/tmp' for temporary file storage. (Note that,
124 as in the __MSDOS__ or _WIN32 case above, the user may override this
127 -DDEBUG_FILE_DIR=/path/to/debug/files
131 # define DEBUG_FILE_DIR /tmp
134 #endif /* not __MSDOS__ or _WIN32 */
137 // For a DEBUGGING version, we need some additional macros,
138 // to direct the captured debug mode output to appropriately named files
139 // in the specified DEBUG_FILE_DIR.
141 # define DEBUG_TEXT(text) #text
142 # define DEBUG_NAME(text) DEBUG_TEXT(text)
143 # define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
146 #define INLINE_LEADER_CHAR '\\'
148 // Don't use colour names here! Otherwise there is a dependency on
149 // a file called `rgb.txt' which maps names to colours.
150 #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
151 #define MIN_ALPHA_BITS 0
152 #define MAX_ALPHA_BITS 4
154 #define PAGE_TEMPLATE_SHORT "pg"
155 #define PAGE_TEMPLATE_LONG "-page-"
156 #define PS_TEMPLATE_SHORT "ps"
157 #define PS_TEMPLATE_LONG "-ps-"
158 #define REGION_TEMPLATE_SHORT "rg"
159 #define REGION_TEMPLATE_LONG "-regions-"
162 CENTERED
, LEFT
, RIGHT
, INLINE
165 typedef enum {xhtml
, html4
} html_dialect
;
167 static int postscriptRes
= -1; // postscript resolution,
169 static int stdoutfd
= 1; // output file descriptor -
170 // normally 1 but might move
172 static char *psFileName
= NULL
; // name of postscript file
173 static char *psPageName
= NULL
; // name of file containing
174 // postscript current page
175 static char *regionFileName
= NULL
; // name of file containing all
177 static char *imagePageName
= NULL
; // name of bitmap image containing
179 static const char *image_device
= "pnmraw";
180 static int image_res
= DEFAULT_IMAGE_RES
;
181 static int vertical_offset
= 0;
182 static char *image_template
= NULL
; // image template filename
183 static char *macroset_template
= NULL
; // image template passed to troff
185 static int troff_arg
= 0; // troff arg index
186 static char *image_dir
= NULL
; // user specified image directory
187 static int textAlphaBits
= MAX_ALPHA_BITS
;
188 static int graphicAlphaBits
= MAX_ALPHA_BITS
;
189 static char *antiAlias
= NULL
; // antialias arguments we pass to gs
190 static int show_progress
= false; // should we display page numbers as
191 // they are processed?
192 static int currentPageNo
= -1; // current image page number
193 #if defined(DEBUGGING)
194 static int debug
= false;
195 static char *troffFileName
= NULL
; // output of pre-html output which
196 // is sent to troff -Tps
197 static char *htmlFileName
= NULL
; // output of pre-html output which
198 // is sent to troff -Thtml
200 static int eqn_flag
= false; // must we preprocess via eqn?
202 static char *linebuf
= NULL
; // for scanning dev-ps/DESC
203 static int linebufsize
= 0;
204 static const char *image_gen
= NULL
; // the `gs' program
206 const char *const FONT_ENV_VAR
= U_ROFF_FONT_PATH
;
207 static search_path
font_path(FONT_ENV_VAR
, FONTPATH
, 0, 0);
208 static html_dialect dialect
= html4
;
210 static int do_file(const char *filename
);
213 * sys_fatal - Write a fatal error message.
216 void sys_fatal(const char *s
)
218 fatal("%1: %2", s
, strerror(errno
));
222 * get_line - Copy a line (w/o newline) from a file to the
223 * global line buffer.
226 int get_line(file_case
*fcp
)
231 linebuf
= new char[128];
235 // skip leading whitespace
237 int c
= fcp
->get_c();
240 if (c
!= ' ' && c
!= '\t') {
246 int c
= fcp
->get_c();
249 if (i
+ 1 >= linebufsize
) {
250 char *old_linebuf
= linebuf
;
251 linebuf
= new char[linebufsize
* 2];
252 memcpy(linebuf
, old_linebuf
, linebufsize
);
253 a_delete old_linebuf
;
267 * get_resolution - Return the postscript resolution from dev-ps/DESC. FIXME
270 static unsigned int get_resolution(void)
274 if ((fcp
= font_path
.open_file("dev-ps/DESC", fcp
->fc_const_path
)) == NULL
)
275 fatal("can't open dev-ps/DESC");
276 while (get_line(fcp
)) {
277 int n
= sscanf(linebuf
, "res %u", &res
);
281 fatal("can't find `res' keyword in dev-ps/DESC");
288 * html_system - A wrapper for system().
291 void html_system(const char *s
, int redirect_stdout
)
293 #if defined(DEBUGGING)
295 fprintf(stderr
, "executing: ");
296 fwrite(s
, sizeof(char), strlen(s
), stderr
);
301 // Redirect standard error to the null device. This is more
302 // portable than using "2> /dev/null", since it doesn't require a
304 int save_stderr
= dup(2);
305 int save_stdout
= dup(1);
306 int fdnull
= open(NULL_DEV
, O_WRONLY
|O_BINARY
, 0666);
307 if (save_stderr
> 2 && fdnull
> 2)
309 if (redirect_stdout
&& save_stdout
> 1 && fdnull
> 1)
313 int status
= system(s
);
314 dup2(save_stderr
, 2);
316 dup2(save_stdout
, 1);
318 fprintf(stderr
, "Calling `%s' failed\n", s
);
320 fprintf(stderr
, "Calling `%s' returned status %d\n", s
, status
);
327 * make_message - Create a string via malloc and place the result of the
328 * va args into string. Finally the new string is returned.
329 * Taken from man page of printf(3).
332 char *make_message(const char *fmt
, ...)
334 /* Guess we need no more than 100 bytes. */
339 if ((p
= (char *)malloc(size
)) == NULL
)
342 /* Try to print in the allocated space. */
344 n
= vsnprintf(p
, size
, fmt
, ap
);
346 /* If that worked, return the string. */
347 if (n
> -1 && n
< size
- 1) { /* glibc 2.1 and pre-ANSI C 99 */
355 /* Else try again with more space. */
357 size
*= 2; /* twice the old size */
358 if ((np
= (char *)realloc(p
, size
)) == NULL
) {
359 free(p
); /* realloc failed, free old, p. */
362 p
= np
; /* use realloc'ed, p */
367 * the class and methods for retaining ascii text
382 * char_block - Constructor. Set the, used, and, next, fields to zero.
385 char_block::char_block()
388 for (int i
= 0; i
< SIZE
; i
++)
397 int run_output_filter(int device_format_selector
, int argc
, char *argv
[]);
402 int read_file(file_case
*fcp
);
403 int do_html(int argc
, char *argv
[]);
404 int do_image(int argc
, char *argv
[]);
405 void emit_troff_output(int device_format_selector
);
406 void write_upto_newline(char_block
**t
, int *i
, int is_html
);
407 int can_see(char_block
**t
, int *i
, const char *string
);
408 int skip_spaces(char_block
**t
, int *i
);
409 void skip_until_newline(char_block
**t
, int *i
);
413 * char_buffer - Constructor.
416 char_buffer::char_buffer()
422 * char_buffer - Destructor. Throw away the whole buffer list.
425 char_buffer::~char_buffer()
427 while (head
!= NULL
) {
428 char_block
*temp
= head
;
435 * read_file - Read in a complete file, fp, placing the contents inside
439 int char_buffer::read_file(file_case
*fcp
)
442 while (!fcp
->is_eof()) {
444 tail
= new char_block
;
448 if (tail
->used
== char_block::SIZE
) {
449 tail
->next
= new char_block
;
453 // at this point we have a tail which is ready for the next SIZE
455 n
= fcp
->get_buf(tail
->buffer
, char_block::SIZE
- tail
->used
);
457 tail
->used
+= n
* sizeof(char);
469 * writeNbytes - Write n bytes to stdout.
472 static void writeNbytes(const char *s
, int l
)
478 r
= write(stdoutfd
, s
, l
- n
);
487 * writeString - Write a string to stdout.
490 static void writeString(const char *s
)
492 writeNbytes(s
, strlen(s
));
496 * makeFileName - Create the image filename template
497 * and the macroset image template.
500 static void makeFileName(void)
502 if ((image_dir
!= NULL
) && (strchr(image_dir
, '%') != NULL
)) {
503 error("cannot use a `%%' within the image directory name");
507 if ((image_template
!= NULL
) && (strchr(image_template
, '%') != NULL
)) {
508 error("cannot use a `%%' within the image template");
512 if (image_dir
== NULL
)
513 image_dir
= (char *)"";
514 else if (strlen(image_dir
) > 0
515 && image_dir
[strlen(image_dir
) - 1] != '/') {
516 image_dir
= make_message("%s/", image_dir
);
517 if (image_dir
== NULL
)
518 sys_fatal("make_message");
521 if (image_template
== NULL
)
522 macroset_template
= make_message("%s" L_D_HTML
"-%d", image_dir
,
525 macroset_template
= make_message("%s%s", image_dir
, image_template
);
527 if (macroset_template
== NULL
)
528 sys_fatal("make_message");
531 (char *)malloc(strlen("-%d") + strlen(macroset_template
) + 1);
532 if (image_template
== NULL
)
534 strcpy(image_template
, macroset_template
);
535 strcat(image_template
, "-%d");
539 * setupAntiAlias - Set up the antialias string, used when we call gs.
542 static void setupAntiAlias(void)
544 if (textAlphaBits
== 0 && graphicAlphaBits
== 0)
545 antiAlias
= make_message(" ");
546 else if (textAlphaBits
== 0)
547 antiAlias
= make_message("-dGraphicsAlphaBits=%d ", graphicAlphaBits
);
548 else if (graphicAlphaBits
== 0)
549 antiAlias
= make_message("-dTextAlphaBits=%d ", textAlphaBits
);
551 antiAlias
= make_message("-dTextAlphaBits=%d -dGraphicsAlphaBits=%d ",
552 textAlphaBits
, graphicAlphaBits
);
556 * checkImageDir - Check whether the image directory is available.
559 static void checkImageDir(void)
561 if (image_dir
!= NULL
&& strcmp(image_dir
, "") != 0)
562 if (!(mkdir(image_dir
, 0777) == 0 || errno
== EEXIST
)) {
563 error("cannot create directory `%1'", image_dir
);
569 * write_end_image - End the image. Write out the image extents if we
573 static void write_end_image(int is_html
)
576 * if we are producing html then these
577 * emit image name and enable output
579 * we are producing images
580 * in which case these generate image
583 writeString("\\O[4]\\O[2]");
585 writeString("\\O[1]");
587 writeString("\\O[0]");
591 * write_start_image - Write troff code which will:
593 * (i) disable html output for the following image
594 * (ii) reset the max/min x/y registers during postscript
598 static void write_start_image(IMAGE_ALIGNMENT pos
, int is_html
)
600 writeString("\\O[5");
616 writeString(image_template
);
617 writeString(".png]");
619 writeString("\\O[0]\\O[3]");
621 // reset min/max registers
622 writeString("\\O[1]\\O[3]");
626 * write_upto_newline - Write the contents of the buffer until a newline
627 * is seen. Check for HTML_IMAGE_INLINE_BEGIN and
628 * HTML_IMAGE_INLINE_END; process them if they are
632 void char_buffer::write_upto_newline(char_block
**t
, int *i
, int is_html
)
637 while (j
< (*t
)->used
638 && (*t
)->buffer
[j
] != '\n'
639 && (*t
)->buffer
[j
] != INLINE_LEADER_CHAR
)
642 && (*t
)->buffer
[j
] == '\n')
644 writeNbytes((*t
)->buffer
+ (*i
), j
- (*i
));
645 if ((*t
)->buffer
[j
] == INLINE_LEADER_CHAR
) {
646 if (can_see(t
, &j
, HTML_IMAGE_INLINE_BEGIN
))
647 write_start_image(INLINE
, is_html
);
648 else if (can_see(t
, &j
, HTML_IMAGE_INLINE_END
))
649 write_end_image(is_html
);
651 if (j
< (*t
)->used
) {
654 writeNbytes((*t
)->buffer
+ (*i
), j
- (*i
));
658 if (j
== (*t
)->used
) {
661 if (*t
&& (*t
)->buffer
[j
- 1] != '\n')
662 write_upto_newline(t
, i
, is_html
);
671 * can_see - Return true if we can see string in t->buffer[i] onwards.
674 int char_buffer::can_see(char_block
**t
, int *i
, const char *str
)
682 while (k
< s
->used
&& j
< l
&& s
->buffer
[k
] == str
[j
]) {
691 else if (k
< s
->used
&& s
->buffer
[k
] != str
[j
])
700 * skip_spaces - Return true if we have not run out of data.
701 * Consume spaces also.
704 int char_buffer::skip_spaces(char_block
**t
, int *i
)
710 while (k
< s
->used
&& isspace(s
->buffer
[k
]))
725 * skip_until_newline - Skip all characters until a newline is seen.
726 * The newline is not consumed.
729 void char_buffer::skip_until_newline(char_block
**t
, int *i
)
734 while (j
< (*t
)->used
&& (*t
)->buffer
[j
] != '\n')
736 if (j
== (*t
)->used
) {
739 skip_until_newline(t
, i
);
747 #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
748 #define HTML_OUTPUT_FILTER 0
749 #define IMAGE_OUTPUT_FILTER 1
750 #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
751 #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
752 #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
755 * emit_troff_output - Write formatted buffer content to the troff
756 * post-processor data pipeline.
759 void char_buffer::emit_troff_output(int device_format_selector
)
761 // Handle output for BOTH html and image device formats
762 // if `device_format_selector' is passed as
764 // HTML_FORMAT(HTML_OUTPUT_FILTER)
765 // Buffer data is written to the output stream
766 // with template image names translated to actual image names.
768 // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
769 // Buffer data is written to the output stream
770 // with no translation, for image file creation in the post-processor.
773 char_block
*element
= head
;
775 while (element
!= NULL
)
776 write_upto_newline(&element
, &idx
, device_format_selector
);
779 if (close(stdoutfd
) < 0)
782 // now we grab fd=1 so that the next pipe cannot use fd=1
784 if (dup(2) != stdoutfd
)
785 sys_fatal ("dup failed to use fd=1");
791 * The image class remembers the position of all images in the
792 * postscript file and assigns names for each image.
808 imageItem(int x1
, int y1
, int x2
, int y2
,
809 int page
, int res
, int max_width
, char *name
);
814 * imageItem - Constructor.
817 imageItem::imageItem(int x1
, int y1
, int x2
, int y2
,
818 int page
, int res
, int max_width
, char *name
)
832 * imageItem - Destructor.
835 imageItem::~imageItem()
842 * imageList - A class containing a list of imageItems.
854 void add(int x1
, int y1
, int x2
, int y2
,
855 int page
, int res
, int maxx
, char *name
);
856 void createImages(void);
857 int createPage(int pageno
);
858 void createImage(imageItem
*i
);
859 int getMaxX(int pageno
);
863 * imageList - Constructor.
866 imageList::imageList()
867 : head(0), tail(0), count(0)
872 * imageList - Destructor.
875 imageList::~imageList()
877 while (head
!= NULL
) {
885 * createPage - Create one image of, page pageno, from the postscript file.
888 int imageList::createPage(int pageno
)
892 if (currentPageNo
== pageno
)
895 if (currentPageNo
>= 1) {
897 * We need to unlink the files which change each time a new page is
898 * processed. The final unlink is done by xtmpfile when pre-grohtml
901 unlink(imagePageName
);
906 fprintf(stderr
, "[%d] ", pageno
);
910 #if defined(DEBUGGING)
912 fprintf(stderr
, "creating page %d\n", pageno
);
915 s
= make_message("psselect -q -p%d %s %s\n",
916 pageno
, psFileName
, psPageName
);
919 sys_fatal("make_message");
922 s
= make_message("echo showpage | "
923 "%s%s -q -dBATCH -dSAFER "
924 "-dDEVICEHEIGHTPOINTS=792 "
925 "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
926 "-sDEVICE=%s -r%d %s "
927 "-sOutputFile=%s %s -\n",
930 (getMaxX(pageno
) * image_res
) / postscriptRes
,
937 sys_fatal("make_message");
940 currentPageNo
= pageno
;
945 * min - Return the minimum of two numbers.
948 int min(int x
, int y
)
957 * max - Return the maximum of two numbers.
960 int max(int x
, int y
)
969 * getMaxX - Return the largest right-hand position for any image
973 int imageList::getMaxX(int pageno
)
976 int x
= postscriptRes
* DEFAULT_LINE_LENGTH
;
979 if (h
->pageNo
== pageno
)
987 * createImage - Generate a minimal png file from the set of page images.
990 void imageList::createImage(imageItem
*i
)
994 int x1
= max(min(i
->X1
, i
->X2
) * image_res
/ postscriptRes
995 - IMAGE_BOARDER_PIXELS
,
997 int y1
= max(image_res
* vertical_offset
/ 72
998 + min(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
999 - IMAGE_BOARDER_PIXELS
,
1001 int x2
= max(i
->X1
, i
->X2
) * image_res
/ postscriptRes
1002 + IMAGE_BOARDER_PIXELS
;
1003 int y2
= image_res
* vertical_offset
/ 72
1004 + max(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
1005 + 1 + IMAGE_BOARDER_PIXELS
;
1006 if (createPage(i
->pageNo
) == 0) {
1007 s
= make_message("pnmcut%s %d %d %d %d < %s "
1008 "| pnmcrop -quiet | pnmtopng%s %s > %s\n",
1010 x1
, y1
, x2
- x1
+ 1, y2
- y1
+ 1,
1016 sys_fatal("make_message");
1022 fprintf(stderr
, "failed to generate image of page %d\n", i
->pageNo
);
1025 #if defined(DEBUGGING)
1029 fprintf(stderr
, "ignoring image as x1 coord is -1\n");
1037 * add - Add an image description to the imageList.
1040 void imageList::add(int x1
, int y1
, int x2
, int y2
,
1041 int page
, int res
, int maxx
, char *name
)
1043 imageItem
*i
= new imageItem(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1056 * createImages - For each image descriptor on the imageList,
1057 * create the actual image.
1060 void imageList::createImages(void)
1062 imageItem
*h
= head
;
1070 static imageList listOfImages
; // List of images defined by the region file.
1073 * generateImages - Parse the region file and generate images
1074 * from the postscript file. The region file
1075 * contains the x1,y1--x2,y2 extents of each
1079 static void generateImages(char *region_file_name
)
1081 pushBackBuffer
*f
=new pushBackBuffer(region_file_name
);
1083 while (f
->putPB(f
->getPB()) != eof
) {
1084 if (f
->isString(L_D_HTML
"-info:page")) {
1085 int page
= f
->readInt();
1086 int x1
= f
->readInt();
1087 int y1
= f
->readInt();
1088 int x2
= f
->readInt();
1089 int y2
= f
->readInt();
1090 int maxx
= f
->readInt();
1091 char *name
= f
->readString();
1092 int res
= postscriptRes
;
1093 listOfImages
.add(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1094 while (f
->putPB(f
->getPB()) != '\n'
1095 && f
->putPB(f
->getPB()) != eof
)
1097 if (f
->putPB(f
->getPB()) == '\n')
1101 /* Write any error messages out to the user. */
1102 fputc(f
->getPB(), stderr
);
1106 listOfImages
.createImages();
1107 if (show_progress
) {
1108 fprintf(stderr
, "done\n");
1115 * set_redirection - Set up I/O Redirection for handle, was, to refer to
1116 * stream on handle, willbe.
1119 static void set_redirection(int was
, int willbe
)
1121 // Nothing to do if `was' and `willbe' already have same handle.
1122 if (was
!= willbe
) {
1123 // Otherwise attempt the specified redirection.
1124 if (dup2 (willbe
, was
) < 0) {
1125 // Redirection failed, so issue diagnostic and bail out.
1126 fprintf(stderr
, "failed to replace fd=%d with %d\n", was
, willbe
);
1127 if (willbe
== STDOUT_FILENO
)
1129 "likely that stdout should be opened before %d\n", was
);
1133 // When redirection has been successfully completed assume redundant
1134 // handle `willbe' is no longer required, so close it.
1135 if (close(willbe
) < 0)
1136 // Issue diagnostic if `close' fails.
1142 * save_and_redirect - Get duplicate handle for stream, was, then
1143 * redirect, was, to refer to, willbe.
1146 static int save_and_redirect(int was
, int willbe
)
1149 // No redirection specified so don't do anything but silently bailing out.
1152 // Proceeding with redirection so first save and verify our duplicate
1153 // handle for `was'.
1154 int saved
= dup(was
);
1156 fprintf(stderr
, "unable to get duplicate handle for %d\n", was
);
1160 // Duplicate handle safely established so complete redirection.
1161 set_redirection(was
, willbe
);
1163 // Finally return the saved duplicate descriptor for the
1164 // original `was' stream.
1169 * alterDeviceTo - If, toImage, is set
1170 * the argument list is altered to include
1171 * IMAGE_DEVICE and we invoke groff rather than troff.
1173 * set -Thtml and groff.
1176 static void alterDeviceTo(int argc
, char *argv
[], int toImage
)
1182 if ((strcmp(argv
[i
], "-Thtml") == 0) ||
1183 (strcmp(argv
[i
], "-Txhtml") == 0))
1184 argv
[i
] = (char *)IMAGE_DEVICE
;
1189 if (strcmp(argv
[i
], IMAGE_DEVICE
) == 0)
1190 argv
[i
] = UNCONST((dialect
== xhtml
) ? "-Txhtml" : "-Thtml");
1194 argv
[troff_arg
] = UNCONST(L_ROFF
); // use groff -Z
1198 * addArg - Append newarg onto the command list for groff.
1201 char **addArg(int argc
, char *argv
[], char *newarg
)
1203 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1206 if (new_argv
== NULL
)
1207 sys_fatal("malloc");
1210 new_argv
[i
] = argv
[i
];
1213 new_argv
[i
] = newarg
;
1215 new_argv
[i
+ 1] = argv
[i
];
1219 new_argv
[argc
] = NULL
;
1224 * addRegDef - Append a defined register or string onto the command
1228 char **addRegDef(int argc
, char *argv
[], const char *numReg
)
1230 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1233 if (new_argv
== NULL
)
1234 sys_fatal("malloc");
1237 new_argv
[i
] = argv
[i
];
1240 new_argv
[argc
] = strsave(numReg
);
1242 new_argv
[argc
] = NULL
;
1247 * dump_args - Display the argument list.
1250 void dump_args(int argc
, char *argv
[])
1252 fprintf(stderr
, " %d arguments:", argc
);
1253 for (int i
= 0; i
< argc
; i
++)
1254 fprintf(stderr
, " %s", argv
[i
]);
1255 fprintf(stderr
, "\n");
1259 * print_args - print arguments as if they were issued on the command line.
1262 void print_args(int argc
, char *argv
[])
1265 fprintf(stderr
, "executing: ");
1266 for (int i
= 0; i
< argc
; i
++)
1267 fprintf(stderr
, "%s ", argv
[i
]);
1268 fprintf(stderr
, "\n");
1272 void print_args(int, char **)
1277 int char_buffer::run_output_filter(int filter
, int argc
, char **argv
)
1283 print_args(argc
, argv
);
1284 if (pipe(pipedes
) < 0)
1287 #if MAY_FORK_CHILD_PROCESS
1288 // This is the UNIX process model. To invoke our post-processor,
1289 // we must `fork' the current process.
1291 if ((child_pid
= fork()) < 0)
1294 else if (child_pid
== 0) {
1295 // This is the child process fork. We redirect its `stdin' stream
1296 // to read data emerging from our pipe. There is no point in saving,
1297 // since we won't be able to restore later!
1299 set_redirection(STDIN_FILENO
, pipedes
[0]);
1301 // The parent process will be writing this data, so we should release
1302 // the child's writeable handle on the pipe, since we have no use for it.
1304 if (close(pipedes
[1]) < 0)
1307 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1309 if (filter
== IMAGE_OUTPUT_FILTER
) {
1310 // with BOTH `stdout' AND `stderr' diverted to files.
1312 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1313 set_redirection(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1316 // Now we are ready to launch the output filter.
1318 execvp(argv
[0], argv
);
1320 // If we get to here then the `exec...' request for the output filter
1321 // failed. Diagnose it and bail out.
1323 error("couldn't exec %1: %2", argv
[0], strerror(errno
), ((char *)0));
1324 fflush(stderr
); // just in case error() didn't
1329 // This is the parent process fork. We will be writing data to the
1330 // filter pipeline, and the child will be reading it. We have no further
1331 // use for our read handle on the pipe, and should close it.
1333 if (close(pipedes
[0]) < 0)
1336 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1337 // and push out the appropiately formatted data to the filter.
1339 pipedes
[1] = save_and_redirect(STDOUT_FILENO
, pipedes
[1]);
1340 emit_troff_output(DEVICE_FORMAT(filter
));
1342 // After emitting all the data we close our connection to the inlet
1343 // end of the pipe so the child process will detect end of data.
1345 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1347 // Finally, we must wait for the child process to complete.
1349 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1353 #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1355 // We do not have `fork', (or we prefer not to use it),
1356 // but asynchronous processes are allowed, passing data through pipes.
1357 // This should be ok for most Win32 systems and is preferred to `fork'
1358 // for starting child processes under Cygwin.
1360 // Before we start the post-processor we bind its inherited `stdin'
1361 // stream to the readable end of our pipe, saving our own `stdin' stream
1364 pipedes
[0] = save_and_redirect(STDIN_FILENO
, pipedes
[0]);
1366 // for the Win32 model,
1367 // we need special provision for saving BOTH `stdout' and `stderr'.
1369 int saved_stdout
= dup(STDOUT_FILENO
);
1370 int saved_stderr
= STDERR_FILENO
;
1372 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1374 if (filter
== IMAGE_OUTPUT_FILTER
) {
1375 // with BOTH `stdout' AND `stderr' diverted to files while saving a
1376 // duplicate handle for `stderr'.
1378 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1379 saved_stderr
= save_and_redirect(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1382 // We then use an asynchronous spawn request to start the post-processor.
1384 if ((child_pid
= spawnvp(_P_NOWAIT
, argv
[0], argv
)) < 0) {
1385 // Should the spawn request fail we issue a diagnostic and bail out.
1387 error("cannot spawn %1: %2", argv
[0], strerror(errno
), ((char *)0));
1391 // Once the post-processor has been started we revert our `stdin'
1392 // to its original saved source, which also closes the readable handle
1395 set_redirection(STDIN_FILENO
, pipedes
[0]);
1397 // if we redirected `stderr', for use by the image post-processor,
1398 // then we also need to reinstate its original assignment.
1400 if (filter
== IMAGE_OUTPUT_FILTER
)
1401 set_redirection(STDERR_FILENO
, saved_stderr
);
1403 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1404 // and push out the appropiately formatted data to the filter.
1406 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1407 emit_troff_output(DEVICE_FORMAT(filter
));
1409 // After emitting all the data we close our connection to the inlet
1410 // end of the pipe so the child process will detect end of data.
1412 set_redirection(STDOUT_FILENO
, saved_stdout
);
1414 // And finally, we must wait for the child process to complete.
1416 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1419 #else /* can't do asynchronous pipes! */
1421 // TODO: code to support an MS-DOS style process model
1424 #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1430 * do_html - Set the troff number htmlflip and
1431 * write out the buffer to troff -Thtml.
1434 int char_buffer::do_html(int argc
, char *argv
[])
1438 alterDeviceTo(argc
, argv
, 0);
1439 argv
+= troff_arg
; // skip all arguments up to groff
1441 argv
= addArg(argc
, argv
, (char *)"-Z");
1444 s
= (char *)"-dwww-image-template=";
1445 s
+= macroset_template
; // do not combine these statements,
1446 // otherwise they will not work
1447 s
+= '\0'; // the trailing `\0' is ignored
1448 argv
= addRegDef(argc
, argv
, s
.contents());
1451 if (dialect
== xhtml
) {
1452 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1455 argv
= addRegDef(argc
, argv
, "-e");
1460 #if defined(DEBUGGING)
1461 # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
1462 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1464 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, HTML_DEBUG_STREAM
);
1465 emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER
));
1466 set_redirection(STDOUT_FILENO
, saved_stdout
);
1470 return run_output_filter(HTML_OUTPUT_FILTER
, argc
, argv
);
1474 * do_image - Write out the buffer to troff -Tps.
1477 int char_buffer::do_image(int argc
, char *argv
[])
1481 alterDeviceTo(argc
, argv
, 1);
1482 argv
+= troff_arg
; // skip all arguments up to troff/groff
1484 argv
= addRegDef(argc
, argv
, "-rps4html=1");
1487 s
= "-dwww-image-template=";
1488 s
+= macroset_template
;
1490 argv
= addRegDef(argc
, argv
, s
.contents());
1493 // override local settings and produce a page size letter postscript file
1494 argv
= addRegDef(argc
, argv
, "-P-pletter");
1497 if (dialect
== xhtml
) {
1499 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1502 argv
= addRegDef(argc
, argv
, "-e");
1506 #if defined(DEBUGGING)
1507 # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
1508 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1510 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, IMAGE_DEBUG_STREAM
);
1511 emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER
));
1512 set_redirection(STDOUT_FILENO
, saved_stdout
);
1516 return run_output_filter(IMAGE_OUTPUT_FILTER
, argc
, argv
);
1519 static char_buffer inputFile
;
1522 * usage - Emit usage arguments.
1525 static void usage(FILE *stream
)
1529 "This program is not intended to be called stand-alone;\n"
1530 "it is part of the " L_ROFF
" pipeline to produce HTML output.\n"
1532 "If there is ever the need to call it manually (e.g., for\n"
1533 "debugging purposes), add command line option `-V' while calling\n"
1534 "the `" L_ROFF
"' program to see which arguments are passed to it.\n"
1539 * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1540 * and -P-I. Return the argument index of the first
1544 static int scanArguments(int argc
, char **argv
)
1546 const char *command_prefix
= getenv(U_ROFF_COMMAND_PREFIX
);
1547 if (!command_prefix
)
1548 command_prefix
= PROG_PREFIX
;
1549 char *troff_name
= new char[strlen(command_prefix
) + strlen("troff") + 1];
1550 strcpy(troff_name
, command_prefix
);
1551 strcat(troff_name
, "troff");
1553 static const struct option long_options
[] = {
1554 { "help", no_argument
, 0, CHAR_MAX
+ 1 },
1555 { "version", no_argument
, 0, 'v' },
1558 while ((c
= getopt_long(argc
, argv
, "+a:bdD:eF:g:hi:I:j:lno:prs:S:vVx:y",
1559 long_options
, NULL
))
1563 textAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1565 if (textAlphaBits
== 3) {
1566 error("cannot use 3 bits of antialiasing information");
1571 // handled by post-grohtml (set background color to white)
1585 font_path
.command_line_dir(optarg
);
1588 graphicAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1590 if (graphicAlphaBits
== 3) {
1591 error("cannot use 3 bits of antialiasing information");
1596 // handled by post-grohtml
1599 image_res
= atoi(optarg
);
1602 image_template
= optarg
;
1605 // handled by post-grohtml (set job name for multiple file output)
1608 // handled by post-grohtml (no automatic section links)
1611 // handled by post-grohtml (generate simple heading anchors)
1614 vertical_offset
= atoi(optarg
);
1617 show_progress
= true;
1620 // handled by post-grohtml (no header and footer lines)
1623 // handled by post-grohtml (use font size n as the html base font size)
1626 // handled by post-grohtml (set file split level)
1629 puts(L_D_PREHTML
" (" T_ROFF
") v" VERSION
);
1632 // handled by post-grohtml (create validator button)
1636 if (strcmp(optarg
, "x") == 0)
1638 else if (strcmp(optarg
, "4") == 0)
1641 printf("unsupported html dialect %s (defaulting to html4)\n", optarg
);
1644 // handled by post-grohtml (create groff signature)
1646 case CHAR_MAX
+ 1: // --help
1660 if (strcmp(argv
[i
], troff_name
) == 0)
1662 else if (argv
[i
][0] != '-')
1666 a_delete troff_name
;
1672 * makeTempFiles - Name the temporary files.
1675 static int makeTempFiles(void)
1677 #if defined(DEBUGGING)
1678 psFileName
= DEBUG_FILE("prehtml-ps");
1679 regionFileName
= DEBUG_FILE("prehtml-region");
1680 imagePageName
= DEBUG_FILE("prehtml-page");
1681 psPageName
= DEBUG_FILE("prehtml-psn");
1682 troffFileName
= DEBUG_FILE("prehtml-troff");
1683 htmlFileName
= DEBUG_FILE("prehtml-html");
1684 #else /* not DEBUGGING */
1687 /* psPageName contains a single page of postscript */
1688 f
= xtmpfile(&psPageName
,
1689 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1692 sys_fatal("xtmpfile");
1697 /* imagePageName contains a bitmap image of the single postscript page */
1698 f
= xtmpfile(&imagePageName
,
1699 PAGE_TEMPLATE_LONG
, PAGE_TEMPLATE_SHORT
,
1702 sys_fatal("xtmpfile");
1707 /* psFileName contains a postscript file of the complete document */
1708 f
= xtmpfile(&psFileName
,
1709 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1712 sys_fatal("xtmpfile");
1717 /* regionFileName contains a list of the images and their boxed coordinates */
1718 f
= xtmpfile(®ionFileName
,
1719 REGION_TEMPLATE_LONG
, REGION_TEMPLATE_SHORT
,
1722 sys_fatal("xtmpfile");
1727 #endif /* not DEBUGGING */
1731 int main(int argc
, char **argv
)
1733 program_name
= argv
[0];
1740 fprintf(stderr
, "%s: invoked with %d arguments ...\n", argv
[0], argc
);
1741 for (i
= 0; i
< argc
; i
++)
1742 fprintf(stderr
, "%2d: %s\n", i
, argv
[i
]);
1743 if ((dump
= fopen(DEBUG_FILE("pre-html-data"), "wb")) != NULL
) {
1744 while((i
= fgetc(stdin
)) >= 0)
1749 #endif /* CAPTURE_MODE */
1751 if (!font::load_desc())
1752 fatal("cannot find dev-html/DESC exiting");
1753 image_gen
= font::image_generator
;
1754 if (image_gen
== NULL
|| (strcmp(image_gen
, "") == 0))
1755 fatal("dev-html/DESC must set the image_generator field, exiting");
1756 postscriptRes
= get_resolution();
1757 i
= scanArguments(argc
, argv
);
1762 if (argv
[i
][0] != '-') {
1763 /* found source file */
1764 ok
= do_file(argv
[i
]);
1774 if (makeTempFiles())
1776 ok
= inputFile
.do_image(argc
, argv
);
1778 generateImages(regionFileName
);
1779 ok
= inputFile
.do_html(argc
, argv
);
1784 static int do_file(const char *filename
)
1788 current_filename
= filename
;
1789 fcp
= file_case::muxer(filename
);
1791 assert(strcmp(filename
, "-"));
1792 error("can't open `%1': %2", filename
, strerror(errno
));
1796 if (inputFile
.read_file(fcp
)) {
1801 current_filename
= NULL
;