2 /* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2007, 2008
3 * Free Software Foundation, Inc.
4 * Written by Gaius Mulley (gaius@glam.ac.uk).
6 * This file is part of groff.
8 * groff 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 * groff 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.
34 #include "file_case.h"
35 #include "stringclass.h"
38 #include "searchpath.h"
44 #include <sys/types.h>
50 # include <sys/wait.h>
52 #else /* not _POSIX_VERSION */
54 #endif /* not _POSIX_VERSION */
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
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 extern "C" const char *Version_string
;
148 #include "pre-html.h"
149 #include "pushback.h"
150 #include "html-strings.h"
152 #define DEFAULT_LINE_LENGTH 7 // inches wide
153 #define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
154 #define IMAGE_BOARDER_PIXELS 0
155 #define INLINE_LEADER_CHAR '\\'
157 // Don't use colour names here! Otherwise there is a dependency on
158 // a file called `rgb.txt' which maps names to colours.
159 #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
160 #define MIN_ALPHA_BITS 0
161 #define MAX_ALPHA_BITS 4
163 #define PAGE_TEMPLATE_SHORT "pg"
164 #define PAGE_TEMPLATE_LONG "-page-"
165 #define PS_TEMPLATE_SHORT "ps"
166 #define PS_TEMPLATE_LONG "-ps-"
167 #define REGION_TEMPLATE_SHORT "rg"
168 #define REGION_TEMPLATE_LONG "-regions-"
174 # define FALSE (1==0)
178 CENTERED
, LEFT
, RIGHT
, INLINE
181 typedef enum {xhtml
, html4
} html_dialect
;
183 static int postscriptRes
= -1; // postscript resolution,
185 static int stdoutfd
= 1; // output file descriptor -
186 // normally 1 but might move
188 static char *psFileName
= NULL
; // name of postscript file
189 static char *psPageName
= NULL
; // name of file containing
190 // postscript current page
191 static char *regionFileName
= NULL
; // name of file containing all
193 static char *imagePageName
= NULL
; // name of bitmap image containing
195 static const char *image_device
= "pnmraw";
196 static int image_res
= DEFAULT_IMAGE_RES
;
197 static int vertical_offset
= 0;
198 static char *image_template
= NULL
; // image template filename
199 static char *macroset_template
= NULL
; // image template passed to troff
201 static int troff_arg
= 0; // troff arg index
202 static char *image_dir
= NULL
; // user specified image directory
203 static int textAlphaBits
= MAX_ALPHA_BITS
;
204 static int graphicAlphaBits
= MAX_ALPHA_BITS
;
205 static char *antiAlias
= NULL
; // antialias arguments we pass to gs
206 static int show_progress
= FALSE
; // should we display page numbers as
207 // they are processed?
208 static int currentPageNo
= -1; // current image page number
209 #if defined(DEBUGGING)
210 static int debug
= FALSE
;
211 static char *troffFileName
= NULL
; // output of pre-html output which
212 // is sent to troff -Tps
213 static char *htmlFileName
= NULL
; // output of pre-html output which
214 // is sent to troff -Thtml
216 static int eqn_flag
= FALSE
; // must we preprocess via eqn?
218 static char *linebuf
= NULL
; // for scanning devps/DESC
219 static int linebufsize
= 0;
220 static const char *image_gen
= NULL
; // the `gs' program
222 const char *const FONT_ENV_VAR
= "GROFF_FONT_PATH";
223 static search_path
font_path(FONT_ENV_VAR
, FONTPATH
, 0, 0);
224 static html_dialect dialect
= html4
;
228 * Images are generated via postscript, gs, and the pnm utilities.
230 #define IMAGE_DEVICE "-Tps"
233 static int do_file(const char *filename
);
237 * sys_fatal - Write a fatal error message.
238 * Taken from src/roff/groff/pipeline.c.
241 void sys_fatal(const char *s
)
243 fatal("%1: %2", s
, strerror(errno
));
247 * get_line - Copy a line (w/o newline) from a file to the
248 * global line buffer.
251 int get_line(file_case
*fcp
)
256 linebuf
= new char[128];
260 // skip leading whitespace
262 int c
= fcp
->get_c();
265 if (c
!= ' ' && c
!= '\t') {
271 int c
= fcp
->get_c();
274 if (i
+ 1 >= linebufsize
) {
275 char *old_linebuf
= linebuf
;
276 linebuf
= new char[linebufsize
* 2];
277 memcpy(linebuf
, old_linebuf
, linebufsize
);
278 a_delete old_linebuf
;
292 * get_resolution - Return the postscript resolution from devps/DESC.
295 static unsigned int get_resolution(void)
299 if ((fcp
= font_path
.open_file("devps/DESC", fcp
->fc_const_path
)) == NULL
)
300 fatal("can't open devps/DESC");
301 while (get_line(fcp
)) {
302 int n
= sscanf(linebuf
, "res %u", &res
);
306 fatal("can't find `res' keyword in devps/DESC");
313 * html_system - A wrapper for system().
316 void html_system(const char *s
, int redirect_stdout
)
318 #if defined(DEBUGGING)
320 fprintf(stderr
, "executing: ");
321 fwrite(s
, sizeof(char), strlen(s
), stderr
);
326 // Redirect standard error to the null device. This is more
327 // portable than using "2> /dev/null", since it doesn't require a
329 int save_stderr
= dup(2);
330 int save_stdout
= dup(1);
331 int fdnull
= open(NULL_DEV
, O_WRONLY
|O_BINARY
, 0666);
332 if (save_stderr
> 2 && fdnull
> 2)
334 if (redirect_stdout
&& save_stdout
> 1 && fdnull
> 1)
338 int status
= system(s
);
339 dup2(save_stderr
, 2);
341 dup2(save_stdout
, 1);
343 fprintf(stderr
, "Calling `%s' failed\n", s
);
345 fprintf(stderr
, "Calling `%s' returned status %d\n", s
, status
);
352 * make_message - Create a string via malloc and place the result of the
353 * va args into string. Finally the new string is returned.
354 * Taken from man page of printf(3).
357 char *make_message(const char *fmt
, ...)
359 /* Guess we need no more than 100 bytes. */
364 if ((p
= (char *)malloc(size
)) == NULL
)
367 /* Try to print in the allocated space. */
369 n
= vsnprintf(p
, size
, fmt
, ap
);
371 /* If that worked, return the string. */
372 if (n
> -1 && n
< size
- 1) { /* glibc 2.1 and pre-ANSI C 99 */
380 /* Else try again with more space. */
382 size
*= 2; /* twice the old size */
383 if ((np
= (char *)realloc(p
, size
)) == NULL
) {
384 free(p
); /* realloc failed, free old, p. */
387 p
= np
; /* use realloc'ed, p */
392 * the class and methods for retaining ascii text
405 * char_block - Constructor. Set the, used, and, next, fields to zero.
408 char_block::char_block()
411 for (int i
= 0; i
< SIZE
; i
++)
419 int read_file(file_case
*fcp
);
420 int do_html(int argc
, char *argv
[]);
421 int do_image(int argc
, char *argv
[]);
422 void emit_troff_output(int device_format_selector
);
423 void write_upto_newline(char_block
**t
, int *i
, int is_html
);
424 int can_see(char_block
**t
, int *i
, const char *string
);
425 int skip_spaces(char_block
**t
, int *i
);
426 void skip_until_newline(char_block
**t
, int *i
);
430 int run_output_filter(int device_format_selector
, int argc
, char *argv
[]);
434 * char_buffer - Constructor.
437 char_buffer::char_buffer()
443 * char_buffer - Destructor. Throw away the whole buffer list.
446 char_buffer::~char_buffer()
448 while (head
!= NULL
) {
449 char_block
*temp
= head
;
456 * read_file - Read in a complete file, fp, placing the contents inside
460 int char_buffer::read_file(file_case
*fcp
)
463 while (!fcp
->is_eof()) {
465 tail
= new char_block
;
469 if (tail
->used
== char_block::SIZE
) {
470 tail
->next
= new char_block
;
474 // at this point we have a tail which is ready for the next SIZE
476 n
= fcp
->get_buf(tail
->buffer
, char_block::SIZE
- tail
->used
);
478 tail
->used
+= n
* sizeof(char);
490 * writeNbytes - Write n bytes to stdout.
493 static void writeNbytes(const char *s
, int l
)
499 r
= write(stdoutfd
, s
, l
- n
);
508 * writeString - Write a string to stdout.
511 static void writeString(const char *s
)
513 writeNbytes(s
, strlen(s
));
517 * makeFileName - Create the image filename template
518 * and the macroset image template.
521 static void makeFileName(void)
523 if ((image_dir
!= NULL
) && (strchr(image_dir
, '%') != NULL
)) {
524 error("cannot use a `%%' within the image directory name");
528 if ((image_template
!= NULL
) && (strchr(image_template
, '%') != NULL
)) {
529 error("cannot use a `%%' within the image template");
533 if (image_dir
== NULL
)
534 image_dir
= (char *)"";
535 else if (strlen(image_dir
) > 0
536 && image_dir
[strlen(image_dir
) - 1] != '/') {
537 image_dir
= make_message("%s/", image_dir
);
538 if (image_dir
== NULL
)
539 sys_fatal("make_message");
542 if (image_template
== NULL
)
543 macroset_template
= make_message("%sgrohtml-%d", image_dir
,
546 macroset_template
= make_message("%s%s", image_dir
, image_template
);
548 if (macroset_template
== NULL
)
549 sys_fatal("make_message");
552 (char *)malloc(strlen("-%d") + strlen(macroset_template
) + 1);
553 if (image_template
== NULL
)
555 strcpy(image_template
, macroset_template
);
556 strcat(image_template
, "-%d");
560 * setupAntiAlias - Set up the antialias string, used when we call gs.
563 static void setupAntiAlias(void)
565 if (textAlphaBits
== 0 && graphicAlphaBits
== 0)
566 antiAlias
= make_message(" ");
567 else if (textAlphaBits
== 0)
568 antiAlias
= make_message("-dGraphicsAlphaBits=%d ", graphicAlphaBits
);
569 else if (graphicAlphaBits
== 0)
570 antiAlias
= make_message("-dTextAlphaBits=%d ", textAlphaBits
);
572 antiAlias
= make_message("-dTextAlphaBits=%d -dGraphicsAlphaBits=%d ",
573 textAlphaBits
, graphicAlphaBits
);
577 * checkImageDir - Check whether the image directory is available.
580 static void checkImageDir(void)
582 if (image_dir
!= NULL
&& strcmp(image_dir
, "") != 0)
583 if (!(mkdir(image_dir
, 0777) == 0 || errno
== EEXIST
)) {
584 error("cannot create directory `%1'", image_dir
);
590 * write_end_image - End the image. Write out the image extents if we
594 static void write_end_image(int is_html
)
597 * if we are producing html then these
598 * emit image name and enable output
600 * we are producing images
601 * in which case these generate image
604 writeString("\\O[4]\\O[2]");
606 writeString("\\O[1]");
608 writeString("\\O[0]");
612 * write_start_image - Write troff code which will:
614 * (i) disable html output for the following image
615 * (ii) reset the max/min x/y registers during postscript
619 static void write_start_image(IMAGE_ALIGNMENT pos
, int is_html
)
621 writeString("\\O[5");
637 writeString(image_template
);
638 writeString(".png]");
640 writeString("\\O[0]\\O[3]");
642 // reset min/max registers
643 writeString("\\O[1]\\O[3]");
647 * write_upto_newline - Write the contents of the buffer until a newline
648 * is seen. Check for HTML_IMAGE_INLINE_BEGIN and
649 * HTML_IMAGE_INLINE_END; process them if they are
653 void char_buffer::write_upto_newline(char_block
**t
, int *i
, int is_html
)
658 while (j
< (*t
)->used
659 && (*t
)->buffer
[j
] != '\n'
660 && (*t
)->buffer
[j
] != INLINE_LEADER_CHAR
)
663 && (*t
)->buffer
[j
] == '\n')
665 writeNbytes((*t
)->buffer
+ (*i
), j
- (*i
));
666 if ((*t
)->buffer
[j
] == INLINE_LEADER_CHAR
) {
667 if (can_see(t
, &j
, HTML_IMAGE_INLINE_BEGIN
))
668 write_start_image(INLINE
, is_html
);
669 else if (can_see(t
, &j
, HTML_IMAGE_INLINE_END
))
670 write_end_image(is_html
);
672 if (j
< (*t
)->used
) {
675 writeNbytes((*t
)->buffer
+ (*i
), j
- (*i
));
679 if (j
== (*t
)->used
) {
682 if (*t
&& (*t
)->buffer
[j
- 1] != '\n')
683 write_upto_newline(t
, i
, is_html
);
692 * can_see - Return TRUE if we can see string in t->buffer[i] onwards.
695 int char_buffer::can_see(char_block
**t
, int *i
, const char *str
)
703 while (k
< s
->used
&& j
< l
&& s
->buffer
[k
] == str
[j
]) {
712 else if (k
< s
->used
&& s
->buffer
[k
] != str
[j
])
721 * skip_spaces - Return TRUE if we have not run out of data.
722 * Consume spaces also.
725 int char_buffer::skip_spaces(char_block
**t
, int *i
)
731 while (k
< s
->used
&& isspace(s
->buffer
[k
]))
746 * skip_until_newline - Skip all characters until a newline is seen.
747 * The newline is not consumed.
750 void char_buffer::skip_until_newline(char_block
**t
, int *i
)
755 while (j
< (*t
)->used
&& (*t
)->buffer
[j
] != '\n')
757 if (j
== (*t
)->used
) {
760 skip_until_newline(t
, i
);
768 #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
769 #define HTML_OUTPUT_FILTER 0
770 #define IMAGE_OUTPUT_FILTER 1
771 #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
772 #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
773 #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
776 * emit_troff_output - Write formatted buffer content to the troff
777 * post-processor data pipeline.
780 void char_buffer::emit_troff_output(int device_format_selector
)
782 // Handle output for BOTH html and image device formats
783 // if `device_format_selector' is passed as
785 // HTML_FORMAT(HTML_OUTPUT_FILTER)
786 // Buffer data is written to the output stream
787 // with template image names translated to actual image names.
789 // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
790 // Buffer data is written to the output stream
791 // with no translation, for image file creation in the post-processor.
794 char_block
*element
= head
;
796 while (element
!= NULL
)
797 write_upto_newline(&element
, &idx
, device_format_selector
);
800 if (close(stdoutfd
) < 0)
803 // now we grab fd=1 so that the next pipe cannot use fd=1
805 if (dup(2) != stdoutfd
)
806 sys_fatal ("dup failed to use fd=1");
812 * The image class remembers the position of all images in the
813 * postscript file and assigns names for each image.
827 imageItem(int x1
, int y1
, int x2
, int y2
,
828 int page
, int res
, int max_width
, char *name
);
833 * imageItem - Constructor.
836 imageItem::imageItem(int x1
, int y1
, int x2
, int y2
,
837 int page
, int res
, int max_width
, char *name
)
851 * imageItem - Destructor.
854 imageItem::~imageItem()
861 * imageList - A class containing a list of imageItems.
872 void add(int x1
, int y1
, int x2
, int y2
,
873 int page
, int res
, int maxx
, char *name
);
874 void createImages(void);
875 int createPage(int pageno
);
876 void createImage(imageItem
*i
);
877 int getMaxX(int pageno
);
881 * imageList - Constructor.
884 imageList::imageList()
885 : head(0), tail(0), count(0)
890 * imageList - Destructor.
893 imageList::~imageList()
895 while (head
!= NULL
) {
903 * createPage - Create one image of, page pageno, from the postscript file.
906 int imageList::createPage(int pageno
)
910 if (currentPageNo
== pageno
)
913 if (currentPageNo
>= 1) {
915 * We need to unlink the files which change each time a new page is
916 * processed. The final unlink is done by xtmpfile when pre-grohtml
919 unlink(imagePageName
);
924 fprintf(stderr
, "[%d] ", pageno
);
928 #if defined(DEBUGGING)
930 fprintf(stderr
, "creating page %d\n", pageno
);
933 s
= make_message("psselect -q -p%d %s %s\n",
934 pageno
, psFileName
, psPageName
);
937 sys_fatal("make_message");
940 s
= make_message("echo showpage | "
941 "%s%s -q -dBATCH -dSAFER "
942 "-dDEVICEHEIGHTPOINTS=792 "
943 "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
944 "-sDEVICE=%s -r%d %s "
945 "-sOutputFile=%s %s -\n",
948 (getMaxX(pageno
) * image_res
) / postscriptRes
,
955 sys_fatal("make_message");
958 currentPageNo
= pageno
;
963 * min - Return the minimum of two numbers.
966 int min(int x
, int y
)
975 * max - Return the maximum of two numbers.
978 int max(int x
, int y
)
987 * getMaxX - Return the largest right-hand position for any image
991 int imageList::getMaxX(int pageno
)
994 int x
= postscriptRes
* DEFAULT_LINE_LENGTH
;
997 if (h
->pageNo
== pageno
)
1005 * createImage - Generate a minimal png file from the set of page images.
1008 void imageList::createImage(imageItem
*i
)
1012 int x1
= max(min(i
->X1
, i
->X2
) * image_res
/ postscriptRes
1013 - IMAGE_BOARDER_PIXELS
,
1015 int y1
= max(image_res
* vertical_offset
/ 72
1016 + min(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
1017 - IMAGE_BOARDER_PIXELS
,
1019 int x2
= max(i
->X1
, i
->X2
) * image_res
/ postscriptRes
1020 + IMAGE_BOARDER_PIXELS
;
1021 int y2
= image_res
* vertical_offset
/ 72
1022 + max(i
->Y1
, i
->Y2
) * image_res
/ postscriptRes
1023 + 1 + IMAGE_BOARDER_PIXELS
;
1024 if (createPage(i
->pageNo
) == 0) {
1025 s
= make_message("pnmcut%s %d %d %d %d < %s "
1026 "| pnmcrop -quiet | pnmtopng%s %s > %s\n",
1028 x1
, y1
, x2
- x1
+ 1, y2
- y1
+ 1,
1034 sys_fatal("make_message");
1040 fprintf(stderr
, "failed to generate image of page %d\n", i
->pageNo
);
1043 #if defined(DEBUGGING)
1047 fprintf(stderr
, "ignoring image as x1 coord is -1\n");
1055 * add - Add an image description to the imageList.
1058 void imageList::add(int x1
, int y1
, int x2
, int y2
,
1059 int page
, int res
, int maxx
, char *name
)
1061 imageItem
*i
= new imageItem(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1074 * createImages - For each image descriptor on the imageList,
1075 * create the actual image.
1078 void imageList::createImages(void)
1080 imageItem
*h
= head
;
1088 static imageList listOfImages
; // List of images defined by the region file.
1091 * generateImages - Parse the region file and generate images
1092 * from the postscript file. The region file
1093 * contains the x1,y1--x2,y2 extents of each
1097 static void generateImages(char *region_file_name
)
1099 pushBackBuffer
*f
=new pushBackBuffer(region_file_name
);
1101 while (f
->putPB(f
->getPB()) != eof
) {
1102 if (f
->isString("grohtml-info:page")) {
1103 int page
= f
->readInt();
1104 int x1
= f
->readInt();
1105 int y1
= f
->readInt();
1106 int x2
= f
->readInt();
1107 int y2
= f
->readInt();
1108 int maxx
= f
->readInt();
1109 char *name
= f
->readString();
1110 int res
= postscriptRes
;
1111 listOfImages
.add(x1
, y1
, x2
, y2
, page
, res
, maxx
, name
);
1112 while (f
->putPB(f
->getPB()) != '\n'
1113 && f
->putPB(f
->getPB()) != eof
)
1115 if (f
->putPB(f
->getPB()) == '\n')
1119 /* Write any error messages out to the user. */
1120 fputc(f
->getPB(), stderr
);
1124 listOfImages
.createImages();
1125 if (show_progress
) {
1126 fprintf(stderr
, "done\n");
1133 * set_redirection - Set up I/O Redirection for handle, was, to refer to
1134 * stream on handle, willbe.
1137 static void set_redirection(int was
, int willbe
)
1139 // Nothing to do if `was' and `willbe' already have same handle.
1140 if (was
!= willbe
) {
1141 // Otherwise attempt the specified redirection.
1142 if (dup2 (willbe
, was
) < 0) {
1143 // Redirection failed, so issue diagnostic and bail out.
1144 fprintf(stderr
, "failed to replace fd=%d with %d\n", was
, willbe
);
1145 if (willbe
== STDOUT_FILENO
)
1147 "likely that stdout should be opened before %d\n", was
);
1151 // When redirection has been successfully completed assume redundant
1152 // handle `willbe' is no longer required, so close it.
1153 if (close(willbe
) < 0)
1154 // Issue diagnostic if `close' fails.
1160 * save_and_redirect - Get duplicate handle for stream, was, then
1161 * redirect, was, to refer to, willbe.
1164 static int save_and_redirect(int was
, int willbe
)
1167 // No redirection specified so don't do anything but silently bailing out.
1170 // Proceeding with redirection so first save and verify our duplicate
1171 // handle for `was'.
1172 int saved
= dup(was
);
1174 fprintf(stderr
, "unable to get duplicate handle for %d\n", was
);
1178 // Duplicate handle safely established so complete redirection.
1179 set_redirection(was
, willbe
);
1181 // Finally return the saved duplicate descriptor for the
1182 // original `was' stream.
1187 * alterDeviceTo - If, toImage, is set
1188 * the argument list is altered to include
1189 * IMAGE_DEVICE and we invoke groff rather than troff.
1191 * set -Thtml and groff.
1194 static void alterDeviceTo(int argc
, char *argv
[], int toImage
)
1200 if ((strcmp(argv
[i
], "-Thtml") == 0) ||
1201 (strcmp(argv
[i
], "-Txhtml") == 0))
1202 argv
[i
] = (char *)IMAGE_DEVICE
;
1205 argv
[troff_arg
] = (char *)"groff"; /* rather than troff */
1209 if (strcmp(argv
[i
], IMAGE_DEVICE
) == 0)
1210 if (dialect
== xhtml
)
1211 argv
[i
] = (char *)"-Txhtml";
1213 argv
[i
] = (char *)"-Thtml";
1216 argv
[troff_arg
] = (char *)"groff"; /* use groff -Z */
1221 * addArg - Append newarg onto the command list for groff.
1224 char **addArg(int argc
, char *argv
[], char *newarg
)
1226 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1229 if (new_argv
== NULL
)
1230 sys_fatal("malloc");
1233 new_argv
[i
] = argv
[i
];
1236 new_argv
[i
] = newarg
;
1238 new_argv
[i
+ 1] = argv
[i
];
1242 new_argv
[argc
] = NULL
;
1247 * addRegDef - Append a defined register or string onto the command
1251 char **addRegDef(int argc
, char *argv
[], const char *numReg
)
1253 char **new_argv
= (char **)malloc((argc
+ 2) * sizeof(char *));
1256 if (new_argv
== NULL
)
1257 sys_fatal("malloc");
1260 new_argv
[i
] = argv
[i
];
1263 new_argv
[argc
] = strsave(numReg
);
1265 new_argv
[argc
] = NULL
;
1270 * dump_args - Display the argument list.
1273 void dump_args(int argc
, char *argv
[])
1275 fprintf(stderr
, " %d arguments:", argc
);
1276 for (int i
= 0; i
< argc
; i
++)
1277 fprintf(stderr
, " %s", argv
[i
]);
1278 fprintf(stderr
, "\n");
1282 * print_args - print arguments as if they were issued on the command line.
1285 #if defined(DEBUGGING)
1287 void print_args(int argc
, char *argv
[])
1290 fprintf(stderr
, "executing: ");
1291 for (int i
= 0; i
< argc
; i
++)
1292 fprintf(stderr
, "%s ", argv
[i
]);
1293 fprintf(stderr
, "\n");
1299 void print_args(int, char **)
1305 int char_buffer::run_output_filter(int filter
, int argc
, char **argv
)
1311 print_args(argc
, argv
);
1312 if (pipe(pipedes
) < 0)
1315 #if MAY_FORK_CHILD_PROCESS
1316 // This is the UNIX process model. To invoke our post-processor,
1317 // we must `fork' the current process.
1319 if ((child_pid
= fork()) < 0)
1322 else if (child_pid
== 0) {
1323 // This is the child process fork. We redirect its `stdin' stream
1324 // to read data emerging from our pipe. There is no point in saving,
1325 // since we won't be able to restore later!
1327 set_redirection(STDIN_FILENO
, pipedes
[0]);
1329 // The parent process will be writing this data, so we should release
1330 // the child's writeable handle on the pipe, since we have no use for it.
1332 if (close(pipedes
[1]) < 0)
1335 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1337 if (filter
== IMAGE_OUTPUT_FILTER
) {
1338 // with BOTH `stdout' AND `stderr' diverted to files.
1340 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1341 set_redirection(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1344 // Now we are ready to launch the output filter.
1346 execvp(argv
[0], argv
);
1348 // If we get to here then the `exec...' request for the output filter
1349 // failed. Diagnose it and bail out.
1351 error("couldn't exec %1: %2", argv
[0], strerror(errno
), ((char *)0));
1352 fflush(stderr
); // just in case error() didn't
1357 // This is the parent process fork. We will be writing data to the
1358 // filter pipeline, and the child will be reading it. We have no further
1359 // use for our read handle on the pipe, and should close it.
1361 if (close(pipedes
[0]) < 0)
1364 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1365 // and push out the appropiately formatted data to the filter.
1367 pipedes
[1] = save_and_redirect(STDOUT_FILENO
, pipedes
[1]);
1368 emit_troff_output(DEVICE_FORMAT(filter
));
1370 // After emitting all the data we close our connection to the inlet
1371 // end of the pipe so the child process will detect end of data.
1373 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1375 // Finally, we must wait for the child process to complete.
1377 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1381 #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1383 // We do not have `fork', (or we prefer not to use it),
1384 // but asynchronous processes are allowed, passing data through pipes.
1385 // This should be ok for most Win32 systems and is preferred to `fork'
1386 // for starting child processes under Cygwin.
1388 // Before we start the post-processor we bind its inherited `stdin'
1389 // stream to the readable end of our pipe, saving our own `stdin' stream
1392 pipedes
[0] = save_and_redirect(STDIN_FILENO
, pipedes
[0]);
1394 // for the Win32 model,
1395 // we need special provision for saving BOTH `stdout' and `stderr'.
1397 int saved_stdout
= dup(STDOUT_FILENO
);
1398 int saved_stderr
= STDERR_FILENO
;
1400 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1402 if (filter
== IMAGE_OUTPUT_FILTER
) {
1403 // with BOTH `stdout' AND `stderr' diverted to files while saving a
1404 // duplicate handle for `stderr'.
1406 set_redirection(STDOUT_FILENO
, PS_OUTPUT_STREAM
);
1407 saved_stderr
= save_and_redirect(STDERR_FILENO
, REGION_OUTPUT_STREAM
);
1410 // We then use an asynchronous spawn request to start the post-processor.
1412 if ((child_pid
= spawnvp(_P_NOWAIT
, argv
[0], argv
)) < 0) {
1413 // Should the spawn request fail we issue a diagnostic and bail out.
1415 error("cannot spawn %1: %2", argv
[0], strerror(errno
), ((char *)0));
1419 // Once the post-processor has been started we revert our `stdin'
1420 // to its original saved source, which also closes the readable handle
1423 set_redirection(STDIN_FILENO
, pipedes
[0]);
1425 // if we redirected `stderr', for use by the image post-processor,
1426 // then we also need to reinstate its original assignment.
1428 if (filter
== IMAGE_OUTPUT_FILTER
)
1429 set_redirection(STDERR_FILENO
, saved_stderr
);
1431 // Now we redirect the `stdout' stream to the inlet end of the pipe,
1432 // and push out the appropiately formatted data to the filter.
1434 set_redirection(STDOUT_FILENO
, pipedes
[1]);
1435 emit_troff_output(DEVICE_FORMAT(filter
));
1437 // After emitting all the data we close our connection to the inlet
1438 // end of the pipe so the child process will detect end of data.
1440 set_redirection(STDOUT_FILENO
, saved_stdout
);
1442 // And finally, we must wait for the child process to complete.
1444 if (WAIT(&status
, child_pid
, _WAIT_CHILD
) != child_pid
)
1447 #else /* can't do asynchronous pipes! */
1449 // TODO: code to support an MS-DOS style process model
1452 #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1458 * do_html - Set the troff number htmlflip and
1459 * write out the buffer to troff -Thtml.
1462 int char_buffer::do_html(int argc
, char *argv
[])
1466 alterDeviceTo(argc
, argv
, 0);
1467 argv
+= troff_arg
; // skip all arguments up to groff
1469 argv
= addArg(argc
, argv
, (char *)"-Z");
1472 s
= (char *)"-dwww-image-template=";
1473 s
+= macroset_template
; // do not combine these statements,
1474 // otherwise they will not work
1475 s
+= '\0'; // the trailing `\0' is ignored
1476 argv
= addRegDef(argc
, argv
, s
.contents());
1479 if (dialect
== xhtml
) {
1480 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1483 argv
= addRegDef(argc
, argv
, "-e");
1488 #if defined(DEBUGGING)
1489 # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
1490 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1492 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, HTML_DEBUG_STREAM
);
1493 emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER
));
1494 set_redirection(STDOUT_FILENO
, saved_stdout
);
1498 return run_output_filter(HTML_OUTPUT_FILTER
, argc
, argv
);
1502 * do_image - Write out the buffer to troff -Tps.
1505 int char_buffer::do_image(int argc
, char *argv
[])
1509 alterDeviceTo(argc
, argv
, 1);
1510 argv
+= troff_arg
; // skip all arguments up to troff/groff
1512 argv
= addRegDef(argc
, argv
, "-rps4html=1");
1515 s
= "-dwww-image-template=";
1516 s
+= macroset_template
;
1518 argv
= addRegDef(argc
, argv
, s
.contents());
1521 // override local settings and produce a page size letter postscript file
1522 argv
= addRegDef(argc
, argv
, "-P-pletter");
1525 if (dialect
== xhtml
) {
1527 argv
= addRegDef(argc
, argv
, "-rxhtml=1");
1530 argv
= addRegDef(argc
, argv
, "-e");
1534 #if defined(DEBUGGING)
1535 # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
1536 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1538 int saved_stdout
= save_and_redirect(STDOUT_FILENO
, IMAGE_DEBUG_STREAM
);
1539 emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER
));
1540 set_redirection(STDOUT_FILENO
, saved_stdout
);
1544 return run_output_filter(IMAGE_OUTPUT_FILTER
, argc
, argv
);
1547 static char_buffer inputFile
;
1550 * usage - Emit usage arguments.
1553 static void usage(FILE *stream
)
1557 "This program is not intended to be called stand-alone;\n"
1558 "it is part of the groff pipeline to produce HTML output.\n"
1560 "If there is ever the need to call it manually (e.g., for\n"
1561 "debugging purposes), add command line option `-V' while calling\n"
1562 "the `groff' program to see which arguments are passed to it.\n"
1567 * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1568 * and -P-I. Return the argument index of the first
1572 static int scanArguments(int argc
, char **argv
)
1574 const char *command_prefix
= getenv("GROFF_COMMAND_PREFIX");
1575 if (!command_prefix
)
1576 command_prefix
= PROG_PREFIX
;
1577 char *troff_name
= new char[strlen(command_prefix
) + strlen("troff") + 1];
1578 strcpy(troff_name
, command_prefix
);
1579 strcat(troff_name
, "troff");
1581 static const struct option long_options
[] = {
1582 { "help", no_argument
, 0, CHAR_MAX
+ 1 },
1583 { "version", no_argument
, 0, 'v' },
1586 while ((c
= getopt_long(argc
, argv
, "+a:bdD:eF:g:hi:I:j:lno:prs:S:vVx:y",
1587 long_options
, NULL
))
1591 textAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1593 if (textAlphaBits
== 3) {
1594 error("cannot use 3 bits of antialiasing information");
1599 // handled by post-grohtml (set background color to white)
1602 #if defined(DEBUGGING)
1613 font_path
.command_line_dir(optarg
);
1616 graphicAlphaBits
= min(max(MIN_ALPHA_BITS
, atoi(optarg
)),
1618 if (graphicAlphaBits
== 3) {
1619 error("cannot use 3 bits of antialiasing information");
1624 // handled by post-grohtml
1627 image_res
= atoi(optarg
);
1630 image_template
= optarg
;
1633 // handled by post-grohtml (set job name for multiple file output)
1636 // handled by post-grohtml (no automatic section links)
1639 // handled by post-grohtml (generate simple heading anchors)
1642 vertical_offset
= atoi(optarg
);
1645 show_progress
= TRUE
;
1648 // handled by post-grohtml (no header and footer lines)
1651 // handled by post-grohtml (use font size n as the html base font size)
1654 // handled by post-grohtml (set file split level)
1657 printf("GNU pre-grohtml (groff) version %s\n", Version_string
);
1660 // handled by post-grohtml (create validator button)
1664 if (strcmp(optarg
, "x") == 0)
1666 else if (strcmp(optarg
, "4") == 0)
1669 printf("unsupported html dialect %s (defaulting to html4)\n", optarg
);
1672 // handled by post-grohtml (create groff signature)
1674 case CHAR_MAX
+ 1: // --help
1688 if (strcmp(argv
[i
], troff_name
) == 0)
1690 else if (argv
[i
][0] != '-')
1694 a_delete troff_name
;
1700 * makeTempFiles - Name the temporary files.
1703 static int makeTempFiles(void)
1705 #if defined(DEBUGGING)
1706 psFileName
= DEBUG_FILE("prehtml-ps");
1707 regionFileName
= DEBUG_FILE("prehtml-region");
1708 imagePageName
= DEBUG_FILE("prehtml-page");
1709 psPageName
= DEBUG_FILE("prehtml-psn");
1710 troffFileName
= DEBUG_FILE("prehtml-troff");
1711 htmlFileName
= DEBUG_FILE("prehtml-html");
1712 #else /* not DEBUGGING */
1715 /* psPageName contains a single page of postscript */
1716 f
= xtmpfile(&psPageName
,
1717 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1720 sys_fatal("xtmpfile");
1725 /* imagePageName contains a bitmap image of the single postscript page */
1726 f
= xtmpfile(&imagePageName
,
1727 PAGE_TEMPLATE_LONG
, PAGE_TEMPLATE_SHORT
,
1730 sys_fatal("xtmpfile");
1735 /* psFileName contains a postscript file of the complete document */
1736 f
= xtmpfile(&psFileName
,
1737 PS_TEMPLATE_LONG
, PS_TEMPLATE_SHORT
,
1740 sys_fatal("xtmpfile");
1745 /* regionFileName contains a list of the images and their boxed coordinates */
1746 f
= xtmpfile(®ionFileName
,
1747 REGION_TEMPLATE_LONG
, REGION_TEMPLATE_SHORT
,
1750 sys_fatal("xtmpfile");
1755 #endif /* not DEBUGGING */
1759 int main(int argc
, char **argv
)
1761 program_name
= argv
[0];
1768 fprintf(stderr
, "%s: invoked with %d arguments ...\n", argv
[0], argc
);
1769 for (i
= 0; i
< argc
; i
++)
1770 fprintf(stderr
, "%2d: %s\n", i
, argv
[i
]);
1771 if ((dump
= fopen(DEBUG_FILE("pre-html-data"), "wb")) != NULL
) {
1772 while((i
= fgetc(stdin
)) >= 0)
1777 #endif /* CAPTURE_MODE */
1779 if (!font::load_desc())
1780 fatal("cannot find devhtml/DESC exiting");
1781 image_gen
= font::image_generator
;
1782 if (image_gen
== NULL
|| (strcmp(image_gen
, "") == 0))
1783 fatal("devhtml/DESC must set the image_generator field, exiting");
1784 postscriptRes
= get_resolution();
1785 i
= scanArguments(argc
, argv
);
1790 if (argv
[i
][0] != '-') {
1791 /* found source file */
1792 ok
= do_file(argv
[i
]);
1802 if (makeTempFiles())
1804 ok
= inputFile
.do_image(argc
, argv
);
1806 generateImages(regionFileName
);
1807 ok
= inputFile
.do_html(argc
, argv
);
1812 static int do_file(const char *filename
)
1816 current_filename
= filename
;
1817 fcp
= file_case::muxer(filename
);
1819 assert(strcmp(filename
, "-"));
1820 error("can't open `%1': %2", filename
, strerror(errno
));
1824 if (inputFile
.read_file(fcp
)) {
1829 current_filename
= NULL
;