about summary refs log tree commit diff
path: root/generator/pbmtextps.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-06-12 17:32:46 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-06-12 17:32:46 +0000
commit19c59f209876d5e57323a88783e26898730028e9 (patch)
tree5d8c67f6d3e0154be2d758150f2de981be4a1053 /generator/pbmtextps.c
parent11b7242986af9199615449b341cbd6e49139cd83 (diff)
downloadnetpbm-mirror-19c59f209876d5e57323a88783e26898730028e9.tar.gz
netpbm-mirror-19c59f209876d5e57323a88783e26898730028e9.tar.xz
netpbm-mirror-19c59f209876d5e57323a88783e26898730028e9.zip
Add -leftmargin, etc., -ascent, -descent, -pad, -crop, -dump-ps; use asciihex encoding of text to avoid corrupting Postscript program; validate font name to avoid corrupting Postscript program; fail if no input text rather than generate single space; more verbose stuff
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@2784 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator/pbmtextps.c')
-rw-r--r--generator/pbmtextps.c554
1 files changed, 432 insertions, 122 deletions
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index 06f7ee3e..fb55fa0a 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -1,4 +1,4 @@
-/*
+ /*
  * pbmtextps.c -  render text into a bitmap using a postscript interpreter
  *
  * Copyright (C) 2002 by James McCann.
@@ -18,6 +18,9 @@
  * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
  * ISBN 0-201-37922-8
  *
+ * Postscript Font Naming Issues:
+ * https://partners.adobe.com/public/developer/en/font/5088.FontNames.pdf
+ *
  * Other resources:
  * http://partners.adobe.com/public/developer/ps/index_specs.html
  */
@@ -38,26 +41,74 @@
 #include "pm_system.h"
 #include "pbm.h"
 
-struct CmdlineInfo {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    unsigned int res;         /* resolution, DPI */
-    unsigned int fontsize;    /* Size of font in points */
-    const char * font;      /* Name of postscript font */
-    float        stroke;
-        /* Width of stroke in points (only for outline font) */
-    unsigned int verbose;
-    const char * text;
-};
+static void
+validateFontName(const char * const name) {
+/*-----------------------------------------------------------------------------
+  Validate font name string.
+
+  Abort with error message if it contains anything other than the printable
+  characters in the ASCII 7-bit range, or any character with a special meaning
+  in PostScript.
+-----------------------------------------------------------------------------*/
+    unsigned int idx;
+
+    for (idx = 0; name[idx] != '\0'; ++idx) {
+        char const c = name[idx]; 
+
+        if (c < 32 || c > 125)
+            pm_error("Invalid character in font name");
+        else 
+            switch (c) {
+              case '[':   case ']':   case '(':   case ')':
+              case '{':   case '}':   case '/':   case '\\':
+              case '<':   case '>':   case '%':   case ' ':
+              case '@':
+                pm_error("Invalid character in font name");
+            }
+    }
+}
+
+
+
+static void
+asciiHexEncode(char *          const inbuff,
+               char *          const outbuff) {
+/*-----------------------------------------------------------------------------
+  Convert the input text string to ASCII-Hex encoding.
+
+  Examples: "ABC abc 123" -> <4142432061626320313233>
+            "FOO(BAR)FOO" -> <464f4f2842415229464f4f>
+-----------------------------------------------------------------------------*/
+    char const hexits[16] = "0123456789abcdef";
+
+    unsigned int idx;
+
+    for (idx = 0; inbuff[idx] != '\0'; ++idx) {
+        unsigned int const item = (unsigned char) inbuff[idx]; 
+
+        outbuff[idx*2]   = hexits[item >> 4];
+        outbuff[idx*2+1] = hexits[item & 0xF];
+    }
+
+    outbuff[idx * 2] = '\0';
+}
 
 
 
 static void
 buildTextFromArgs(int           const argc,
                   const char ** const argv,
-                  const char ** const textP) {
-
+                  const char ** const asciiHexTextP) {
+/*----------------------------------------------------------------------------
+   Build the array of text to be included in the Postscript program to
+   be rendered, from the arguments of this program.
+
+   We encode it in ASCII-Hex notation as opposed to using the plain text from
+   the command line because 1) the command line might have Postscript control
+   characters in it; and 2) the command line might have text in 8-bit or
+   multibyte code, but a Postscript program is supposed to be entirely
+   printable ASCII characters.
+-----------------------------------------------------------------------------*/
     char * text;
     unsigned int totalTextSize;
     unsigned int i;
@@ -65,6 +116,9 @@ buildTextFromArgs(int           const argc,
     text = strdup("");
     totalTextSize = 1;
 
+    if (argc-1 < 1)
+        pm_error("No text");
+
     for (i = 1; i < argc; ++i) {
         if (i > 1) {
             totalTextSize += 1;
@@ -79,11 +133,46 @@ buildTextFromArgs(int           const argc,
             pm_error("out of memory");
         strcat(text, argv[i]);
     }
-    *textP = text;
+
+    { 
+        char * asciiHexText;
+
+        MALLOCARRAY(asciiHexText, totalTextSize * 2);
+
+        if (!asciiHexText)
+            pm_error("Unable to allocate memory for hex encoding of %u "
+                     "characters of text", totalTextSize);
+
+        asciiHexEncode(text, asciiHexText);
+        *asciiHexTextP = asciiHexText;
+    }
+    pm_strfree(text);
 }
 
 
 
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int res;
+    float        fontsize;
+    const char * font;
+    float        stroke;
+    float        ascent;
+    float        descent;
+    float        leftmargin;
+    float        rightmargin;
+    float        topmargin;
+    float        bottommargin;
+    unsigned int pad;
+    unsigned int verbose;
+    unsigned int dump;
+    const char * text;
+};
+
+
+
 static void
 parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
@@ -97,21 +186,55 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    unsigned int cropSpec, ascentSpec, descentSpec;
+    unsigned int leftmarginSpec, rightmarginSpec;
+    unsigned int topmarginSpec, bottommarginSpec;
 
     MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "resolution", OPT_UINT,   &cmdlineP->res,            NULL,  0);
-    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,           NULL,  0);
-    OPTENT3(0, "fontsize",   OPT_UINT,   &cmdlineP->fontsize,       NULL,  0);
-    OPTENT3(0, "stroke",     OPT_FLOAT,  &cmdlineP->stroke,         NULL,  0);
-    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,         0);
+    OPTENT3(0, "resolution",    OPT_UINT,
+            &cmdlineP->res,          NULL,                      0);
+    OPTENT3(0, "font",          OPT_STRING,
+            &cmdlineP->font,         NULL,                      0);
+    OPTENT3(0, "fontsize",      OPT_FLOAT,
+            &cmdlineP->fontsize,     NULL,                      0);
+    OPTENT3(0, "stroke",        OPT_FLOAT,
+            &cmdlineP->stroke,       NULL,                      0);
+    OPTENT3(0, "ascent",        OPT_FLOAT,
+            &cmdlineP->ascent,       &ascentSpec,               0);
+    OPTENT3(0, "descent",       OPT_FLOAT,
+            &cmdlineP->descent,      &descentSpec,              0);
+    OPTENT3(0, "leftmargin",    OPT_FLOAT,
+            &cmdlineP->leftmargin,   &leftmarginSpec,           0);
+    OPTENT3(0, "rightmargin",   OPT_FLOAT,
+            &cmdlineP->rightmargin,  &rightmarginSpec,          0);
+    OPTENT3(0, "topmargin",     OPT_FLOAT,
+            &cmdlineP->topmargin,    &topmarginSpec,            0);
+    OPTENT3(0, "bottommargin",  OPT_FLOAT,
+            &cmdlineP->bottommargin, &bottommarginSpec,         0);
+    OPTENT3(0, "crop",          OPT_FLAG,
+            NULL,                    &cropSpec,                 0);
+    OPTENT3(0, "pad",           OPT_FLAG,
+            NULL,                    &cmdlineP->pad,            0);
+    OPTENT3(0, "verbose",       OPT_FLAG,
+            NULL,                    &cmdlineP->verbose,        0);
+    OPTENT3(0, "dump-ps",       OPT_FLAG,
+            NULL,                    &cmdlineP->dump,           0);
 
     /* Set the defaults */
     cmdlineP->res = 150;
     cmdlineP->fontsize = 24;
     cmdlineP->font = "Times-Roman";
