Line data Source code
1 : /* Copyright (C) 2000-2025 Free Software Foundation, Inc.
2 : * Written by Gaius Mulley (gaius@glam.ac.uk).
3 : *
4 : * This file is part of groff, the GNU roff typesetting system.
5 : *
6 : * groff is free software; you can redistribute it and/or modify it
7 : * under the terms of the GNU General Public License as published by the
8 : * Free Software Foundation, either version 3 of the License, or (at
9 : * your option) any later version.
10 : *
11 : * groff is distributed in the hope that it will be useful, but WITHOUT
12 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 : * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 : * for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #ifdef HAVE_CONFIG_H
21 : #include <config.h>
22 : #endif
23 :
24 : #include <assert.h>
25 : #include <ctype.h> // isspace()
26 : #include <errno.h>
27 : #include <stdarg.h> // va_list, va_end(), va_start(), vsnprintf()
28 : #include <stdio.h> // EOF, FILE, fclose(), feof(), fflush(), fopen(),
29 : // fprintf(), fputc(), fread(), getc(), printf(),
30 : // stderr, stdin, stdout, ungetc()
31 : #include <stdlib.h> // atexit(), atoi(), exit(), free(), getenv(),
32 : // malloc(), system()
33 : #include <string.h> // memcpy(), strchr(), strcmp(), strcpy(),
34 : // strerror(), strlen(), strncmp(), strsignal()
35 :
36 : #include <getopt.h> // getopt_long()
37 :
38 : #include <new> // std::bad_alloc
39 :
40 : // needed for close(), creat(), dup(), dup2(), execvp(), fork(),
41 : // getpid(), mkdir(), open(), pipe(), unlink(), wait(), write()
42 : #include "posix.h"
43 : #include "nonposix.h"
44 :
45 : #define PREHTMLC
46 :
47 : #include "lib.h"
48 :
49 : #include "errarg.h"
50 : #include "error.h"
51 : #include "stringclass.h"
52 : #include "defs.h"
53 : #include "searchpath.h"
54 : #include "paper.h"
55 : #include "device.h"
56 : #include "font.h"
57 :
58 : #ifdef _POSIX_VERSION
59 : # include <sys/wait.h>
60 : # define PID_T pid_t
61 : #else /* not _POSIX_VERSION */
62 : # define PID_T int
63 : #endif /* not _POSIX_VERSION */
64 :
65 : /* Establish some definitions to facilitate discrimination between
66 : differing runtime environments. */
67 :
68 : #undef MAY_FORK_CHILD_PROCESS
69 : #undef MAY_SPAWN_ASYNCHRONOUS_CHILD
70 :
71 : #if defined(__MSDOS__) || defined(_WIN32)
72 :
73 : // Most MS-DOS and Win32 environments will be missing the 'fork'
74 : // capability (some, like Cygwin, have it, but it is better avoided).
75 : //
76 : // Bruno Haible adds in 2023:
77 : // However, because on Cygwin, neither ... __MSDOS__ [nor] _WIN32 is
78 : // defined, the code [here] *will* use fork() on Cygwin. Which works
79 : // fine, but is known to be slow. However, I don't know whether the
80 : // native Windows code (_WIN32) will work on Cygwin: many native
81 : // Windows APIs don't work right from within Cygwin, because the
82 : // worlds inside and outside a Cygwin process are quite different.
83 :
84 : # define MAY_FORK_CHILD_PROCESS 0
85 :
86 : // On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'.
87 : # include <process.h> // for 'spawn...'
88 : # include <fcntl.h> // for attributes of pipes
89 :
90 : # if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
91 :
92 : // These Win32 implementations allow parent and 'spawn...'ed child to
93 : // multitask asynchronously.
94 :
95 : # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
96 :
97 : # else
98 :
99 : // Others may adopt MS-DOS behaviour where parent must sleep,
100 : // from 'spawn...' until child terminates.
101 :
102 : # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
103 :
104 : # endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
105 :
106 : # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
107 : /* When we are building a DEBUGGING version we need to tell pre-grohtml
108 : where to put intermediate files (the DEBUGGING version will preserve
109 : these on exit).
110 :
111 : On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will
112 : probably not have this on all disk drives, so default to using
113 : 'c:/temp' instead. (Note that user may choose to override this by
114 : supplying a definition such as
115 :
116 : -DDEBUG_FILE_DIR=d:/path/to/debug/files
117 :
118 : in the CPPFLAGS to 'make'.) */
119 :
120 : # define DEBUG_FILE_DIR c:/temp
121 : # endif
122 :
123 : #else /* not __MSDOS__ or _WIN32 */
124 :
125 : // For non-Microsoft environments assume Unix conventions,
126 : // so 'fork' is required and child processes are asynchronous.
127 : # define MAY_FORK_CHILD_PROCESS 1
128 : # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
129 :
130 : # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
131 : /* For a DEBUGGING version, on the Unix host, we can also usually rely
132 : on being able to use '/tmp' for temporary file storage. (Note that,
133 : as in the __MSDOS__ or _WIN32 case above, the user may override this
134 : by defining
135 :
136 : -DDEBUG_FILE_DIR=/path/to/debug/files
137 :
138 : in the CPPFLAGS.) */
139 :
140 : # define DEBUG_FILE_DIR /tmp
141 : # endif
142 :
143 : #endif /* not __MSDOS__ or _WIN32 */
144 :
145 : // For a DEBUGGING version, we need some additional macros,
146 : // to direct the captured debugging mode output to appropriately named
147 : // files in the specified DEBUG_FILE_DIR.
148 :
149 : # define DEBUG_TEXT(text) #text
150 : # define DEBUG_NAME(text) DEBUG_TEXT(text)
151 : # define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
152 :
153 : extern "C" const char *Version_string;
154 :
155 : #include "pre-html.h"
156 : #include "pushback.h"
157 : #include "html-strings.h"
158 :
159 : #define DEFAULT_LINE_LENGTH 7 // inches wide
160 : #define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
161 : #define IMAGE_BORDER_PIXELS 0
162 : #define INLINE_LEADER_CHAR '\\'
163 :
164 : // Don't use colour names here! Otherwise there is a dependency on
165 : // a file called 'rgb.txt' which maps names to colours.
166 : #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
167 : #define MIN_ALPHA_BITS 0
168 : #define MAX_ALPHA_BITS 4
169 :
170 : #define PAGE_TEMPLATE_SHORT "pg"
171 : #define PAGE_TEMPLATE_LONG "-page-"
172 : #define PS_TEMPLATE_SHORT "ps"
173 : #define PS_TEMPLATE_LONG "-ps-"
174 : #define REGION_TEMPLATE_SHORT "rg"
175 : #define REGION_TEMPLATE_LONG "-regions-"
176 :
177 : typedef enum {
178 : CENTERED, LEFT, RIGHT, INLINE
179 : } IMAGE_ALIGNMENT;
180 :
181 : typedef enum {xhtml, html4} html_dialect;
182 :
183 : static int postscriptRes = -1; // PostScript resolution,
184 : // dots per inch
185 : static int stdoutfd = 1; // output file descriptor -
186 : // normally 1 but might move
187 : // -1 means closed
188 : static const char *psFileName = 0 /* nullptr */; // PostScript
189 : // file name
190 : static const char *psPageName = 0 /* nullptr */; // name of file
191 : // containing
192 : // current
193 : // PostScript
194 : // page
195 : static const char *regionFileName = 0 /* nullptr */; // name of file
196 : // containing
197 : // all image
198 : // regions
199 : static const char *imagePageName = 0 /* nullptr */; // name of
200 : // bitmap image
201 : // file
202 : // containing
203 : // current page
204 : static const char *image_device = "pnmraw";
205 : static int image_res = DEFAULT_IMAGE_RES;
206 : static int vertical_offset = 0;
207 : static char *image_template = 0 /* nullptr */; // image file name
208 : // template
209 : static const char *macroset_template= 0 /* nullptr */; // image file
210 : // name template
211 : // passed to
212 : // troff by -D
213 : static int troff_arg = 0; // troff arg index
214 : static const char *image_dir = 0 /* nullptr */; // user-specified image
215 : // directory
216 : static int textAlphaBits = MAX_ALPHA_BITS;
217 : static int graphicAlphaBits = MAX_ALPHA_BITS;
218 : static const char *antiAlias = 0 /* nullptr */; // anti-alias arguments
219 : // to be passed to gs
220 : static bool want_progress_report = false; // display page numbers
221 : // as they are processed
222 : static int currentPageNo = -1; // current image page number
223 : static bool debugging = false;
224 : static const char *troffFileName = 0 /* nullptr */; // pre-html
225 : // output sent
226 : // to troff -Tps
227 : static const char *htmlFileName = 0 /* nullptr */; // pre-html
228 : // output sent
229 : // to troff
230 : // -Thtml
231 : static bool need_eqn = false; // must we preprocess via eqn?
232 :
233 : static char *linebuf = 0 /* nullptr */; // for scanning devps/DESC
234 : static int linebufsize = 0;
235 : static const char *image_gen = 0 /* nullptr */; // the 'gs' program
236 :
237 : static const char devhtml_desc[] = "devhtml/DESC";
238 : static const char devps_desc[] = "devps/DESC";
239 :
240 : const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
241 : static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
242 : static html_dialect dialect = html4;
243 :
244 :
245 : /*
246 : * Images are generated via PostScript, gs, and the pnm utilities.
247 : */
248 : #define IMAGE_DEVICE "-Tps"
249 :
250 :
251 : /*
252 : * sys_fatal - Write a fatal error message.
253 : * Taken from src/roff/groff/pipeline.c.
254 : */
255 :
256 0 : void sys_fatal(const char *s)
257 : {
258 0 : fatal("%1: %2", s, strerror(errno));
259 0 : }
260 :
261 : /*
262 : * get_line - Copy a line (w/o newline) from a file to the
263 : * global line buffer.
264 : *
265 : * TODO: Discard; migrate callers to POSIX `getline()`.
266 : * https://pubs.opengroup.org/onlinepubs/9799919799/functions/\
267 : * getline.html
268 : */
269 :
270 510 : static bool get_line(FILE *f, const char *file_name, int lineno)
271 : {
272 510 : if (0 /* nullptr */ == f)
273 0 : return false;
274 510 : if (0 /* nullptr */ == linebuf) {
275 17 : linebufsize = 128;
276 : try {
277 17 : linebuf = new char[linebufsize]; // C++03: new int[linebufsize]();
278 17 : (void) memset(linebuf, '\0', (linebufsize * sizeof(char)));
279 : }
280 0 : catch (std::bad_alloc &e) {
281 0 : fatal_with_file_and_line(file_name, lineno, "cannot allocate %1"
282 : " bytes to read line; aborting",
283 0 : linebufsize);
284 : }
285 : }
286 510 : int i = 0;
287 : // skip leading whitespace
288 : for (;;) {
289 510 : int c = getc(f);
290 510 : if (EOF == c)
291 34 : return false;
292 476 : if (c != ' ' && c != '\t') {
293 476 : ungetc(c, f);
294 476 : break;
295 : }
296 0 : }
297 : for (;;) {
298 7514 : int c = getc(f);
299 7514 : if (EOF == c)
300 0 : break;
301 7514 : if (i + 1 >= linebufsize) {
302 0 : int newbufsize = linebufsize * 2;
303 0 : char *old_linebuf = linebuf;
304 : try {
305 0 : linebuf = new char[newbufsize]; // C++03: new int[newbufsize]();
306 0 : (void) memset(linebuf, '\0', (newbufsize * sizeof(char)));
307 : }
308 0 : catch (std::bad_alloc &e) {
309 0 : fatal_with_file_and_line(file_name, lineno, "cannot allocate"
310 : " more than %1 bytes to read line;"
311 0 : " aborting", linebufsize);
312 : }
313 0 : memcpy(linebuf, old_linebuf, linebufsize);
314 0 : delete[] old_linebuf;
315 0 : linebufsize = newbufsize;
316 : }
317 7514 : linebuf[i++] = c;
318 7514 : if ('\n' == c) {
319 476 : i--;
320 476 : break;
321 : }
322 7038 : }
323 476 : linebuf[i] = '\0';
324 476 : return true;
325 : }
326 :
327 : /*
328 : * get_resolution - Return the PostScript device resolution.
329 : */
330 :
331 17 : static unsigned int get_resolution(void)
332 : {
333 : char *pathp;
334 : FILE *f;
335 17 : unsigned int res = 0;
336 17 : f = font_path.open_file(devps_desc, &pathp);
337 17 : if (0 /* nullptr */ == f)
338 0 : fatal("cannot open file '%1': %2", devps_desc, strerror(errno));
339 17 : int lineno = 0;
340 255 : while (get_line(f, pathp, lineno++)) {
341 238 : (void) sscanf(linebuf, "res %u", &res);
342 : // We must stop reading at a "charset" line; see groff_font(5).
343 238 : if (strncmp(linebuf, "charset", sizeof "charset") == 0) {
344 : // Don't be fooled by non-groff extensions.
345 0 : char trailing_char = linebuf[(sizeof "charset") - 1];
346 0 : if (isspace(trailing_char) || '\0' == trailing_char)
347 : break;
348 : }
349 : }
350 17 : free(pathp);
351 17 : fclose(f);
352 17 : return res;
353 : }
354 :
355 :
356 : /*
357 : * get_image_generator - Return the declared program from the HTML
358 : * device description.
359 : */
360 :
361 17 : static char *get_image_generator(void)
362 : {
363 : char *pathp;
364 : FILE *f;
365 17 : char *generator = 0 /* nullptr */;
366 17 : const char keyword[] = "image_generator";
367 17 : const size_t keyword_len = strlen(keyword);
368 17 : f = font_path.open_file(devhtml_desc, &pathp);
369 17 : if (0 /* nullptr */ == f)
370 0 : fatal("cannot open file '%1': %2", devhtml_desc, strerror(errno));
371 17 : int lineno = 0;
372 255 : while (get_line(f, pathp, lineno++)) {
373 238 : char *cursor = linebuf;
374 238 : size_t limit = strlen(linebuf);
375 238 : char *end = linebuf + limit;
376 238 : if (0 == (strncmp(linebuf, keyword, keyword_len))) {
377 17 : cursor += keyword_len;
378 : // At least one space or tab is required.
379 17 : if(!(' ' == *cursor) || ('\t' == *cursor))
380 0 : continue;
381 17 : cursor++;
382 17 : while((cursor < end) && ((' ' == *cursor) || ('\t' == *cursor)))
383 0 : cursor++;
384 17 : if (cursor == end)
385 0 : continue;
386 17 : generator = cursor;
387 : }
388 : // We must stop reading at a "charset" line; see groff_font(5).
389 238 : if (strncmp(linebuf, "charset", sizeof "charset") == 0) {
390 : // Don't be fooled by non-groff extensions.
391 0 : char trailing_char = linebuf[(sizeof "charset") - 1];
392 0 : if (isspace(trailing_char) || '\0' == trailing_char)
393 : break;
394 : }
395 : }
396 17 : free(pathp);
397 17 : fclose(f);
398 17 : return generator;
399 : }
400 :
401 : /*
402 : * html_system - A wrapper for system().
403 : */
404 :
405 123 : void html_system(const char *s, int redirect_stdout)
406 : {
407 123 : if (debugging) {
408 0 : fprintf(stderr, "%s: debug: executing: ", program_name);
409 0 : fwrite(s, sizeof(char), strlen(s), stderr);
410 0 : fflush(stderr);
411 : }
412 : {
413 123 : int saved_stdout = dup(STDOUT_FILENO);
414 123 : int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
415 123 : if (redirect_stdout && (saved_stdout > STDOUT_FILENO)
416 66 : && (fdnull > STDOUT_FILENO))
417 66 : dup2(fdnull, STDOUT_FILENO);
418 123 : if (fdnull >= 0)
419 123 : close(fdnull);
420 123 : int status = system(s);
421 123 : if (redirect_stdout)
422 66 : dup2(saved_stdout, STDOUT_FILENO);
423 123 : if (-1 == status)
424 0 : fprintf(stderr, "%s: unable to execute command '%s': %s\n",
425 0 : program_name, s, strerror(errno));
426 123 : else if (status > 0) {
427 0 : if (WIFEXITED(status))
428 0 : fprintf(stderr, "%s: command '%s' returned status %d\n",
429 0 : program_name, s, WEXITSTATUS(status));
430 0 : else if (WIFSIGNALED(status))
431 0 : fprintf(stderr, "%s: command '%s' exited by signal: %s\n",
432 : program_name, s, strsignal(WTERMSIG(status)));
433 0 : else if (WIFSTOPPED(status))
434 0 : fprintf(stderr, "%s: command '%s' stopped: %s\n",
435 0 : program_name, s, strsignal(WSTOPSIG(status)));
436 : else
437 0 : fprintf(stderr, "%s: command '%s' exited abnormally\n",
438 : program_name, s);
439 : }
440 123 : close(saved_stdout);
441 : }
442 123 : }
443 :
444 : /*
445 : * make_string - Create a string via `malloc()`, place the variadic
446 : * arguments as formatted by `fmt` into it, and return
447 : * it. Adapted from Linux man-pages' printf(3) example.
448 : * We never return a null pointer, instead treating
449 : * failure as invariably fatal.
450 : */
451 :
452 159 : const char *make_string(const char *fmt, ...)
453 : {
454 159 : size_t size = 0;
455 159 : char *p = 0 /* nullptr */;
456 : va_list ap;
457 159 : va_start(ap, fmt);
458 159 : int n = vsnprintf(p, size, fmt, ap);
459 159 : va_end(ap);
460 159 : if (n < 0)
461 0 : sys_fatal("vsnprintf");
462 159 : size = static_cast<size_t>(n) + 1 /* '\0' */;
463 159 : p = static_cast<char *>(malloc(size));
464 159 : if (0 /* nullptr */ == p)
465 0 : sys_fatal("vsnprintf");
466 159 : va_start(ap, fmt);
467 159 : n = vsnprintf(p, size, fmt, ap);
468 159 : va_end(ap);
469 159 : if (n < 0)
470 0 : sys_fatal("vsnprintf");
471 159 : assert(p != 0 /* nullptr */);
472 159 : return p;
473 : }
474 :
475 : /*
476 : * classes and methods for retaining ascii text
477 : */
478 :
479 : struct char_block {
480 : enum { SIZE = 256 };
481 : char buffer[SIZE];
482 : int used;
483 : char_block *next;
484 :
485 : char_block();
486 : };
487 :
488 1408 : char_block::char_block()
489 1408 : : used(0), next(0)
490 : {
491 361856 : for (int i = 0; i < SIZE; i++)
492 360448 : buffer[i] = 0;
493 1408 : }
494 :
495 : class char_buffer {
496 : public:
497 : char_buffer();
498 : ~char_buffer();
499 : void read_file(FILE *fp);
500 : int do_html(int argc, char *argv[]);
501 : int do_image(int argc, char *argv[]);
502 : void emit_troff_output(int device_format_selector);
503 : void write_upto_newline(char_block **t, int *i, int is_html);
504 : bool can_see(char_block **t, int *i, const char *string);
505 : void skip_until_newline(char_block **t, int *i);
506 : private:
507 : char_block *head;
508 : char_block *tail;
509 : int run_output_filter(int device_format_selector, int argc,
510 : char *argv[]);
511 : };
512 :
513 17 : char_buffer::char_buffer()
514 17 : : head(0), tail(0)
515 : {
516 17 : }
517 :
518 1442 : char_buffer::~char_buffer()
519 : {
520 1425 : while (head != 0 /* nullptr */) {
521 1408 : char_block *temp = head;
522 1408 : head = head->next;
523 1408 : delete temp;
524 : }
525 17 : }
526 :
527 : /*
528 : * read_file - Read file `fp` into char_blocks.
529 : */
530 :
531 1425 : void char_buffer::read_file(FILE *fp)
532 : {
533 : int n;
534 1425 : while (!feof(fp)) {
535 1408 : if (0 /* nullptr */ == tail) {
536 17 : tail = new char_block;
537 17 : head = tail;
538 : }
539 : else {
540 1391 : if (tail->used == char_block::SIZE) {
541 1391 : tail->next = new char_block;
542 1391 : tail = tail->next;
543 : }
544 : }
545 : // We now have a tail ready for the next `SIZE` bytes of the file.
546 1408 : n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used,
547 : fp);
548 1408 : if ((n < 0) || ((0 == n) && !feof(fp)))
549 0 : sys_fatal("fread");
550 1408 : tail->used += n * sizeof(char);
551 : }
552 17 : }
553 :
554 : /*
555 : * writeNbytes - Write n bytes to stdout.
556 : */
557 :
558 86278 : static void writeNbytes(const char *s, int l)
559 : {
560 86278 : int n = 0;
561 : int r;
562 :
563 172376 : while (n < l) {
564 86098 : r = write(stdoutfd, s, l - n);
565 86098 : if (r < 0)
566 0 : sys_fatal("write");
567 86098 : n += r;
568 86098 : s += r;
569 : }
570 86278 : }
571 :
572 : /*
573 : * writeString - Write a string to stdout.
574 : */
575 :
576 28 : static void writeString(const char *s)
577 : {
578 28 : writeNbytes(s, strlen(s));
579 28 : }
580 :
581 : /*
582 : * makeFileName - Create the image filename template
583 : * and the macroset image template.
584 : */
585 :
586 17 : static void makeFileName(void)
587 : {
588 17 : if ((image_dir != 0 /* nullptr */)
589 2 : && (strchr(image_dir, '%') != 0 /* nullptr */))
590 0 : fatal("'%%' is prohibited within the image directory name");
591 17 : if ((image_template != 0 /* nullptr */)
592 2 : && (strchr(image_template, '%') != 0 /* nullptr */))
593 0 : fatal("'%%' is prohibited within the image template");
594 17 : if (0 /* nullptr */ == image_dir)
595 15 : image_dir = (char *)"";
596 2 : else if (strlen(image_dir) > 0
597 2 : && image_dir[strlen(image_dir) - 1] != '/')
598 2 : image_dir = make_string("%s/", image_dir);
599 17 : if (0 /* nullptr */ == image_template)
600 15 : macroset_template = make_string("%sgrohtml-%d-", image_dir,
601 15 : int(getpid()));
602 : else
603 2 : macroset_template = make_string("%s%s-", image_dir,
604 : image_template);
605 17 : size_t mtlen = strlen(macroset_template);
606 17 : image_template = (char *)malloc(strlen("%d") + mtlen + 1);
607 17 : if (0 /* nullptr */ == image_template)
608 0 : sys_fatal("malloc");
609 17 : char *s = strcpy(image_template, macroset_template);
610 17 : s += mtlen;
611 : // Keep this format string synced with troff:suppress_node::tprint().
612 17 : strcpy(s, "%d");
613 17 : }
614 :
615 : /*
616 : * setupAntiAlias - Set up the antialias string, used when we call gs.
617 : */
618 :
619 17 : static void setupAntiAlias(void)
620 : {
621 17 : if (textAlphaBits == 0 && graphicAlphaBits == 0)
622 0 : antiAlias = make_string(" ");
623 17 : else if (textAlphaBits == 0)
624 0 : antiAlias = make_string("-dGraphicsAlphaBits=%d ",
625 : graphicAlphaBits);
626 17 : else if (graphicAlphaBits == 0)
627 0 : antiAlias = make_string("-dTextAlphaBits=%d ", textAlphaBits);
628 : else
629 17 : antiAlias = make_string("-dTextAlphaBits=%d"
630 : " -dGraphicsAlphaBits=%d ", textAlphaBits,
631 : graphicAlphaBits);
632 17 : }
633 :
634 : /*
635 : * checkImageDir - Check whether the image directory is available.
636 : */
637 :
638 17 : static void checkImageDir(void)
639 : {
640 17 : if (image_dir != 0 /* nullptr */ && strcmp(image_dir, "") != 0)
641 2 : if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST))
642 0 : fatal("cannot create directory '%1': %2", image_dir,
643 0 : strerror(errno));
644 17 : }
645 :
646 : /*
647 : * write_end_image - End the image. Write out the image extents if we
648 : * are using -Tps.
649 : */
650 :
651 4 : static void write_end_image(int is_html)
652 : {
653 : /*
654 : * if we are producing html then these
655 : * emit image name and enable output
656 : * else
657 : * we are producing images
658 : * in which case these generate image
659 : * boundaries
660 : */
661 4 : writeString("\\O[4]\\O[2]");
662 4 : if (is_html)
663 2 : writeString("\\O[1]");
664 : else
665 2 : writeString("\\O[0]");
666 4 : }
667 :
668 : /*
669 : * write_start_image - Write troff code which will:
670 : *
671 : * (i) disable html output for the following image
672 : * (ii) reset the max/min x/y registers during
673 : * Postscript Rendering.
674 : */
675 :
676 4 : static void write_start_image(IMAGE_ALIGNMENT pos, int is_html)
677 : {
678 4 : writeString("\\O[5");
679 4 : switch (pos) {
680 4 : case INLINE:
681 4 : writeString("i");
682 4 : break;
683 0 : case LEFT:
684 0 : writeString("l");
685 0 : break;
686 0 : case RIGHT:
687 0 : writeString("r");
688 0 : break;
689 0 : case CENTERED:
690 : default:
691 0 : writeString("c");
692 0 : break;
693 : }
694 4 : writeString(image_template);
695 4 : writeString(".png]");
696 4 : if (is_html)
697 2 : writeString("\\O[0]\\O[3]");
698 : else
699 : // reset min/max registers
700 2 : writeString("\\O[1]\\O[3]");
701 4 : }
702 :
703 : /*
704 : * write_upto_newline - Write the contents of the buffer until a
705 : * newline is seen. Check for
706 : * HTML_IMAGE_INLINE_BEGIN and
707 : * HTML_IMAGE_INLINE_END; process them if they are
708 : * present.
709 : */
710 :
711 56392 : void char_buffer::write_upto_newline(char_block **t, int *i,
712 : int is_html)
713 : {
714 56392 : int j = *i;
715 :
716 56392 : if (*t) {
717 1365492 : while (j < (*t)->used
718 708396 : && (*t)->buffer[j] != '\n'
719 1389074 : && (*t)->buffer[j] != INLINE_LEADER_CHAR)
720 654550 : j++;
721 56392 : if (j < (*t)->used
722 53846 : && (*t)->buffer[j] == '\n')
723 30264 : j++;
724 56392 : writeNbytes((*t)->buffer + (*i), j - (*i));
725 56392 : if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) {
726 29866 : if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN))
727 4 : write_start_image(INLINE, is_html);
728 29862 : else if (can_see(t, &j, HTML_IMAGE_INLINE_END))
729 4 : write_end_image(is_html);
730 : else {
731 29858 : if (j < (*t)->used) {
732 29858 : *i = j;
733 29858 : j++;
734 29858 : writeNbytes((*t)->buffer + (*i), j - (*i));
735 : }
736 : }
737 : }
738 56392 : if (j == (*t)->used) {
739 2814 : *i = 0;
740 2814 : *t = (*t)->next;
741 2814 : if (*t && (*t)->buffer[j - 1] != '\n')
742 2694 : write_upto_newline(t, i, is_html);
743 : }
744 : else
745 : // newline was seen
746 53578 : *i = j;
747 : }
748 56392 : }
749 :
750 : /*
751 : * can_see - Return true if we can see string in t->buffer[i] onward.
752 : */
753 :
754 59728 : bool char_buffer::can_see(char_block **t, int *i, const char *str)
755 : {
756 59728 : size_t j = 0;
757 59728 : size_t l = strlen(str);
758 59728 : int k = *i;
759 59728 : char_block *s = *t;
760 :
761 60024 : while (s) {
762 120032 : while (k < s->used && j < l && s->buffer[k] == str[j]) {
763 60008 : j++;
764 60008 : k++;
765 : }
766 60024 : if (j == l) {
767 8 : *i = k;
768 8 : *t = s;
769 8 : return true;
770 : }
771 60016 : else if (k < s->used && s->buffer[k] != str[j])
772 59720 : return false;
773 296 : s = s->next;
774 296 : k = 0;
775 : }
776 0 : return false;
777 : }
778 :
779 : /*
780 : * skip_until_newline - Skip all characters until a newline is seen.
781 : * The newline is not consumed.
782 : */
783 :
784 0 : void char_buffer::skip_until_newline(char_block **t, int *i)
785 : {
786 0 : int j = *i;
787 :
788 0 : if (*t) {
789 0 : while (j < (*t)->used && (*t)->buffer[j] != '\n')
790 0 : j++;
791 0 : if (j == (*t)->used) {
792 0 : *i = 0;
793 0 : *t = (*t)->next;
794 0 : skip_until_newline(t, i);
795 : }
796 : else
797 : // newline was seen
798 0 : *i = j;
799 : }
800 0 : }
801 :
802 : #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
803 : #define HTML_OUTPUT_FILTER 0
804 : #define IMAGE_OUTPUT_FILTER 1
805 : #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
806 : #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
807 : #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
808 :
809 : /*
810 : * emit_troff_output - Write formatted buffer content to the troff
811 : * post-processor data pipeline.
812 : */
813 :
814 34 : void char_buffer::emit_troff_output(int device_format_selector)
815 : {
816 : // Handle output for BOTH html and image device formats
817 : // if 'device_format_selector' is passed as
818 : //
819 : // HTML_FORMAT(HTML_OUTPUT_FILTER)
820 : // Buffer data is written to the output stream
821 : // with template image names translated to actual image names.
822 : //
823 : // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
824 : // Buffer data is written to the output stream
825 : // with no translation, for image file creation in the
826 : // post-processor.
827 :
828 34 : int idx = 0;
829 34 : char_block *element = head;
830 :
831 53732 : while (element != 0 /* nullptr */)
832 53698 : write_upto_newline(&element, &idx, device_format_selector);
833 :
834 : #if 0
835 : if (close(stdoutfd) < 0)
836 : sys_fatal ("close");
837 :
838 : // now we grab fd=1 so that the next pipe cannot use fd=1
839 : if (stdoutfd == 1) {
840 : if (dup(2) != stdoutfd)
841 : sys_fatal ("dup failed to use fd=1");
842 : }
843 : #endif /* 0 */
844 34 : }
845 :
846 : /*
847 : * The image class remembers the position of all images in the
848 : * PostScript file and assigns names for each image.
849 : */
850 :
851 : struct imageItem {
852 : imageItem *next;
853 : int X1;
854 : int Y1;
855 : int X2;
856 : int Y2;
857 : char *imageName;
858 : int resolution;
859 : int maxx;
860 : int pageNo;
861 :
862 : imageItem(int x1, int y1, int x2, int y2,
863 : int page, int res, int max_width, char *name);
864 : ~imageItem();
865 : };
866 :
867 : /*
868 : * imageItem - Constructor.
869 : */
870 :
871 59 : imageItem::imageItem(int x1, int y1, int x2, int y2,
872 59 : int page, int res, int max_width, char *name)
873 : {
874 59 : X1 = x1;
875 59 : Y1 = y1;
876 59 : X2 = x2;
877 59 : Y2 = y2;
878 59 : pageNo = page;
879 59 : resolution = res;
880 59 : maxx = max_width;
881 59 : imageName = name;
882 59 : next = 0 /* nullptr */;
883 59 : }
884 :
885 : /*
886 : * imageItem - Destructor.
887 : */
888 :
889 118 : imageItem::~imageItem()
890 : {
891 59 : if (imageName)
892 59 : free(imageName);
893 59 : }
894 :
895 : /*
896 : * imageList - A class containing a list of imageItems.
897 : */
898 :
899 : class imageList {
900 : private:
901 : imageItem *head;
902 : imageItem *tail;
903 : int count;
904 : public:
905 : imageList();
906 : ~imageList();
907 : void add(int x1, int y1, int x2, int y2,
908 : int page, int res, int maxx, char *name);
909 : void createImages(void);
910 : int createPage(int pageno);
911 : void createImage(imageItem *i);
912 : int getMaxX(int pageno);
913 : };
914 :
915 : /*
916 : * imageList - Constructor.
917 : */
918 :
919 17 : imageList::imageList()
920 17 : : head(0), tail(0), count(0)
921 : {
922 17 : }
923 :
924 : /*
925 : * imageList - Destructor.
926 : */
927 :
928 93 : imageList::~imageList()
929 : {
930 76 : while (head != 0 /* nullptr */) {
931 59 : imageItem *i = head;
932 59 : head = head->next;
933 59 : delete i;
934 : }
935 17 : }
936 :
937 : /*
938 : * createPage - Create image of page `pageno` from PostScript file.
939 : */
940 :
941 57 : int imageList::createPage(int pageno)
942 : {
943 57 : if (currentPageNo == pageno)
944 24 : return 0;
945 :
946 33 : if (currentPageNo >= 1) {
947 : /*
948 : * We need to unlink the files which change each time a new page is
949 : * processed. The final unlink is done by xtmpfile when
950 : * pre-grohtml exits.
951 : */
952 29 : unlink(imagePageName);
953 29 : unlink(psPageName);
954 : }
955 :
956 33 : if (want_progress_report) {
957 0 : fprintf(stderr, "[%d] ", pageno);
958 0 : fflush(stderr);
959 : }
960 :
961 33 : if (debugging)
962 0 : fprintf(stderr, "%s: debug: creating page %d\n", program_name,
963 : pageno);
964 :
965 33 : const char *s = make_string("ps2ps -sPageList=%d %s %s",
966 : pageno, psFileName, psPageName);
967 33 : html_system(s, 1);
968 33 : assert(strlen(image_gen) > 0);
969 99 : s = make_string("echo showpage | "
970 : "%s%s -q -dBATCH -dSAFER "
971 : "-dDEVICEHEIGHTPOINTS=792 "
972 : "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
973 : "-sDEVICE=%s -r%d %s "
974 : "-sOutputFile=%s %s -",
975 : image_gen,
976 : EXE_EXT,
977 33 : (getMaxX(pageno) * image_res) / postscriptRes,
978 : image_device,
979 : image_res,
980 : antiAlias,
981 : imagePageName,
982 : psPageName);
983 33 : html_system(s, 1);
984 33 : free(const_cast<char *>(s));
985 33 : currentPageNo = pageno;
986 33 : return 0;
987 : }
988 :
989 : /*
990 : * min - Return the minimum of two numbers.
991 : */
992 :
993 114 : int min(int x, int y)
994 : {
995 114 : if (x < y)
996 114 : return x;
997 : else
998 0 : return y;
999 : }
1000 :
1001 : /*
1002 : * max - Return the maximum of two numbers.
1003 : */
1004 :
1005 286 : int max(int x, int y)
1006 : {
1007 286 : if (x > y)
1008 123 : return x;
1009 : else
1010 163 : return y;
1011 : }
1012 :
1013 : /*
1014 : * getMaxX - Return the largest right-hand position for any image
1015 : * on `pageno`.
1016 : */
1017 :
1018 33 : int imageList::getMaxX(int pageno)
1019 : {
1020 33 : imageItem *h = head;
1021 33 : int x = postscriptRes * DEFAULT_LINE_LENGTH;
1022 :
1023 1577 : while (h != 0 /* nullptr */) {
1024 1544 : if (h->pageNo == pageno)
1025 58 : x = max(h->X2, x);
1026 1544 : h = h->next;
1027 : }
1028 33 : return x;
1029 : }
1030 :
1031 : /*
1032 : * createImage - Generate a minimal PNG file from the set of page
1033 : * images.
1034 : */
1035 :
1036 59 : void imageList::createImage(imageItem *i)
1037 : {
1038 59 : if (i->X1 != -1) {
1039 57 : int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes
1040 : - IMAGE_BORDER_PIXELS,
1041 : 0);
1042 114 : int y1 = max(image_res * vertical_offset / 72
1043 57 : + min(i->Y1, i->Y2) * image_res / postscriptRes
1044 : - IMAGE_BORDER_PIXELS,
1045 : 0);
1046 57 : int x2 = max(i->X1, i->X2) * image_res / postscriptRes
1047 57 : + IMAGE_BORDER_PIXELS;
1048 57 : int y2 = image_res * vertical_offset / 72
1049 57 : + max(i->Y1, i->Y2) * image_res / postscriptRes
1050 57 : + 1 + IMAGE_BORDER_PIXELS;
1051 57 : if (createPage(i->pageNo) == 0) {
1052 114 : const char *s = make_string("pamcut%s %d %d %d %d < %s "
1053 : "| pnmcrop%s " PNMTOOLS_QUIET
1054 : "| pnmtopng%s " PNMTOOLS_QUIET " %s"
1055 : "> %s",
1056 : EXE_EXT,
1057 57 : x1, y1, x2 - x1 + 1, y2 - y1 + 1,
1058 : imagePageName,
1059 : EXE_EXT,
1060 : EXE_EXT,
1061 : TRANSPARENT,
1062 : i->imageName);
1063 57 : html_system(s, 0);
1064 57 : free(const_cast<char *>(s));
1065 : }
1066 : else {
1067 0 : fprintf(stderr, "%s: failed to generate image of page %d\n",
1068 : program_name, i->pageNo);
1069 0 : fflush(stderr);
1070 : }
1071 : #if defined(DEBUGGING)
1072 : }
1073 : else {
1074 : if (debugging) {
1075 : fprintf(stderr, "%s: debug: ignoring image as x1 coord is -1\n",
1076 : program_name);
1077 : fflush(stderr);
1078 : }
1079 : #endif
1080 : }
1081 59 : }
1082 :
1083 : /*
1084 : * add - Add an image description to the imageList.
1085 : */
1086 :
1087 59 : void imageList::add(int x1, int y1, int x2, int y2,
1088 : int page, int res, int maxx, char *name)
1089 : {
1090 59 : imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name);
1091 :
1092 59 : if (0 /* nullptr */ == head) {
1093 5 : head = i;
1094 5 : tail = i;
1095 : }
1096 : else {
1097 54 : tail->next = i;
1098 54 : tail = i;
1099 : }
1100 59 : }
1101 :
1102 : /*
1103 : * createImages - For each image descriptor on the imageList,
1104 : * create the actual image.
1105 : */
1106 :
1107 17 : void imageList::createImages(void)
1108 : {
1109 17 : imageItem *h = head;
1110 :
1111 76 : while (h != 0 /* nullptr */) {
1112 59 : createImage(h);
1113 59 : h = h->next;
1114 : }
1115 17 : }
1116 :
1117 : static imageList listOfImages; // list of images defined by region file
1118 :
1119 : /*
1120 : * generateImages - Parse the region file and generate images from the
1121 : * PostScript file. The region file contains the
1122 : * x1,y1--x2,y2 extents of each image.
1123 : */
1124 :
1125 17 : static void generateImages(const char *region_file_name)
1126 : {
1127 17 : pushBackBuffer *f=new pushBackBuffer(region_file_name);
1128 :
1129 128 : while (f->putPB(f->getPB()) != eof) {
1130 111 : if (f->isString("grohtml-info:page")) {
1131 59 : int page = f->readInt();
1132 59 : int x1 = f->readInt();
1133 59 : int y1 = f->readInt();
1134 59 : int x2 = f->readInt();
1135 59 : int y2 = f->readInt();
1136 59 : int maxx = f->readInt();
1137 59 : char *name = f->readString();
1138 59 : int res = postscriptRes;
1139 59 : listOfImages.add(x1, y1, x2, y2, page, res, maxx, name);
1140 3382 : while (f->putPB(f->getPB()) != '\n'
1141 3382 : && f->putPB(f->getPB()) != eof)
1142 3323 : (void)f->getPB();
1143 59 : if (f->putPB(f->getPB()) == '\n')
1144 59 : (void)f->getPB();
1145 : }
1146 : else {
1147 : /* Write any error messages out to the user. */
1148 52 : fputc(f->getPB(), stderr);
1149 : }
1150 : }
1151 17 : fflush(stderr);
1152 :
1153 17 : listOfImages.createImages();
1154 17 : if (want_progress_report) {
1155 0 : fprintf(stderr, "done\n");
1156 0 : fflush(stderr);
1157 : }
1158 17 : delete f;
1159 17 : }
1160 :
1161 : /*
1162 : * set_redirection - Redirect file descriptor `was` to file descriptor
1163 : * `willbe`.
1164 : */
1165 :
1166 136 : static void set_redirection(int was, int willbe)
1167 : {
1168 : // Nothing to do if 'was' and 'willbe' already have same handle.
1169 136 : if (was != willbe) {
1170 : // Otherwise attempt the specified redirection.
1171 136 : if (dup2(willbe, was) < 0) {
1172 : // Redirection failed, so issue diagnostic and bail out.
1173 0 : fprintf(stderr, "%s: unable to replace fd=%d with %d",
1174 : program_name, was, willbe);
1175 0 : if (willbe == STDOUT_FILENO)
1176 0 : fprintf(stderr,
1177 : "; likely that stdout should be opened before %d", was);
1178 0 : fputc('\n', stderr);
1179 0 : sys_fatal("dup2");
1180 : }
1181 :
1182 : // When redirection has been successfully completed assume redundant
1183 : // handle 'willbe' is no longer required, so close it.
1184 136 : if (close(willbe) < 0)
1185 : // Issue diagnostic if 'close' fails.
1186 0 : sys_fatal("close");
1187 : }
1188 136 : }
1189 :
1190 : /*
1191 : * save_and_redirect - Duplicate file descriptor for `was` on file
1192 : * descriptor `willbe`.
1193 : */
1194 :
1195 34 : static int save_and_redirect(int was, int willbe)
1196 : {
1197 34 : if (was == willbe)
1198 : // No redirection specified; silently bail out.
1199 0 : return (was);
1200 :
1201 : // Proceeding with redirection so first save and verify our duplicate
1202 : // handle for 'was'.
1203 34 : int saved = dup(was);
1204 34 : if (saved < 0) {
1205 0 : fprintf(stderr, "%s: unable to get duplicate file descriptor for"
1206 : " %d\n", program_name, was);
1207 0 : sys_fatal("dup");
1208 : }
1209 :
1210 : // Duplicate handle safely established so complete redirection.
1211 34 : set_redirection(was, willbe);
1212 :
1213 : // Finally return the saved duplicate descriptor for the original
1214 : // 'was' descriptor.
1215 34 : return saved;
1216 : }
1217 :
1218 : /*
1219 : * alterDeviceTo - If toImage is set
1220 : * the argument list is altered to include
1221 : * IMAGE_DEVICE; we invoke groff rather than troff.
1222 : * Else
1223 : * set -Thtml and groff.
1224 : */
1225 :
1226 34 : static void alterDeviceTo(int argc, char *argv[], int toImage)
1227 : {
1228 34 : int i = 0;
1229 :
1230 34 : if (toImage) {
1231 119 : while (i < argc) {
1232 102 : if ((strcmp(argv[i], "-Thtml") == 0) ||
1233 85 : (strcmp(argv[i], "-Txhtml") == 0))
1234 17 : argv[i] = (char *)IMAGE_DEVICE;
1235 102 : i++;
1236 : }
1237 17 : argv[troff_arg] = (char *)"groff"; /* rather than troff */
1238 : }
1239 : else {
1240 119 : while (i < argc) {
1241 102 : if (strcmp(argv[i], IMAGE_DEVICE) == 0) {
1242 17 : if (dialect == xhtml)
1243 0 : argv[i] = (char *)"-Txhtml";
1244 : else
1245 17 : argv[i] = (char *)"-Thtml";
1246 : }
1247 102 : i++;
1248 : }
1249 17 : argv[troff_arg] = (char *)"groff"; /* use groff -Z */
1250 : }
1251 34 : }
1252 :
1253 : /*
1254 : * addArg - Append newarg onto the command list for groff.
1255 : */
1256 :
1257 17 : char **addArg(int argc, char *argv[], char *newarg)
1258 : {
1259 17 : char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1260 17 : int i = 0;
1261 :
1262 17 : if (0 /* nullptr */ == new_argv)
1263 0 : sys_fatal("malloc");
1264 :
1265 17 : if (argc > 0) {
1266 17 : new_argv[i] = argv[i];
1267 17 : i++;
1268 : }
1269 17 : new_argv[i] = newarg;
1270 56 : while (i < argc) {
1271 39 : new_argv[i + 1] = argv[i];
1272 39 : i++;
1273 : }
1274 17 : argc++;
1275 17 : new_argv[argc] = 0 /* nullptr */;
1276 17 : return new_argv;
1277 : }
1278 :
1279 : /*
1280 : * addRegDef - Append a defined register or string onto the command
1281 : * list for troff.
1282 : */
1283 :
1284 68 : char **addRegDef(int argc, char *argv[], const char *numReg)
1285 : {
1286 68 : char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
1287 68 : int i = 0;
1288 :
1289 68 : if (0 /* nullptr */ == new_argv)
1290 0 : sys_fatal("malloc");
1291 :
1292 360 : while (i < argc) {
1293 292 : new_argv[i] = argv[i];
1294 292 : i++;
1295 : }
1296 68 : new_argv[argc] = strsave(numReg);
1297 68 : argc++;
1298 68 : new_argv[argc] = 0 /* nullptr */;
1299 68 : return new_argv;
1300 : }
1301 :
1302 : #if 0
1303 : /*
1304 : * dump_args - Display the argument list.
1305 : */
1306 :
1307 : void dump_args(int argc, char *argv[])
1308 : {
1309 : fprintf(stderr, " %d arguments:", argc);
1310 : for (int i = 0; i < argc; i++)
1311 : fprintf(stderr, " %s", argv[i]);
1312 : fprintf(stderr, "\n");
1313 : }
1314 : #endif
1315 :
1316 : /*
1317 : * print_args - Print arguments as if issued on the command line.
1318 : */
1319 :
1320 34 : void print_args(int argc, char *argv[])
1321 : {
1322 34 : if (debugging) {
1323 0 : fprintf(stderr, "%s: debug: executing: ", program_name);
1324 0 : for (int i = 0; i < argc; i++)
1325 0 : fprintf(stderr, "%s ", argv[i]);
1326 0 : fputc('\n', stderr);
1327 : }
1328 34 : }
1329 :
1330 34 : int char_buffer::run_output_filter(int filter, int argc, char **argv)
1331 : {
1332 : int pipedes[2];
1333 : PID_T child_pid;
1334 : int wstatus;
1335 :
1336 34 : print_args(argc, argv);
1337 34 : if (pipe(pipedes) < 0)
1338 0 : sys_fatal("pipe");
1339 :
1340 : #if MAY_FORK_CHILD_PROCESS
1341 : // This is the Unix process model. To invoke our post-processor,
1342 : // we must 'fork' the current process.
1343 :
1344 34 : if ((child_pid = fork()) < 0)
1345 0 : sys_fatal("fork");
1346 :
1347 68 : else if (child_pid == 0) {
1348 : // This is the child process. We redirect its input file descriptor
1349 : // to read data emerging from our pipe. There is no point in
1350 : // saving, since we won't be able to restore later!
1351 :
1352 34 : set_redirection(STDIN_FILENO, pipedes[0]);
1353 :
1354 : // The parent process will be writing this data; release the child's
1355 : // writeable handle on the pipe since we have no use for it.
1356 :
1357 34 : if (close(pipedes[1]) < 0)
1358 0 : sys_fatal("close");
1359 :
1360 : // The IMAGE_OUTPUT_FILTER needs special output redirection...
1361 :
1362 34 : if (filter == IMAGE_OUTPUT_FILTER) {
1363 : // ...with BOTH 'stdout' AND 'stderr' diverted to files, the
1364 : // latter so that `generateImages()` can scrape "grohtml-info"
1365 : // from it.
1366 :
1367 17 : set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
1368 17 : set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM);
1369 : }
1370 :
1371 : // Now we are ready to launch the output filter.
1372 :
1373 34 : execvp(argv[0], argv); // does not return unless it fails
1374 34 : fatal("cannot execute '%1': %2", argv[0], strerror(errno));
1375 : }
1376 :
1377 : else {
1378 : // This is the parent process. We write data to the filter pipeline
1379 : // where the child will read it. We have no need to read from the
1380 : // input side ourselves, so close it.
1381 :
1382 34 : if (close(pipedes[0]) < 0)
1383 0 : sys_fatal("close");
1384 :
1385 : // Now redirect the standard output file descriptor to the inlet end
1386 : // of the pipe, and push the formatted data to the filter.
1387 :
1388 34 : pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]);
1389 34 : emit_troff_output(DEVICE_FORMAT(filter));
1390 :
1391 : // After emitting all the data we close our connection to the inlet
1392 : // end of the pipe so the child process will detect end of data.
1393 :
1394 34 : set_redirection(STDOUT_FILENO, pipedes[1]);
1395 :
1396 : // Finally, we must wait for the child process to complete.
1397 :
1398 34 : if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
1399 0 : sys_fatal("wait");
1400 : }
1401 :
1402 : #elif MAY_SPAWN_ASYNCHRONOUS_CHILD
1403 :
1404 : // We do not have `fork` (or we prefer not to use it), but
1405 : // asynchronous processes are allowed, passing data through pipes.
1406 : // This should be okay for most Win32 systems and is preferred to
1407 : // `fork` for starting child processes under Cygwin.
1408 :
1409 : // Before we start the post-processor we bind its inherited standard
1410 : // input file descriptor to the readable end of our pipe, saving our
1411 : // own standard input file descriptor in `pipedes[0]`.
1412 :
1413 : pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]);
1414 :
1415 : // For the Win32 model,
1416 : // we need special provision for saving BOTH 'stdout' and 'stderr'.
1417 :
1418 : int saved_stdout = dup(STDOUT_FILENO);
1419 : int saved_stderr = STDERR_FILENO;
1420 :
1421 : // The IMAGE_OUTPUT_FILTER needs special output redirection...
1422 :
1423 : if (filter == IMAGE_OUTPUT_FILTER) {
1424 : // with BOTH 'stdout' AND 'stderr' diverted to files while saving a
1425 : // duplicate handle for 'stderr'.
1426 :
1427 : set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
1428 : saved_stderr = save_and_redirect(STDERR_FILENO,
1429 : REGION_OUTPUT_STREAM);
1430 : }
1431 :
1432 : // Use an asynchronous spawn request to start the post-processor.
1433 :
1434 : if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
1435 : fatal("cannot spawn %1: %2", argv[0], strerror(errno));
1436 : }
1437 :
1438 : // Once the post-processor has been started we revert our 'stdin'
1439 : // to its original saved source, which also closes the readable handle
1440 : // for the pipe.
1441 :
1442 : set_redirection(STDIN_FILENO, pipedes[0]);
1443 :
1444 : // if we redirected 'stderr', for use by the image post-processor,
1445 : // then we also need to reinstate its original assignment.
1446 :
1447 : if (filter == IMAGE_OUTPUT_FILTER)
1448 : set_redirection(STDERR_FILENO, saved_stderr);
1449 :
1450 : // Now we redirect the standard output to the inlet end of the pipe,
1451 : // and push out the appropriately formatted data to the filter.
1452 :
1453 : set_redirection(STDOUT_FILENO, pipedes[1]);
1454 : emit_troff_output(DEVICE_FORMAT(filter));
1455 :
1456 : // After emitting all the data we close our connection to the inlet
1457 : // end of the pipe so the child process will detect end of data.
1458 :
1459 : set_redirection(STDOUT_FILENO, saved_stdout);
1460 :
1461 : // And finally, we must wait for the child process to complete.
1462 :
1463 : if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
1464 : sys_fatal("wait");
1465 :
1466 : #else /* can't do asynchronous pipes! */
1467 :
1468 : // TODO: code to support an MS-DOS style process model should go here
1469 : fatal("output filtering not supported on this platform");
1470 :
1471 : #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
1472 :
1473 34 : return wstatus;
1474 : }
1475 :
1476 : /*
1477 : * do_html - Set the troff number htmlflip and
1478 : * write out the buffer to troff -Thtml.
1479 : */
1480 :
1481 17 : int char_buffer::do_html(int argc, char *argv[])
1482 : {
1483 34 : string s;
1484 :
1485 17 : alterDeviceTo(argc, argv, 0);
1486 17 : argv += troff_arg; // skip all arguments up to groff
1487 17 : argc -= troff_arg;
1488 17 : argv = addArg(argc, argv, (char *)"-Z");
1489 17 : argc++;
1490 :
1491 17 : s = (char *)"-dwww-image-template=";
1492 17 : s += macroset_template; // Do not combine these statements,
1493 : // otherwise they will not work.
1494 17 : s += '\0'; // The trailing '\0' is ignored.
1495 17 : argv = addRegDef(argc, argv, s.contents());
1496 17 : argc++;
1497 :
1498 17 : if (dialect == xhtml) {
1499 0 : argv = addRegDef(argc, argv, "-rxhtml=1");
1500 0 : argc++;
1501 0 : if (need_eqn) {
1502 0 : argv = addRegDef(argc, argv, "-e");
1503 0 : argc++;
1504 : }
1505 : }
1506 :
1507 : # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
1508 : // slight security risk: only enabled if defined(DEBUGGING)
1509 17 : if (debugging) {
1510 0 : int saved_stdout = save_and_redirect(STDOUT_FILENO,
1511 : HTML_DEBUG_STREAM);
1512 0 : emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER));
1513 0 : set_redirection(STDOUT_FILENO, saved_stdout);
1514 : }
1515 :
1516 34 : return run_output_filter(HTML_OUTPUT_FILTER, argc, argv);
1517 : }
1518 :
1519 : /*
1520 : * do_image - Write out the buffer to troff -Tps.
1521 : */
1522 :
1523 17 : int char_buffer::do_image(int argc, char *argv[])
1524 : {
1525 34 : string s;
1526 :
1527 17 : alterDeviceTo(argc, argv, 1);
1528 17 : argv += troff_arg; // skip all arguments up to troff/groff
1529 17 : argc -= troff_arg;
1530 17 : argv = addRegDef(argc, argv, "-rps4html=1");
1531 17 : argc++;
1532 :
1533 17 : s = "-dwww-image-template=";
1534 17 : s += macroset_template;
1535 17 : s += '\0';
1536 17 : argv = addRegDef(argc, argv, s.contents());
1537 17 : argc++;
1538 :
1539 : // Override local settings and produce a letter-size PostScript page
1540 : // file.
1541 17 : argv = addRegDef(argc, argv, "-P-pletter");
1542 17 : argc++;
1543 :
1544 17 : if (dialect == xhtml) {
1545 0 : if (need_eqn) {
1546 0 : argv = addRegDef(argc, argv, "-rxhtml=1");
1547 0 : argc++;
1548 : }
1549 0 : argv = addRegDef(argc, argv, "-e");
1550 0 : argc++;
1551 : }
1552 :
1553 : # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
1554 : // slight security risk: only enabled if defined(DEBUGGING)
1555 17 : if (debugging) {
1556 0 : int saved_stdout = save_and_redirect(STDOUT_FILENO,
1557 : IMAGE_DEBUG_STREAM);
1558 0 : emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER));
1559 0 : set_redirection(STDOUT_FILENO, saved_stdout);
1560 : }
1561 :
1562 34 : return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
1563 : }
1564 :
1565 : static char_buffer inputFile;
1566 :
1567 : /*
1568 : * usage - Emit usage message.
1569 : */
1570 :
1571 0 : static void usage(FILE *stream)
1572 : {
1573 0 : fprintf(stream,
1574 : "usage: %s [-epV] [-a anti-aliasing-text-bits] [-D image-directory]"
1575 : " [-F font-directory] [-g anti-aliasing-graphics-bits] [-i resolution]"
1576 : " [-I image-stem] [-o image-vertical-offset] [-x html-dialect]"
1577 : " troff-command troff-argument ...\n"
1578 : "usage: %s {-v | --version}\n"
1579 : "usage: %s --help\n",
1580 : program_name, program_name, program_name);
1581 0 : if (stdout == stream) {
1582 0 : fputs("\n"
1583 : "Prepare a troff(1) document for HTML formatting.\n"
1584 : "\n"
1585 : "This program is not intended to be executed standalone; it is\n"
1586 : "normally part of a groff pipeline. If your need to run it manually\n"
1587 : "(e.g., for debugging purposes), give the 'groff' program the\n"
1588 : "command-line option '-V' to inspect the arguments with which\n",
1589 : stream);
1590 0 : fprintf(stream,
1591 : "'%s' is called. See the grohtml(1) manual page.\n",
1592 : program_name);
1593 : }
1594 0 : }
1595 :
1596 : /*
1597 : * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
1598 : * and -P-I. Return the argument index of the first
1599 : * non-option.
1600 : */
1601 :
1602 17 : static int scanArguments(int argc, char **argv)
1603 : {
1604 17 : const char *cmdprefix = getenv("GROFF_COMMAND_PREFIX");
1605 17 : if (!cmdprefix)
1606 0 : cmdprefix = PROG_PREFIX;
1607 17 : size_t pfxlen = strlen(cmdprefix);
1608 17 : char *troff_name = new char[pfxlen + strlen("troff") + 1];
1609 17 : char *s = strcpy(troff_name, cmdprefix);
1610 17 : s += pfxlen;
1611 17 : strcpy(s, "troff");
1612 : int c, i;
1613 : static const struct option long_options[] = {
1614 : { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
1615 : { "version", no_argument, 0 /* nullptr */, 'v' },
1616 : { 0 /* nullptr */, 0, 0, 0 }
1617 : };
1618 31 : while ((c = getopt_long(argc, argv,
1619 : "+:a:bCdD:eF:g:Ghi:I:j:k:lno:prs:S:vVx:y",
1620 : long_options, 0 /* nullptr */))
1621 31 : != EOF)
1622 14 : switch (c) {
1623 0 : case 'a':
1624 0 : textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1625 : MAX_ALPHA_BITS);
1626 0 : if (textAlphaBits == 3)
1627 0 : fatal("cannot use 3 bits of antialiasing information");
1628 0 : break;
1629 1 : case 'b':
1630 : // handled by post-grohtml (set background color to white)
1631 1 : break;
1632 2 : case 'C':
1633 : // handled by post-grohtml (don't write Creator HTML comment)
1634 2 : break;
1635 0 : case 'd':
1636 0 : debugging = true;
1637 0 : break;
1638 2 : case 'D':
1639 2 : image_dir = optarg;
1640 2 : break;
1641 0 : case 'e':
1642 0 : need_eqn = true;
1643 0 : break;
1644 0 : case 'F':
1645 0 : font_path.command_line_dir(optarg);
1646 0 : break;
1647 0 : case 'g':
1648 0 : graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
1649 : MAX_ALPHA_BITS);
1650 0 : if (graphicAlphaBits == 3)
1651 0 : fatal("cannot use 3 bits of antialiasing information");
1652 0 : break;
1653 2 : case 'G':
1654 : // handled by post-grohtml (don't write CreationDate HTML comment)
1655 2 : break;
1656 0 : case 'h':
1657 : // handled by post-grohtml (write headings with font size changes)
1658 0 : break;
1659 0 : case 'i':
1660 0 : image_res = atoi(optarg);
1661 0 : break;
1662 3 : case 'I':
1663 3 : image_template = optarg;
1664 3 : break;
1665 2 : case 'j':
1666 : // handled by post-grohtml (set job name for multiple file output)
1667 2 : break;
1668 0 : case 'k':
1669 : // handled by post-grohtml (charset ASCII/mixed/UTF-8)
1670 0 : break;
1671 0 : case 'l':
1672 : // handled by post-grohtml (no automatic section links)
1673 0 : break;
1674 1 : case 'n':
1675 : // handled by post-grohtml (generate simple heading anchors)
1676 1 : break;
1677 0 : case 'o':
1678 0 : vertical_offset = atoi(optarg);
1679 0 : break;
1680 0 : case 'p':
1681 0 : want_progress_report = true;
1682 0 : break;
1683 1 : case 'r':
1684 : // handled by post-grohtml (no header and footer lines)
1685 1 : break;
1686 0 : case 's':
1687 : // handled by post-grohtml (use font size n as the HTML base size)
1688 0 : break;
1689 0 : case 'S':
1690 : // handled by post-grohtml (set file split level)
1691 0 : break;
1692 0 : case 'v':
1693 0 : printf("GNU pre-grohtml (groff) version %s\n", Version_string);
1694 0 : exit(EXIT_SUCCESS);
1695 0 : case 'V':
1696 : // handled by post-grohtml (create validator button)
1697 0 : break;
1698 0 : case 'x':
1699 : // html dialect
1700 0 : if (strcmp(optarg, "x") == 0)
1701 0 : dialect = xhtml;
1702 0 : else if (strcmp(optarg, "4") == 0)
1703 0 : dialect = html4;
1704 : else
1705 0 : warning("unsupported HTML dialect: '%1'", optarg);
1706 0 : break;
1707 0 : case 'y':
1708 : // handled by post-grohtml (create groff signature)
1709 0 : break;
1710 0 : case CHAR_MAX + 1: // --help
1711 0 : usage(stdout);
1712 0 : exit(EXIT_SUCCESS);
1713 : break;
1714 0 : case '?':
1715 0 : if (optopt != 0)
1716 0 : error("unrecognized command-line option '%1'", char(optopt));
1717 : else
1718 0 : error("unrecognized command-line option '%1'",
1719 0 : argv[(optind - 1)]);
1720 0 : usage(stderr);
1721 0 : exit(2);
1722 : break;
1723 0 : case ':':
1724 0 : error("command-line option '%1' requires an argument",
1725 0 : char(optopt));
1726 0 : usage(stderr);
1727 0 : exit(2);
1728 : break;
1729 0 : default:
1730 0 : break;
1731 : }
1732 :
1733 17 : i = optind;
1734 73 : while (i < argc) {
1735 56 : if (strcmp(argv[i], troff_name) == 0)
1736 17 : troff_arg = i;
1737 39 : else if (argv[i][0] != '-')
1738 0 : return i;
1739 56 : i++;
1740 : }
1741 17 : delete[] troff_name;
1742 :
1743 17 : return argc;
1744 : }
1745 :
1746 : /*
1747 : * makeTempFiles - Name the temporary files.
1748 : */
1749 :
1750 17 : static void makeTempFiles(void)
1751 : {
1752 17 : psFileName = DEBUG_FILE("prehtml-ps");
1753 17 : regionFileName = DEBUG_FILE("prehtml-region");
1754 17 : imagePageName = DEBUG_FILE("prehtml-page");
1755 17 : psPageName = DEBUG_FILE("prehtml-psn");
1756 17 : troffFileName = DEBUG_FILE("prehtml-troff");
1757 17 : htmlFileName = DEBUG_FILE("prehtml-html");
1758 : FILE *f;
1759 :
1760 : // psPageName contains a single page of PostScript.
1761 17 : f = xtmpfile(&psPageName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT);
1762 17 : if (0 /* nullptr */ == f)
1763 0 : sys_fatal("xtmpfile");
1764 17 : fclose(f);
1765 :
1766 : // imagePageName contains a bitmap image of a single PostScript page.
1767 17 : f = xtmpfile(&imagePageName, PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT);
1768 17 : if (0 /* nullptr */ == f)
1769 0 : sys_fatal("xtmpfile");
1770 17 : fclose(f);
1771 :
1772 : // psFileName contains a PostScript file of the complete document.
1773 17 : f = xtmpfile(&psFileName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT);
1774 17 : if (0 /* nullptr */ == f)
1775 0 : sys_fatal("xtmpfile");
1776 17 : fclose(f);
1777 :
1778 : // regionFileName contains a list of the images and their boxed
1779 : // coordinates.
1780 17 : f = xtmpfile(®ionFileName,
1781 : REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT);
1782 17 : if (0 /* nullptr */ == f)
1783 0 : sys_fatal("xtmpfile");
1784 17 : fclose(f);
1785 17 : }
1786 :
1787 17 : static bool do_file(const char *filename)
1788 : {
1789 : FILE *fp;
1790 :
1791 17 : current_filename = filename;
1792 17 : if (strcmp(filename, "-") == 0)
1793 17 : fp = stdin;
1794 : else {
1795 0 : fp = fopen(filename, "r");
1796 0 : if (0 /* nullptr*/ == fp) {
1797 0 : current_filename = 0 /* nullptr */;
1798 0 : error("unable to open '%1': %2", filename, strerror(errno));
1799 0 : return false;
1800 : }
1801 : }
1802 17 : inputFile.read_file(fp);
1803 17 : if (fp != stdin)
1804 0 : if (fclose(fp) != 0)
1805 0 : sys_fatal("fclose");
1806 17 : current_filename = 0 /* nullptr */;
1807 17 : return true;
1808 : }
1809 :
1810 17 : static void cleanup(void)
1811 : {
1812 17 : free(const_cast<char *>(image_gen));
1813 17 : }
1814 :
1815 17 : int main(int argc, char **argv)
1816 : {
1817 17 : program_name = argv[0];
1818 : #ifdef CAPTURE_MODE
1819 : fprintf(stderr, "%s: invoked with %d arguments ...\n", program_name,
1820 : argc);
1821 : for (int i = 0; i < argc; i++)
1822 : fprintf(stderr, "%2d: %s\n", i, argv[i]);
1823 : FILE *dump = fopen(DEBUG_FILE("pre-html-data"), "wb");
1824 : if (dump != 0 /* nullptr */) {
1825 : while((int ch = fgetc(stdin)) >= 0)
1826 : fputc(ch, dump);
1827 : fclose(dump);
1828 : }
1829 : exit(EXIT_FAILURE);
1830 : #endif /* CAPTURE_MODE */
1831 17 : if (atexit(&cleanup) != 0)
1832 0 : sys_fatal("atexit");
1833 17 : int operand_index = scanArguments(argc, argv);
1834 17 : image_gen = strsave(get_image_generator());
1835 17 : if (0 /* nullptr */ == image_gen)
1836 0 : fatal("'image_generator' directive not found in file '%1'",
1837 0 : devhtml_desc);
1838 17 : postscriptRes = get_resolution();
1839 17 : if (postscriptRes < 1) // TODO: what's a more sane minimum value?
1840 0 : fatal("'res' directive missing or invalid in file '%1'",
1841 0 : devps_desc);
1842 17 : setupAntiAlias();
1843 17 : checkImageDir();
1844 17 : makeFileName();
1845 17 : bool have_file_operand = false;
1846 17 : while (operand_index < argc) {
1847 0 : if (argv[operand_index][0] != '-') {
1848 0 : if(!do_file(argv[operand_index]))
1849 0 : exit(EXIT_FAILURE);
1850 0 : have_file_operand = true;
1851 : }
1852 0 : operand_index++;
1853 : }
1854 :
1855 17 : if (!have_file_operand)
1856 17 : do_file("-");
1857 17 : makeTempFiles();
1858 17 : int wstatus = inputFile.do_image(argc, argv);
1859 17 : if (wstatus == 0) {
1860 17 : generateImages(regionFileName);
1861 17 : wstatus = inputFile.do_html(argc, argv);
1862 : }
1863 : else
1864 0 : if (WEXITSTATUS(wstatus) != 0)
1865 : // XXX: This is a crappy suggestion. See Savannah #62673.
1866 0 : fatal("'%1' exited with status %2; re-run with a different output"
1867 : " driver to see diagnostic messages", argv[0],
1868 0 : WEXITSTATUS(wstatus));
1869 17 : exit(EXIT_SUCCESS);
1870 : }
1871 :
1872 : // Local Variables:
1873 : // fill-column: 72
1874 : // mode: C++
1875 : // End:
1876 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|