about summary refs log tree commit diff
path: root/generator/pbmtextps.c
diff options
context:
space:
mode:
Diffstat (limited to 'generator/pbmtextps.c')
-rw-r--r--generator/pbmtextps.c736
1 files changed, 458 insertions, 278 deletions
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index e6367530..f543618d 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.
@@ -14,68 +14,33 @@
  *
  * Additions by Bryan Henderson contributed to public domain by author.
  *
+ * PostScript(R) Language Reference, Third Edition  (a.k.a. "Red Book")
+ * 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
  */
-#define _XOPEN_SOURCE   /* Make sure popen() is in stdio.h */
-#define _BSD_SOURCE     /* Make sure stdrup() is in string.h */
+
+#define _XOPEN_SOURCE 500
+  /* Make sure popen() is in stdio.h, strdup() is in string.h */
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <assert.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
 #include "shhopt.h"
+#include "pm_system.h"
 #include "pbm.h"
 
-
-#define BUFFER_SIZE 2048
-
-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
-writeFileToStdout(const char * const fileName){
-    /* simple pbmtopbm */
-
-    FILE * ifP;
-    int format;
-    int cols, rows, row ;
-    unsigned char * bitrow; 
-    
-    ifP = pm_openr(fileName);
-    pbm_readpbminit(ifP, &cols, &rows, &format);
-
-    if (cols==0 || rows==0 || cols>INT_MAX-10 || rows>INT_MAX-10)
-      pm_error("Abnormal output from gs program.  "
-               "width x height = %u x %u", cols, rows);
-               
-    pbm_writepbminit(stdout, 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_freerow_packed(bitrow);
-}
-
-
-
 static void
 validateFontName(const char * const name) {
 /*-----------------------------------------------------------------------------
@@ -88,11 +53,11 @@ validateFontName(const char * const name) {
     unsigned int idx;
 
     for (idx = 0; name[idx] != '\0'; ++idx) {
-        char const c = name[idx]; 
+        char const c = name[idx];
 
         if (c < 32 || c > 125)
             pm_error("Invalid character in font name");
-        else 
+        else
             switch (c) {
               case '[':   case ']':   case '(':   case ')':
               case '{':   case '}':   case '/':   case '\\':
@@ -119,7 +84,7 @@ asciiHexEncode(char *          const inbuff,
     unsigned int idx;
 
     for (idx = 0; inbuff[idx] != '\0'; ++idx) {
-        unsigned int const item = (unsigned char) inbuff[idx]; 
+        unsigned int const item = (unsigned char) inbuff[idx];
 
         outbuff[idx*2]   = hexits[item >> 4];
         outbuff[idx*2+1] = hexits[item & 0xF];
@@ -132,7 +97,7 @@ asciiHexEncode(char *          const inbuff,
 
 static void
 buildTextFromArgs(int           const argc,
-                  char **       const argv,
+                  const char ** const argv,
                   const char ** const asciiHexTextP) {
 /*----------------------------------------------------------------------------
    Build the array of text to be included in the Postscript program to
@@ -161,7 +126,7 @@ buildTextFromArgs(int           const argc,
             if (text == NULL)
                 pm_error("out of memory");
             strcat(text, " ");
-        } 
+        }
         totalTextSize += strlen(argv[i]);
         text = realloc(text, totalTextSize);
         if (text == NULL)
@@ -169,7 +134,7 @@ buildTextFromArgs(int           const argc,
         strcat(text, argv[i]);
     }
 
-    { 
+    {
         char * asciiHexText;
 
         MALLOCARRAY(asciiHexText, totalTextSize * 2);
@@ -186,9 +151,31 @@ buildTextFromArgs(int           const argc,
 
 
 
+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, char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, const 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.
@@ -199,37 +186,104 @@ parseCommandLine(int argc, 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 */
     opt.allowNegNum = FALSE;
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    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);
 }
 
 
 
 static void
