about summary refs log tree commit diff
path: root/converter/other/pstopnm.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pstopnm.c')
-rw-r--r--converter/other/pstopnm.c899
1 files changed, 899 insertions, 0 deletions
diff --git a/converter/other/pstopnm.c b/converter/other/pstopnm.c
new file mode 100644
index 00000000..a31c3f64
--- /dev/null
+++ b/converter/other/pstopnm.c
@@ -0,0 +1,899 @@
+/*----------------------------------------------------------------------------
+                                 pstopnm
+------------------------------------------------------------------------------
+  Use Ghostscript to convert a Postscript file into a PBM, PGM, or PNM
+  file.
+
+  Implementation note: This program feeds the input file to Ghostcript
+  directly (with possible statements preceding it), and uses
+  Ghostscript's PNM output device drivers.  As an alternative,
+  Ghostscript also comes with the Postscript program pstoppm.ps which
+  we could run and it would read the input file and produce PNM
+  output.  It isn't clear to me what pstoppm.ps adds to what you get
+  from just feeding your input directly to Ghostscript as the main program.
+
+-----------------------------------------------------------------------------*/
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  
+    /* Make sure fdopen() is in stdio.h and strdup() is in string.h */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sys/wait.h>  
+#include <sys/stat.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+enum orientation {PORTRAIT, LANDSCAPE, UNSPECIFIED};
+struct box {
+    /* Description of a rectangle within an image; all coordinates 
+       measured in points (1/72") with lower left corner of page being the 
+       origin.
+    */
+    int llx;  /* lower left X coord */
+        /* -1 for llx means whole box is undefined. */
+    int lly;  /* lower left Y coord */
+    int urx;  /* upper right X coord */
+    int ury;  /* upper right Y coord */
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int forceplain;
+    struct box extract_box;
+    unsigned int nocrop;
+    unsigned int format_type;
+    unsigned int verbose;
+    float xborder;
+    unsigned int xmax;
+    unsigned int xsize;  /* zero means unspecified */
+    float yborder;
+    unsigned int ymax;
+    unsigned int ysize;  /* zero means unspecified */
+    unsigned int dpi;    /* zero means unspecified */
+    enum orientation orientation;
+    unsigned int goto_stdout;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int pbm_opt, pgm_opt, ppm_opt;
+    unsigned int portrait_opt, landscape_opt;
+    float llx, lly, urx, ury;
+    unsigned int llxSpec, llySpec, urxSpec, urySpec;
+    unsigned int xmaxSpec, ymaxSpec, xsizeSpec, ysizeSpec, dpiSpec;
+    
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "forceplain", OPT_FLAG,  NULL, &cmdlineP->forceplain,     0);
+    OPTENT3(0, "llx",        OPT_FLOAT, &llx, &llxSpec,                  0);
+    OPTENT3(0, "lly",        OPT_FLOAT, &lly, &llySpec,                  0);
+    OPTENT3(0, "urx",        OPT_FLOAT, &urx, &urxSpec,                  0);
+    OPTENT3(0, "ury",        OPT_FLOAT, &ury, &urySpec,                  0);
+    OPTENT3(0, "nocrop",     OPT_FLAG,  NULL, &cmdlineP->nocrop,         0);
+    OPTENT3(0, "pbm",        OPT_FLAG,  NULL, &pbm_opt,                  0);
+    OPTENT3(0, "pgm",        OPT_FLAG,  NULL, &pgm_opt,                  0);
+    OPTENT3(0, "ppm",        OPT_FLAG,  NULL, &ppm_opt,                  0);
+    OPTENT3(0, "verbose",    OPT_FLAG,  NULL, &cmdlineP->verbose,        0);
+    OPTENT3(0, "xborder",    OPT_FLOAT, &cmdlineP->xborder, NULL,        0);
+    OPTENT3(0, "xmax",       OPT_UINT,  &cmdlineP->xmax, &xmaxSpec,      0);
+    OPTENT3(0, "xsize",      OPT_UINT,  &cmdlineP->xsize, &xsizeSpec,    0);
+    OPTENT3(0, "yborder",    OPT_FLOAT, &cmdlineP->yborder, NULL,        0);
+    OPTENT3(0, "ymax",       OPT_UINT,  &cmdlineP->ymax, &ymaxSpec,      0);
+    OPTENT3(0, "ysize",      OPT_UINT,  &cmdlineP->ysize, &ysizeSpec,    0);
+    OPTENT3(0, "dpi",        OPT_UINT,  &cmdlineP->dpi, &dpiSpec,        0);
+    OPTENT3(0, "portrait",   OPT_FLAG,  NULL, &portrait_opt,             0);
+    OPTENT3(0, "landscape",  OPT_FLAG,  NULL, &landscape_opt,            0);
+    OPTENT3(0, "stdout",     OPT_FLAG,  NULL, &cmdlineP->goto_stdout,    0);
+
+    /* Set the defaults */
+    cmdlineP->xborder = cmdlineP->yborder = 0.1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (xmaxSpec) {
+        if (cmdlineP->xmax == 0)
+            pm_error("zero is not a valid value for -xmax");
+    } else
+        cmdlineP->xmax = 612;
+
+    if (ymaxSpec) {
+        if (cmdlineP->ymax == 0)
+            pm_error("zero is not a valid value for -ymax");
+    } else 
+        cmdlineP->ymax = 792;
+
+    if (xsizeSpec) {
+        if (cmdlineP->xsize == 0)
+            pm_error("zero is not a valid value for -xsize");
+    } else
+        cmdlineP->xsize = 0;
+
+    if (ysizeSpec) {
+        if (cmdlineP->ysize == 0)
+            pm_error("zero is not a valid value for -ysize");
+    } else 
+        cmdlineP->ysize = 0;
+
+    if (portrait_opt & !landscape_opt)
+        cmdlineP->orientation = PORTRAIT;
+    else if (!portrait_opt & landscape_opt)
+        cmdlineP->orientation = LANDSCAPE;
+    else if (!portrait_opt & !landscape_opt)
+        cmdlineP->orientation = UNSPECIFIED;
+    else
+        pm_error("Cannot specify both -portrait and -landscape options");
+
+    if (pbm_opt)
+        cmdlineP->format_type = PBM_TYPE;
+    else if (pgm_opt)
+        cmdlineP->format_type = PGM_TYPE;
+    else if (ppm_opt)
+        cmdlineP->format_type = PPM_TYPE;
+    else
+        cmdlineP->format_type = PPM_TYPE;
+
+    /* If any one of the 4 bounding box coordinates is given on the
+       command line, we default any of the 4 that aren't.  
+    */
+    if (llxSpec || llySpec || urxSpec || urySpec) {
+        if (!llxSpec) cmdlineP->extract_box.llx = 72;
+        else cmdlineP->extract_box.llx = llx * 72;
+        if (!llySpec) cmdlineP->extract_box.lly = 72;
+        else cmdlineP->extract_box.lly = lly * 72;
+        if (!urxSpec) cmdlineP->extract_box.urx = 540;
+        else cmdlineP->extract_box.urx = urx * 72;
+        if (!urySpec) cmdlineP->extract_box.ury = 720;
+        else cmdlineP->extract_box.ury = ury * 72;
+    } else {
+        cmdlineP->extract_box.llx = -1;
+    }
+
+    if (dpiSpec) {
+        if (cmdlineP->dpi == 0)
+            pm_error("Zero is not a valid value for -dpi");
+    } else
+        cmdlineP->dpi = 0;
+
+    if (dpiSpec && xsizeSpec + ysizeSpec + xmaxSpec + ymaxSpec > 0)
+        pm_error("You may not specify both size options and -dpi");
+
+    if (argc-1 == 0)
+        cmdlineP->input_filespec = "-";  /* stdin */
+    else if (argc-1 == 1)
+        cmdlineP->input_filespec = argv[1];
+    else 
+        pm_error("Too many arguments (%d).  "
+                 "Only need one: the Postscript filespec", argc-1);
+}
+
+
+
+static void
+add_ps_to_filespec(const char orig_filespec[], char ** const new_filespec_p,
+                   const int verbose) {
+/*----------------------------------------------------------------------------
+   If orig_filespec[] does not name an existing file, but the same
+   name with ".ps" added to the end does, return the name with the .ps
+   attached.  Otherwise, just return orig_filespec[].
+
+   Return the name in newly malloc'ed storage, pointed to by
+   *new_filespec_p.
+-----------------------------------------------------------------------------*/
+    struct stat statbuf;
+    int stat_rc;
+
+    stat_rc = lstat(orig_filespec, &statbuf);
+    
+    if (stat_rc == 0)
+        *new_filespec_p = strdup(orig_filespec);
+    else {
+        const char *filespec_plus_ps;
+
+        asprintfN(&filespec_plus_ps, "%s.ps", orig_filespec);
+
+        stat_rc = lstat(filespec_plus_ps, &statbuf);
+        if (stat_rc == 0)
+            *new_filespec_p = strdup(filespec_plus_ps);
+        else
+            *new_filespec_p = strdup(orig_filespec);
+        strfree(filespec_plus_ps);
+    }
+    if (verbose)
+        pm_message("Input file is %s", *new_filespec_p);
+}
+
+
+
+static void
+computeSizeResFromSizeSpec(unsigned int   const requestedXsize,
+                           unsigned int   const requestedYsize,
+                           unsigned int   const imageWidth,
+                           unsigned int   const imageHeight,
+                           unsigned int * const xsizeP,
+                           unsigned int * const ysizeP,
+                           unsigned int * const xresP,
+                           unsigned int * const yresP) {
+
+    if (requestedXsize) {
+        *xsizeP = requestedXsize;
+        *xresP = (unsigned int) (requestedXsize * 72 / imageWidth + 0.5);
+        if (!requestedYsize) {
+            *yresP = *xresP;
+            *ysizeP = (unsigned int) (imageHeight * (float)*yresP/72 + 0.5);
+            }
+        }
+
+    if (requestedYsize) {
+        *ysizeP = requestedYsize;
+        *yresP = (unsigned int) (requestedYsize * 72 / imageHeight + 0.5);
+        if (!requestedXsize) {
+            *xresP = *yresP;
+            *xsizeP = (unsigned int) (imageWidth * (float)*xresP/72 + 0.5);
+        }
+    } 
+}
+
+
+
+static void
+computeSizeResBlind(unsigned int   const xmax,
+                    unsigned int   const ymax,
+                    unsigned int   const imageWidth,
+                    unsigned int   const imageHeight,
+                    bool           const nocrop,
+                    unsigned int * const xsizeP,
+                    unsigned int * const ysizeP,
+                    unsigned int * const xresP,
+                    unsigned int * const yresP) {
+
+    *xresP = *yresP = MIN(xmax * 72 / imageWidth, 
+                          ymax * 72 / imageHeight);
+    
+    if (nocrop) {
+        *xsizeP = xmax;
+        *ysizeP = ymax;
+    } else {
+        *xsizeP = (unsigned int) (imageWidth * (float)*xresP / 72 + 0.5);
+        *ysizeP = (unsigned int) (imageHeight * (float)*yresP / 72 + 0.5);
+    }
+}
+
+
+
+static void
+compute_size_res(struct cmdlineInfo const cmdline, 
+                 enum orientation   const orientation, 
+                 struct box         const bordered_box,
+                 unsigned int *     const xsizeP, 
+                 unsigned int *     const ysizeP,
+                 unsigned int *     const xresP, 
+                 unsigned int *     const yresP) {
+/*----------------------------------------------------------------------------
+  Figure out how big the output image should be (return as
+  *xsizeP and *ysizeP) and what output device resolution Ghostscript
+  should assume (return as *xresP, *yresP).
+
+  A resolution number is the number of pixels per inch that the a
+  printer prints.  Since we're emulating a printed page with a PNM
+  image, and a PNM image has no spatial dimension (you can't say how
+  many inches wide a PNM image is), it's kind of confusing.  
+
+  If the user doesn't select a resolution, we choose the resolution
+  that causes the image to be a certain number of pixels, knowing how
+  big (in inches) Ghostscript wants the printed picture to be.  For
+  example, the part of the Postscript image we are going to print is 2
+  inches wide.  We want the PNM image to be 1000 pixels wide.  So we
+  tell Ghostscript that our horizontal output device resolution is 500
+  pixels per inch.
+  
+  *xresP and *yresP are in dots per inch.
+-----------------------------------------------------------------------------*/
+    unsigned int sx, sy;
+        /* The horizontal and vertical sizes of the input image, in points
+           (1/72 inch)
+        */
+
+    if (orientation == LANDSCAPE) {
+        sx = bordered_box.ury - bordered_box.lly;
+        sy = bordered_box.urx - bordered_box.llx;
+    } else {
+        sx = bordered_box.urx - bordered_box.llx;
+        sy = bordered_box.ury - bordered_box.lly;
+    }
+
+    if (cmdline.dpi) {
+        /* User gave resolution; we figure out output image size */
+        *xresP = *yresP = cmdline.dpi;
+        *xsizeP = (int) (cmdline.dpi * sx / 72 + 0.5);
+        *ysizeP = (int) (cmdline.dpi * sy / 72 + 0.5);
+    } else  if (cmdline.xsize || cmdline.ysize)
+        computeSizeResFromSizeSpec(cmdline.xsize, cmdline.ysize, sx, sy,
+                                   xsizeP, ysizeP, xresP, yresP);
+    else 
+        computeSizeResBlind(cmdline.xmax, cmdline.ymax, sx, sy, cmdline.nocrop,
+                            xsizeP, ysizeP, xresP, yresP);
+
+    if (cmdline.verbose) {
+        pm_message("output is %u pixels wide X %u pixels high",
+                   *xsizeP, *ysizeP);
+        pm_message("output device resolution is %u dpi horiz, %u dpi vert",
+                   *xresP, *yresP);
+    }
+}
+
+
+
+enum postscript_language {COMMON_POSTSCRIPT, ENCAPSULATED_POSTSCRIPT};
+
+static enum postscript_language
+language_declaration(const char input_filespec[], int const verbose) {
+/*----------------------------------------------------------------------------
+  Return the Postscript language in which the file declares it is written.
+  (Except that if the file is on Standard Input or doesn't validly declare
+  a languages, just say it is Common Postscript).
+-----------------------------------------------------------------------------*/
+    enum postscript_language language;
+
+    if (STREQ(input_filespec, "-"))
+        /* Can't read stdin, because we need it to remain positioned for the 
+           Ghostscript interpreter to read it.
+        */
+        language = COMMON_POSTSCRIPT;
+    else {
+        FILE *infile;
+        char line[80];
+
+        infile = pm_openr(input_filespec);
+
+        if (fgets(line, sizeof(line), infile) == NULL)
+            language = COMMON_POSTSCRIPT;
+        else {
+            const char eps_header[] = " EPSF-";
+
+            if (strstr(line, eps_header))
+                language = ENCAPSULATED_POSTSCRIPT;
+            else
+                language = COMMON_POSTSCRIPT;
+        }
+        fclose(infile);
+    }
+    if (verbose)
+        pm_message("language is %s",
+                   language == ENCAPSULATED_POSTSCRIPT ?
+                   "encapsulated postscript" :
+                   "not encapsulated postscript");
+    return language;
+}
+
+
+
+static struct box
+compute_box_to_extract(struct box const cmdline_extract_box,
+                       char       const input_filespec[],
+                       bool       const verbose) {
+
+    struct box retval;
+
+    if (cmdline_extract_box.llx != -1)
+        /* User told us what box to extract, so that's what we'll do */
+        retval = cmdline_extract_box;
+    else {
+        /* Try to get the bounding box from the DSC %%BoundingBox
+           statement (A Postscript comment) in the input.
+        */
+        struct box ps_bb;  /* Box described by %%BoundingBox stmt in input */
+
+        if (STREQ(input_filespec, "-"))
+            /* Can't read stdin, because we need it to remain
+               positioned for the Ghostscript interpreter to read it.  
+            */
+            ps_bb.llx = -1;
+        else {
+            FILE *infile;
+            int found_BB, eof;  /* logical */
+            infile = pm_openr(input_filespec);
+            
+            found_BB = FALSE;
+            eof = FALSE;
+            while (!eof && !found_BB) {
+                char line[200];
+                
+                if (fgets(line, sizeof(line), infile) == NULL)
+                    eof = TRUE;
+                else {
+                    int rc;
+                    rc = sscanf(line, "%%%%BoundingBox: %d %d %d %d",
+                                &ps_bb.llx, &ps_bb.lly, 
+                                &ps_bb.urx, &ps_bb.ury);
+                    if (rc == 4) 
+                        found_BB = TRUE;
+                }
+            }
+            fclose(infile);
+
+            if (!found_BB) {
+                ps_bb.llx = -1;
+                pm_message("Warning: no %%%%BoundingBox statement "
+                           "in the input or command line.\n"
+                           "Will use defaults");
+            }
+        }
+        if (ps_bb.llx != -1) {
+            if (verbose)
+                pm_message("Using %%%%BoundingBox statement from input.");
+            retval = ps_bb;
+        } else { 
+            /* Use the center of an 8.5" x 11" page with 1" border all around*/
+            retval.llx = 72;
+            retval.lly = 72;
+            retval.urx = 540;
+            retval.ury = 720;
+        }
+    }
+    if (verbose)
+        pm_message("Extracting the box ((%d,%d),(%d,%d))",
+                   retval.llx, retval.lly, retval.urx, retval.ury);
+    return retval;
+}
+
+
+
+static enum orientation
+compute_orientation(struct cmdlineInfo const cmdline, 
+                    struct box         const extract_box) {
+
+    enum orientation retval;
+    unsigned int const input_width  = extract_box.urx - extract_box.llx;
+    unsigned int const input_height = extract_box.ury - extract_box.lly;
+
+    if (cmdline.orientation != UNSPECIFIED)
+        retval = cmdline.orientation;
+    else {
+        if ((!cmdline.xsize || !cmdline.ysize) &
+            (cmdline.xsize || cmdline.ysize)) {
+            /* User specified one output dimension, but not the other,
+               so we can't use output dimensions to make the decision.  So
+               just use the input dimensions.
+            */
+            if (input_height > input_width) retval = PORTRAIT;
+            else retval = LANDSCAPE;
+        } else {
+            int output_width, output_height;
+            if (cmdline.xsize) {
+                /* He gave xsize and ysize, so that's the output size */
+                output_width = cmdline.xsize;
+                output_height = cmdline.ysize;
+            } else {
+                /* Well then we'll just use his (or default) xmax, ymax */
+                output_width = cmdline.xmax;
+                output_height = cmdline.ymax;
+            }
+
+            if (input_height > input_width && output_height > output_width)
+                retval = PORTRAIT;
+            else if (input_height < input_width && 
+                     output_height < output_width)
+                retval = PORTRAIT;
+            else 
+                retval = LANDSCAPE;
+        }
+    }
+    return retval;
+}
+
+
+
+static struct box
+add_borders(const struct box input_box, 
+            const float xborder_scale, float yborder_scale,
+            const int verbose) {
+/*----------------------------------------------------------------------------
+   Return a box which is 'input_box' plus some borders.
+
+   Add left and right borders that are the fraction 'xborder_scale' of the
+   width of the input box; likewise for top and bottom borders with 
+   'yborder_scale'.
+-----------------------------------------------------------------------------*/
+    struct box retval;
+
+    const int left_right_border_size = 
+        (int) ((input_box.urx - input_box.llx) * xborder_scale + 0.5);
+    const int top_bottom_border_size = 
+        (int) ((input_box.ury - input_box.lly) * yborder_scale + 0.5);
+
+    retval.llx = input_box.llx - left_right_border_size;
+    retval.lly = input_box.lly - top_bottom_border_size;
+    retval.urx = input_box.urx + left_right_border_size;
+    retval.ury = input_box.ury + top_bottom_border_size;
+
+    if (verbose)
+        pm_message("With borders, extracted box is ((%d,%d),(%d,%d))",
+                   retval.llx, retval.lly, retval.urx, retval.ury);
+
+    return retval;
+}
+
+
+
+static const char *
+compute_pstrans(const struct box box, const enum orientation orientation,
+                const int xsize, const int ysize, 
+                const int xres, const int yres) {
+
+    const char * retval;
+
+    if (orientation == PORTRAIT) {
+        int llx, lly;
+        llx = box.llx - (xsize * 72 / xres - (box.urx - box.llx)) / 2;
+        lly = box.lly - (ysize * 72 / yres - (box.ury - box.lly)) / 2;
+        asprintfN(&retval, "%d neg %d neg translate", llx, lly);
+    } else {
+        int llx, ury;
+        llx = box.llx - (ysize * 72 / yres - (box.urx - box.llx)) / 2;
+        ury = box.ury + (xsize * 72 / xres - (box.ury - box.lly)) / 2;
+        asprintfN(&retval, "90 rotate %d neg %d neg translate", llx, ury);
+    }
+
+    if (retval == NULL)
+        pm_error("Unable to allocate memory for pstrans");
+
+    return retval;
+}
+
+
+
+static const char *
+compute_outfile_arg(const struct cmdlineInfo cmdline) {
+
+    const char *retval;  /* malloc'ed */
+
+    if (cmdline.goto_stdout)
+        retval = strdup("-");
+    else if (STREQ(cmdline.input_filespec, "-"))
+        retval = strdup("-");
+    else {
+        char * basename;
+        const char * suffix;
+        
+        basename  = strdup(cmdline.input_filespec);
+        if (strlen(basename) > 3 && 
+            STREQ(basename+strlen(basename)-3, ".ps")) 
+            /* The input filespec ends in ".ps".  Chop it off. */
+            basename[strlen(basename)-3] = '\0';
+
+        switch (cmdline.format_type) {
+        case PBM_TYPE: suffix = "pbm"; break;
+        case PGM_TYPE: suffix = "pgm"; break;
+        case PPM_TYPE: suffix = "ppm"; break;
+        default: pm_error("Internal error: invalid value for format_type: %d",
+                          cmdline.format_type);
+        }
+        asprintfN(&retval, "%s%%03d.%s", basename, suffix);
+
+        strfree(basename);
+    }
+    return(retval);
+}
+
+
+
+static const char *
+compute_gs_device(const int format_type, const int forceplain) {
+
+    const char * basetype;
+    const char * retval;
+
+    switch (format_type) {
+    case PBM_TYPE: basetype = "pbm"; break;
+    case PGM_TYPE: basetype = "pgm"; break;
+    case PPM_TYPE: basetype = "ppm"; break;
+    default: pm_error("Internal error: invalid value format_type");
+    }
+    if (forceplain)
+        retval = strdup(basetype);
+    else
+        asprintfN(&retval, "%sraw", basetype);
+
+    if (retval == NULL)
+        pm_error("Unable to allocate memory for gs device");
+
+    return(retval);
+}
+
+
+
+static void
+findGhostscriptProg(const char ** const retvalP) {
+    
+    *retvalP = NULL;  /* initial assumption */
+    if (getenv("GHOSTSCRIPT"))
+        *retvalP = strdup(getenv("GHOSTSCRIPT"));
+    if (*retvalP == NULL) {
+        if (getenv("PATH") != NULL) {
+            char *pathwork;  /* malloc'ed */
+            const char * candidate;
+
+            pathwork = strdup(getenv("PATH"));
+            
+            candidate = strtok(pathwork, ":");
+
+            *retvalP = NULL;
+            while (!*retvalP && candidate) {
+                struct stat statbuf;
+                const char * filename;
+                int rc;
+
+                asprintfN(&filename, "%s/gs", candidate);
+                rc = stat(filename, &statbuf);
+                if (rc == 0) {
+                    if (S_ISREG(statbuf.st_mode))
+                        *retvalP = strdup(filename);
+                } else if (errno != ENOENT)
+                    pm_error("Error looking for Ghostscript program.  "
+                             "stat(\"%s\") returns errno %d (%s)",
+                             filename, errno, strerror(errno));
+                strfree(filename);
+
+                candidate = strtok(NULL, ":");
+            }
+            free(pathwork);
+        }
+    }
+    if (*retvalP == NULL)
+        *retvalP = strdup("/usr/bin/gs");
+}
+
+
+
+static void
+execGhostscript(int const inputPipeFd,
+                const char ghostscript_device[],
+                const char outfile_arg[], 
+                int const xsize, int const ysize, 
+                int const xres, int const yres,
+                const char input_filespec[], int const verbose) {
+    
+    const char *arg0;
+    const char *ghostscriptProg;
+    const char *deviceopt;
+    const char *outfileopt;
+    const char *gopt;
+    const char *ropt;
+    int rc;
+
+    findGhostscriptProg(&ghostscriptProg);
+
+    /* Put the input pipe on Standard Input */
+    rc = dup2(inputPipeFd, STDIN_FILENO);
+    close(inputPipeFd);
+
+    asprintfN(&arg0, "gs");
+    asprintfN(&deviceopt, "-sDEVICE=%s", ghostscript_device);
+    asprintfN(&outfileopt, "-sOutputFile=%s", outfile_arg);
+    asprintfN(&gopt, "-g%dx%d", xsize, ysize);
+    asprintfN(&ropt, "-r%dx%d", xres, yres);
+
+    /* -dSAFER causes Postscript to disable %pipe and file operations,
+       which are almost certainly not needed here.  This prevents our
+       Postscript program from doing crazy unexpected things, possibly
+       as a result of a malicious booby trapping of our Postscript file.
+    */
+
+    if (verbose) {
+        pm_message("execing '%s' with args '%s' (arg 0), "
+                   "'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'",
+                   ghostscriptProg, arg0,
+                   deviceopt, outfileopt, gopt, ropt, "-q", "-dNOPAUSE", 
+                   "-dSAFER", "-");
+    }
+
+    execl(ghostscriptProg, arg0, deviceopt, outfileopt, gopt, ropt, "-q",
+          "-dNOPAUSE", "-dSAFER", "-", NULL);
+    
+    pm_error("execl() of Ghostscript ('%s') failed, errno=%d (%s)",
+             ghostscriptProg, errno, strerror(errno));
+}
+
+
+
+
+static void
+execute_ghostscript(const char pstrans[], const char ghostscript_device[],
+                    const char outfile_arg[], 
+                    const int xsize, const int ysize, 
+                    const int xres, const int yres,
+                    const char input_filespec[], 
+                    const enum postscript_language language,
+                    const int verbose) {
+
+    int gs_exit;  /* wait4 exit code from Ghostscript */
+    FILE *gs;  /* Pipe to Ghostscript's standard input */
+    FILE *infile;
+    int rc;
+    int eof;  /* End of file on input */
+    int pipefd[2];
+
+    if (strlen(outfile_arg) > 80)
+        pm_error("output file spec too long.");
+    
+    rc = pipe(pipefd);
+    if (rc < 0)
+        pm_error("Unable to create pipe to talk to Ghostscript process.  "
+                 "errno = %d (%s)", errno, strerror(errno));
+    
+    rc = fork();
+    if (rc < 0)
+        pm_error("Unable to fork a Ghostscript process.  errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc == 0) {
+        /* Child process */
+        close(pipefd[1]);
+        execGhostscript(pipefd[0], ghostscript_device, outfile_arg,
+                        xsize, ysize, xres, yres, input_filespec, verbose);
+    } else {
+        pid_t const ghostscriptPid = rc;
+        int const pipeToGhostscriptFd = pipefd[1];
+        /* parent process */
+        close(pipefd[0]);
+
+        gs = fdopen(pipeToGhostscriptFd, "w");
+        if (gs == NULL) 
+            pm_error("Unable to open stream on pipe to Ghostscript process.");
+    
+        infile = pm_openr(input_filespec);
+        /*
+          In encapsulated Postscript, we the encapsulator are supposed to
+          handle showing the page (which we do by passing a showpage
+          statement to Ghostscript).  Any showpage statement in the 
+          input must be defined to have no effect.
+          
+          See "Enscapsulated PostScript Format File Specification",
+          v. 3.0, 1 May 1992, in particular Example 2, p. 21.  I found
+          it at 
+          http://partners.adobe.com/asn/developer/pdfs/tn/5002.EPSF_Spec.pdf
+          The example given is a much fancier solution than we need
+          here, I think, so I boiled it down a bit.  JM 
+        */
+        if (language == ENCAPSULATED_POSTSCRIPT)
+            fprintf(gs, "\n/b4_Inc_state save def /showpage { } def\n");
+ 
+        if (verbose) 
+            pm_message("Postscript prefix command: '%s'", pstrans);
+
+        fprintf(gs, "%s\n", pstrans);
+
+        /* If our child dies, it closes the pipe and when we next write to it,
+           we get a SIGPIPE.  We must survive that signal in order to report
+           on the fate of the child.  So we ignore SIGPIPE:
+        */
+        signal(SIGPIPE, SIG_IGN);
+
+        eof = FALSE;
+        while (!eof) {
+            char buffer[4096];
+            int bytes_read;
+            
+            bytes_read = fread(buffer, 1, sizeof(buffer), infile);
+            if (bytes_read == 0) 
+                eof = TRUE;
+            else 
+                fwrite(buffer, 1, bytes_read, gs);
+        }
+        pm_close(infile);
+
+        if (language == ENCAPSULATED_POSTSCRIPT)
+            fprintf(gs, "\nb4_Inc_state restore showpage\n");
+
+        fclose(gs);
+        
+        waitpid(ghostscriptPid, &gs_exit, 0);
+        if (rc < 0)
+            pm_error("Wait for Ghostscript process to terminated failed.  "
+                     "errno = %d (%s)", errno, strerror(errno));
+
+        if (gs_exit != 0) {
+            if (WIFEXITED(gs_exit))
+                pm_error("Ghostscript failed.  Exit code=%d\n", 
+                         WEXITSTATUS(gs_exit));
+            else if (WIFSIGNALED(gs_exit))
+                pm_error("Ghostscript process died due to a signal %d.",
+                         WTERMSIG(gs_exit));
+            else 
+                pm_error("Ghostscript process died with exit code %d", 
+                         gs_exit);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    char *input_filespec;  /* malloc'ed */
+        /* The file specification of our Postscript input file */
+    unsigned int xres, yres;    /* Resolution in pixels per inch */
+    unsigned int xsize, ysize;  /* output image size in pixels */
+    struct box extract_box;
+        /* coordinates of the box within the input we are to extract; i.e.
+           that will become the output. 
+           */
+    struct box bordered_box;
+        /* Same as above, but expanded to include borders */
+
+    enum postscript_language language;
+    enum orientation orientation;
+    const char *ghostscript_device;
+    const char *outfile_arg;
+    const char *pstrans;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    add_ps_to_filespec(cmdline.input_filespec, &input_filespec,
+                       cmdline.verbose);
+
+    extract_box = compute_box_to_extract(cmdline.extract_box, input_filespec, 
+                                         cmdline.verbose);
+
+    language = language_declaration(input_filespec, cmdline.verbose);
+    
+    orientation = compute_orientation(cmdline, extract_box);
+
+    bordered_box = add_borders(extract_box, cmdline.xborder, cmdline.yborder,
+                               cmdline.verbose);
+
+    compute_size_res(cmdline, orientation, bordered_box, 
+                     &xsize, &ysize, &xres, &yres);
+    
+    pstrans = compute_pstrans(bordered_box, orientation,
+                              xsize, ysize, xres, yres);
+
+    outfile_arg = compute_outfile_arg(cmdline);
+
+    ghostscript_device = 
+        compute_gs_device(cmdline.format_type, cmdline.forceplain);
+    
+    pm_message("Writing %s file", ghostscript_device);
+    
+    execute_ghostscript(pstrans, ghostscript_device, outfile_arg, 
+                        xsize, ysize, xres, yres, input_filespec,
+                        language, cmdline.verbose);
+
+    strfree(ghostscript_device);
+    strfree(outfile_arg);
+    strfree(pstrans);
+    
+    return 0;
+}
+
+