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>
42 #include "file_case.h"
48 #include "searchpath.h"
49 #include "stringclass.h"
51 #include "html-strings.h"
59 /* Establish some definitions to facilitate discrimination between
60 differing runtime environments. */
62 #undef MAY_FORK_CHILD_PROCESS
63 #undef MAY_SPAWN_ASYNCHRONOUS_CHILD
65 #if defined(__MSDOS__) || defined(_WIN32)
67 // Most MS-DOS and Win32 environments will be missing the `fork' capability
68 // (some like Cygwin have it, but it is best avoided).
70 # define MAY_FORK_CHILD_PROCESS 0
72 // On these systems, we use `spawn...', instead of `fork' ... `exec...'.
73 # include <process.h> // for `spawn...'
74 # include <fcntl.h> // for attributes of pipes
76 # if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
78 // These Win32 implementations allow parent and `spawn...'ed child to
79 // multitask asynchronously.
81 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
85 // Others may adopt MS-DOS behaviour where parent must sleep,
86 // from `spawn...' until child terminates.
88 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
90 # endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
92 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
93 /* When we are building a DEBUGGING version we need to tell pre-grohtml
94 where to put intermediate files (the DEBUGGING version will preserve
97 On a UNIX host, we might simply use `/tmp', but MS-DOS and Win32 will
98 probably not have this on all disk drives, so default to using
99 `c:/temp' instead. (Note that user may choose to override this by
100 supplying a definition such as
102 -DDEBUG_FILE_DIR=d:/path/to/debug/files FIXME what more special to come?
104 in the CPPFLAGS to `make'.) */
106 # define DEBUG_FILE_DIR c:/temp
109 #else /* not __MSDOS__ or _WIN32 */
111 // For non-Microsoft environments assume UNIX conventions,
112 // so `fork' is required and child processes are asynchronous.
113 # define MAY_FORK_CHILD_PROCESS 1
114 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
116 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
117 /* For a DEBUGGING version, on the UNIX host, we can also usually rely
118 on being able to use `/tmp' for temporary file storage. (Note that,
119 as in the __MSDOS__ or _WIN32 case above, the user may override this
122 -DDEBUG_FILE_DIR=/path/to/debug/files
126 # define DEBUG_FILE_DIR /tmp
129 #endif /* not __MSDOS__ or _WIN32 */
132 // For a DEBUGGING version, we need some additional macros,
133 // to direct the captured debug mode output to appropriately named files
134 // in the specified DEBUG_FILE_DIR.
136 # define DEBUG_TEXT(text) #text
137 # define DEBUG_NAME(text) DEBUG_TEXT(text)
138 # define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
141 // Don't use colour names here! Otherwise there is a dependency on
142 // a file called `rgb.txt' which maps names to colours.
143 #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
144 #define MIN_ALPHA_BITS 0
145 #define MAX_ALPHA_BITS 4
147 #define PAGE_TEMPLATE_SHORT "pg"
148 #define PAGE_TEMPLATE_LONG "-page-"
149 #define PS_TEMPLATE_SHORT "ps"
150 #define PS_TEMPLATE_LONG "-ps-"
151 #define REGION_TEMPLATE_SHORT "rg"
152 #define REGION_TEMPLATE_LONG "-regions-"
155 CENTERED
, LEFT
, RIGHT
, INLINE
158 typedef enum {xhtml
, html4
} html_dialect
;
160 static int postscriptRes
= -1; // postscript resolution,
162 static int stdoutfd
= 1; // output file descriptor -
163 // normally 1 but might move
165 static char *psFileName
= NULL
; // name of postscript file
166 static char *psPageName
= NULL
; // name of file containing
167 // postscript current page
168 static char *regionFileName
= NULL
; // name of file containing all
170 static char *imagePageName
= NULL
; // name of bitmap image containing
172 static const char *image_device
= "pnmraw";
173 static int image_res
= DEFAULT_IMAGE_RES
;
174 static int vertical_offset
= 0;
175 static char *image_template
= NULL
; // image template filename
176 static char *macroset_template
= NULL
; // image template passed to troff
178 static int troff_arg
= 0; // troff arg index
179 static char *image_dir
= NULL
; // user specified image directory
180 static int textAlphaBits
= MAX_ALPHA_BITS
;
181 static int graphicAlphaBits
= MAX_ALPHA_BITS
;
182 static char *antiAlias
= NULL
; // antialias arguments we pass to gs
183 static int show_progress
= false; // should we display page numbers as
184 // they are processed?
185 static int currentPageNo
= -1; // current image page number
186 #if defined(DEBUGGING)
187 static int debug
= false;
188 static char *troffFileName
= NULL
; // output of pre-html output which
189 // is sent to troff -Tps
190 static char *htmlFileName
= NULL
; // output of pre-html output which
191 // is sent to troff -Thtml
193 static int eqn_flag
= false; // must we preprocess via eqn?
195 static char *linebuf
= NULL
; // for scanning dev-ps/DESC
196 static int linebufsize
= 0;
197 static const char *image_gen
= NULL
; // the `gs' program
199 const char *const FONT_ENV_VAR
= U_ROFF_FONT_PATH
;
200 static search_path
font_path(FONT_ENV_VAR
, FONTPATH
, 0, 0);
201 static html_dialect dialect
= html4
;
203 static int do_file(const char *filename
);
206 * sys_fatal - Write a fatal error message.
209 void sys_fatal(const char *s
)
211 fatal("%1: %2", s
, strerror(errno
));
215 * get_line - Copy a line (w/o newline) from a file to the
216 * global line buffer.
219 int get_line(file_case
*fcp
)
224 linebuf
= new char[128];
228 // skip leading whitespace
230 int c
= fcp
->get_c();
233 if (c
!= ' ' && c
!= '\t') {
239 int c
= fcp
->get_c();
242 if (i
+ 1 >= linebufsize
) {
243 char *old_linebuf
= linebuf
;
244 linebuf
= new char[linebufsize
* 2];
245 memcpy(linebuf
, old_linebuf
, linebufsize
);
246 a_delete old_linebuf
;
260 * get_resolution - Return the postscript resolution from dev-ps/DESC. FIXME
263 static unsigned int get_resolution(void)
267 if ((fcp
= font_path
.open_file("dev-ps/DESC", fcp
->fc_const_path
)) == NULL
)
268 fatal("can't open dev-ps/DESC");
269 while (get_line(fcp
)) {
270 int n
= sscanf(linebuf
, "res %u", &res
);
274 fatal("can't find `res' keyword in dev-ps/DESC");
281 * html_system - A wrapper for system().
284 void html_system(const char *s
, int redirect_stdout
)
286 #if defined(DEBUGGING)
288 fprintf(stderr
, "executing: ");
289 fwrite(s
, sizeof(char), strlen(s
), stderr
);
294 // Redirect standard error to the null device. This is more
295 // portable than using "2> /dev/null", since it doesn't require a
297 int save_stderr
= dup(2);
298 int save_stdout
= dup(1);
299 int fdnull
= open(NULL_DEV
, O_WRONLY
|O_BINARY
, 0666);
300 if (save_stderr
> 2 && fdnull
> 2)
302 if (redirect_stdout
&& save_stdout
> 1 && fdnull
> 1)
306 int status
= system(s
);
307 dup2(save_stderr
, 2);
309 dup2(save_stdout
, 1);
311 fprintf(stderr
, "Calling `%s' failed\n", s
);
313 fprintf(stderr
, "Calling `%s' returned status %d\n", s
, status
);
320 * make_message - Create a string via malloc and place the result of the
321 * va args into string. Finally the new string is returned.
322 * Taken from man page of printf(3).
325 char *make_message(const char *fmt
, ...)
327 /* Guess we need no more than 100 bytes. */
332 if ((p
= (char *)malloc(size
)) == NULL
)
335 /* Try to print in the allocated space. */
337 n
= vsnprintf(p
, size
, fmt
, ap
);
339 /* If that worked, return the string. */
340 if (n
> -1 && n
< size
- 1) { /* glibc 2.1 and pre-ANSI C 99 */
348 /* Else try again with more space. */
350 size
*= 2; /* twice the old size */
351 if ((np
= (char *)realloc(p
, size
)) == NULL
) {
352 free(p
); /* realloc failed, free old, p. */
355 p
= np
; /* use realloc'ed, p */
360 * the class and methods for retaining ascii text
368 int used
; // TODO size_t
375 * char_block - Constructor. Set the, used, and, next, fields to zero.
378 char_block::char_block()
381 for (int i
= 0; i
< SIZE
; i
++)
390 int run_output_filter(int device_format_selector
, int argc
, char *argv
[]);
395 int read_file(file_case
*fcp
);
396 int do_html(int argc
, char *argv
[]);
397 int do_image(int argc
, char *argv
[]);
398 void emit_troff_output(int device_format_selector
);
401 // Write the contents of the buffer until a newline is seen.
402 // Check for HTML_IMAGE_INLINE_BEGIN and HTML_IMAGE_INLINE_END; process them
403 // if they are present.
404 static void write_upto_newline(char_block
**t
, int *i
, int is_html
);
406 static int can_see(char_block
**t
, int *i
, const char *string
);
407 static int skip_spaces(char_block
**t
, int *i
);
408 static void skip_until_newline(char_block
**t
, int *i
);
412 * char_buffer - Constructor.
415 char_buffer::char_buffer()
421 * char_buffer - Destructor. Throw away the whole buffer list.
424 char_buffer::~char_buffer()
426 while (head
!= NULL
) {
427 char_block
*temp
= head
;
434 * read_file - Read in a complete file, fp, placing the contents inside
438 int char_buffer::read_file(file_case
*fcp
)
441 while (!fcp
->is_eof()) {
443 tail
= new char_block
;
447 if (tail
->used
== char_block::SIZE
) {
448 tail
->next
= new char_block
;
452 // at this point we have a tail which is ready for the next SIZE
454 n
= fcp
->get_buf(tail
->buffer
, char_block::SIZE
- tail
->used
);
456 tail
->used
+= n
* sizeof(char);
468 * writeNbytes - Write n bytes to stdout.
471 static void writeNbytes(const char *s
, int l
)
477 r
= write(stdoutfd
, s
, l
- n
);
486 * writeString - Write a string to stdout.
489 static void writeString(const char *s
)
491 writeNbytes(s
, strlen(s
));
495 * makeFileName - Create the image filename template
496 * and the macroset image template.
499 static void makeFileName(void)
501 if ((image_dir
!= NULL
) && (strchr(image_dir
, '%') != NULL
)) {
502 error("cannot use a `%%' within the image directory name");
506 if ((image_template
!= NULL
) && (strchr(image_template
, '%') != NULL
)) {
507 error("cannot use a `%%' within the image template");
511 if (image_dir
== NULL
)
512 image_dir
= (char *)"";
513 else if (strlen(image_dir
) > 0
514 && image_dir
[strlen(image_dir
) - 1] != '/') {
515 image_dir
= make_message("%s/", image_dir
);
516 if (image_dir
== NULL
)
517 sys_fatal("make_message");
520 if (image_template
== NULL
)
521 macroset_template
= make_message("%s" L_D_HTML
"-%d", image_dir
,
524 macroset_template
= make_message("%s%s", image_dir
, image_template
);
526 if (macroset_template
== NULL
)
527 sys_fatal("make_message");
530 (char *)malloc(strlen("-%d") + strlen(macroset_template
) + 1);
531 if (image_template
== NULL
)
533 strcpy(image_template
, macroset_template
);
534 strcat(image_template
, "-%d");
538 * setupAntiAlias - Set up the antialias string, used when we call gs.
541 static void setupAntiAlias(void)
543 if (textAlphaBits
== 0 && graphicAlphaBits
== 0)
544 antiAlias
= make_message(" ");
545 else if (textAlphaBits
== 0)
546 antiAlias
= make_message("-dGraphicsAlphaBits=%d ", graphicAlphaBits
);
547 else if (graphicAlphaBits
== 0)
548 antiAlias
= make_message("-dTextAlphaBits=%d ", textAlphaBits
);
550 antiAlias
= make_message("-dTextAlphaBits=%d -dGraphicsAlphaBits=%d ",
551 textAlphaBits
, graphicAlphaBits
);
555 * checkImageDir - Check whether the image directory is available.
558 static void checkImageDir(void)
560 if (image_dir
!= NULL
&& strcmp(image_dir
, "") != 0)
561 if (!(mkdir(image_dir
, 0777) == 0 || errno
== EEXIST
)) {
562 error("cannot create directory `%1'", image_dir
);
568 * write_end_image - End the image. Write out the image extents if we
572 static void write_end_image(int is_html
)
575 * if we are producing html then these
576 * emit image name and enable output
578 * we are producing images
579 * in which case these generate image
582 writeString("\\O[4]\\O[2]");
584 writeString("\\O[1]");
586 writeString("\\O[0]");
590 * write_start_image - Write troff code which will:
592 * (i) disable html output for the following image
593 * (ii) reset the max/min x/y registers during postscript
597 static void write_start_image(IMAGE_ALIGNMENT pos
, int is_html
)
599 writeString("\\O[5");
615 writeString(image_template
);
616 writeString(".png]");
618 writeString("\\O[0]\\O[3]");
620 // reset min/max registers
621 writeString("\\O[1]\\O[3]");
625 char_buffer::write_upto_newline(char_block
**t
, int *i
, int is_html
){
626 enum {a_NONE
, a_NL
, a_LEADER
} ev
;
640 ev
= (++j
< u
&& b
[j
] == HTML_INLINE_LEADER_CHAR
) ? a_LEADER
: a_NL
;
642 }else if(b
[j
] == HTML_INLINE_LEADER_CHAR
){
648 writeNbytes(&b
[*i
], j
- *i
);
652 if(can_see(t
, &j
, HTML_IMAGE_INLINE_BEGIN
))
653 write_start_image(INLINE
, is_html
);
654 else if(can_see(t
, &j
, HTML_IMAGE_INLINE_END
))
655 write_end_image(is_html
);
658 writeNbytes(&b
[*i
], 1);
662 // Rotate block if all the buffer was consumed
665 if((*t
= (*t
)->next
) != NULL
)
666 write_upto_newline(t
, i
, is_html
);
672 * can_see - Return true if we can see string in t->buffer[i] onwards.
676 int char_buffer::can_see(char_block
**t
, int *i
, const char *str
)
684 while (k
< s
->used
&& j
< l
&& s
->buffer
[k
] == str
[j
]) {
693 else if (k
< s
->used
&& s
->buffer
[k
] != str
[j
])
702 * skip_spaces - Return true if we have not run out of data.
703 * Consume spaces also.
707 int char_buffer::skip_spaces(char_block
**t
, int *i
)
713 while (k
< s
->used
&& isspace(s
->buffer
[k
]))
728 * skip_until_newline - Skip all characters until a newline is seen.
729 * The newline is not consumed.
733 void char_buffer::skip_until_newline(char_block
**t
, int *i
)
738 while (j
< (*t
)->used
&& (*t
)->buffer
[j
] != '\n')
740 if (j
== (*t
)->used
) {
743 skip_until_newline(t
, i
);
751 #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
752 #define HTML_OUTPUT_FILTER 0
753 #define IMAGE_OUTPUT_FILTER 1
754 #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
755 #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
756 #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
759 * emit_troff_output - Write formatted buffer content to the troff
760 * post-processor data pipeline.
763 void char_buffer::emit_troff_output(int device_format_selector
)
765 // Handle output for BOTH html and image device formats
766 // if `device_format_selector' is passed as
768 // HTML_FORMAT(HTML_OUTPUT_FILTER)
769 // Buffer data is written to the output stream
770 // with template image names translated to actual image names.
772 // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
773 // Buffer data is written to the output stream
774 // with no translation, for image file creation in the post-processor.
777 char_block
*element
= head
;
779 while (element
!= NULL
)
780 write_upto_newline(&element
, &idx
, device_format_selector
);
783 if (close(stdoutfd
) < 0)
786 // now we grab fd=1 so that the next pipe cannot use fd=1
788 if (dup(2) != stdoutfd
)
789 sys_fatal ("dup failed to use fd=1");
795 * The image class remembers the position of all images in the
796 * postscript file and assigns names for each image.
812 imageItem(int x1
, int y1
, int x2
, int y2
,
813 int page
, int res
, int max_width
, char *name
);
818 * imageItem - Constructor.
821 imageItem::imageItem(int x1
, int y1
, int x2
, int y2
,
822 int page
, int res
, int max_width
, char *name
)
836 * imageItem - Destructor.
839 imageItem::~imageItem()
846 * imageList - A class containing a list of imageItems.
858 void add(int x1
, int y1
, int x2
, int y2
,
859 int page
, int res
, int maxx
, char *name
);
860 void createImages(void);
861 int createPage(int pageno
);
862 void createImage(imageItem
*i
);
863 int getMaxX(int pageno
);
867 * imageList - Constructor.
870 imageList::imageList()
871 : head(0), tail(0), count(0)
876 * imageList - Destructor.
879 imageList::~imageList()
881 while (head
!= NULL
) {
889 * createPage - Create one image of, page pageno, from the postscript file.
892 int imageList::createPage(int pageno
)
896 if (currentPageNo
== pageno
)
899 if (currentPageNo
>= 1) {
901 * We need to unlink the files which change each time a new page is
902 * processed. The final unlink is done by xtmpfile when pre-grohtml
905 unlink(imagePageName
);
910 fprintf(stderr
, "[%d] ", pageno
);
914 #if defined(DEBUGGING)
916 fprintf(stderr
, "creating page %d\n", pageno
);
919 s
= make_message("psselect -q -p%d %s %s\n",
920 pageno
, psFileName
, psPageName
);
923 sys_fatal("make_message");
926 s
= make_message("echo showpage | "
927 "%s%s -q -dBATCH -dSAFER "
928 "-dDEVICEHEIGHTPOINTS=792 "
929 "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
930 "-sDEVICE=%s -r%d %s "
931 "-sOutputFile=%s %s -\n",
934 (getMaxX(pageno
) * image_res
) / postscriptRes
,
941 sys_fatal("make_message");
944 currentPageNo
= pageno
;
949 * min - Return the minimum of two numbers.
952 int min(int x
, int y
)
961 * max - Return the maximum of two numbers.
964 int max(int x
, int y
)
973 * getMaxX - Return the largest right-hand position for any image
977 int imageList::getMaxX(int pageno
)
980 int x
= postscriptRes
* DEFAULT_LINE_LENGTH
;
983 if (h
->pageNo
== pageno
)
991 * createImage - Generate a minimal png file from the set of page images.
994 void imageList::createImage(imageItem
*i
)
998 int x1
= max(min(i
->X1
, i
->X2
) * image_res
/ postscriptRes
999 - IMAGE_BOARDER_PIXELS
,
1001 int y1
= max(image_res
* vertical_offset
/ 72
1002 + min(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
1003 - IMAGE_BOARDER_PIXELS
,
1005 int x2
= max(i
->X1
, i
->X2
) * image_res
/ postscriptRes
1006 + IMAGE_BOARDER_PIXELS
;
1007 int y2
= image_res
* vertical_offset
/ 72
1008 + max(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
1009 + 1 + IMAGE_BOARDER_PIXELS
;
1010 if (createPage(i
->pageNo
) == 0) {
1011 s
= make_message("pnmcut%s %d %d %d %d < %s "
1012 "| pnmcrop -quiet | pnmtopng%s %s > %s\n",
1014 x1
, y1
, x2
- x1
+ 1, y2
- y1
+ 1,
1020 sys_fatal("make_message");
1026 fprintf(stderr
, "failed to generate image of page %d\n", i
->pageNo
);
1029 #if defined(DEBUGGING)
1033 fprintf(stderr
, "ignoring image as x1 coord is -1\n");
1041 * add - Add an image description to the imageList.
1044 void imageList::add(int x1
, int y1
, int x2
, int y2
,
1045 int page
, int res
, int maxx
, char *name
)
1047 imageItem
*i
= new imageItem(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1060 * createImages - For each image descriptor on the imageList,
1061 * create the actual image.
1064 void imageList::createImages(void)
1066 imageItem
*h
= head
;
1074 static imageList listOfImages
; // List of images defined by the region file.
1077 * generateImages - Parse the region file and generate images
1078 * from the postscript file. The region file
1079 * contains the x1,y1--x2,y2 extents of each
1083 static void generateImages(char *region_file_name
)
1085 pushBackBuffer
*f
=new pushBackBuffer(region_file_name
);
1087 while (f
->putPB(f
->getPB()) != eof
) {
1088 if (f
->isString(L_D_HTML
"-info:page")) {
1089 int page
= f
->readInt();
1090 int x1
= f
->readInt();
1091 int y1
= f
->readInt();
1092 int x2
= f
->readInt();
1093 int y2
= f
->readInt();
1094 int maxx
= f
->readInt();
1095 char *name
= f
->readString();
1096 int res
= postscriptRes
;
1097 listOfImages
.add(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1098 while (f
->putPB(f
->getPB()) != '\n'
1099 && f
->putPB(f
->getPB()) != eof
)
1101 if (f
->putPB(f
->getPB()) == '\n')
1105 /* Write any error messages out to the user. */
1106 fputc(f
->getPB(), stderr
);
1110 listOfImages
.createImages();
1111 if (show_progress
) {
1112 fprintf(stderr
, "done\n");
1119 * set_redirection - Set up I/O Redirection for handle, was, to refer to
1120 * stream on handle, willbe.
1123 static void set_redirection(int was
, int willbe
)
1125 // Nothing to do if `was' and `willbe' already have same handle.
1126 if (was
!= willbe
) {
1127 // Otherwise attempt the specified redirection.
1128 if (dup2 (willbe
, was
) < 0) {
1129 // Redirection failed, so issue diagnostic and bail out.
1130 fprintf(stderr
, "failed to replace fd=%d with %d\n", was
, willbe
);
1131 if (willbe
== STDOUT_FILENO
)
1133 "likely that stdout should be opened before %d\n", was
);
1137 // When redirection has been successfully completed assume redundant
1138 // handle `willbe' is no longer required, so close it.
1139 if (close(willbe
) < 0)
1140 // Issue diagnostic if `close' fails.
1146 * save_and_redirect - Get duplicate handle for stream, was, then
1147 * redirect, was, to refer to, willbe.
1150 static int save_and_redirect(int was
, int willbe
)
1153 // No redirection specified so don't do anything but silently bailing out.
1156 // Proceeding with redirection so first save and verify our duplicate
1157 // handle for `was'.
1158 int saved
= dup(was
);
1160 fprintf(stderr
, "unable to get duplicate handle for %d\n", was
);
1164 // Duplicate handle safely established so complete redirection.
1165 set_redirection(was
, willbe
);
1167 // Finally return the saved duplicate descriptor for the
1168 // original `was' stream.
1173 * alterDeviceTo - If, toImage, is set
1174 * the argument list is altered to include
1175 * IMAGE_DEVICE and we invoke groff rather than troff.
1177 * set -Thtml and groff.
1180 static void alterDeviceTo(int argc
, char *argv
[], int toImage
)
1186 if ((strcmp(argv
[i
], "-Thtml") == 0) ||
1187 (strcmp(argv
[i
], "-Txhtml") == 0))
1188 argv
[i
] = (char *)IMAGE_DEVICE
;
1193 if (strcmp(argv
[i
], IMAGE_DEVICE
) == 0)
1194 argv
[i
] = UNCONST((dialect
== xhtml
) ? "-Txhtml" : "-Thtml");
1198 argv
[troff_arg
] = UNCONST(L_ROFF
); // use groff -Z
1202 * addArg - Append newarg onto the command list for groff.
1205 char **addArg(int argc
, char *argv
[], char *newarg
)
1207 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1210 if (new_argv
== NULL
)
1211 sys_fatal("malloc");
1214 new_argv
[i
] = argv
[i
];
1217 new_argv
[i
] = newarg
;
1219 new_argv
[i
+ 1] = argv
[i
];
1223 new_argv
[argc
] = NULL
;
1228 * addRegDef - Append a defined register or string onto the command
1232 char **addRegDef(int argc
, char *argv
[], const char *numReg
)
1234 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1237 if (new_argv
== NULL
)
1238 sys_fatal("malloc");
1241 new_argv
[i
] = argv
[i
];
1244 new_argv
[argc
] = strsave(numReg
);
1246 new_argv
[argc
] = NULL
;
1251 * dump_args - Display the argument list.
1254 void dump_args(int argc
, char *argv
[])
1256 fprintf(stderr
, " %d arguments:", argc
);
1257 for (int i
= 0; i
< argc
; i
++)
1258 fprintf(stderr
, " %s", argv
[i
]);
1259 fprintf(stderr
, "\n");
1263 * print_args - print arguments as if they were issued on the command line.
1266 void print_args(int argc
, char *argv
[])
1269 fprintf(stderr
, "executing: ");
1270 for (int i
= 0; i
< argc
; i
++)
1271 fprintf(stderr
, "%s ", argv
[i
]);
1272 fprintf(stderr
, "\n");
1276 void print_args(int, char **)
1281 int char_buffer::run_output_filter(int filter
, int argc
, char **argv
)
1287 print_args(argc
, argv
);
1288 if (pipe(pipedes
) < 0)
1291 #if MAY_FORK_CHILD_PROCESS
1292 // This is the UNIX process model. To invoke our post-processor,
1293 // we must `fork' the current process.
1295 if ((child_pid
= fork()) < 0)
1298 else if (child_pid
== 0) {
1299 // This is the child process fork. We redirect its `stdin' stream
1300 // to read data emerging from our pipe. There is no point in saving,
1301 // since we won't be able to restore later!
1303 set_redirection(STDIN_FILENO
, pipedes
[0]);
1305 // The parent process will be writing this data, so we should release
1306 // the child's writeable handle on the pipe, since we have no use for it.
1308 if (close(pipedes
[1]) < 0)
1311 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1313 if (filter
== IMAGE_OUTPUT_FILTER
) {
1314 // with BOTH `stdout' AND `stderr' diverted to files.
1316 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1317 set_redirection(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1320 // Now we are ready to launch the output filter.
1322 execvp(argv
[0], argv
);
1324 // If we get to here then the `exec...' request for the output filter
1325 // failed. Diagnose it and bail out.
1327 error("couldn't exec %1: %2", argv
[0], strerror(errno
), ((char *)0));
1328 fflush(stderr
); // just in case error() didn't
1333 // This is the parent process fork. We will be writing data to the
1334 // filter pipeline, and the child will be reading it. We have no further
1335 // use for our read handle on the pipe, and should close it.
1337 if (close(pipedes
[0]) < 0)
1340 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1341 // and push out the appropiately formatted data to the filter.
1343 pipedes
[1] = save_and_redirect(STDOUT_FILENO
, pipedes
[1]);
1344 emit_troff_output(DEVICE_FORMAT(filter
));
1346 // After emitting all the data we close our connection to the inlet
1347 // end of the pipe so the child process will detect end of data.
1349 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1351 // Finally, we must wait for the child process to complete.
1353 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1357 #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1359 // We do not have `fork', (or we prefer not to use it),
1360 // but asynchronous processes are allowed, passing data through pipes.
1361 // This should be ok for most Win32 systems and is preferred to `fork'
1362 // for starting child processes under Cygwin.
1364 // Before we start the post-processor we bind its inherited `stdin'
1365 // stream to the readable end of our pipe, saving our own `stdin' stream
1368 pipedes
[0] = save_and_redirect(STDIN_FILENO
, pipedes
[0]);
1370 // for the Win32 model,
1371 // we need special provision for saving BOTH `stdout' and `stderr'.
1373 int saved_stdout
= dup(STDOUT_FILENO
);
1374 int saved_stderr
= STDERR_FILENO
;
1376 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1378 if (filter
== IMAGE_OUTPUT_FILTER
) {
1379 // with BOTH `stdout' AND `stderr' diverted to files while saving a
1380 // duplicate handle for `stderr'.
1382 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1383 saved_stderr
= save_and_redirect(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1386 // We then use an asynchronous spawn request to start the post-processor.
1388 if ((child_pid
= spawnvp(_P_NOWAIT
, argv
[0], argv
)) < 0) {
1389 // Should the spawn request fail we issue a diagnostic and bail out.
1391 error("cannot spawn %1: %2", argv
[0], strerror(errno
), ((char *)0));
1395 // Once the post-processor has been started we revert our `stdin'
1396 // to its original saved source, which also closes the readable handle
1399 set_redirection(STDIN_FILENO
, pipedes
[0]);
1401 // if we redirected `stderr', for use by the image post-processor,
1402 // then we also need to reinstantiate its original assignment.
1404 if (filter
== IMAGE_OUTPUT_FILTER
)
1405 set_redirection(STDERR_FILENO
, saved_stderr
);
1407 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1408 // and push out the appropriately formatted data to the filter.
1410 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1411 emit_troff_output(DEVICE_FORMAT(filter
));
1413 // After emitting all the data we close our connection to the inlet
1414 // end of the pipe so the child process will detect end of data.
1416 set_redirection(STDOUT_FILENO
, saved_stdout
);
1418 // And finally, we must wait for the child process to complete.
1420 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1423 #else /* can't do asynchronous pipes! */
1425 // TODO: code to support an MS-DOS style process model
1428 #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1434 * do_html - Set the troff number htmlflip and
1435 * write out the buffer to troff -Thtml.
1438 int char_buffer::do_html(int argc
, char *argv
[])
1442 alterDeviceTo(argc
, argv
, 0);
1443 argv
+= troff_arg
; // skip all arguments up to groff
1445 argv
= addArg(argc
, argv
, (char *)"-Z");
1448 s
= (char *)"-dwww-image-template=";
1449 s
+= macroset_template
; // do not combine these statements,
1450 // otherwise they will not work
1451 s
+= '\0'; // the trailing `\0' is ignored
1452 argv
= addRegDef(argc
, argv
, s
.contents());
1455 if (dialect
== xhtml
) {
1456 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1459 argv
= addRegDef(argc
, argv
, "-e");
1464 #if defined(DEBUGGING)
1465 # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
1466 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1468 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, HTML_DEBUG_STREAM
);
1469 emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER
));
1470 set_redirection(STDOUT_FILENO
, saved_stdout
);
1474 return run_output_filter(HTML_OUTPUT_FILTER
, argc
, argv
);
1478 * do_image - Write out the buffer to troff -Tps.
1481 int char_buffer::do_image(int argc
, char *argv
[])
1485 alterDeviceTo(argc
, argv
, 1);
1486 argv
+= troff_arg
; // skip all arguments up to troff/groff
1488 argv
= addRegDef(argc
, argv
, "-rps4html=1");
1491 s
= "-dwww-image-template=";
1492 s
+= macroset_template
;
1494 argv
= addRegDef(argc
, argv
, s
.contents());
1497 // override local settings and produce a page size letter postscript file
1498 argv
= addRegDef(argc
, argv
, "-P-pletter");
1501 if (dialect
== xhtml
) {
1503 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1506 argv
= addRegDef(argc
, argv
, "-e");
1510 #if defined(DEBUGGING)
1511 # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
1512 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1514 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, IMAGE_DEBUG_STREAM
);
1515 emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER
));
1516 set_redirection(STDOUT_FILENO
, saved_stdout
);
1520 return run_output_filter(IMAGE_OUTPUT_FILTER
, argc
, argv
);
1523 static char_buffer inputFile
;
1526 * usage - Emit usage arguments.
1529 static void usage(FILE *stream
)
1533 "This program is not intended to be called stand-alone;\n"
1534 "it is part of the " L_ROFF
" pipeline to produce HTML output.\n"
1536 "If there is ever the need to call it manually (e.g., for\n"
1537 "debugging purposes), add command line option `-V' while calling\n"
1538 "the `" L_ROFF
"' program to see which arguments are passed to it.\n"
1543 * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1544 * and -P-I. Return the argument index of the first
1548 static int scanArguments(int argc
, char **argv
)
1550 const char *command_prefix
= getenv(U_ROFF_COMMAND_PREFIX
);
1551 if (!command_prefix
)
1552 command_prefix
= PROG_PREFIX
;
1553 char *troff_name
= new char[strlen(command_prefix
) + strlen("troff") + 1];
1554 strcpy(troff_name
, command_prefix
);
1555 strcat(troff_name
, "troff");
1557 static const struct option long_options
[] = {
1558 { "help", no_argument
, 0, CHAR_MAX
+ 1 },
1559 { "version", no_argument
, 0, 'v' },
1562 while ((c
= getopt_long(argc
, argv
, "+a:bdD:eF:g:hi:I:j:lno:prs:S:vVx:y",
1563 long_options
, NULL
))
1567 textAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1569 if (textAlphaBits
== 3) {
1570 error("cannot use 3 bits of antialiasing information");
1575 // handled by post-grohtml (set background color to white)
1589 font_path
.command_line_dir(optarg
);
1592 graphicAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1594 if (graphicAlphaBits
== 3) {
1595 error("cannot use 3 bits of antialiasing information");
1600 // handled by post-grohtml
1603 image_res
= atoi(optarg
);
1606 image_template
= optarg
;
1609 // handled by post-grohtml (set job name for multiple file output)
1612 // handled by post-grohtml (no automatic section links)
1615 // handled by post-grohtml (generate simple heading anchors)
1618 vertical_offset
= atoi(optarg
);
1621 show_progress
= true;
1624 // handled by post-grohtml (no header and footer lines)
1627 // handled by post-grohtml (use font size n as the html base font size)
1630 // handled by post-grohtml (set file split level)
1633 puts(L_D_PREHTML
" (" T_ROFF
") v" VERSION
);
1636 // handled by post-grohtml (create validator button)
1640 if (strcmp(optarg
, "x") == 0)
1642 else if (strcmp(optarg
, "4") == 0)
1645 printf("unsupported html dialect %s (defaulting to html4)\n", optarg
);
1648 // handled by post-grohtml (create groff signature)
1650 case CHAR_MAX
+ 1: // --help
1664 if (strcmp(argv
[i
], troff_name
) == 0)
1666 else if (argv
[i
][0] != '-')
1670 a_delete troff_name
;
1676 * makeTempFiles - Name the temporary files.
1679 static int makeTempFiles(void)
1681 #if defined(DEBUGGING)
1682 psFileName
= DEBUG_FILE("prehtml-ps");
1683 regionFileName
= DEBUG_FILE("prehtml-region");
1684 imagePageName
= DEBUG_FILE("prehtml-page");
1685 psPageName
= DEBUG_FILE("prehtml-psn");
1686 troffFileName
= DEBUG_FILE("prehtml-troff");
1687 htmlFileName
= DEBUG_FILE("prehtml-html");
1688 #else /* not DEBUGGING */
1691 /* psPageName contains a single page of postscript */
1692 f
= xtmpfile(&psPageName
,
1693 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1696 sys_fatal("xtmpfile");
1701 /* imagePageName contains a bitmap image of the single postscript page */
1702 f
= xtmpfile(&imagePageName
,
1703 PAGE_TEMPLATE_LONG
, PAGE_TEMPLATE_SHORT
,
1706 sys_fatal("xtmpfile");
1711 /* psFileName contains a postscript file of the complete document */
1712 f
= xtmpfile(&psFileName
,
1713 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1716 sys_fatal("xtmpfile");
1721 /* regionFileName contains a list of the images and their boxed coordinates */
1722 f
= xtmpfile(®ionFileName
,
1723 REGION_TEMPLATE_LONG
, REGION_TEMPLATE_SHORT
,
1726 sys_fatal("xtmpfile");
1731 #endif /* not DEBUGGING */
1735 int main(int argc
, char **argv
)
1737 program_name
= argv
[0];
1744 fprintf(stderr
, "%s: invoked with %d arguments ...\n", argv
[0], argc
);
1745 for (i
= 0; i
< argc
; i
++)
1746 fprintf(stderr
, "%2d: %s\n", i
, argv
[i
]);
1747 if ((dump
= fopen(DEBUG_FILE("pre-html-data"), "wb")) != NULL
) {
1748 while((i
= fgetc(stdin
)) >= 0)
1753 #endif /* CAPTURE_MODE */
1755 if (!font::load_desc())
1756 fatal("cannot find dev-html/DESC, exiting");
1757 image_gen
= font::image_generator
;
1758 if (image_gen
== NULL
|| (strcmp(image_gen
, "") == 0))
1759 fatal("dev-html/DESC must set the image_generator field, exiting");
1760 postscriptRes
= get_resolution();
1761 i
= scanArguments(argc
, argv
);
1766 if (argv
[i
][0] != '-') {
1767 /* found source file */
1768 ok
= do_file(argv
[i
]);
1778 if (makeTempFiles())
1780 ok
= inputFile
.do_image(argc
, argv
);
1782 generateImages(regionFileName
);
1783 ok
= inputFile
.do_html(argc
, argv
);
1788 static int do_file(const char *filename
)
1792 current_filename
= filename
;
1793 fcp
= file_case::muxer(filename
);
1795 assert(strcmp(filename
, "-"));
1796 error("can't open `%1': %2", filename
, strerror(errno
));
1800 if (inputFile
.read_file(fcp
)) {
1805 current_filename
= NULL
;