-termCmdline(struct cmdlineInfo const cmdline) {
+termCmdline(struct CmdlineInfo const cmdline) {
 
     pm_strfree(cmdline.text);
 }
@@ -237,305 +291,431 @@ termCmdline(struct cmdlineInfo const cmdline) {
 
 
 static const char *
-construct_postscript(struct cmdlineInfo const cmdline) {
+postscriptProgram(struct CmdlineInfo const cmdline) {
+/*-----------------------------------------------------------------------------
+  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 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.
+-----------------------------------------------------------------------------*/
+
+    /* 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 =
+        "/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 =
+        "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"
+        "xorigin yorigin moveto\n"
+        "pensize 0 lt\n"
+        "  {textstring show}\n"
+        "  {pensize setlinewidth 0 setgray\n"
+        "  textstring true charpath stroke}\n"
+        "  ifelse\n"
+        "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 * template;
-
-    if (cmdline.stroke < 0) 
-        template =
-            "/%s findfont\n"
-            "%d scalefont\n"
-            "setfont\n"
-            "12 36 moveto\n"
-            "<%s> show\n"
-            "showpage\n";
-    else 
-        template =
-            "/%s findfont\n"
-            "%d scalefont\n"
-            "setfont\n"
-            "12 36 moveto\n"
-            "%f setlinewidth\n"
-            "0 setgray\n"
-            "<%s> true charpath\n"
-            "stroke\n"
-            "showpage\n";
-
-    if (cmdline.stroke < 0)
-        pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, 
-                    cmdline.text);
-    else
-        pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, 
-                    cmdline.stroke, cmdline.text);
+    const char * psVariable;
+
+    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%s%s", psVariable,
+                psFixed1, psFixed2, psFixed3, psFixed4);
+
+    pm_strfree(psVariable);
 
     return retval;
 }
 
 
 
-static const char *
-gsExecutableName() {
+static const char **
+gsArgList(const char *       const outputFilename,
+          struct CmdlineInfo const cmdline) {
+
+    unsigned int const maxArgCt = 50;
+
+    const char ** retval;
+    unsigned int argCt;  /* Number of arguments in 'retval' so far */
+
+    if (cmdline.res <= 0)
+         pm_error("Resolution (dpi) must be positive.");
 
-    const char * const which = "which gs";
+    if (cmdline.fontsize <= 0)
+         pm_error("Font size must be positive.");
 
-    static char buffer[BUFFER_SIZE];
+    MALLOCARRAY_NOFAIL(retval, maxArgCt+2);
 
-    FILE * f;
+    argCt = 0;  /* initial value */
 
-    memset(buffer, 0, BUFFER_SIZE);
+    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);
+    pm_asprintf(&retval[argCt++], "-q");
+    pm_asprintf(&retval[argCt++], "-dBATCH");
+    pm_asprintf(&retval[argCt++], "-dSAFER");
+    pm_asprintf(&retval[argCt++], "-dNOPAUSE");
+    pm_asprintf(&retval[argCt++], "-");
 
-    f = popen(which, "r");
-    if (!f)
-        pm_error("Can't find ghostscript");
+    retval[argCt++] = NULL;
 
-    fread(buffer, 1, BUFFER_SIZE, f);
-    if (buffer[strlen(buffer) - 1] == '\n')
-        buffer[strlen(buffer) - 1] = '\0';
-    pclose(f);
-    
-    if (buffer[0] != '/' && buffer[0] != '.')
-        pm_error("Can't find ghostscript");
+    assert(argCt < maxArgCt);
 
-    return buffer;
+    return retval;
 }
 
 
 
-static const char *
-cropExecutableName(void) {
+static void
+reportGhostScript(const char *  const executableNm,
+                  const char ** const argList) {
 
-    const char * const which = "which pnmcrop";
+    unsigned int i;
 
-    static char buffer[BUFFER_SIZE];
-    const char * retval;
+    pm_message("Running Ghostscript interpreter '%s'", executableNm);
 
-    FILE * f;
-
-    memset(buffer, 0, BUFFER_SIZE);
-
-    f = popen(which, "r");
-    if (!f)
-        retval = NULL;
-    else {
-        fread(buffer, 1, BUFFER_SIZE, f);
-        if (buffer[strlen(buffer) - 1] == '\n')
-            buffer[strlen(buffer) - 1] = 0;
-        pclose(f);
-            
-        if (buffer[0] != '/' && buffer[0] != '.') {
-            retval = NULL;
-            pm_message("Can't find pnmcrop");
-        } else
-            retval = buffer;
-    }
-    return retval;
+    pm_message("Program arguments:");
+
+    for (i = 0; argList[i]; ++i)
+        pm_message("  '%s'", argList[i]);
 }
 
 
 
-static const char *
-gsCommand(const char *       const psFname,
-          const char *       const outputFilename, 
-          struct cmdlineInfo const cmdline) {
+static void
+freeArgList(const char ** const argList) {
 
-    const char * retval;
-    double const x = (double) cmdline.res * 11;
-    double const y = (double) cmdline.res * 
-                     ((double) cmdline.fontsize * 2 + 72)  / 72;
-    
-    if (cmdline.res <= 0)
-         pm_error("Resolution (dpi) must be positive.");
-    
-    if (cmdline.fontsize <= 0)
-         pm_error("Font size must be positive.");
-    
-    /* The following checks are for guarding against overflows in this 
-       function.  Huge x,y values that pass these checks may be
-       rejected by the 'gs' program.
-    */
-    
-    if (x > (double) INT_MAX-10)
-         pm_error("Absurdly fine resolution: %u. Output width too large.",
-                   cmdline.res );
-    if (y > (double) INT_MAX-10)
-         pm_error("Absurdly fine resolution (%u) and/or huge font size (%u). "
-                  "Output height too large.", cmdline.res, cmdline.fontsize);
-         
-    pm_asprintf(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm "
-                "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s "
-                "</dev/null >/dev/null", 
-                gsExecutableName(), (int) x, (int) y, cmdline.res, 
-                outputFilename, psFname);
+    unsigned int i;
 
-    return retval;
+    for (i = 0; argList[i]; ++i)
+        pm_strfree(argList[i]);
+
+    free(argList);
 }
 
 
 
-static const char *
-cropCommand(const char * const inputFileName) {
+static void
+reportFontName(const char * const fontname) {
 
-    const char * retval;
-    const char * plainOpt = pm_plain_output ? "-plain" : "" ;
-    
-    if (cropExecutableName()) {
-        pm_asprintf(&retval, "%s -top -right %s %s", 
-                    cropExecutableName(), plainOpt, inputFileName);
-        if (retval == pm_strsol)
-            pm_error("Unable to allocate memory");
-    } else
-        retval = NULL;
+    pm_message("Font: '%s'", fontname);
 
-    return retval;
 }
 
 
 
 static void
-writeProgram(const char *       const psFname,
-             struct cmdlineInfo const cmdline) {
+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);
 
-    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 = construct_postscript(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");
+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 */
 
-    fclose(psfile);
+    MALLOCARRAY_NOFAIL(lineBuff, lineBuffSize);
 
-    pm_strfree(ps);
+    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 psFname, 
+executeProgram(const char *       const psProgram,
                const char *       const outputFname,
-               struct cmdlineInfo const cmdline) {
+               struct CmdlineInfo const cmdline) {
 
-    const char * com;
-    int rc;
+    const char *  const executableNm = "gs";
+    const char ** const argList = gsArgList(outputFname, cmdline);
+
+    struct bufferDesc feedBuffer;
+    int               termStatus;
+    unsigned int      bytesFed;
+
+    bytesFed = 0;  /* Initial value */
+
+    feedBuffer.buffer            = (unsigned char *) psProgram;
+    feedBuffer.size              = strlen(psProgram);
+    feedBuffer.bytesTransferredP = &bytesFed;
 
-    com = gsCommand(psFname, outputFname, cmdline);
-    if (com == NULL)
-        pm_error("Can't allocate memory for a 'ghostscript' command");
-    
     if (cmdline.verbose)
-        pm_message("Running Postscript interpreter '%s'", com);
+        reportGhostScript(executableNm, argList);
+
+    pm_system2_vp(executableNm,
+                  argList,
+                  &pm_feed_from_memory, &feedBuffer,
+                  cmdline.verbose ? &acceptGSoutput : &pm_accept_null, NULL,
+                  &termStatus);
+
+    if (termStatus != 0) {
+        const char * const msg = pm_termStatusDesc(termStatus);
 
-    rc = system(com);
-    if (rc != 0)
-        pm_error("Failed to run Ghostscript process.  rc=%d", rc);
+        pm_error("Failed to run Ghostscript process.  %s", msg);
 
-    pm_strfree(com);
+        pm_strfree(msg);
+    }
+    freeArgList(argList);
 }
 
 
 
 static void
-cropToStdout(const char * const inputFileName,
-             bool         const verbose) {
+writePbm(const char * const fileName,
+         FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+  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
+  libnetpbm to write our output in its specific way.
+----------------------------------------------------------------------------*/
+    FILE * ifP;
+    int format;
+    int cols, rows, row ;
+    unsigned char * bitrow;
 
-    const char * com;
+    ifP = pm_openr(fileName);
+    pbm_readpbminit(ifP, &cols, &rows, &format);
 
-    com = cropCommand(inputFileName);
+    if (cols == 0 || rows == 0 || cols > INT_MAX - 10 || rows > INT_MAX - 10)
+        pm_error("Abnormal output from gs program.  "
+                 "width x height = %u x %u", cols, rows);
 
-    if (com == NULL) {
-        /* No pnmcrop.  So don't crop. */
-        pm_message("Can't find pnmcrop command, image will be large");
-        writeFileToStdout(inputFileName);
-    } else {
-        FILE * pnmcrop;
-
-        if (verbose)
-            pm_message("Running crop command '%s'", com);
-        
-        pnmcrop = popen(com, "r");
-        if (pnmcrop == NULL)
-            pm_error("Can't run pnmcrop process");
-        else {
-            char buf[2048];
-            bool eof;
-
-            eof = FALSE;
-            
-            while (!eof) {
-                int bytesRead;
-
-                bytesRead = fread(buf, 1, sizeof(buf), pnmcrop);
-                if (bytesRead > 0) {
-                    int rc;
-                    rc = fwrite(buf, 1, bytesRead, stdout);
-                    if (rc != bytesRead)
-                        pm_error("Can't write to stdout");
-                } else if (bytesRead == 0)
-                    eof = TRUE;
-                else
-                    pm_error("Failed to read output of Pnmcrop process.  "
-                             "Errno=%d (%s)", errno, strerror(errno));
-            }
-            fclose(pnmcrop);
-        }
-        pm_strfree(com);
+    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(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 * tempPbmFname;
+    FILE * pbmFileP;
+
+    pm_make_tmpfile(&pbmFileP, &tempPbmFname);
+    assert(pbmFileP != NULL && tempPbmFname != NULL);
+    fclose(pbmFileP);
+
+    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 *ofP byte for
+       byte, we copy it as a PBM image.
+    */
+    writePbm(tempPbmFname, ofP);
+
+    unlink(tempPbmFname);
+    pm_strfree(tempPbmFname);
+    pm_strfree(psProgram);
+}
 
-    const char * const template = "./pstextpbm.%d.tmp.%s";
-    
-    const char * psFname;
-    const char * uncroppedPbmFname;
 
-    pm_asprintf(&psFname, template, getpid(), "ps");
-    if (psFname == NULL)
-        pm_error("Unable to allocate memory");
- 
-    writeProgram(psFname, cmdline);
 
-    pm_asprintf(&uncroppedPbmFname, template, getpid(), "pbm");
-    if (uncroppedPbmFname == NULL)
-        pm_error("Unable to allocate memory");
- 
-    executeProgram(psFname, uncroppedPbmFname, cmdline);
+static void
+dumpPsProgram(struct CmdlineInfo const cmdline) {
+
+    const char * psProgram;
 
-    unlink(psFname);
-    pm_strfree(psFname);
+    psProgram = postscriptProgram(cmdline);
 
-    cropToStdout(uncroppedPbmFname, cmdline.verbose);
+    puts(psProgram);
 
-    unlink(uncroppedPbmFname);
-    pm_strfree(uncroppedPbmFname);
+    pm_strfree(psProgram);
 }
 
 
 
-int 
-main(int argc, char *argv[]) {
+int
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
-    pbm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    createOutputFile(cmdline);
+    if (cmdline.dump)
+        dumpPsProgram(cmdline);
+    else
+        generatePbm(cmdline, stdout);
 
     termCmdline(cmdline);
 
     return 0;
 }
+
+
+