/
niceSOFT
/
groff
Обзор
Документация
Войти
/
niceSOFT
/
groff
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/preproc/html/pre-html.cpp
1 877 строк
48 KB
G. Branden Robinson
[pre-grohtml]: Make crappy suggestion less crappy.
18 июл 2026, 02:14
18 июл 2026, 02:14
6804421
Код
Авторство
О чём код?
/* Copyright (C) 2000-2025 Free Software Foundation, Inc. * Written by Gaius Mulley (gaius@glam.ac.uk). * * This file is part of groff, the GNU roff typesetting system. * * groff is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * groff is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <assert.h> #include <ctype.h> // isspace() #include <errno.h> #include <stdarg.h> // va_list, va_end(), va_start(), vsnprintf() #include <stdio.h> // EOF, FILE, fclose(), feof(), fflush(), fopen(), // fprintf(), fputc(), fread(), getc(), printf(), // stderr, stdin, stdout, ungetc() #include <stdlib.h> // atexit(), atoi(), exit(), free(), getenv(), // malloc(), system() #include <string.h> // memcpy(), strchr(), strcmp(), strcpy(), // strerror(), strlen(), strncmp(), strsignal() #include <getopt.h> // getopt_long() #include <new> // std::bad_alloc // needed for close(), creat(), dup(), dup2(), execvp(), fork(), // getpid(), mkdir(), open(), pipe(), unlink(), wait(), write() #include "posix.h" #include "nonposix.h" #define PREHTMLC #include "lib.h" #include "errarg.h" #include "error.h" #include "stringclass.h" #include "defs.h" #include "searchpath.h" #include "paper.h" #include "device.h" #include "font.h" #ifdef _POSIX_VERSION # include <sys/wait.h> # define PID_T pid_t #else /* not _POSIX_VERSION */ # define PID_T int #endif /* not _POSIX_VERSION */ /* Establish some definitions to facilitate discrimination between differing runtime environments. */ #undef MAY_FORK_CHILD_PROCESS #undef MAY_SPAWN_ASYNCHRONOUS_CHILD #if defined(__MSDOS__) || defined(_WIN32) // Most MS-DOS and Win32 environments will be missing the 'fork' // capability (some, like Cygwin, have it, but it is better avoided). // // Bruno Haible adds in 2023: // However, because on Cygwin, neither ... __MSDOS__ [nor] _WIN32 is // defined, the code [here] *will* use fork() on Cygwin. Which works // fine, but is known to be slow. However, I don't know whether the // native Windows code (_WIN32) will work on Cygwin: many native // Windows APIs don't work right from within Cygwin, because the // worlds inside and outside a Cygwin process are quite different. # define MAY_FORK_CHILD_PROCESS 0 // On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'. # include <process.h> // for 'spawn...' # include <fcntl.h> // for attributes of pipes # if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32) // These Win32 implementations allow parent and 'spawn...'ed child to // multitask asynchronously. # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1 # else // Others may adopt MS-DOS behaviour where parent must sleep, // from 'spawn...' until child terminates. # define MAY_SPAWN_ASYNCHRONOUS_CHILD 0 # endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */ # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR) /* When we are building a DEBUGGING version we need to tell pre-grohtml where to put intermediate files (the DEBUGGING version will preserve these on exit). On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will probably not have this on all disk drives, so default to using 'c:/temp' instead. (Note that user may choose to override this by supplying a definition such as -DDEBUG_FILE_DIR=d:/path/to/debug/files in the CPPFLAGS to 'make'.) */ # define DEBUG_FILE_DIR c:/temp # endif #else /* not __MSDOS__ or _WIN32 */ // For non-Microsoft environments assume Unix conventions, // so 'fork' is required and child processes are asynchronous. # define MAY_FORK_CHILD_PROCESS 1 # define MAY_SPAWN_ASYNCHRONOUS_CHILD 1 # if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR) /* For a DEBUGGING version, on the Unix host, we can also usually rely on being able to use '/tmp' for temporary file storage. (Note that, as in the __MSDOS__ or _WIN32 case above, the user may override this by defining -DDEBUG_FILE_DIR=/path/to/debug/files in the CPPFLAGS.) */ # define DEBUG_FILE_DIR /tmp # endif #endif /* not __MSDOS__ or _WIN32 */ // For a DEBUGGING version, we need some additional macros, // to direct the captured debugging mode output to appropriately named // files in the specified DEBUG_FILE_DIR. # define DEBUG_TEXT(text) #text # define DEBUG_NAME(text) DEBUG_TEXT(text) # define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name extern "C" const char *Version_string; #include "pre-html.h" #include "pushback.h" #include "html-strings.h" #define DEFAULT_LINE_LENGTH 7 // inches wide #define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution #define IMAGE_BORDER_PIXELS 0 #define INLINE_LEADER_CHAR '\\' // Don't use colour names here! Otherwise there is a dependency on // a file called 'rgb.txt' which maps names to colours. #define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f" #define MIN_ALPHA_BITS 0 #define MAX_ALPHA_BITS 4 #define PAGE_TEMPLATE_SHORT "pg" #define PAGE_TEMPLATE_LONG "-page-" #define PS_TEMPLATE_SHORT "ps" #define PS_TEMPLATE_LONG "-ps-" #define REGION_TEMPLATE_SHORT "rg" #define REGION_TEMPLATE_LONG "-regions-" typedef enum { CENTERED, LEFT, RIGHT, INLINE } IMAGE_ALIGNMENT; typedef enum {xhtml, html4} html_dialect; static int postscriptRes = -1; // PostScript resolution, // dots per inch static int stdoutfd = 1; // output file descriptor - // normally 1 but might move // -1 means closed static const char *psFileName = 0 /* nullptr */; // PostScript // file name static const char *psPageName = 0 /* nullptr */; // name of file // containing // current // PostScript // page static const char *regionFileName = 0 /* nullptr */; // name of file // containing // all image // regions static const char *imagePageName = 0 /* nullptr */; // name of // bitmap image // file // containing // current page static const char *image_device = "pnmraw"; static int image_res = DEFAULT_IMAGE_RES; static int vertical_offset = 0; static char *image_template = 0 /* nullptr */; // image file name // template static const char *macroset_template= 0 /* nullptr */; // image file // name template // passed to // troff by -D static int troff_arg = 0; // troff arg index static const char *image_dir = 0 /* nullptr */; // user-specified image // directory static int textAlphaBits = MAX_ALPHA_BITS; static int graphicAlphaBits = MAX_ALPHA_BITS; static const char *antiAlias = 0 /* nullptr */; // anti-alias arguments // to be passed to gs static bool want_progress_report = false; // display page numbers // as they are processed static int currentPageNo = -1; // current image page number static bool debugging = false; static const char *troffFileName = 0 /* nullptr */; // pre-html // output sent // to troff -Tps static const char *htmlFileName = 0 /* nullptr */; // pre-html // output sent // to troff // -Thtml static bool need_eqn = false; // must we preprocess via eqn? static char *linebuf = 0 /* nullptr */; // for scanning devps/DESC static int linebufsize = 0; static const char *image_gen = 0 /* nullptr */; // the 'gs' program static const char devhtml_desc[] = "devhtml/DESC"; static const char devps_desc[] = "devps/DESC"; const char *const FONT_ENV_VAR = "GROFF_FONT_PATH"; static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0); static html_dialect dialect = html4; /* * Images are generated via PostScript, gs, and the pnm utilities. */ #define IMAGE_DEVICE "-Tps" /* * sys_fatal - Write a fatal error message. * Taken from src/roff/groff/pipeline.c. */ void sys_fatal(const char *s) { fatal("%1: %2", s, strerror(errno)); } /* * get_line - Copy a line (w/o newline) from a file to the * global line buffer. * * TODO: Discard; migrate callers to POSIX `getline()`. * https://pubs.opengroup.org/onlinepubs/9799919799/functions/\ * getline.html */ static bool get_line(FILE *f, const char *file_name, int lineno) { if (0 /* nullptr */ == f) return false; if (0 /* nullptr */ == linebuf) { linebufsize = 128; try { linebuf = new char[linebufsize]; // C++03: new int[linebufsize](); (void) memset(linebuf, '\0', (linebufsize * sizeof(char))); } catch (std::bad_alloc &e) { fatal_with_file_and_line(file_name, lineno, "cannot allocate %1" " bytes to read line; aborting", linebufsize); } } int i = 0; // skip leading whitespace for (;;) { int c = getc(f); if (EOF == c) return false; if (c != ' ' && c != '\t') { ungetc(c, f); break; } } for (;;) { int c = getc(f); if (EOF == c) break; if (i + 1 >= linebufsize) { int newbufsize = linebufsize * 2; char *old_linebuf = linebuf; try { linebuf = new char[newbufsize]; // C++03: new int[newbufsize](); (void) memset(linebuf, '\0', (newbufsize * sizeof(char))); } catch (std::bad_alloc &e) { fatal_with_file_and_line(file_name, lineno, "cannot allocate" " more than %1 bytes to read line;" " aborting", linebufsize); } memcpy(linebuf, old_linebuf, linebufsize); delete[] old_linebuf; linebufsize = newbufsize; } linebuf[i++] = c; if ('\n' == c) { i--; break; } } linebuf[i] = '\0'; return true; } /* * get_resolution - Return the PostScript device resolution. */ static unsigned int get_resolution(void) { char *pathp; FILE *f; unsigned int res = 0; f = font_path.open_file(devps_desc, &pathp); if (0 /* nullptr */ == f) fatal("cannot open file '%1': %2", devps_desc, strerror(errno)); int lineno = 0; while (get_line(f, pathp, lineno++)) { (void) sscanf(linebuf, "res %u", &res); // We must stop reading at a "charset" line; see groff_font(5). if (strncmp(linebuf, "charset", sizeof "charset") == 0) { // Don't be fooled by non-groff extensions. char trailing_char = linebuf[(sizeof "charset") - 1]; if (isspace(trailing_char) || '\0' == trailing_char) break; } } free(pathp); fclose(f); return res; } /* * get_image_generator - Return the declared program from the HTML * device description. */ static char *get_image_generator(void) { char *pathp; FILE *f; char *generator = 0 /* nullptr */; const char keyword[] = "image_generator"; const size_t keyword_len = strlen(keyword); f = font_path.open_file(devhtml_desc, &pathp); if (0 /* nullptr */ == f) fatal("cannot open file '%1': %2", devhtml_desc, strerror(errno)); int lineno = 0; while (get_line(f, pathp, lineno++)) { char *cursor = linebuf; size_t limit = strlen(linebuf); char *end = linebuf + limit; if (0 == (strncmp(linebuf, keyword, keyword_len))) { cursor += keyword_len; // At least one space or tab is required. if(!(' ' == *cursor) || ('\t' == *cursor)) continue; cursor++; while((cursor < end) && ((' ' == *cursor) || ('\t' == *cursor))) cursor++; if (cursor == end) continue; generator = cursor; } // We must stop reading at a "charset" line; see groff_font(5). if (strncmp(linebuf, "charset", sizeof "charset") == 0) { // Don't be fooled by non-groff extensions. char trailing_char = linebuf[(sizeof "charset") - 1]; if (isspace(trailing_char) || '\0' == trailing_char) break; } } free(pathp); fclose(f); return generator; } /* * html_system - A wrapper for system(). */ static void html_system(const char *s, int redirect_stdout) { if (debugging) { fprintf(stderr, "%s: debug: executing: ", program_name); fwrite(s, sizeof(char), strlen(s), stderr); fflush(stderr); } { int saved_stdout = dup(STDOUT_FILENO); int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666); if (redirect_stdout && (saved_stdout > STDOUT_FILENO) && (fdnull > STDOUT_FILENO)) dup2(fdnull, STDOUT_FILENO); if (fdnull >= 0) close(fdnull); int status = system(s); if (redirect_stdout) dup2(saved_stdout, STDOUT_FILENO); if (-1 == status) fprintf(stderr, "%s: unable to execute command '%s': %s\n", program_name, s, strerror(errno)); else if (status > 0) { if (WIFEXITED(status)) fprintf(stderr, "%s: command '%s' returned status %d\n", program_name, s, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) fprintf(stderr, "%s: command '%s' exited by signal: %s\n", program_name, s, strsignal(WTERMSIG(status))); else if (WIFSTOPPED(status)) fprintf(stderr, "%s: command '%s' stopped: %s\n", program_name, s, strsignal(WSTOPSIG(status))); else fprintf(stderr, "%s: command '%s' exited abnormally\n", program_name, s); } close(saved_stdout); } } /* * make_string - Create a string via `malloc()`, place the variadic * arguments as formatted by `fmt` into it, and return * it. Adapted from Linux man-pages' printf(3) example. * We never return a null pointer, instead treating * failure as invariably fatal. */ static const char *make_string(const char *fmt, ...) { size_t size = 0; char *p = 0 /* nullptr */; va_list ap; va_start(ap, fmt); int n = vsnprintf(p, size, fmt, ap); va_end(ap); if (n < 0) sys_fatal("vsnprintf"); size = static_cast<size_t>(n) + 1 /* '\0' */; p = static_cast<char *>(malloc(size)); if (0 /* nullptr */ == p) sys_fatal("vsnprintf"); va_start(ap, fmt); n = vsnprintf(p, size, fmt, ap); va_end(ap); if (n < 0) sys_fatal("vsnprintf"); assert(p != 0 /* nullptr */); return p; } /* * classes and methods for retaining ascii text */ struct char_block { enum { SIZE = 256 }; char buffer[SIZE]; int used; char_block *next; char_block(); }; char_block::char_block() : used(0), next(0) { for (int i = 0; i < SIZE; i++) buffer[i] = 0; } class char_buffer { public: char_buffer(); ~char_buffer(); void read_file(FILE *fp); int do_html(int argc, char *argv[]); int do_image(int argc, char *argv[]); void emit_troff_output(int device_format_selector); void write_upto_newline(char_block **t, int *i, int is_html); bool can_see(char_block **t, int *i, const char *string); void skip_until_newline(char_block **t, int *i); private: char_block *head; char_block *tail; int run_output_filter(int device_format_selector, int argc, char *argv[]); }; char_buffer::char_buffer() : head(0), tail(0) { } char_buffer::~char_buffer() { while (head != 0 /* nullptr */) { char_block *temp = head; head = head->next; delete temp; } } /* * read_file - Read file `fp` into char_blocks. */ void char_buffer::read_file(FILE *fp) { int n; while (!feof(fp)) { if (0 /* nullptr */ == tail) { tail = new char_block; head = tail; } else { if (tail->used == char_block::SIZE) { tail->next = new char_block; tail = tail->next; } } // We now have a tail ready for the next `SIZE` bytes of the file. n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used, fp); if ((n < 0) || ((0 == n) && !feof(fp))) sys_fatal("fread"); tail->used += n * sizeof(char); } } /* * writeNbytes - Write n bytes to stdout. */ static void writeNbytes(const char *s, int l) { int n = 0; int r; while (n < l) { r = write(stdoutfd, s, l - n); if (r < 0) sys_fatal("write"); n += r; s += r; } } /* * writeString - Write a string to stdout. */ static void writeString(const char *s) { writeNbytes(s, strlen(s)); } /* * makeFileName - Create the image filename template * and the macroset image template. */ static void makeFileName(void) { if ((image_dir != 0 /* nullptr */) && (strchr(image_dir, '%') != 0 /* nullptr */)) fatal("'%%' is prohibited within the image directory name"); if ((image_template != 0 /* nullptr */) && (strchr(image_template, '%') != 0 /* nullptr */)) fatal("'%%' is prohibited within the image template"); if (0 /* nullptr */ == image_dir) image_dir = (char *)""; else if (strlen(image_dir) > 0 && image_dir[strlen(image_dir) - 1] != '/') image_dir = make_string("%s/", image_dir); if (0 /* nullptr */ == image_template) macroset_template = make_string("%sgrohtml-%d-", image_dir, int(getpid())); else macroset_template = make_string("%s%s-", image_dir, image_template); size_t mtlen = strlen(macroset_template); image_template = (char *)malloc(strlen("%d") + mtlen + 1); if (0 /* nullptr */ == image_template) sys_fatal("malloc"); char *s = strcpy(image_template, macroset_template); s += mtlen; // Keep this format string synced with troff:suppress_node::tprint(). strcpy(s, "%d"); } /* * setupAntiAlias - Set up the antialias string, used when we call gs. */ static void setupAntiAlias(void) { if (textAlphaBits == 0 && graphicAlphaBits == 0) antiAlias = make_string(" "); else if (textAlphaBits == 0) antiAlias = make_string("-dGraphicsAlphaBits=%d ", graphicAlphaBits); else if (graphicAlphaBits == 0) antiAlias = make_string("-dTextAlphaBits=%d ", textAlphaBits); else antiAlias = make_string("-dTextAlphaBits=%d" " -dGraphicsAlphaBits=%d ", textAlphaBits, graphicAlphaBits); } /* * checkImageDir - Check whether the image directory is available. */ static void checkImageDir(void) { if (image_dir != 0 /* nullptr */ && strcmp(image_dir, "") != 0) if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST)) fatal("cannot create directory '%1': %2", image_dir, strerror(errno)); } /* * write_end_image - End the image. Write out the image extents if we * are using -Tps. */ static void write_end_image(int is_html) { /* * if we are producing html then these * emit image name and enable output * else * we are producing images * in which case these generate image * boundaries */ writeString("\\O[4]\\O[2]"); if (is_html) writeString("\\O[1]"); else writeString("\\O[0]"); } /* * write_start_image - Write troff code which will: * * (i) disable html output for the following image * (ii) reset the max/min x/y registers during * Postscript Rendering. */ static void write_start_image(IMAGE_ALIGNMENT pos, int is_html) { writeString("\\O[5"); switch (pos) { case INLINE: writeString("i"); break; case LEFT: writeString("l"); break; case RIGHT: writeString("r"); break; case CENTERED: default: writeString("c"); break; } writeString(image_template); writeString(".png]"); if (is_html) writeString("\\O[0]\\O[3]"); else // reset min/max registers writeString("\\O[1]\\O[3]"); } /* * write_upto_newline - Write the contents of the buffer until a * newline is seen. Check for * HTML_IMAGE_INLINE_BEGIN and * HTML_IMAGE_INLINE_END; process them if they are * present. */ void char_buffer::write_upto_newline(char_block **t, int *i, int is_html) { int j = *i; if (*t) { while (j < (*t)->used && (*t)->buffer[j] != '\n' && (*t)->buffer[j] != INLINE_LEADER_CHAR) j++; if (j < (*t)->used && (*t)->buffer[j] == '\n') j++; writeNbytes((*t)->buffer + (*i), j - (*i)); if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) { if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN)) write_start_image(INLINE, is_html); else if (can_see(t, &j, HTML_IMAGE_INLINE_END)) write_end_image(is_html); else { if (j < (*t)->used) { *i = j; j++; writeNbytes((*t)->buffer + (*i), j - (*i)); } } } if (j == (*t)->used) { *i = 0; *t = (*t)->next; if (*t && (*t)->buffer[j - 1] != '\n') write_upto_newline(t, i, is_html); } else // newline was seen *i = j; } } /* * can_see - Return true if we can see string in t->buffer[i] onward. */ bool char_buffer::can_see(char_block **t, int *i, const char *str) { size_t j = 0; size_t l = strlen(str); int k = *i; char_block *s = *t; while (s) { while (k < s->used && j < l && s->buffer[k] == str[j]) { j++; k++; } if (j == l) { *i = k; *t = s; return true; } else if (k < s->used && s->buffer[k] != str[j]) return false; s = s->next; k = 0; } return false; } /* * skip_until_newline - Skip all characters until a newline is seen. * The newline is not consumed. */ void char_buffer::skip_until_newline(char_block **t, int *i) { int j = *i; if (*t) { while (j < (*t)->used && (*t)->buffer[j] != '\n') j++; if (j == (*t)->used) { *i = 0; *t = (*t)->next; skip_until_newline(t, i); } else // newline was seen *i = j; } } #define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER) #define HTML_OUTPUT_FILTER 0 #define IMAGE_OUTPUT_FILTER 1 #define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR) #define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName) #define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName) /* * emit_troff_output - Write formatted buffer content to the troff * post-processor data pipeline. */ void char_buffer::emit_troff_output(int device_format_selector) { // Handle output for BOTH html and image device formats // if 'device_format_selector' is passed as // // HTML_FORMAT(HTML_OUTPUT_FILTER) // Buffer data is written to the output stream // with template image names translated to actual image names. // // HTML_FORMAT(IMAGE_OUTPUT_FILTER) // Buffer data is written to the output stream // with no translation, for image file creation in the // post-processor. int idx = 0; char_block *element = head; while (element != 0 /* nullptr */) write_upto_newline(&element, &idx, device_format_selector); #if 0 if (close(stdoutfd) < 0) sys_fatal ("close"); // now we grab fd=1 so that the next pipe cannot use fd=1 if (stdoutfd == 1) { if (dup(2) != stdoutfd) sys_fatal ("dup failed to use fd=1"); } #endif /* 0 */ } /* * The image class remembers the position of all images in the * PostScript file and assigns names for each image. */ struct imageItem { imageItem *next; int X1; int Y1; int X2; int Y2; char *imageName; int resolution; int maxx; int pageNo; imageItem(int x1, int y1, int x2, int y2, int page, int res, int max_width, char *name); ~imageItem(); }; /* * imageItem - Constructor. */ imageItem::imageItem(int x1, int y1, int x2, int y2, int page, int res, int max_width, char *name) { X1 = x1; Y1 = y1; X2 = x2; Y2 = y2; pageNo = page; resolution = res; maxx = max_width; imageName = name; next = 0 /* nullptr */; } /* * imageItem - Destructor. */ imageItem::~imageItem() { if (imageName) free(imageName); } /* * imageList - A class containing a list of imageItems. */ class imageList { private: imageItem *head; imageItem *tail; int count; public: imageList(); ~imageList(); void add(int x1, int y1, int x2, int y2, int page, int res, int maxx, char *name); void createImages(void); int createPage(int pageno); void createImage(imageItem *i); int getMaxX(int pageno); }; /* * imageList - Constructor. */ imageList::imageList() : head(0), tail(0), count(0) { } /* * imageList - Destructor. */ imageList::~imageList() { while (head != 0 /* nullptr */) { imageItem *i = head; head = head->next; delete i; } } /* * createPage - Create image of page `pageno` from PostScript file. */ int imageList::createPage(int pageno) { if (currentPageNo == pageno) return 0; if (currentPageNo >= 1) { /* * We need to unlink the files which change each time a new page is * processed. The final unlink is done by xtmpfile when * pre-grohtml exits. */ unlink(imagePageName); unlink(psPageName); } if (want_progress_report) { fprintf(stderr, "[%d] ", pageno); fflush(stderr); } if (debugging) fprintf(stderr, "%s: debug: creating page %d\n", program_name, pageno); const char *s = make_string("ps2ps -sPageList=%d %s %s", pageno, psFileName, psPageName); html_system(s, 1); assert(strlen(image_gen) > 0); s = make_string("echo showpage | " "%s%s -q -dBATCH -dSAFER " "-dDEVICEHEIGHTPOINTS=792 " "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true " "-sDEVICE=%s -r%d %s " "-sOutputFile=%s %s -", image_gen, EXE_EXT, (getMaxX(pageno) * image_res) / postscriptRes, image_device, image_res, antiAlias, imagePageName, psPageName); html_system(s, 1); free(const_cast<char *>(s)); currentPageNo = pageno; return 0; } /* * min - Return the minimum of two numbers. */ static int min(int x, int y) { if (x < y) return x; else return y; } /* * max - Return the maximum of two numbers. */ static int max(int x, int y) { if (x > y) return x; else return y; } /* * getMaxX - Return the largest right-hand position for any image * on `pageno`. */ int imageList::getMaxX(int pageno) { imageItem *h = head; int x = postscriptRes * DEFAULT_LINE_LENGTH; while (h != 0 /* nullptr */) { if (h->pageNo == pageno) x = max(h->X2, x); h = h->next; } return x; } /* * createImage - Generate a minimal PNG file from the set of page * images. */ void imageList::createImage(imageItem *i) { if (i->X1 != -1) { int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes - IMAGE_BORDER_PIXELS, 0); int y1 = max(image_res * vertical_offset / 72 + min(i->Y1, i->Y2) * image_res / postscriptRes - IMAGE_BORDER_PIXELS, 0); int x2 = max(i->X1, i->X2) * image_res / postscriptRes + IMAGE_BORDER_PIXELS; int y2 = image_res * vertical_offset / 72 + max(i->Y1, i->Y2) * image_res / postscriptRes + 1 + IMAGE_BORDER_PIXELS; if (createPage(i->pageNo) == 0) { const char *s = make_string("pamcut%s %d %d %d %d < %s " "| pnmcrop%s " PNMTOOLS_QUIET "| pnmtopng%s " PNMTOOLS_QUIET " %s" "> %s", EXE_EXT, x1, y1, x2 - x1 + 1, y2 - y1 + 1, imagePageName, EXE_EXT, EXE_EXT, TRANSPARENT, i->imageName); html_system(s, 0); free(const_cast<char *>(s)); } else { fprintf(stderr, "%s: failed to generate image of page %d\n", program_name, i->pageNo); fflush(stderr); } #if defined(DEBUGGING) } else { if (debugging) { fprintf(stderr, "%s: debug: ignoring image as x1 coord is -1\n", program_name); fflush(stderr); } #endif } } /* * add - Add an image description to the imageList. */ void imageList::add(int x1, int y1, int x2, int y2, int page, int res, int maxx, char *name) { imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name); if (0 /* nullptr */ == head) { head = i; tail = i; } else { tail->next = i; tail = i; } } /* * createImages - For each image descriptor on the imageList, * create the actual image. */ void imageList::createImages(void) { imageItem *h = head; while (h != 0 /* nullptr */) { createImage(h); h = h->next; } } static imageList listOfImages; // list of images defined by region file /* * generateImages - Parse the region file and generate images from the * PostScript file. The region file contains the * x1,y1--x2,y2 extents of each image. */ static void generateImages(const char *region_file_name) { pushBackBuffer *f=new pushBackBuffer(region_file_name); while (f->putPB(f->getPB()) != eof) { if (f->isString("grohtml-info:page")) { int page = f->readInt(); int x1 = f->readInt(); int y1 = f->readInt(); int x2 = f->readInt(); int y2 = f->readInt(); int maxx = f->readInt(); char *name = f->readString(); int res = postscriptRes; listOfImages.add(x1, y1, x2, y2, page, res, maxx, name); while (f->putPB(f->getPB()) != '\n' && f->putPB(f->getPB()) != eof) (void)f->getPB(); if (f->putPB(f->getPB()) == '\n') (void)f->getPB(); } else { /* Write any error messages out to the user. */ fputc(f->getPB(), stderr); } } fflush(stderr); listOfImages.createImages(); if (want_progress_report) { fprintf(stderr, "done\n"); fflush(stderr); } delete f; } /* * set_redirection - Redirect file descriptor `was` to file descriptor * `willbe`. */ static void set_redirection(int was, int willbe) { // Nothing to do if 'was' and 'willbe' already have same handle. if (was != willbe) { // Otherwise attempt the specified redirection. if (dup2(willbe, was) < 0) { // Redirection failed, so issue diagnostic and bail out. fprintf(stderr, "%s: unable to replace fd=%d with %d", program_name, was, willbe); if (willbe == STDOUT_FILENO) fprintf(stderr, "; likely that stdout should be opened before %d", was); fputc('\n', stderr); sys_fatal("dup2"); } // When redirection has been successfully completed assume redundant // handle 'willbe' is no longer required, so close it. if (close(willbe) < 0) // Issue diagnostic if 'close' fails. sys_fatal("close"); } } /* * save_and_redirect - Duplicate file descriptor for `was` on file * descriptor `willbe`. */ static int save_and_redirect(int was, int willbe) { if (was == willbe) // No redirection specified; silently bail out. return (was); // Proceeding with redirection so first save and verify our duplicate // handle for 'was'. int saved = dup(was); if (saved < 0) { fprintf(stderr, "%s: unable to get duplicate file descriptor for" " %d\n", program_name, was); sys_fatal("dup"); } // Duplicate handle safely established so complete redirection. set_redirection(was, willbe); // Finally return the saved duplicate descriptor for the original // 'was' descriptor. return saved; } /* * alterDeviceTo - If toImage is set * the argument list is altered to include * IMAGE_DEVICE; we invoke groff rather than troff. * Else * set -Thtml and groff. */ static void alterDeviceTo(int argc, char *argv[], int toImage) { int i = 0; if (toImage) { while (i < argc) { if ((strcmp(argv[i], "-Thtml") == 0) || (strcmp(argv[i], "-Txhtml") == 0)) argv[i] = (char *)IMAGE_DEVICE; i++; } argv[troff_arg] = (char *)"groff"; /* rather than troff */ } else { while (i < argc) { if (strcmp(argv[i], IMAGE_DEVICE) == 0) { if (dialect == xhtml) argv[i] = (char *)"-Txhtml"; else argv[i] = (char *)"-Thtml"; } i++; } argv[troff_arg] = (char *)"groff"; /* use groff -Z */ } } /* * addArg - Append newarg onto the command list for groff. */ static char **addArg(int argc, char *argv[], char *newarg) { char **new_argv = (char **)malloc((argc + 2) * sizeof(char *)); int i = 0; if (0 /* nullptr */ == new_argv) sys_fatal("malloc"); if (argc > 0) { new_argv[i] = argv[i]; i++; } new_argv[i] = newarg; while (i < argc) { new_argv[i + 1] = argv[i]; i++; } argc++; new_argv[argc] = 0 /* nullptr */; return new_argv; } /* * addRegDef - Append a defined register or string onto the command * list for troff. */ static char **addRegDef(int argc, char *argv[], const char *numReg) { char **new_argv = (char **)malloc((argc + 2) * sizeof(char *)); int i = 0; if (0 /* nullptr */ == new_argv) sys_fatal("malloc"); while (i < argc) { new_argv[i] = argv[i]; i++; } new_argv[argc] = strsave(numReg); argc++; new_argv[argc] = 0 /* nullptr */; return new_argv; } #if 0 /* * dump_args - Display the argument list. */ void dump_args(int argc, char *argv[]) { fprintf(stderr, " %d arguments:", argc); for (int i = 0; i < argc; i++) fprintf(stderr, " %s", argv[i]); fprintf(stderr, "\n"); } #endif /* * print_args - Print arguments as if issued on the command line. */ static void print_args(int argc, char *argv[]) { if (debugging) { fprintf(stderr, "%s: debug: executing: ", program_name); for (int i = 0; i < argc; i++) fprintf(stderr, "%s ", argv[i]); fputc('\n', stderr); } } int char_buffer::run_output_filter(int filter, int argc, char **argv) { int pipedes[2]; PID_T child_pid; int wstatus; print_args(argc, argv); if (pipe(pipedes) < 0) sys_fatal("pipe"); #if MAY_FORK_CHILD_PROCESS // This is the Unix process model. To invoke our post-processor, // we must 'fork' the current process. if ((child_pid = fork()) < 0) sys_fatal("fork"); else if (child_pid == 0) { // This is the child process. We redirect its input file descriptor // to read data emerging from our pipe. There is no point in // saving, since we won't be able to restore later! set_redirection(STDIN_FILENO, pipedes[0]); // The parent process will be writing this data; release the child's // writeable handle on the pipe since we have no use for it. if (close(pipedes[1]) < 0) sys_fatal("close"); // The IMAGE_OUTPUT_FILTER needs special output redirection... if (filter == IMAGE_OUTPUT_FILTER) { // ...with BOTH 'stdout' AND 'stderr' diverted to files, the // latter so that `generateImages()` can scrape "grohtml-info" // from it. set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM); set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM); } // Now we are ready to launch the output filter. execvp(argv[0], argv); // does not return unless it fails fatal("cannot execute '%1': %2", argv[0], strerror(errno)); } else { // This is the parent process. We write data to the filter pipeline // where the child will read it. We have no need to read from the // input side ourselves, so close it. if (close(pipedes[0]) < 0) sys_fatal("close"); // Now redirect the standard output file descriptor to the inlet end // of the pipe, and push the formatted data to the filter. pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]); emit_troff_output(DEVICE_FORMAT(filter)); // After emitting all the data we close our connection to the inlet // end of the pipe so the child process will detect end of data. set_redirection(STDOUT_FILENO, pipedes[1]); // Finally, we must wait for the child process to complete. if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid) sys_fatal("wait"); } #elif MAY_SPAWN_ASYNCHRONOUS_CHILD // We do not have `fork` (or we prefer not to use it), but // asynchronous processes are allowed, passing data through pipes. // This should be okay for most Win32 systems and is preferred to // `fork` for starting child processes under Cygwin. // Before we start the post-processor we bind its inherited standard // input file descriptor to the readable end of our pipe, saving our // own standard input file descriptor in `pipedes[0]`. pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]); // For the Win32 model, // we need special provision for saving BOTH 'stdout' and 'stderr'. int saved_stdout = dup(STDOUT_FILENO); int saved_stderr = STDERR_FILENO; // The IMAGE_OUTPUT_FILTER needs special output redirection... if (filter == IMAGE_OUTPUT_FILTER) { // with BOTH 'stdout' AND 'stderr' diverted to files while saving a // duplicate handle for 'stderr'. set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM); saved_stderr = save_and_redirect(STDERR_FILENO, REGION_OUTPUT_STREAM); } // Use an asynchronous spawn request to start the post-processor. if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) { fatal("cannot spawn %1: %2", argv[0], strerror(errno)); } // Once the post-processor has been started we revert our 'stdin' // to its original saved source, which also closes the readable handle // for the pipe. set_redirection(STDIN_FILENO, pipedes[0]); // if we redirected 'stderr', for use by the image post-processor, // then we also need to reinstate its original assignment. if (filter == IMAGE_OUTPUT_FILTER) set_redirection(STDERR_FILENO, saved_stderr); // Now we redirect the standard output to the inlet end of the pipe, // and push out the appropriately formatted data to the filter. set_redirection(STDOUT_FILENO, pipedes[1]); emit_troff_output(DEVICE_FORMAT(filter)); // After emitting all the data we close our connection to the inlet // end of the pipe so the child process will detect end of data. set_redirection(STDOUT_FILENO, saved_stdout); // And finally, we must wait for the child process to complete. if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid) sys_fatal("wait"); #else /* can't do asynchronous pipes! */ // TODO: code to support an MS-DOS style process model should go here fatal("output filtering not supported on this platform"); #endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */ return wstatus; } /* * do_html - Set the troff number htmlflip and * write out the buffer to troff -Thtml. */ int char_buffer::do_html(int argc, char *argv[]) { string s; alterDeviceTo(argc, argv, 0); argv += troff_arg; // skip all arguments up to groff argc -= troff_arg; argv = addArg(argc, argv, (char *)"-Z"); argc++; s = (char *)"-dwww-image-template="; s += macroset_template; // Do not combine these statements, // otherwise they will not work. s += '\0'; // The trailing '\0' is ignored. argv = addRegDef(argc, argv, s.contents()); argc++; if (dialect == xhtml) { argv = addRegDef(argc, argv, "-rxhtml=1"); argc++; if (need_eqn) { argv = addRegDef(argc, argv, "-e"); argc++; } } # define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName) // slight security risk: only enabled if defined(DEBUGGING) if (debugging) { int saved_stdout = save_and_redirect(STDOUT_FILENO, HTML_DEBUG_STREAM); emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER)); set_redirection(STDOUT_FILENO, saved_stdout); } return run_output_filter(HTML_OUTPUT_FILTER, argc, argv); } /* * do_image - Write out the buffer to troff -Tps. */ int char_buffer::do_image(int argc, char *argv[]) { string s; alterDeviceTo(argc, argv, 1); argv += troff_arg; // skip all arguments up to troff/groff argc -= troff_arg; argv = addRegDef(argc, argv, "-rps4html=1"); argc++; s = "-dwww-image-template="; s += macroset_template; s += '\0'; argv = addRegDef(argc, argv, s.contents()); argc++; // Override local settings and produce a letter-size PostScript page // file. argv = addRegDef(argc, argv, "-P-pletter"); argc++; if (dialect == xhtml) { if (need_eqn) { argv = addRegDef(argc, argv, "-rxhtml=1"); argc++; } argv = addRegDef(argc, argv, "-e"); argc++; } # define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName) // slight security risk: only enabled if defined(DEBUGGING) if (debugging) { int saved_stdout = save_and_redirect(STDOUT_FILENO, IMAGE_DEBUG_STREAM); emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER)); set_redirection(STDOUT_FILENO, saved_stdout); } return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv); } static char_buffer inputFile; /* * usage - Emit usage message. */ static void usage(FILE *stream) { fprintf(stream, "usage: %s [-epV] [-a anti-aliasing-text-bits] [-D image-directory]" " [-F font-directory] [-g anti-aliasing-graphics-bits] [-i resolution]" " [-I image-stem] [-o image-vertical-offset] [-x html-dialect]" " troff-command troff-argument ...\n" "usage: %s {-v | --version}\n" "usage: %s --help\n", program_name, program_name, program_name); if (stdout == stream) { fputs("\n" "Prepare a troff(1) document for HTML formatting.\n" "\n" "This program is not intended to be executed standalone; it is\n" "normally part of a groff pipeline. If your need to run it manually\n" "(e.g., for debugging purposes), give the 'groff' program the\n" "command-line option '-V' to inspect the arguments with which\n", stream); fprintf(stream, "'%s' is called. See the grohtml(1) manual page.\n", program_name); } } /* * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D, * and -P-I. Return the argument index of the first * non-option. */ static int scanArguments(int argc, char **argv) { const char *cmdprefix = getenv("GROFF_COMMAND_PREFIX"); if (0 /* nullptr */ == cmdprefix) cmdprefix = PROG_PREFIX; size_t pfxlen = strlen(cmdprefix); char *troff_name = new char[pfxlen + strlen("troff") + 1]; char *s = strcpy(troff_name, cmdprefix); s += pfxlen; strcpy(s, "troff"); int c, i; static const struct option long_options[] = { { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 }, { "version", no_argument, 0 /* nullptr */, 'v' }, { 0 /* nullptr */, 0, 0, 0 } }; while ((c = getopt_long(argc, argv, "+:a:bCdD:eF:g:Ghi:I:j:k:lno:prs:S:vVx:y", long_options, 0 /* nullptr */)) != EOF) switch (c) { case 'a': textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)), MAX_ALPHA_BITS); if (textAlphaBits == 3) fatal("cannot use 3 bits of antialiasing information"); break; case 'b': // handled by post-grohtml (set background color to white) break; case 'C': // handled by post-grohtml (don't write Creator HTML comment) break; case 'd': debugging = true; break; case 'D': image_dir = optarg; break; case 'e': need_eqn = true; break; case 'F': font_path.command_line_dir(optarg); break; case 'g': graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)), MAX_ALPHA_BITS); if (graphicAlphaBits == 3) fatal("cannot use 3 bits of antialiasing information"); break; case 'G': // handled by post-grohtml (don't write CreationDate HTML comment) break; case 'h': // handled by post-grohtml (write headings with font size changes) break; case 'i': image_res = atoi(optarg); break; case 'I': image_template = optarg; break; case 'j': // handled by post-grohtml (set job name for multiple file output) break; case 'k': // handled by post-grohtml (charset ASCII/mixed/UTF-8) break; case 'l': // handled by post-grohtml (no automatic section links) break; case 'n': // handled by post-grohtml (generate simple heading anchors) break; case 'o': vertical_offset = atoi(optarg); break; case 'p': want_progress_report = true; break; case 'r': // handled by post-grohtml (no header and footer lines) break; case 's': // handled by post-grohtml (use font size n as the HTML base size) break; case 'S': // handled by post-grohtml (set file split level) break; case 'v': printf("GNU pre-grohtml (groff) version %s\n", Version_string); exit(EXIT_SUCCESS); case 'V': // handled by post-grohtml (create validator button) break; case 'x': // html dialect if (strcmp(optarg, "x") == 0) dialect = xhtml; else if (strcmp(optarg, "4") == 0) dialect = html4; else warning("unsupported HTML dialect: '%1'", optarg); break; case 'y': // handled by post-grohtml (create groff signature) break; case CHAR_MAX + 1: // --help usage(stdout); exit(EXIT_SUCCESS); break; case '?': if (optopt != 0) error("unrecognized command-line option '%1'", char(optopt)); else error("unrecognized command-line option '%1'", argv[(optind - 1)]); usage(stderr); exit(2); break; case ':': error("command-line option '%1' requires an argument", char(optopt)); usage(stderr); exit(2); break; default: break; } i = optind; while (i < argc) { if (strcmp(argv[i], troff_name) == 0) troff_arg = i; else if (argv[i][0] != '-') return i; i++; } delete[] troff_name; return argc; } /* * makeTempFiles - Name the temporary files. */ static void makeTempFiles(void) { psFileName = DEBUG_FILE("prehtml-ps"); regionFileName = DEBUG_FILE("prehtml-region"); imagePageName = DEBUG_FILE("prehtml-page"); psPageName = DEBUG_FILE("prehtml-psn"); troffFileName = DEBUG_FILE("prehtml-troff"); htmlFileName = DEBUG_FILE("prehtml-html"); FILE *f; // psPageName contains a single page of PostScript. f = xtmpfile(&psPageName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT); if (0 /* nullptr */ == f) sys_fatal("xtmpfile"); fclose(f); // imagePageName contains a bitmap image of a single PostScript page. f = xtmpfile(&imagePageName, PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT); if (0 /* nullptr */ == f) sys_fatal("xtmpfile"); fclose(f); // psFileName contains a PostScript file of the complete document. f = xtmpfile(&psFileName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT); if (0 /* nullptr */ == f) sys_fatal("xtmpfile"); fclose(f); // regionFileName contains a list of the images and their boxed // coordinates. f = xtmpfile(®ionFileName, REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT); if (0 /* nullptr */ == f) sys_fatal("xtmpfile"); fclose(f); } static bool do_file(const char *filename) { FILE *fp; current_filename = filename; if (strcmp(filename, "-") == 0) fp = stdin; else { fp = fopen(filename, "r"); if (0 /* nullptr*/ == fp) { current_filename = 0 /* nullptr */; error("unable to open '%1': %2", filename, strerror(errno)); return false; } } inputFile.read_file(fp); if (fp != stdin) if (fclose(fp) != 0) sys_fatal("fclose"); current_filename = 0 /* nullptr */; return true; } static void cleanup(void) { free(const_cast<char *>(image_gen)); } int main(int argc, char **argv) { program_name = argv[0]; #ifdef CAPTURE_MODE fprintf(stderr, "%s: invoked with %d arguments ...\n", program_name, argc); for (int i = 0; i < argc; i++) fprintf(stderr, "%2d: %s\n", i, argv[i]); FILE *dump = fopen(DEBUG_FILE("pre-html-data"), "wb"); if (dump != 0 /* nullptr */) { while((int ch = fgetc(stdin)) >= 0) fputc(ch, dump); fclose(dump); } exit(EXIT_FAILURE); #endif /* CAPTURE_MODE */ if (atexit(&cleanup) != 0) sys_fatal("atexit"); int operand_index = scanArguments(argc, argv); image_gen = strsave(get_image_generator()); if (0 /* nullptr */ == image_gen) fatal("'image_generator' directive not found in file '%1'", devhtml_desc); postscriptRes = get_resolution(); if (postscriptRes < 1) // TODO: what's a more sane minimum value? fatal("'res' directive missing or invalid in file '%1'", devps_desc); setupAntiAlias(); checkImageDir(); makeFileName(); bool have_file_operand = false; while (operand_index < argc) { if (argv[operand_index][0] != '-') { if(!do_file(argv[operand_index])) exit(EXIT_FAILURE); have_file_operand = true; } operand_index++; } if (!have_file_operand) do_file("-"); makeTempFiles(); int wstatus = inputFile.do_image(argc, argv); if (wstatus == 0) { generateImages(regionFileName); wstatus = inputFile.do_html(argc, argv); } else if (WEXITSTATUS(wstatus) != 0) // XXX: This is a crappy suggestion. See Savannah #62673. fatal("'%1' exited with status %2; re-run groff with " IMAGE_DEVICE " (and omit any '-P' postprocessor options)" " to see diagnostic messages", argv[0], WEXITSTATUS(wstatus)); exit(EXIT_SUCCESS); } // Local Variables: // fill-column: 72 // mode: C++ // End: // vim: set cindent noexpandtab shiftwidth=2 textwidth=72: