* src/libs/libgroff/quotearg.c: New file, providing proper argument
[s-roff.git] / src / preproc / html / pre-html.cpp
blob604547d16fc86275282566a9a5f4bf26118b485e
1 // -*- C++ -*-
2 /* Copyright (C) 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
3 Written by Gaius Mulley (gaius@glam.ac.uk).
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING. If not, write to the Free Software
19 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 #define PREHTMLC
23 #include "lib.h"
25 #include <signal.h>
26 #include <ctype.h>
27 #include <assert.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 #include "errarg.h"
31 #include "error.h"
32 #include "stringclass.h"
33 #include "posix.h"
34 #include "defs.h"
35 #include "searchpath.h"
36 #include "paper.h"
37 #include "font.h"
39 #include <errno.h>
40 #include <sys/types.h>
41 #ifdef HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif
45 #ifdef _POSIX_VERSION
46 # include <sys/wait.h>
47 # define PID_T pid_t
48 #else /* not _POSIX_VERSION */
49 # define PID_T int
50 #endif /* not _POSIX_VERSION */
52 #include <stdarg.h>
54 #include "nonposix.h"
56 /* Establish some definitions to facilitate discrimination between
57 differing runtime environments. */
59 #undef MAY_FORK_CHILD_PROCESS
60 #undef MAY_SPAWN_ASYNCHRONOUS_CHILD
62 #if defined(__MSDOS__) || defined(_WIN32)
64 // Most MS-DOS and Win32 environments will be missing the `fork' capability
65 // (some like Cygwin have it, but it is best avoided).
67 # define MAY_FORK_CHILD_PROCESS 0
69 // On these systems, we use `spawn...', instead of `fork' ... `exec...'.
70 # include <process.h> // for `spawn...'
71 # include <fcntl.h> // for attributes of pipes
73 # if defined(__CYGWIN__) || defined(_UWIN) \
74 || defined(__MINGW32__) || defined(_WIN32)
76 // These Win32 implementations allow parent and `spawn...'ed child to
77 // multitask asynchronously.
79 # ifdef __cplusplus
80 extern "C" spawnvp_wrapper(int, char *, char **);
81 # endif
83 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
85 # else
87 // Others may adopt MS-DOS behaviour where parent must sleep,
88 // from `spawn...' until child terminates. */
90 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
92 # endif /* not defined __CYGWIN__, _UWIN, or __MINGW32__ */
94 # if defined(DEBUGGING) && !defined(DEBUG_FILE)
95 /* When we are building a DEBUGGING version we need to tell pre-grohtml
96 where to put intermediate files (the DEBUGGING version will preserve
97 these on exit).
99 On a UNIX host, we might simply use `/tmp', but MS-DOS and Win32 will
100 probably not have this on all disk drives, so default to using
101 `c:/temp' instead. (Note that user may choose to override this by
102 supplying a definition such as
104 -DDEBUG_FILE="d:/path/to/debug/files/"
106 in the CPPFLAGS to `make'. If overriding in this manner, the trailing
107 `/' MUST be included in the definition.) */
108 # define DEBUG_FILE "c:/temp/"
109 # endif
111 #else /* not __MSDOS__ or _WIN32 */
113 // For non-Microsoft environments assume UNIX conventions,
114 // so `fork' is required and child processes are asynchronous.
115 # define MAY_FORK_CHILD_PROCESS 1
116 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
118 # if defined(DEBUGGING) && !defined(DEBUG_FILE)
119 /* For a DEBUGGING version, on the UNIX host, we can also usually rely
120 on being able to use `/tmp' for temporary file storage. (Note that,
121 as in the __MSDOS__ or _WIN32 case above, the user may override this
122 by defining
124 -DDEBUG_FILE="/path/to/debug/files/"
126 in the CPPFLAGS, again noting that the trailing `/' is REQUIRED.) */
127 # define DEBUG_FILE "/tmp/"
128 # endif
130 #endif /* not __MSDOS__ or _WIN32 */
132 extern "C" const char *Version_string;
134 #include "pre-html.h"
135 #include "pushback.h"
136 #include "html-strings.h"
138 #define DEFAULT_LINE_LENGTH 7 // inches wide
139 #define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
140 #define IMAGE_BOARDER_PIXELS 0
141 #define INLINE_LEADER_CHAR '\\'
143 // Don't use colour names here! Otherwise there is a dependency on
144 // a file called `rgb.txt' which maps names to colours.
145 #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
146 #define MIN_ALPHA_BITS 0
147 #define MAX_ALPHA_BITS 4
149 #define PAGE_TEMPLATE_SHORT "pg"
150 #define PAGE_TEMPLATE_LONG "-page-"
151 #define PS_TEMPLATE_SHORT "ps"
152 #define PS_TEMPLATE_LONG "-ps-"
153 #define REGION_TEMPLATE_SHORT "rg"
154 #define REGION_TEMPLATE_LONG "-regions-"
156 #if 0
157 # define DEBUGGING
158 #endif
160 #if !defined(TRUE)
161 # define TRUE (1==1)
162 #endif
163 #if !defined(FALSE)
164 # define FALSE (1==0)
165 #endif
167 typedef enum {
168 CENTERED, LEFT, RIGHT, INLINE
169 } IMAGE_ALIGNMENT;
171 static int postscriptRes = -1; // postscript resolution,
172 // dots per inch
173 static int stdoutfd = 1; // output file descriptor -
174 // normally 1 but might move
175 // -1 means closed
176 static char *psFileName = NULL; // name of postscript file
177 static char *psPageName = NULL; // name of file containing
178 // postscript current page
179 static char *regionFileName = NULL; // name of file containing all
180 // image regions
181 static char *imagePageName = NULL; // name of bitmap image containing
182 // current page
183 static const char *image_device = "pnmraw";
184 static int image_res = DEFAULT_IMAGE_RES;
185 static int vertical_offset = 0;
186 static char *image_template = NULL; // image template filename
187 static char *macroset_template= NULL; // image template passed to troff
188 // by -D
189 static int troff_arg = 0; // troff arg index
190 static char *image_dir = NULL; // user specified image directory
191 static int textAlphaBits = MAX_ALPHA_BITS;
192 static int graphicAlphaBits = MAX_ALPHA_BITS;
193 static char *antiAlias = NULL; // antialias arguments we pass to gs
194 static int show_progress = FALSE; // should we display page numbers as
195 // they are processed?
196 static int currentPageNo = -1; // current image page number
197 #if defined(DEBUGGING)
198 static int debug = FALSE;
199 static char *troffFileName = NULL; // output of pre-html output which
200 // is sent to troff -Tps
201 static char *htmlFileName = NULL; // output of pre-html output which
202 // is sent to troff -Thtml
203 #endif
205 static char *linebuf = NULL; // for scanning devps/DESC
206 static int linebufsize = 0;
208 const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
209 static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
213 * Images are generated via postscript, gs, and the pnm utilities.
215 #define IMAGE_DEVICE "-Tps"
218 static int do_file(const char *filename);
222 * sys_fatal - Write a fatal error message.
223 * Taken from src/roff/groff/pipeline.c.
226 void sys_fatal(const char *s)
228 fatal("%1: %2", s, strerror(errno));
232 * get_line - Copy a line (w/o newline) from a file to the
233 * global line buffer.
236 int get_line(FILE *f)
238 if (f == 0)
239 return 0;
240 if (linebuf == 0) {
241 linebuf = new char[128];
242 linebufsize = 128;
244 int i = 0;
245 // skip leading whitespace
246 for (;;) {
247 int c = getc(f);
248 if (c == EOF)
249 return 0;
250 if (c != ' ' && c != '\t') {
251 ungetc(c, f);
252 break;
255 for (;;) {
256 int c = getc(f);
257 if (c == EOF)
258 break;
259 if (i + 1 >= linebufsize) {
260 char *old_linebuf = linebuf;
261 linebuf = new char[linebufsize * 2];
262 memcpy(linebuf, old_linebuf, linebufsize);
263 a_delete old_linebuf;
264 linebufsize *= 2;
266 linebuf[i++] = c;
267 if (c == '\n') {
268 i--;
269 break;
272 linebuf[i] = '\0';
273 return 1;
277 * get_resolution - Return the postscript resolution from devps/DESC.
280 static unsigned int get_resolution(void)
282 char *pathp;
283 FILE *f;
284 unsigned int res;
285 f = font_path.open_file("devps/DESC", &pathp);
286 a_delete pathp;
287 if (f == 0)
288 fatal("can't open devps/DESC");
289 while (get_line(f)) {
290 int n = sscanf(linebuf, "res %u", &res);
291 if (n >= 1) {
292 fclose(f);
293 return res;
296 fatal("can't find `res' keyword in devps/DESC");
297 return 0;
301 * html_system - A wrapper for system().
304 void html_system(const char *s, int redirect_stdout)
306 // Redirect standard error to the null device. This is more
307 // portable than using "2> /dev/null", since it doesn't require a
308 // Unixy shell.
309 int save_stderr = dup(2);
310 int save_stdout = dup(1);
311 int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
312 if (save_stderr > 2 && fdnull > 2)
313 dup2(fdnull, 2);
314 if (redirect_stdout && save_stdout > 1 && fdnull > 1)
315 dup2(fdnull, 1);
316 if (fdnull >= 0)
317 close(fdnull);
318 int status = system(s);
319 dup2(save_stderr, 2);
320 if (redirect_stdout)
321 dup2(save_stdout, 1);
322 if (status == -1)
323 fprintf(stderr, "Calling `%s' failed\n", s);
324 else if (status)
325 fprintf(stderr, "Calling `%s' returned status %d\n", s, status);
326 close(save_stderr);
327 close(save_stdout);
331 * make_message - Create a string via malloc and place the result of the
332 * va args into string. Finally the new string is returned.
333 * Taken from man page of printf(3).
336 char *make_message(const char *fmt, ...)
338 /* Guess we need no more than 100 bytes. */
339 int n, size = 100;
340 char *p;
341 char *np;
342 va_list ap;
343 if ((p = (char *)malloc(size)) == NULL)
344 return NULL;
345 while (1) {
346 /* Try to print in the allocated space. */
347 va_start(ap, fmt);
348 n = vsnprintf(p, size, fmt, ap);
349 va_end(ap);
350 /* If that worked, return the string. */
351 if (n > -1 && n < size) {
352 if (size > n + 1) {
353 np = strsave(p);
354 free(p);
355 return np;
357 return p;
359 /* Else try again with more space. */
360 if (n > -1) /* glibc 2.1 */
361 size = n + 1; /* precisely what is needed */
362 else /* glibc 2.0 */
363 size *= 2; /* twice the old size */
364 if ((np = (char *)realloc(p, size)) == NULL) {
365 free(p); /* realloc failed, free old, p. */
366 return NULL;
368 p = np; /* use realloc'ed, p */
373 * the class and methods for retaining ascii text
376 struct char_block {
377 enum { SIZE = 256 };
378 char buffer[SIZE];
379 int used;
380 char_block *next;
382 char_block();
386 * char_block - Constructor. Set the, used, and, next, fields to zero.
389 char_block::char_block()
390 : used(0), next(0)
392 for (int i = 0; i < SIZE; i++)
393 buffer[i] = 0;
396 class char_buffer {
397 public:
398 char_buffer();
399 ~char_buffer();
400 int read_file(FILE *fp);
401 int do_html(int argc, char *argv[]);
402 int do_image(int argc, char *argv[]);
403 void emit_troff_output(int device_format_selector);
404 void write_upto_newline(char_block **t, int *i, int is_html);
405 int can_see(char_block **t, int *i, const char *string);
406 int skip_spaces(char_block **t, int *i);
407 void skip_until_newline(char_block **t, int *i);
408 private:
409 char_block *head;
410 char_block *tail;
411 int run_output_filter(int device_format_selector, int argc, char *argv[]);
415 * char_buffer - Constructor.
418 char_buffer::char_buffer()
419 : head(0), tail(0)
424 * char_buffer - Destructor. Throw away the whole buffer list.
427 char_buffer::~char_buffer()
429 while (head != NULL) {
430 char_block *temp = head;
431 head = head->next;
432 delete temp;
437 * read_file - Read in a complete file, fp, placing the contents inside
438 * char_blocks.
441 int char_buffer::read_file(FILE *fp)
443 int n;
444 while (!feof(fp)) {
445 if (tail == NULL) {
446 tail = new char_block;
447 head = tail;
449 else {
450 if (tail->used == char_block::SIZE) {
451 tail->next = new char_block;
452 tail = tail->next;
455 // at this point we have a tail which is ready for the next SIZE
456 // bytes of the file
457 n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used, fp);
458 if (n <= 0)
459 // error
460 return 0;
461 else
462 tail->used += n * sizeof(char);
464 return 1;
468 * writeNbytes - Write n bytes to stdout.
471 static void writeNbytes(const char *s, int l)
473 int n = 0;
474 int r;
476 while (n < l) {
477 r = write(stdoutfd, s, l - n);
478 if (r < 0)
479 sys_fatal("write");
480 n += r;
481 s += r;
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");
503 exit(1);
506 if ((image_template != NULL) && (strchr(image_template, '%') != NULL)) {
507 error("cannot use a `%%' within the image template");
508 exit(1);
511 if (image_dir == NULL)
512 image_dir = "";
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("%sgrohtml-%d", image_dir,
522 (int)getpid());
523 else
524 macroset_template = make_message("%s%s", image_dir, image_template);
526 if (macroset_template == NULL)
527 sys_fatal("make_message");
529 image_template =
530 (char *)malloc(strlen("-%d") + strlen(macroset_template) + 1);
531 if (image_template == NULL)
532 sys_fatal("malloc");
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);
549 else
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);
563 exit(1);
568 * write_end_image - End the image. Write out the image extents if we
569 * are using -Tps.
572 static void write_end_image(int is_html)
575 * if we are producing html then these
576 * emit image name and enable output
577 * else
578 * we are producing images
579 * in which case these generate image
580 * boundaries
582 writeString("\\O[4]\\O[2]");
583 if (is_html)
584 writeString("\\O[1]");
585 else
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
594 * rendering.
597 static void write_start_image(IMAGE_ALIGNMENT pos, int is_html)
599 writeString("\\O[5");
600 switch (pos) {
601 case INLINE:
602 writeString("i");
603 break;
604 case LEFT:
605 writeString("l");
606 break;
607 case RIGHT:
608 writeString("r");
609 break;
610 case CENTERED:
611 default:
612 writeString("c");
613 break;
615 writeString(image_template);
616 writeString(".png]");
617 if (is_html)
618 writeString("\\O[0]\\O[3]");
619 else
620 // reset min/max registers
621 writeString("\\O[1]\\O[3]");
625 * write_upto_newline - Write the contents of the buffer until a newline
626 * is seen. Check for HTML_IMAGE_INLINE_BEGIN and
627 * HTML_IMAGE_INLINE_END; process them if they are
628 * present.
631 void char_buffer::write_upto_newline(char_block **t, int *i, int is_html)
633 int j = *i;
635 if (*t) {
636 while (j < (*t)->used
637 && (*t)->buffer[j] != '\n'
638 && (*t)->buffer[j] != INLINE_LEADER_CHAR)
639 j++;
640 if (j < (*t)->used
641 && (*t)->buffer[j] == '\n')
642 j++;
643 writeNbytes((*t)->buffer + (*i), j - (*i));
644 if ((*t)->buffer[j] == INLINE_LEADER_CHAR) {
645 if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN))
646 write_start_image(INLINE, is_html);
647 else if (can_see(t, &j, HTML_IMAGE_INLINE_END))
648 write_end_image(is_html);
649 else {
650 if (j < (*t)->used) {
651 *i = j;
652 j++;
653 writeNbytes((*t)->buffer + (*i), j - (*i));
657 if (j == (*t)->used) {
658 *i = 0;
659 *t = (*t)->next;
660 if (*t && (*t)->buffer[j - 1] != '\n')
661 write_upto_newline(t, i, is_html);
663 else
664 // newline was seen
665 *i = j;
670 * can_see - Return TRUE if we can see string in t->buffer[i] onwards.
673 int char_buffer::can_see(char_block **t, int *i, const char *string)
675 int j = 0;
676 int l = strlen(string);
677 int k = *i;
678 char_block *s = *t;
680 while (s) {
681 while (k < s->used && j < l && s->buffer[k] == string[j]) {
682 j++;
683 k++;
685 if (j == l) {
686 *i = k;
687 *t = s;
688 return TRUE;
690 else if (k < s->used && s->buffer[k] != string[j])
691 return( FALSE );
692 s = s->next;
693 k = 0;
695 return FALSE;
699 * skip_spaces - Return TRUE if we have not run out of data.
700 * Consume spaces also.
703 int char_buffer::skip_spaces(char_block **t, int *i)
705 char_block *s = *t;
706 int k = *i;
708 while (s) {
709 while (k < s->used && isspace(s->buffer[k]))
710 k++;
711 if (k == s->used) {
712 k = 0;
713 s = s->next;
715 else {
716 *i = k;
717 return TRUE;
720 return FALSE;
724 * skip_until_newline - Skip all characters until a newline is seen.
725 * The newline is not consumed.
728 void char_buffer::skip_until_newline(char_block **t, int *i)
730 int j = *i;
732 if (*t) {
733 while (j < (*t)->used && (*t)->buffer[j] != '\n')
734 j++;
735 if (j == (*t)->used) {
736 *i = 0;
737 *t = (*t)->next;
738 skip_until_newline(t, i);
740 else
741 // newline was seen
742 *i = j;
746 #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
747 #define HTML_OUTPUT_FILTER 0
748 #define IMAGE_OUTPUT_FILTER 1
751 * emit_troff_output - Write formatted buffer content to the troff
752 * post-processor data pipeline.
755 void char_buffer::emit_troff_output(int device_format_selector)
757 // Handle output for BOTH html and image device formats
758 // if `device_format_selector' is passed as
760 // HTML_FORMAT(HTML_OUTPUT_FILTER)
761 // Buffer data is written to the output stream
762 // with template image names translated to actual image names.
764 // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
765 // Buffer data is written to the output stream
766 // with no translation, for image file creation in the post-processor.
768 int index = 0;
769 char_block *element = head;
771 while (element != NULL)
772 write_upto_newline(&element, &index, device_format_selector);
774 #if 0
775 if (close(stdoutfd) < 0)
776 sys_fatal ("close");
778 // now we grab fd=1 so that the next pipe cannot use fd=1
779 if (stdoutfd == 1) {
780 if (dup(2) != stdoutfd)
781 sys_fatal ("dup failed to use fd=1");
783 #endif /* 0 */
787 * The image class remembers the position of all images in the
788 * postscript file and assigns names for each image.
791 struct imageItem {
792 imageItem *next;
793 int X1;
794 int Y1;
795 int X2;
796 int Y2;
797 char *imageName;
798 int resolution;
799 int maxx;
800 int pageNo;
802 imageItem(int x1, int y1, int x2, int y2,
803 int page, int res, int max_width, char *name);
804 ~imageItem();
808 * imageItem - Constructor.
811 imageItem::imageItem(int x1, int y1, int x2, int y2,
812 int page, int res, int max_width, char *name)
814 X1 = x1;
815 Y1 = y1;
816 X2 = x2;
817 Y2 = y2;
818 pageNo = page;
819 resolution = res;
820 maxx = max_width;
821 imageName = name;
822 next = NULL;
826 * imageItem - Destructor.
829 imageItem::~imageItem()
831 if (imageName)
832 free(imageName);
836 * imageList - A class containing a list of imageItems.
839 class imageList {
840 private:
841 imageItem *head;
842 imageItem *tail;
843 int count;
844 public:
845 imageList();
846 ~imageList();
847 void add(int x1, int y1, int x2, int y2,
848 int page, int res, int maxx, char *name);
849 void createImages(void);
850 int createPage(int pageno);
851 void createImage(imageItem *i);
852 int getMaxX(int pageno);
856 * imageList - Constructor.
859 imageList::imageList()
860 : head(0), tail(0), count(0)
865 * imageList - Destructor.
868 imageList::~imageList()
870 while (head != NULL) {
871 imageItem *i = head;
872 head = head->next;
873 delete i;
878 * createPage - Create one image of, page pageno, from the postscript file.
881 int imageList::createPage(int pageno)
883 char *s;
885 if (currentPageNo == pageno)
886 return 0;
888 if (currentPageNo >= 1) {
890 * We need to unlink the files which change each time a new page is
891 * processed. The final unlink is done by xtmpfile when pre-grohtml
892 * exits.
894 unlink(imagePageName);
895 unlink(psPageName);
898 if (show_progress) {
899 fprintf(stderr, "[%d] ", pageno);
900 fflush(stderr);
903 #if defined(DEBUGGING)
904 if (debug)
905 fprintf(stderr, "creating page %d\n", pageno);
906 #endif
908 s = make_message("psselect -q -p%d %s %s\n",
909 pageno, psFileName, psPageName);
911 if (s == NULL)
912 sys_fatal("make_message");
913 #if defined(DEBUGGING)
914 if (debug) {
915 fwrite(s, sizeof(char), strlen(s), stderr);
916 fflush(stderr);
918 #endif
919 html_system(s, 1);
921 s = make_message("echo showpage | "
922 "gs%s -q -dBATCH -dSAFER "
923 "-dDEVICEHEIGHTPOINTS=792 "
924 "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
925 "-sDEVICE=%s -r%d %s "
926 "-sOutputFile=%s %s -\n",
927 EXE_EXT,
928 (getMaxX(pageno) * image_res) / postscriptRes,
929 image_device,
930 image_res,
931 antiAlias,
932 imagePageName,
933 psPageName);
934 if (s == NULL)
935 sys_fatal("make_message");
936 #if defined(DEBUGGING)
937 if (debug) {
938 fwrite(s, sizeof(char), strlen(s), stderr);
939 fflush(stderr);
941 #endif
942 html_system(s, 1);
943 free(s);
944 currentPageNo = pageno;
945 return 0;
949 * min - Return the minimum of two numbers.
952 int min(int x, int y)
954 if (x < y)
955 return x;
956 else
957 return y;
961 * max - Return the maximum of two numbers.
964 int max(int x, int y)
966 if (x > y)
967 return x;
968 else
969 return y;
973 * getMaxX - Return the largest right-hand position for any image
974 * on, pageno.
977 int imageList::getMaxX(int pageno)
979 imageItem *h = head;
980 int x = postscriptRes * DEFAULT_LINE_LENGTH;
982 while (h != NULL) {
983 if (h->pageNo == pageno)
984 x = max(h->X2, x);
985 h = h->next;
987 return x;
991 * createImage - Generate a minimal png file from the set of page images.
994 void imageList::createImage(imageItem *i)
996 if (i->X1 != -1) {
997 char *s;
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",
1013 EXE_EXT,
1014 x1, y1, x2 - x1 + 1, y2 - y1 + 1,
1015 imagePageName,
1016 EXE_EXT,
1017 TRANSPARENT,
1018 i->imageName);
1019 if (s == NULL)
1020 sys_fatal("make_message");
1022 #if defined(DEBUGGING)
1023 if (debug) {
1024 fprintf(stderr, s);
1025 fflush(stderr);
1027 #endif
1028 html_system(s, 0);
1029 free(s);
1031 else {
1032 fprintf(stderr, "failed to generate image of page %d\n", i->pageNo);
1033 fflush(stderr);
1035 #if defined(DEBUGGING)
1037 else {
1038 if (debug) {
1039 fprintf(stderr, "ignoring image as x1 coord is -1\n");
1040 fflush(stderr);
1042 #endif
1047 * add - Add an image description to the imageList.
1050 void imageList::add(int x1, int y1, int x2, int y2,
1051 int page, int res, int maxx, char *name)
1053 imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name);
1055 if (head == NULL) {
1056 head = i;
1057 tail = i;
1059 else {
1060 tail->next = i;
1061 tail = i;
1066 * createImages - For each image descriptor on the imageList,
1067 * create the actual image.
1070 void imageList::createImages(void)
1072 imageItem *h = head;
1074 while (h != NULL) {
1075 createImage(h);
1076 h = h->next;
1080 static imageList listOfImages; // List of images defined by the region file.
1083 * generateImages - Parse the region file and generate images
1084 * from the postscript file. The region file
1085 * contains the x1,y1--x2,y2 extents of each
1086 * image.
1089 static void generateImages(char *regionFileName)
1091 pushBackBuffer *f=new pushBackBuffer(regionFileName);
1093 while (f->putPB(f->getPB()) != eof) {
1094 if (f->isString("grohtml-info:page")) {
1095 int page = f->readInt();
1096 int x1 = f->readInt();
1097 int y1 = f->readInt();
1098 int x2 = f->readInt();
1099 int y2 = f->readInt();
1100 int maxx = f->readInt();
1101 char *name = f->readString();
1102 int res = postscriptRes;
1103 listOfImages.add(x1, y1, x2, y2, page, res, maxx, name);
1104 while (f->putPB(f->getPB()) != '\n'
1105 && f->putPB(f->getPB()) != eof)
1106 (void)f->getPB();
1107 if (f->putPB(f->getPB()) == '\n')
1108 (void)f->getPB();
1110 else {
1111 /* Write any error messages out to the user. */
1112 fputc(f->getPB(), stderr);
1116 listOfImages.createImages();
1117 if (show_progress) {
1118 fprintf(stderr, "done\n");
1119 fflush(stderr);
1121 delete f;
1125 * set_redirection - Set up I/O Redirection for handle, was, to refer to
1126 * stream on handle, willbe.
1129 static void set_redirection(int was, int willbe)
1131 // Nothing to do if `was' and `willbe' already have same handle.
1132 if (was != willbe) {
1133 // Otherwise attempt the specified redirection.
1134 if (dup2 (willbe, was) < 0) {
1135 // Redirection failed, so issue diagnostic and bail out.
1136 fprintf(stderr, "failed to replace fd=%d with %d\n", was, willbe);
1137 if (willbe == STDOUT_FILENO)
1138 fprintf(stderr,
1139 "likely that stdout should be opened before %d\n", was);
1140 sys_fatal("dup2");
1143 // When redirection has been successfully completed assume redundant
1144 // handle `willbe' is no longer required, so close it.
1145 if (close(willbe) < 0)
1146 // Issue diagnostic if `close' fails.
1147 sys_fatal("close");
1152 * save_and_redirect - Get duplicate handle for stream, was, then
1153 * redirect, was, to refer to, willbe.
1156 static int save_and_redirect(int was, int willbe)
1158 if (was == willbe)
1159 // No redirection specified so don't do anything but silently bailing out.
1160 return (was);
1162 // Proceeding with redirection so first save and verify our duplicate
1163 // handle for `was'.
1164 int saved = dup(was);
1165 if (saved < 0) {
1166 fprintf(stderr, "unable to get duplicate handle for %d\n", was);
1167 sys_fatal("dup");
1170 // Duplicate handle safely established so complete redirection.
1171 set_redirection(was, willbe);
1173 // Finally return the saved duplicate descriptor for the
1174 // original `was' stream.
1175 return saved;
1179 * alterDeviceTo - If, toImage, is set
1180 * the argument list is altered to include
1181 * IMAGE_DEVICE and we invoke groff rather than troff.
1182 * Else
1183 * set -Thtml and groff.
1186 static void alterDeviceTo(int argc, char *argv[], int toImage)
1188 int i = 0;
1190 if (toImage) {
1191 while (i < argc) {
1192 if (strcmp(argv[i], "-Thtml") == 0)
1193 argv[i] = IMAGE_DEVICE;
1194 i++;
1196 argv[troff_arg] = "groff"; /* rather than troff */
1198 else {
1199 while (i < argc) {
1200 if (strcmp(argv[i], IMAGE_DEVICE) == 0)
1201 argv[i] = "-Thtml";
1202 i++;
1204 argv[troff_arg] = "groff"; /* use groff -Z */
1209 * addZ - Append -Z onto the command list for groff.
1212 char **addZ(int argc, char *argv[])
1214 char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1215 int i = 0;
1217 if (new_argv == NULL)
1218 sys_fatal("malloc");
1220 if (argc > 0) {
1221 new_argv[i] = argv[i];
1222 i++;
1224 new_argv[i] = "-Z";
1225 while (i < argc) {
1226 new_argv[i + 1] = argv[i];
1227 i++;
1229 argc++;
1230 new_argv[argc] = NULL;
1231 return new_argv;
1235 * addRegDef - Append a defined register or string onto the command
1236 * list for troff.
1239 char **addRegDef(int argc, char *argv[], const char *numReg)
1241 char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1242 int i = 0;
1244 if (new_argv == NULL)
1245 sys_fatal("malloc");
1247 while (i < argc) {
1248 new_argv[i] = argv[i];
1249 i++;
1251 new_argv[argc] = strsave(numReg);
1252 argc++;
1253 new_argv[argc] = NULL;
1254 return new_argv;
1258 * dump_args - Display the argument list.
1261 void dump_args(int argc, char *argv[])
1263 fprintf(stderr, " %d arguments:", argc);
1264 for (int i = 0; i < argc; i++)
1265 fprintf(stderr, " %s", argv[i]);
1266 fprintf(stderr, "\n");
1269 int char_buffer::run_output_filter(int filter, int argc, char **argv)
1271 int pipedes[2];
1272 PID_T child_pid;
1273 int status;
1275 if (pipe(pipedes) < 0)
1276 sys_fatal("pipe");
1278 #if MAY_FORK_CHILD_PROCESS
1280 // This is the UNIX process model. To invoke our post-processor,
1281 // we must `fork' the current process.
1283 if ((child_pid = fork()) < 0)
1284 sys_fatal("fork");
1286 else if (child_pid == 0) {
1287 // This is the child process fork. We redirect its `stdin' stream
1288 // to read data emerging from our pipe. There is no point in saving,
1289 // since we won't be able to restore later!
1291 set_redirection(STDIN_FILENO, pipedes[0]);
1293 // The parent process will be writing this data so we should release
1294 // the child's writeable handle on the pipe.
1296 if (close(pipedes[1]) < 0)
1297 sys_fatal("close");
1299 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1301 if (filter == IMAGE_OUTPUT_FILTER) {
1302 // with BOTH `stdout' AND `stderr' diverted to files.
1304 set_redirection(STDOUT_FILENO,
1305 creat(psFileName, S_IWUSR | S_IRUSR));
1306 set_redirection(STDERR_FILENO,
1307 creat(regionFileName, S_IWUSR | S_IRUSR));
1310 // Now we are ready to launch the output filter.
1312 execvp(argv[0], argv);
1314 // If we get to here then the `exec...' request for the output filter
1315 // failed. Diagnose it and bail out.
1317 error("couldn't exec %1: %2", argv[0], strerror(errno), ((char *)0));
1318 fflush(stderr); // just in case error() didn't
1319 exit(1);
1322 else {
1323 // This is the parent process fork. We will be writing data to the
1324 // filter pipeline but we can close our handle on the output end of
1325 // the pipe so we don't block the output data stream.
1327 if (close(pipedes[0]) < 0)
1328 sys_fatal("close");
1330 // Now we redirect the `stdout' stream to the inlet end of the pipe.
1331 // Push out the appropiately formatted data to the filter.
1333 pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]);
1334 emit_troff_output(DEVICE_FORMAT(filter));
1336 // After emitting all the data we close our connection to the inlet
1337 // end of the pipe so the child process will detect end of data.
1339 set_redirection(STDOUT_FILENO, pipedes[1]);
1341 // Finally we must wait for the child process to complete.
1343 if (WAIT(&status, &child_pid, _WAIT_CHILD) != child_pid)
1344 sys_fatal("wait");
1347 #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1349 int i, j;
1351 // This should be ok for most Win32 systems and is preferred to `fork'
1352 // for starting child processes under Cygwin.
1354 // Before we start the post-processor we bind its inherited `stdin'
1355 // stream to the readable end of our pipe, saving our own `stdin' stream
1356 // in `pipedes[0]'.
1358 pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]);
1360 int saved_stdout = dup(STDOUT_FILENO);
1361 int saved_stderr = STDERR_FILENO;
1363 // The IMAGE_OUTPUT_FILTER needs special output redirection...
1365 if (filter == IMAGE_OUTPUT_FILTER) {
1366 // with BOTH `stdout' AND `stderr' diverted to files while saving a
1367 // duplicate handle for `stderr'.
1369 set_redirection(STDOUT_FILENO, creat(psFileName, S_IWUSR | S_IRUSR));
1370 saved_stderr =
1371 save_and_redirect(STDERR_FILENO,
1372 creat(regionFileName, S_IWUSR | S_IRUSR));
1375 // We then use an asynchronous spawn request to start the post-processor.
1377 if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
1378 // Should the spawn request fail we issue a diagnostic and bail out.
1380 error("cannot spawn %1: %2", argv[0], strerror(errno), ((char *)0));
1381 exit(1);
1384 // Once the post-processor has been started we revert our `stdin'
1385 // to its original saved source which also closes the readable handle
1386 // for the pipe.
1388 set_redirection(STDIN_FILENO, pipedes[0]);
1390 set_redirection(STDERR_FILENO, saved_stderr);
1391 set_redirection(STDOUT_FILENO, pipedes[1]);
1392 emit_troff_output(DEVICE_FORMAT(filter));
1394 set_redirection(STDOUT_FILENO, saved_stdout);
1396 if (WAIT(&status, child_pid, _WAIT_CHILD) != child_pid)
1397 sys_fatal("wait");
1399 #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1401 return 0;
1405 * do_html - Set the troff number htmlflip and
1406 * write out the buffer to troff -Thtml.
1409 int char_buffer::do_html(int argc, char *argv[])
1411 string s;
1413 alterDeviceTo(argc, argv, 0);
1414 argv += troff_arg; // skip all arguments up to groff
1415 argc -= troff_arg;
1416 argv = addZ(argc, argv);
1417 argc++;
1419 s = "-dwww-image-template=";
1420 s += macroset_template; // do not combine these statements,
1421 // otherwise they will not work
1422 s += '\0'; // the trailing `\0' is ignored
1423 argv = addRegDef(argc, argv, s.contents());
1424 argc++;
1426 #if defined(DEBUGGING)
1427 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1428 if (debug) {
1429 set_redirection(STDOUT_FILENO, creat(htmlFileName, S_IWUSR | S_IRUSR));
1430 emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER));
1432 #endif
1434 return run_output_filter(HTML_OUTPUT_FILTER, argc, argv);
1438 * do_image - Write out the buffer to troff -Tps.
1441 int char_buffer::do_image(int argc, char *argv[])
1443 string s;
1445 alterDeviceTo(argc, argv, 1);
1446 argv += troff_arg; // skip all arguments up to troff/groff
1447 argc -= troff_arg;
1448 argv = addRegDef(argc, argv, "-rps4html=1");
1449 argc++;
1451 s = "-dwww-image-template=";
1452 s += macroset_template;
1453 s += '\0';
1454 argv = addRegDef(argc, argv, s.contents());
1455 argc++;
1457 // override local settings and produce a page size letter postscript file
1458 argv = addRegDef(argc, argv, "-P-pletter");
1459 argc++;
1461 #if defined(DEBUGGING)
1462 // slight security risk so only enabled if compiled with defined(DEBUGGING)
1463 if (debug) {
1464 set_redirection(STDOUT_FILENO, creat(troffFileName, S_IWUSR | S_IRUSR));
1465 emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER));
1467 #endif
1469 return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
1472 static char_buffer inputFile;
1475 * usage - Emit usage arguments.
1478 static void usage(FILE *stream)
1480 fprintf(stream,
1481 "usage: %s troffname [-Iimage_name] [-Dimage_directory]\n"
1482 " [-P-o vertical_image_offset] [-P-i image_resolution]\n"
1483 " [troff flags] [files]\n",
1484 program_name);
1485 fprintf(stream,
1486 " vertical_image_offset (default %d/72 of an inch)\n",
1487 vertical_offset);
1488 fprintf(stream,
1489 " image_resolution (default %d) pixels per inch\n",
1490 image_res);
1491 fprintf(stream,
1492 " image_name is the name of the stem for all images\n"
1493 " (default is grohtml-<pid>)\n");
1494 fprintf(stream,
1495 " place all png files into image_directory\n");
1499 * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1500 * and -P-I. Return the argument index of the first
1501 * non-option.
1504 static int scanArguments(int argc, char **argv)
1506 const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
1507 if (!command_prefix)
1508 command_prefix = PROG_PREFIX;
1509 char *troff_name = new char[strlen(command_prefix) + strlen("troff") + 1];
1510 strcpy(troff_name, command_prefix);
1511 strcat(troff_name, "troff");
1512 int c, i;
1513 static const struct option long_options[] = {
1514 { "help", no_argument, 0, CHAR_MAX + 1 },
1515 { "version", no_argument, 0, 'v' },
1516 { NULL, 0, 0, 0 }
1518 while ((c = getopt_long(argc, argv,
1519 "+a:g:o:i:I:j:D:F:vbdhlrnp", long_options, NULL))
1520 != EOF)
1521 switch(c) {
1522 case 'v':
1523 printf("GNU pre-grohtml (groff) version %s\n", Version_string);
1524 exit(0);
1525 case 'a':
1526 textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1527 MAX_ALPHA_BITS);
1528 if (textAlphaBits == 3) {
1529 error("cannot use 3 bits of antialiasing information");
1530 exit(1);
1532 break;
1533 case 'g':
1534 graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1535 MAX_ALPHA_BITS);
1536 if (graphicAlphaBits == 3) {
1537 error("cannot use 3 bits of antialiasing information");
1538 exit(1);
1540 break;
1541 case 'b':
1542 // handled by post-grohtml (set background color to white)
1543 break;
1544 case 'D':
1545 image_dir = optarg;
1546 break;
1547 case 'I':
1548 image_template = optarg;
1549 break;
1550 case 'i':
1551 image_res = atoi(optarg);
1552 break;
1553 case 'F':
1554 font_path.command_line_dir(optarg);
1555 break;
1556 case 'j':
1557 // handled by post-grohtml (set job name for multiple file output)
1558 break;
1559 case 'o':
1560 vertical_offset = atoi(optarg);
1561 break;
1562 case 'p':
1563 show_progress = TRUE;
1564 break;
1565 case 'd':
1566 #if defined(DEBUGGING)
1567 debug = TRUE;
1568 #endif
1569 break;
1570 case 'h':
1571 // handled by post-grohtml
1572 break;
1573 case CHAR_MAX + 1: // --help
1574 usage(stdout);
1575 exit(0);
1576 break;
1577 case '?':
1578 usage(stderr);
1579 exit(1);
1580 break;
1581 default:
1582 break;
1585 i = optind;
1586 while (i < argc) {
1587 if (strcmp(argv[i], troff_name) == 0)
1588 troff_arg = i;
1589 else if (argv[i][0] != '-')
1590 return i;
1591 i++;
1593 a_delete troff_name;
1595 return argc;
1599 * makeTempFiles - Name the temporary files.
1602 static int makeTempFiles(void)
1604 #if defined(DEBUGGING)
1605 psFileName = DEBUG_FILE"prehtml-ps";
1606 regionFileName = DEBUG_FILE"prehtml-region";
1607 imagePageName = DEBUG_FILE"prehtml-page";
1608 psPageName = DEBUG_FILE"prehtml-psn";
1609 troffFileName = DEBUG_FILE"prehtml-troff";
1610 htmlFileName = DEBUG_FILE"prehtml-html";
1611 #else /* not DEBUGGING */
1612 FILE *f;
1614 /* psPageName contains a single page of postscript */
1615 f = xtmpfile(&psPageName,
1616 PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
1617 TRUE);
1618 if (f == NULL) {
1619 sys_fatal("xtmpfile");
1620 return -1;
1622 fclose(f);
1624 /* imagePageName contains a bitmap image of the single postscript page */
1625 f = xtmpfile(&imagePageName,
1626 PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT,
1627 TRUE);
1628 if (f == NULL) {
1629 sys_fatal("xtmpfile");
1630 return -1;
1632 fclose(f);
1634 /* psFileName contains a postscript file of the complete document */
1635 f = xtmpfile(&psFileName,
1636 PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
1637 TRUE);
1638 if (f == NULL) {
1639 sys_fatal("xtmpfile");
1640 return -1;
1642 fclose(f);
1644 /* regionFileName contains a list of the images and their boxed coordinates */
1645 f = xtmpfile(&regionFileName,
1646 REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT,
1647 TRUE);
1648 if (f == NULL) {
1649 sys_fatal("xtmpfile");
1650 return -1;
1652 fclose(f);
1654 #endif /* not DEBUGGING */
1655 return 0;
1658 int main(int argc, char **argv)
1660 program_name = argv[0];
1661 int i;
1662 int found = 0;
1663 int ok = 1;
1665 #ifdef CAPTURE_MODE
1666 FILE *dump;
1667 fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc);
1668 for (i = 0; i < argc; i++)
1669 fprintf(stderr, "%2d: %s\n", i, argv[i]);
1670 if ((dump = fopen(DEBUG_FILE"pre-html-data", "wb")) != NULL) {
1671 while((i = fgetc(stdin)) >= 0)
1672 fputc(i, dump);
1673 fclose(dump);
1675 exit(1);
1676 #endif /* CAPTURE_MODE */
1677 postscriptRes = get_resolution();
1678 i = scanArguments(argc, argv);
1679 setupAntiAlias();
1680 checkImageDir();
1681 makeFileName();
1682 while (i < argc) {
1683 if (argv[i][0] != '-') {
1684 /* found source file */
1685 ok = do_file(argv[i]);
1686 if (!ok)
1687 return 0;
1688 found = 1;
1690 i++;
1693 if (!found)
1694 do_file("-");
1695 if (makeTempFiles())
1696 return 1;
1697 ok = inputFile.do_image(argc, argv);
1698 if (ok == 0) {
1699 generateImages(regionFileName);
1700 ok = inputFile.do_html(argc, argv);
1702 return ok;
1705 static int do_file(const char *filename)
1707 FILE *fp;
1709 current_filename = filename;
1710 if (strcmp(filename, "-") == 0)
1711 fp = stdin;
1712 else {
1713 fp = fopen(filename, "r");
1714 if (fp == 0) {
1715 error("can't open `%1': %2", filename, strerror(errno));
1716 return 0;
1720 if (inputFile.read_file(fp)) {
1721 // XXX
1724 if (fp != stdin)
1725 fclose(fp);
1726 current_filename = NULL;
1727 return 1;