/* $NetBSD: pre-html.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $ */
// -*- C++ -*-
/* Copyright (C) 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
Written by Gaius Mulley (gaius@glam.ac.uk).
This file is part of groff.
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 2, 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 groff; see the file COPYING. If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
#define PREHTMLC
#include "lib.h"
#include
#include
#include
#include
#include
#include "errarg.h"
#include "error.h"
#include "stringclass.h"
#include "posix.h"
#include "defs.h"
#include "searchpath.h"
#include "paper.h"
#include "device.h"
#include "font.h"
#include
#include
#ifdef HAVE_UNISTD_H
# include
#endif
#ifdef _POSIX_VERSION
# include
# define PID_T pid_t
#else /* not _POSIX_VERSION */
# define PID_T int
#endif /* not _POSIX_VERSION */
#include
#include "nonposix.h"
/* 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 best avoided).
# define MAY_FORK_CHILD_PROCESS 0
// On these systems, we use `spawn...', instead of `fork' ... `exec...'.
# include // for `spawn...'
# include // 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 */
#ifdef DEBUGGING
// For a DEBUGGING version, we need some additional macros,
// to direct the captured debug 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
#endif
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_BOARDER_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-"
#if 0
# define DEBUGGING
#endif
#if !defined(TRUE)
# define TRUE (1==1)
#endif
#if !defined(FALSE)
# define FALSE (1==0)
#endif
typedef enum {
CENTERED, LEFT, RIGHT, INLINE
} IMAGE_ALIGNMENT;
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 char *psFileName = NULL; // name of postscript file
static char *psPageName = NULL; // name of file containing
// postscript current page
static char *regionFileName = NULL; // name of file containing all
// image regions
static char *imagePageName = NULL; // name of bitmap image 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 = NULL; // image template filename
static char *macroset_template= NULL; // image template passed to troff
// by -D
static int troff_arg = 0; // troff arg index
static char *image_dir = NULL; // user specified image directory
static int textAlphaBits = MAX_ALPHA_BITS;
static int graphicAlphaBits = MAX_ALPHA_BITS;
static char *antiAlias = NULL; // antialias arguments we pass to gs
static int show_progress = FALSE; // should we display page numbers as
// they are processed?
static int currentPageNo = -1; // current image page number
#if defined(DEBUGGING)
static int debug = FALSE;
static char *troffFileName = NULL; // output of pre-html output which
// is sent to troff -Tps
static char *htmlFileName = NULL; // output of pre-html output which
// is sent to troff -Thtml
#endif
static char *linebuf = NULL; // for scanning devps/DESC
static int linebufsize = 0;
static const char *image_gen = NULL; // the `gs' program
const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
/*
* Images are generated via postscript, gs, and the pnm utilities.
*/
#define IMAGE_DEVICE "-Tps"
static int do_file(const char *filename);
/*
* 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.
*/
int get_line(FILE *f)
{
if (f == 0)
return 0;
if (linebuf == 0) {
linebuf = new char[128];
linebufsize = 128;
}
int i = 0;
// skip leading whitespace
for (;;) {
int c = getc(f);
if (c == EOF)
return 0;
if (c != ' ' && c != '\t') {
ungetc(c, f);
break;
}
}
for (;;) {
int c = getc(f);
if (c == EOF)
break;
if (i + 1 >= linebufsize) {
char *old_linebuf = linebuf;
linebuf = new char[linebufsize * 2];
memcpy(linebuf, old_linebuf, linebufsize);
a_delete old_linebuf;
linebufsize *= 2;
}
linebuf[i++] = c;
if (c == '\n') {
i--;
break;
}
}
linebuf[i] = '\0';
return 1;
}
/*
* get_resolution - Return the postscript resolution from devps/DESC.
*/
static unsigned int get_resolution(void)
{
char *pathp;
FILE *f;
unsigned int res;
f = font_path.open_file("devps/DESC", &pathp);
a_delete pathp;
if (f == 0)
fatal("can't open devps/DESC");
while (get_line(f)) {
int n = sscanf(linebuf, "res %u", &res);
if (n >= 1) {
fclose(f);
return res;
}
}
fatal("can't find `res' keyword in devps/DESC");
return 0;
}
/*
* html_system - A wrapper for system().
*/
void html_system(const char *s, int redirect_stdout)
{
// Redirect standard error to the null device. This is more
// portable than using "2> /dev/null", since it doesn't require a
// Unixy shell.
int save_stderr = dup(2);
int save_stdout = dup(1);
int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
if (save_stderr > 2 && fdnull > 2)
dup2(fdnull, 2);
if (redirect_stdout && save_stdout > 1 && fdnull > 1)
dup2(fdnull, 1);
if (fdnull >= 0)
close(fdnull);
int status = system(s);
dup2(save_stderr, 2);
if (redirect_stdout)
dup2(save_stdout, 1);
if (status == -1)
fprintf(stderr, "Calling `%s' failed\n", s);
else if (status)
fprintf(stderr, "Calling `%s' returned status %d\n", s, status);
close(save_stderr);
close(save_stdout);
}
/*
* make_message - Create a string via malloc and place the result of the
* va args into string. Finally the new string is returned.
* Taken from man page of printf(3).
*/
char *make_message(const char *fmt, ...)
{
/* Guess we need no more than 100 bytes. */
int n, size = 100;
char *p;
char *np;
va_list ap;
if ((p = (char *)malloc(size)) == NULL)
return NULL;
while (1) {
/* Try to print in the allocated space. */
va_start(ap, fmt);
n = vsnprintf(p, size, fmt, ap);
va_end(ap);
/* If that worked, return the string. */
if (n > -1 && n < size - 1) { /* glibc 2.1 and pre-ANSI C 99 */
if (size > n + 1) {
np = strsave(p);
free(p);
return np;
}
return p;
}
/* Else try again with more space. */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
if ((np = (char *)realloc(p, size)) == NULL) {
free(p); /* realloc failed, free old, p. */
return NULL;
}
p = np; /* use realloc'ed, p */
}
}
/*
* the class and methods for retaining ascii text
*/
struct char_block {
enum { SIZE = 256 };
char buffer[SIZE];
int used;
char_block *next;
char_block();
};
/*
* char_block - Constructor. Set the, used, and, next, fields to zero.
*/
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();
int 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);
int can_see(char_block **t, int *i, const char *string);
int skip_spaces(char_block **t, int *i);
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 - Constructor.
*/
char_buffer::char_buffer()
: head(0), tail(0)
{
}
/*
* char_buffer - Destructor. Throw away the whole buffer list.
*/
char_buffer::~char_buffer()
{
while (head != NULL) {
char_block *temp = head;
head = head->next;
delete temp;
}
}
/*
* read_file - Read in a complete file, fp, placing the contents inside
* char_blocks.
*/
int char_buffer::read_file(FILE *fp)
{
int n;
while (!feof(fp)) {
if (tail == NULL) {
tail = new char_block;
head = tail;
}
else {
if (tail->used == char_block::SIZE) {
tail->next = new char_block;
tail = tail->next;
}
}
// at this point we have a tail which is ready for the next SIZE
// bytes of the file
n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used, fp);
if (n <= 0)
// error
return 0;
else
tail->used += n * sizeof(char);
}
return 1;
}
/*
* 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 != NULL) && (strchr(image_dir, '%') != NULL)) {
error("cannot use a `%%' within the image directory name");
exit(1);
}
if ((image_template != NULL) && (strchr(image_template, '%') != NULL)) {
error("cannot use a `%%' within the image template");
exit(1);
}
if (image_dir == NULL)
image_dir = (char *)"";
else if (strlen(image_dir) > 0
&& image_dir[strlen(image_dir) - 1] != '/') {
image_dir = make_message("%s/", image_dir);
if (image_dir == NULL)
sys_fatal("make_message");
}
if (image_template == NULL)
macroset_template = make_message("%sgrohtml-%d", image_dir,
(int)getpid());
else
macroset_template = make_message("%s%s", image_dir, image_template);
if (macroset_template == NULL)
sys_fatal("make_message");
image_template =
(char *)malloc(strlen("-%d") + strlen(macroset_template) + 1);
if (image_template == NULL)
sys_fatal("malloc");
strcpy(image_template, macroset_template);
strcat(image_template, "-%d");
}
/*
* setupAntiAlias - Set up the antialias string, used when we call gs.
*/
static void setupAntiAlias(void)
{
if (textAlphaBits == 0 && graphicAlphaBits == 0)
antiAlias = make_message(" ");
else if (textAlphaBits == 0)
antiAlias = make_message("-dGraphicsAlphaBits=%d ", graphicAlphaBits);
else if (graphicAlphaBits == 0)
antiAlias = make_message("-dTextAlphaBits=%d ", textAlphaBits);
else
antiAlias = make_message("-dTextAlphaBits=%d -dGraphicsAlphaBits=%d ",
textAlphaBits, graphicAlphaBits);
}
/*
* checkImageDir - Check whether the image directory is available.
*/
static void checkImageDir(void)
{
if (image_dir != NULL && strcmp(image_dir, "") != 0)
if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST)) {
error("cannot create directory `%1'", image_dir);
exit(1);
}
}
/*
* 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 ((*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] onwards.
*/
int char_buffer::can_see(char_block **t, int *i, const char *str)
{
int j = 0;
int 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_spaces - Return TRUE if we have not run out of data.
* Consume spaces also.
*/
int char_buffer::skip_spaces(char_block **t, int *i)
{
char_block *s = *t;
int k = *i;
while (s) {
while (k < s->used && isspace(s->buffer[k]))
k++;
if (k == s->used) {
k = 0;
s = s->next;
}
else {
*i = k;
return TRUE;
}
}
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 != NULL)
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 = NULL;
}
/*
* 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 != NULL) {
imageItem *i = head;
head = head->next;
delete i;
}
}
/*
* createPage - Create one image of, page pageno, from the postscript file.
*/
int imageList::createPage(int pageno)
{
char *s;
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 (show_progress) {
fprintf(stderr, "[%d] ", pageno);
fflush(stderr);
}
#if defined(DEBUGGING)
if (debug)
fprintf(stderr, "creating page %d\n", pageno);
#endif
s = make_message("psselect -q -p%d %s %s\n",
pageno, psFileName, psPageName);
if (s == NULL)
sys_fatal("make_message");
#if defined(DEBUGGING)
if (debug) {
fwrite(s, sizeof(char), strlen(s), stderr);
fflush(stderr);
}
#endif
html_system(s, 1);
s = make_message("echo showpage | "
"%s%s -q -dBATCH -dSAFER "
"-dDEVICEHEIGHTPOINTS=792 "
"-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
"-sDEVICE=%s -r%d %s "
"-sOutputFile=%s %s -\n",
image_gen,
EXE_EXT,
(getMaxX(pageno) * image_res) / postscriptRes,
image_device,
image_res,
antiAlias,
imagePageName,
psPageName);
if (s == NULL)
sys_fatal("make_message");
#if defined(DEBUGGING)
if (debug) {
fwrite(s, sizeof(char), strlen(s), stderr);
fflush(stderr);
}
#endif
html_system(s, 1);
free(s);
currentPageNo = pageno;
return 0;
}
/*
* min - Return the minimum of two numbers.
*/
int min(int x, int y)
{
if (x < y)
return x;
else
return y;
}
/*
* max - Return the maximum of two numbers.
*/
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 != NULL) {
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) {
char *s;
int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes
- IMAGE_BOARDER_PIXELS,
0);
int y1 = max(image_res * vertical_offset / 72
+ min(i->Y1, i->Y2) * image_res / postscriptRes
- IMAGE_BOARDER_PIXELS,
0);
int x2 = max(i->X1, i->X2) * image_res / postscriptRes
+ IMAGE_BOARDER_PIXELS;
int y2 = image_res * vertical_offset / 72
+ max(i->Y1, i->Y2) * image_res / postscriptRes
+ 1 + IMAGE_BOARDER_PIXELS;
if (createPage(i->pageNo) == 0) {
s = make_message("pnmcut%s %d %d %d %d < %s "
"| pnmcrop -quiet | pnmtopng%s %s > %s\n",
EXE_EXT,
x1, y1, x2 - x1 + 1, y2 - y1 + 1,
imagePageName,
EXE_EXT,
TRANSPARENT,
i->imageName);
if (s == NULL)
sys_fatal("make_message");
#if defined(DEBUGGING)
if (debug) {
fprintf(stderr, s);
fflush(stderr);
}
#endif
html_system(s, 0);
free(s);
}
else {
fprintf(stderr, "failed to generate image of page %d\n", i->pageNo);
fflush(stderr);
}
#if defined(DEBUGGING)
}
else {
if (debug) {
fprintf(stderr, "ignoring image as x1 coord is -1\n");
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 (head == NULL) {
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 != NULL) {
createImage(h);
h = h->next;
}
}
static imageList listOfImages; // List of images defined by the 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(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);
}
}
listOfImages.createImages();
if (show_progress) {
fprintf(stderr, "done\n");
fflush(stderr);
}
delete f;
}
/*
* set_redirection - Set up I/O Redirection for handle, was, to refer to
* stream on handle, 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, "failed to replace fd=%d with %d\n", was, willbe);
if (willbe == STDOUT_FILENO)
fprintf(stderr,
"likely that stdout should be opened before %d\n", was);
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 - Get duplicate handle for stream, was, then
* redirect, was, to refer to, willbe.
*/
static int save_and_redirect(int was, int willbe)
{
if (was == willbe)
// No redirection specified so don't do anything but silently bailing 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, "unable to get duplicate handle for %d\n", 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' stream.
return saved;
}
/*
* alterDeviceTo - If, toImage, is set
* the argument list is altered to include
* IMAGE_DEVICE and 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)
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)
argv[i] = (char *)"-Thtml";
i++;
}
argv[troff_arg] = (char *)"groff"; /* use groff -Z */
}
}
/*
* addZ - Append -Z onto the command list for groff.
*/
char **addZ(int argc, char *argv[])
{
char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
int i = 0;
if (new_argv == NULL)
sys_fatal("malloc");
if (argc > 0) {
new_argv[i] = argv[i];
i++;
}
new_argv[i] = (char *)"-Z";
while (i < argc) {
new_argv[i + 1] = argv[i];
i++;
}
argc++;
new_argv[argc] = NULL;
return new_argv;
}
/*
* addRegDef - Append a defined register or string onto the command
* list for troff.
*/
char **addRegDef(int argc, char *argv[], const char *numReg)
{
char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
int i = 0;
if (new_argv == NULL)
sys_fatal("malloc");
while (i < argc) {
new_argv[i] = argv[i];
i++;
}
new_argv[argc] = strsave(numReg);
argc++;
new_argv[argc] = NULL;
return new_argv;
}
/*
* 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");
}
int char_buffer::run_output_filter(int filter, int /* argc */, char **argv)
{
int pipedes[2];
PID_T child_pid;
int status;
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 fork. We redirect its `stdin' stream
// 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, so we should 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.
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);
// If we get to here then the `exec...' request for the output filter
// failed. Diagnose it and bail out.
error("couldn't exec %1: %2", argv[0], strerror(errno), ((char *)0));
fflush(stderr); // just in case error() didn't
exit(1);
}
else {
// This is the parent process fork. We will be writing data to the
// filter pipeline, and the child will be reading it. We have no further
// use for our read handle on the pipe, and should close it.
if (close(pipedes[0]) < 0)
sys_fatal("close");
// Now we redirect the `stdout' stream to the inlet end of the pipe,
// and push out the appropiately 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(&status, 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 ok 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 `stdin'
// stream to the readable end of our pipe, saving our own `stdin' stream
// 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);
}
// We then use an asynchronous spawn request to start the post-processor.
if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
// Should the spawn request fail we issue a diagnostic and bail out.
error("cannot spawn %1: %2", argv[0], strerror(errno), ((char *)0));
exit(1);
}
// 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 `stdout' stream to the inlet end of the pipe,
// and push out the appropiately 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(&status, 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
#endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
return 0;
}
/*
* 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 = addZ(argc, argv);
argc++;
s = "-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 defined(DEBUGGING)
# define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
// slight security risk so only enabled if compiled with defined(DEBUGGING)
if (debug) {
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);
}
#endif
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 page size letter postscript file
argv = addRegDef(argc, argv, "-P-pletter");
argc++;
#if defined(DEBUGGING)
# define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
// slight security risk so only enabled if compiled with defined(DEBUGGING)
if (debug) {
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);
}
#endif
return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
}
static char_buffer inputFile;
/*
* usage - Emit usage arguments.
*/
static void usage(FILE *stream)
{
fprintf(stream,
"usage: %s troffname [-Iimage_name] [-Dimage_directory]\n"
" [-P-o vertical_image_offset] [-P-i image_resolution]\n"
" [troff flags]\n",
program_name);
fprintf(stream,
" vertical_image_offset (default %d/72 of an inch)\n",
vertical_offset);
fprintf(stream,
" image_resolution (default %d) pixels per inch\n",
image_res);
fprintf(stream,
" image_name is the name of the stem for all images\n"
" (default is grohtml-)\n");
fprintf(stream,
" place all png files into image_directory\n");
}
/*
* 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 *command_prefix = getenv("GROFF_COMMAND_PREFIX");
if (!command_prefix)
command_prefix = PROG_PREFIX;
char *troff_name = new char[strlen(command_prefix) + strlen("troff") + 1];
strcpy(troff_name, command_prefix);
strcat(troff_name, "troff");
int c, i;
static const struct option long_options[] = {
{ "help", no_argument, 0, CHAR_MAX + 1 },
{ "version", no_argument, 0, 'v' },
{ NULL, 0, 0, 0 }
};
while ((c = getopt_long(argc, argv, "+a:bdD:F:g:hi:I:j:lno:prs:S:v",
long_options, NULL))
!= EOF)
switch(c) {
case 'a':
textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
MAX_ALPHA_BITS);
if (textAlphaBits == 3) {
error("cannot use 3 bits of antialiasing information");
exit(1);
}
break;
case 'b':
// handled by post-grohtml (set background color to white)
break;
case 'd':
#if defined(DEBUGGING)
debug = TRUE;
#endif
break;
case 'D':
image_dir = optarg;
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) {
error("cannot use 3 bits of antialiasing information");
exit(1);
}
break;
case 'h':
// handled by post-grohtml
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 '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':
show_progress = 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 font 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(0);
case CHAR_MAX + 1: // --help
usage(stdout);
exit(0);
break;
case '?':
usage(stderr);
exit(1);
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++;
}
a_delete troff_name;
return argc;
}
/*
* makeTempFiles - Name the temporary files.
*/
static int makeTempFiles(void)
{
#if defined(DEBUGGING)
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");
#else /* not DEBUGGING */
FILE *f;
/* psPageName contains a single page of postscript */
f = xtmpfile(&psPageName,
PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
TRUE);
if (f == NULL) {
sys_fatal("xtmpfile");
return -1;
}
fclose(f);
/* imagePageName contains a bitmap image of the single postscript page */
f = xtmpfile(&imagePageName,
PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT,
TRUE);
if (f == NULL) {
sys_fatal("xtmpfile");
return -1;
}
fclose(f);
/* psFileName contains a postscript file of the complete document */
f = xtmpfile(&psFileName,
PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT,
TRUE);
if (f == NULL) {
sys_fatal("xtmpfile");
return -1;
}
fclose(f);
/* regionFileName contains a list of the images and their boxed coordinates */
f = xtmpfile(®ionFileName,
REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT,
TRUE);
if (f == NULL) {
sys_fatal("xtmpfile");
return -1;
}
fclose(f);
#endif /* not DEBUGGING */
return 0;
}
int main(int argc, char **argv)
{
program_name = argv[0];
int i;
int found = 0;
int ok = 1;
#ifdef CAPTURE_MODE
FILE *dump;
fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc);
for (i = 0; i < argc; i++)
fprintf(stderr, "%2d: %s\n", i, argv[i]);
if ((dump = fopen(DEBUG_FILE("pre-html-data"), "wb")) != NULL) {
while((i = fgetc(stdin)) >= 0)
fputc(i, dump);
fclose(dump);
}
exit(1);
#endif /* CAPTURE_MODE */
device = "html";
if (!font::load_desc())
fatal("cannot find devhtml/DESC exiting");
image_gen = font::image_generator;
if (image_gen == NULL || (strcmp(image_gen, "") == 0))
fatal("devhtml/DESC must set the image_generator field, exiting");
postscriptRes = get_resolution();
i = scanArguments(argc, argv);
setupAntiAlias();
checkImageDir();
makeFileName();
while (i < argc) {
if (argv[i][0] != '-') {
/* found source file */
ok = do_file(argv[i]);
if (!ok)
return 0;
found = 1;
}
i++;
}
if (!found)
do_file("-");
if (makeTempFiles())
return 1;
ok = inputFile.do_image(argc, argv);
if (ok == 0) {
generateImages(regionFileName);
ok = inputFile.do_html(argc, argv);
}
return ok;
}
static int do_file(const char *filename)
{
FILE *fp;
current_filename = filename;
if (strcmp(filename, "-") == 0)
fp = stdin;
else {
fp = fopen(filename, "r");
if (fp == 0) {
error("can't open `%1': %2", filename, strerror(errno));
return 0;
}
}
if (inputFile.read_file(fp)) {
// XXX
}
if (fp != stdin)
fclose(fp);
current_filename = NULL;
return 1;
}