-    cmdlineP->stroke = -1;
+    cmdlineP->stroke  = -1;
+    cmdlineP->ascent  = 0;
+    cmdlineP->descent = 0;
+    cmdlineP->rightmargin = 0;
+    cmdlineP->leftmargin  = 0;
+    cmdlineP->topmargin   = 0;
+    cmdlineP->bottommargin = 0;
+    cropSpec       = FALSE;
+    cmdlineP->pad  = FALSE;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -119,6 +242,39 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
 
+    validateFontName(cmdlineP->font);
+
+    if (cmdlineP->fontsize <= 0)
+        pm_error("-fontsize must be positive");
+    if (cmdlineP->ascent < 0)
+        pm_error("-ascent must not be negative");
+    if (cmdlineP->descent < 0)
+        pm_error("-descent must not be negative");
+    if (cmdlineP->leftmargin <0)
+        pm_error("-leftmargin must not be negative");
+    if (cmdlineP->rightmargin <0)
+        pm_error("-rightmargin must not be negative");
+    if (cmdlineP->topmargin <0)
+        pm_error("-topmargin must not be negative");
+    if (cmdlineP->bottommargin <0)
+        pm_error("-bottommargin must not be negative");
+
+    if (cropSpec == TRUE) {
+        if (ascentSpec || descentSpec ||
+            leftmarginSpec || rightmarginSpec ||
+            topmarginSpec || bottommarginSpec || 
+            cmdlineP->pad)
+              pm_error("-crop cannot be specified with -ascent, -descent, "
+                       "-leftmargin, -rightmargin, "
+                       "-topmargin, -bottommargin or -pad");
+    } else {
+        if (!descentSpec && !bottommarginSpec && !cmdlineP->pad)
+            cmdlineP->descent = cmdlineP->fontsize * 1.5;
+
+        if (!leftmarginSpec)
+             cmdlineP->leftmargin = cmdlineP->fontsize / 2;
+    }
+
     buildTextFromArgs(argc, argv, &cmdlineP->text);
 
     free(option_def);
@@ -126,82 +282,130 @@ parseCommandLine(int argc, const char ** argv,
 
 
 
+static void
+termCmdline(struct CmdlineInfo const cmdline) {
+
+    pm_strfree(cmdline.text);
+}
+
+
+
 static const char *
-constructPostscript(struct CmdlineInfo const cmdline) {
+postscriptProgram(struct CmdlineInfo const cmdline) {
 /*-----------------------------------------------------------------------------
-  In Postscript, the bottom of the page is row zero.  Negative values are
-  allowed but negative regions are clipped from the output image.  We make
-  adjustments to ensure that nothing is lost.
+  In Postscript, the bottom of the page is row zero.  Postscript allows
+  negative values but negative regions are clipped from the output image.
+  We make adjustments to ensure that nothing is lost.
 
-  Postscript fonts also allow negative values in the bounding box coordinates.
-  The bottom edge of "L" is row zero.  The feet of "g" "p" "y" extend into
-  negative region.  In a similar manner the left edge of the bounding box may
-  be negative.  We add margins on the left and the bottom with "xinit" and
-  "yinit" to provide for such characters.
+  Postscript also allow fonts to have negative values in the bounding box
+  coordinates.  The bottom edge of "L" is row zero: this row is called the
+  "baseline".  The feet of "g" "p" "y" extend into negative region.  In a
+  similar manner the left edge of the bounding box may be negative.  We add
+  margins on the left and the bottom with "xorigin" and "yorigin" to
+  provide for such characters.
 
   The sequence "textstring false charpath flattenpath pathbbox" determines
-  the bounding box of the entire text when rendered.  We make adjustments
-  with  "xdelta", "ydelta" whenever "xinit", "yinit" is insufficient.
-  This may sound unlikely, but is likely to happen with right-to-left or
-  vertical writing, which we haven't tested.
+  the bounding box of the entire text when rendered.
 -----------------------------------------------------------------------------*/
 
     /* C89 limits the size of a string constant, so we have to build the
        Postscript command in pieces.
+
+       psVariable, psTemplate: Set variables.
+       psFixed1: Scale font.  Calculate pad metrics.
+       psFixed2: Determine width, height, xorigin, yorigin.
+       psFixed3: Render.
+       psFixed4: Verbose mode: Report font name, metrics.
+
+       We could add code to psFixed2 for handling right-to-left writing
+       (Hebrew, Arabic) and vertical writing (Chinese, Korean, Japanese).
     */
 
     const char * const psTemplate =
-        "/%s findfont\n"
-        "/fontsize %u def\n"
-        "/stroke %f def\n"
-        "/textstring (%s) def\n";
+        "/FindFont {/%s findfont} def\n"
+        "/fontsize %f def\n"
+        "/pensize %f def\n"
+        "/textstring <%s> def\n"
+        "/ascent %f def\n"
+        "/descent %f def\n"
+        "/leftmargin %f def\n"
+        "/rightmargin %f def\n"
+        "/topmargin %f def\n"
+        "/bottommargin %f def\n"
+        "/pad %s def\n"
+        "/verbose %s def\n";
 
     const char * const psFixed1 =
-        "fontsize scalefont\n"
-        "setfont\n"
-        "/xinit fontsize 2   div def\n"
-        "/yinit fontsize 1.5 mul def\n"
-        "xinit yinit moveto\n"
-        "textstring false charpath flattenpath pathbbox\n"
-        "/top    exch def\n"
-        "/right  exch def\n"
-        "/bottom exch def\n"
-        "/left   exch def\n"
-        "/xdelta left   0 lt {left neg}   {0} ifelse def\n"
-        "/ydelta bottom 0 lt {bottom neg} {0} ifelse def\n"
-        "/width  right xdelta add def\n"
-        "/height top   ydelta add def\n";
-
+        "FindFont fontsize scalefont\n"
+        "pad { dup dup\n"
+        "  /FontMatrix get 3 get /yscale exch def\n"
+        "  /FontBBox get dup\n"
+        "  1 get yscale mul neg /padbottom exch def\n"
+        "  3 get yscale mul /padtop exch def}\n"
+        "  {/padbottom 0 def /padtop 0 def}\n"
+        "  ifelse\n"
+        "setfont\n";
+    
     const char * const psFixed2 =
+        "0 0 moveto\n"
+        "textstring false charpath flattenpath pathbbox\n"
+        "/BBtop    exch def\n"
+        "/BBright  exch def\n"
+        "/BBbottom exch neg def\n"
+        "/BBleft   exch neg def\n"
+        "/max { 2 copy lt { exch } if pop } bind def\n"
+        "/yorigin descent padbottom max BBbottom max bottommargin add def\n"
+        "/xorigin leftmargin BBleft max def\n"
+        "/width xorigin BBright add rightmargin add def\n"
+        "/height ascent BBtop max padtop max topmargin add yorigin add def\n";
+    
+    const char * const psFixed3 =
         "<</PageSize [width height]>> setpagedevice\n"
-        "xinit yinit moveto\n"
-        "xdelta ydelta rmoveto\n" 
-        "stroke 0 lt\n"
+        "xorigin yorigin moveto\n"
+        "pensize 0 lt\n"
         "  {textstring show}\n"
-        "  {stroke setlinewidth 0 setgray\n"
+        "  {pensize setlinewidth 0 setgray\n"
         "  textstring true charpath stroke}\n"
         "  ifelse\n"
-        "textstring false charpath flattenpath pathbbox\n"
-        "showpage";
-
+        "showpage\n";
+    
+    const char * const psFixed4 =
+        "verbose\n"
+        "  {xorigin yorigin moveto\n"
+        "   [(width height) width height] ==\n"
+        "   [(ascent descent) height yorigin sub yorigin] ==\n"
+        "   [(bounding box) \n"
+        "     textstring false charpath flattenpath pathbbox] ==\n"
+        "   [(Fontname) FindFont dup /FontName\n"
+        "     known\n"
+        "       {/FontName get}\n"
+        "       {pop (anonymous)}\n"
+        "       ifelse]  ==}\n"
+        "  if";
+    
     const char * retval;
     const char * psVariable;
 
-    pm_asprintf(&psVariable, psTemplate, cmdline.font, cmdline.fontsize, 
-                cmdline.stroke, cmdline.text);
+    pm_asprintf(&psVariable, psTemplate, cmdline.font,
+                cmdline.fontsize, cmdline.stroke, cmdline.text,
+                cmdline.ascent, cmdline.descent,
+                cmdline.leftmargin, cmdline.rightmargin,
+                cmdline.topmargin,  cmdline.bottommargin,
+                cmdline.pad ? "true" : "false",
+                cmdline.verbose ? "true" : "false" );
 
-    pm_asprintf(&retval, "%s%s%s", psVariable, psFixed1, psFixed2);
+    pm_asprintf(&retval, "%s%s%s%s%s", psVariable,
+                psFixed1, psFixed2, psFixed3, psFixed4);
 
     pm_strfree(psVariable);
-
+        
     return retval;
 }
 
 
 
 static const char **
-gsArgList(const char *       const psFname,
-          const char *       const outputFilename, 
+gsArgList(const char *       const outputFilename, 
           struct CmdlineInfo const cmdline) {
 
     unsigned int const maxArgCt = 50;
@@ -220,7 +424,6 @@ gsArgList(const char *       const psFname,
     argCt = 0;  /* initial value */
 
     pm_asprintf(&retval[argCt++], "ghostscript");
-
     pm_asprintf(&retval[argCt++], "-r%d", cmdline.res);
     pm_asprintf(&retval[argCt++], "-sDEVICE=pbmraw");
     pm_asprintf(&retval[argCt++], "-sOutputFile=%s", outputFilename);
@@ -228,7 +431,7 @@ gsArgList(const char *       const psFname,
     pm_asprintf(&retval[argCt++], "-dBATCH");
     pm_asprintf(&retval[argCt++], "-dSAFER");
     pm_asprintf(&retval[argCt++], "-dNOPAUSE");
-    pm_asprintf(&retval[argCt++], "%s", psFname);
+    pm_asprintf(&retval[argCt++], "-");
 
     retval[argCt++] = NULL;
 
@@ -240,32 +443,6 @@ gsArgList(const char *       const psFname,
 
 
 static void
-writeProgram(const char *       const psFname,
-             struct CmdlineInfo const cmdline) {
-
-    const char * ps;
-    FILE * psfile;
-
-    psfile = fopen(psFname, "w");
-    if (psfile == NULL)
-        pm_error("Can't open temp file '%s'.  Errno=%d (%s)",
-                 psFname, errno, strerror(errno));
-
-    ps = constructPostscript(cmdline);
-
-    if (cmdline.verbose)
-        pm_message("Postscript program = '%s'", ps);
-        
-    if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps))
-        pm_error("Can't write postscript to temp file");
-
-    fclose(psfile);
-    pm_strfree(ps);
-}
-
-
-
-static void
 reportGhostScript(const char *  const executableNm,
                   const char ** const argList) {
 
@@ -295,22 +472,138 @@ freeArgList(const char ** const argList) {
 
 
 static void
-executeProgram(const char *       const psFname, 
+reportFontName(const char * const fontname) {
+
+    pm_message("Font: '%s'", fontname);
+
+}
+
+
+
+static void
+reportMetrics(float const  width,
+              float const  height,
+              float const  ascent,
+              float const  descent,
+              float const  BBoxleft,
+              float const  BBoxbottom,
+              float const  BBoxright,
+              float const  BBoxtop) {
+
+    pm_message("-- Metrics in points.  Bottom left is (0,0) --");  
+    pm_message("Width:   %f", width);
+    pm_message("Height:  %f", height);
+    pm_message("Ascent:  %f", ascent);
+    pm_message("Descent: %f", descent);
+    pm_message("BoundingBox_Left:   %f", BBoxleft);
+    pm_message("BoundingBox_Right:  %f", BBoxright);
+    pm_message("BoundingBox_Top:    %f", BBoxtop);
+    pm_message("BoundingBox_Bottom: %f", BBoxbottom);
+
+}
+
+
+
+static void
+acceptGSoutput(int             const pipetosuckFd,
+               void *          const nullParams ) {
+/*-----------------------------------------------------------------------------
+  Accept text written to stdout by the PostScript program.
+
+  There are two kinds of output:
+    (1) Metrics and fontname reported, when verbose is on.
+    (2) Error messages from ghostscript.
+
+  We read one line at a time.
+
+  We cannot predict how long one line can be in case (2).  In practice
+  the "execute stack" report gets long.  We provide by setting lineBuffSize
+  to a large number.
+-----------------------------------------------------------------------------*/
+    unsigned int const lineBuffSize = 1024*32;
+    FILE *       const inFileP = fdopen(pipetosuckFd, "r");
+
+    float width, height, ascent, descent;
+    float BBoxleft, BBoxbottom, BBoxright, BBoxtop;
+    char * lineBuff;  /* malloc'd */
+    char fontname [2048];
+    bool fontnameReported, widthHeightReported;
+    bool ascentDescentReported, BBoxReported;
+
+    assert(nullParams == NULL);
+
+    fontnameReported      = FALSE; /* Initial value */
+    widthHeightReported   = FALSE; /* Initial value */
+    ascentDescentReported = FALSE; /* Initial value */
+    BBoxReported          = FALSE; /* Initial value */
+    
+    MALLOCARRAY_NOFAIL(lineBuff, lineBuffSize);
+
+    while (fgets(lineBuff, lineBuffSize, inFileP) != NULL) {
+        unsigned int rWidthHeight, rAscentDescent, rBBox, rFontname;
+
+        rWidthHeight = sscanf(lineBuff, "[(width height) %f %f]",
+                              &width, &height);
+
+        rAscentDescent = sscanf(lineBuff, "[(ascent descent) %f %f]",
+                                &ascent, &descent);
+
+        rBBox =  sscanf(lineBuff, "[(bounding box) %f %f %f %f]",
+                        &BBoxleft, &BBoxbottom, &BBoxright, &BBoxtop);
+
+        rFontname = sscanf(lineBuff, "[(Fontname) /%2047s", fontname);
+
+        if (rFontname == 1)
+            fontnameReported = TRUE;
+        else if (rWidthHeight == 2)
+            widthHeightReported = TRUE;
+        else if (rAscentDescent == 2)
+            ascentDescentReported = TRUE;
+        else if (rBBox == 4)
+            BBoxReported = TRUE;
+        else
+            pm_message("[gs] %s", lineBuff);
+    }
+
+    if (fontnameReported) {
+        fontname[strlen(fontname)-1] = 0; 
+        reportFontName(fontname);
+
+        if (widthHeightReported && ascentDescentReported && BBoxReported)
+            reportMetrics(width, height, ascent, descent,
+                          BBoxleft, BBoxbottom, BBoxright, BBoxtop);
+    }
+    fclose(inFileP);
+    pm_strfree(lineBuff);
+}
+
+
+
+static void
+executeProgram(const char *       const psProgram, 
                const char *       const outputFname,
                struct CmdlineInfo const cmdline) {
 
     const char *  const executableNm = "gs";
-    const char ** const argList = gsArgList(psFname, outputFname, cmdline);
+    const char ** const argList = gsArgList(outputFname, cmdline);
+
+    struct bufferDesc feedBuffer;
+    int               termStatus;
+    unsigned int      bytesFed;
+
+    bytesFed = 0;  /* Initial value */
 
-    int termStatus;
+    feedBuffer.buffer            = (unsigned char *) psProgram;
+    feedBuffer.size              = strlen(psProgram);
+    feedBuffer.bytesTransferredP = &bytesFed;
 
     if (cmdline.verbose)
         reportGhostScript(executableNm, argList);
 
     pm_system2_vp(executableNm,
                   argList,
-                  pm_feed_null, NULL,
-                  pm_accept_null, NULL,
+                  &pm_feed_from_memory, &feedBuffer,
+                  cmdline.verbose ? &acceptGSoutput : &pm_accept_null, NULL,
                   &termStatus);
 
     if (termStatus != 0) {
@@ -326,10 +619,11 @@ executeProgram(const char *       const psFname,
 
 
 static void
-writePbmToStdout(const char * const fileName){
+writePbm(const char * const fileName,
+         FILE *       const ofP) {
 /*----------------------------------------------------------------------------
-  Write the PBM image that is in the file named 'fileName" to Standard
-  Output.  I.e. pbmtopbm.
+  Write the PBM image that is in the file named 'fileName" to file *ofP.
+  I.e. pbmtopbm.
 
   It's not a byte-for-byte copy because PBM allows the same image to be
   represented many ways (all of which we can accept as our input), but we use
@@ -347,51 +641,59 @@ writePbmToStdout(const char * const fileName){
         pm_error("Abnormal output from gs program.  "
                  "width x height = %u x %u", cols, rows);
                
-    pbm_writepbminit(stdout, cols, rows, 0);           
+    pbm_writepbminit(ofP, cols, rows, 0);           
                
     bitrow = pbm_allocrow_packed(cols);
     
     for (row = 0; row < rows; ++row) {
         pbm_readpbmrow_packed(ifP, bitrow, cols, format);
-        pbm_writepbmrow_packed(stdout, bitrow, cols, 0);
+        pbm_writepbmrow_packed(ofP, bitrow, cols, 0);
     }
     pbm_freerow_packed(bitrow);
+    pm_close(ifP);
 }
 
 
 
 static void
-createOutputFile(struct CmdlineInfo const cmdline) {
+generatePbm(struct CmdlineInfo const cmdline,
+            FILE *             const ofP) {
+
+    const char * const psProgram = postscriptProgram(cmdline);
 
-    const char * psFname;
     const char * tempPbmFname;
-    FILE * psFileP;
     FILE * pbmFileP;
 
-    pm_make_tmpfile(&psFileP, &psFname);
-    assert(psFileP != NULL && psFname != NULL);
-    fclose(psFileP);
- 
-    writeProgram(psFname, cmdline);
-
     pm_make_tmpfile(&pbmFileP, &tempPbmFname);
     assert(pbmFileP != NULL && tempPbmFname != NULL);
     fclose(pbmFileP);
 
-    executeProgram(psFname, tempPbmFname, cmdline);
-
-    unlink(psFname);
-    pm_strfree(psFname);
+    executeProgram(psProgram, tempPbmFname, cmdline);
 
     /* Although Ghostscript created a legal PBM file, it uses a different
        implementation of the format from libnetpbm's canonical output format,
-       so instead of copying the content of 'tempPbmFname' to Standard output
-       byte for byte, we copy it as a PBM image.
+       so instead of copying the content of 'tempPbmFname' to *ofP byte for
+       byte, we copy it as a PBM image.
     */
-    writePbmToStdout(tempPbmFname);
+    writePbm(tempPbmFname, ofP);
 
     unlink(tempPbmFname);
     pm_strfree(tempPbmFname);
+    pm_strfree(psProgram);
+}
+
+
+
+static void
+dumpPsProgram(struct CmdlineInfo const cmdline) {
+
+    const char * psProgram;
+
+    psProgram = postscriptProgram(cmdline);
+
+    puts(psProgram);
+
+    pm_strfree(psProgram);
 }
 
 
@@ -405,7 +707,15 @@ main(int argc, const char *argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    createOutputFile(cmdline);
+    if (cmdline.dump)
+        dumpPsProgram(cmdline);
+    else
+        generatePbm(cmdline, stdout);
+
+    termCmdline(cmdline);
 
     return 0;
 }
+
+
+