about summary refs log tree commit diff
path: root/generator
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-06-26 18:15:09 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-06-26 18:15:09 +0000
commitaab4792db8e0adcaf63cf57a1f5e0b18666dae47 (patch)
treebbfa9c4e12783d2dfd785c4e10964366550e9a60 /generator
parentfe14f983ade44baa0794e4ce58a1a5334e46cf68 (diff)
downloadnetpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.tar.gz
netpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.tar.xz
netpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.zip
Promote Development to Advanced as Release 10.75.00
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@2803 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator')
-rw-r--r--generator/Makefile5
-rw-r--r--generator/pbmtext.c425
-rw-r--r--generator/pbmtextps.c722
-rwxr-xr-xgenerator/pgmcrater20
-rwxr-xr-xgenerator/ppmrainbow41
5 files changed, 798 insertions, 415 deletions
diff --git a/generator/Makefile b/generator/Makefile
index d0ea6b60..5120008c 100644
--- a/generator/Makefile
+++ b/generator/Makefile
@@ -16,10 +16,13 @@ include $(BUILDDIR)/config.mk
 
 PORTBINARIES = pamcrater pamgauss pamgradient \
 	       pamseq pamshadedrelief pamstereogram \
-	       pbmpage pbmmake pbmtext pbmtextps pbmupc \
+	       pbmpage pbmmake pbmtext pbmupc \
 	       pgmkernel pgmmake pgmnoise pgmramp \
 	       ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \
 
+ifneq ($(DONT_HAVE_PROCESS_MGMT),Y)
+PORTBINARIES += pbmtextps 
+endif
 # We don't include programs that have special library dependencies in the
 # merge scheme, because we don't want those dependencies to prevent us
 # from building all the other programs.
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index 8ae28616..cfb858a6 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -20,6 +20,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
 #include "pbm.h"
 #include "pbmfont.h"
@@ -33,13 +34,14 @@ struct CmdlineInfo {
     const char * text;    /* text from command line or NULL if none */
     const char * font;    /* -font option value or NULL if none */
     const char * builtin; /* -builtin option value or NULL if none */
-    unsigned int dump;   
-        /* undocumented dump option for installing a new built-in font */
-    float space;   /* -space option value or default */
-    unsigned int width;     /* -width option value or zero */
-    int lspace;    /* lspace option value or default */
-    unsigned int nomargins;     /* -nomargins */
-    unsigned int verbose;
+    float space;          /* -space option value or default */
+    int lspace;           /* -lspace option value or default */
+    unsigned int width;   /* -width option value or zero */
+    unsigned int nomargins;  /* -nomargins option specified  */
+    unsigned int dryrun;  /* -dry-run option specified */ 
+    unsigned int verbose; /* -verbose option specified */
+        /* undocumented option */
+    unsigned int dumpsheet; /* font data sheet in PBM format for -font */   
 };
 
 
@@ -61,21 +63,22 @@ parseCommandLine(int argc, const char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "font",      OPT_STRING, &cmdlineP->font, NULL,        0);
-    OPTENT3(0, "builtin",   OPT_STRING, &cmdlineP->builtin, NULL,     0);
-    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdlineP->dump,        0);
-    OPTENT3(0, "space",     OPT_FLOAT,  &cmdlineP->space, NULL,       0);
-    OPTENT3(0, "width",     OPT_UINT,   &cmdlineP->width, NULL,       0);
-    OPTENT3(0, "lspace",    OPT_INT,    &cmdlineP->lspace, NULL,      0);
-    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdlineP->nomargins,   0);
-    OPTENT3(0, "verbose",   OPT_FLAG,   NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,    NULL,   0);
+    OPTENT3(0, "builtin",    OPT_STRING, &cmdlineP->builtin, NULL,   0);
+    OPTENT3(0, "space",      OPT_FLOAT,  &cmdlineP->space,   NULL,   0);
+    OPTENT3(0, "lspace",     OPT_INT,    &cmdlineP->lspace,  NULL,   0);
+    OPTENT3(0, "width",      OPT_UINT,   &cmdlineP->width,   NULL,   0);
+    OPTENT3(0, "nomargins",  OPT_FLAG,   NULL, &cmdlineP->nomargins, 0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,   0);
+    OPTENT3(0, "dry-run",    OPT_FLAG,   NULL, &cmdlineP->dryrun,    0);
+    OPTENT3(0, "dump-sheet", OPT_FLAG,   NULL, &cmdlineP->dumpsheet, 0);
 
     /* Set the defaults */
-    cmdlineP->font = NULL;
+    cmdlineP->font    = NULL;
     cmdlineP->builtin = NULL;
-    cmdlineP->space = 0.0;
-    cmdlineP->width = 0;
-    cmdlineP->lspace = 0;
+    cmdlineP->space   = 0.0;
+    cmdlineP->width   = 0;
+    cmdlineP->lspace  = 0;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -128,6 +131,7 @@ parseCommandLine(int argc, const char ** argv,
         }
         cmdlineP->text = text;
     }
+    free(option_def);
 }
 
 
@@ -170,16 +174,12 @@ computeFont(struct CmdlineInfo const cmdline,
     if (cmdline.verbose)
         reportFont(fontP);
 
-    if (cmdline.dump) {
-        pbm_dumpfont(fontP);
-        exit(0);
-    }
     *fontPP = fontP;
 }
 
 
 
-struct text {
+struct Text {
     char **      textArray;  /* malloc'ed */
     unsigned int allocatedLineCount;
     unsigned int lineCount;
@@ -188,7 +188,7 @@ struct text {
 
 
 static void
-allocTextArray(struct text * const textP,
+allocTextArray(struct Text * const textP,
                unsigned int  const maxLineCount,
                unsigned int  const maxColumnCount) {
 
@@ -198,7 +198,7 @@ allocTextArray(struct text * const textP,
     MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
 
     for (line = 0; line < maxLineCount; ++line) {
-        if(maxColumnCount > 0)
+        if (maxColumnCount > 0)
             MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
     else
         textP->textArray[line] = NULL;
@@ -209,7 +209,7 @@ allocTextArray(struct text * const textP,
 
 
 static void
-freeTextArray(struct text const text) {
+freeTextArray(struct Text const text) {
 
     unsigned int line;
 
@@ -221,10 +221,16 @@ freeTextArray(struct text const text) {
 
 
 
+enum FixMode {SILENT, /* convert silently */
+              WARN,   /* output message to stderr */
+              QUIT    /* abort */ };
+
+
 static void
 fixControlChars(const char *  const input,
                 struct font * const fontP,
-                const char ** const outputP) {
+                const char ** const outputP,
+                enum FixMode  const fixMode) {
 /*----------------------------------------------------------------------------
    Return a translation of input[] that can be rendered as glyphs in
    the font 'fontP'.  Return it as newly malloced *outputP.
@@ -233,9 +239,10 @@ fixControlChars(const char *  const input,
 
    Remove any trailing newline.  (But leave intermediate ones as line
    delimiters).
-
-   Turn anything that isn't a code point in the font to a single space
-   (which isn't guaranteed to be in the font either, of course).
+   
+   Depending on value of fixMode, turn anything that isn't a code point
+   in the font to a single space (which isn't guaranteed to be in the
+   font either, of course).
 -----------------------------------------------------------------------------*/
     /* We don't know in advance how big the output will be because of the
        tab expansions.  So we make sure before processing each input
@@ -281,10 +288,17 @@ fixControlChars(const char *  const input,
                 output[outCursor++] = ' ';
         } else if (!fontP->glyph[(unsigned char)input[inCursor]]) {
             /* Turn this unknown char into a single space. */
-            if(fontP->glyph[(unsigned char) ' '] == NULL)
+            if (fontP->glyph[(unsigned char) ' '] == NULL)
                 pm_error("space character not defined in font");
-            else
+            else if (fixMode == QUIT)
+                pm_error("character %d not defined in font",
+                         (unsigned int )input[inCursor] );
+            else {
+                if (fixMode == WARN)
+                    pm_message("converting character %d to space",
+                               (unsigned int) input[inCursor] );
                 output[outCursor++] = ' ';
+            }
         } else
             output[outCursor++] = input[inCursor];
 
@@ -300,17 +314,16 @@ fixControlChars(const char *  const input,
 
 
 static void
-fillRect(bit** const bits, 
-         int   const height, 
-         int   const width, 
-         bit   const color) {
+clearBackground(bit ** const bits, 
+                int    const cols, 
+                int    const rows) {
 
     unsigned int row;
-
-    for (row = 0; row < height; ++row) {
-        unsigned int col;
-        for (col = 0; col < width; ++col)
-            bits[row][col] = color;
+    
+    for (row = 0; row < rows; ++row) {
+        unsigned int colChar;
+        for (colChar = 0; colChar < pbm_packed_bytes(cols); ++colChar)
+            bits[row][colChar] = 0x00;
     }
 }
 
@@ -514,7 +527,7 @@ getCharsWithinWidth(char                const line[],
 
 
 static void
-insertCharacter(const struct glyph * const glyph, 
+insertCharacter(const struct glyph * const glyphP,
                 int                  const toprow, 
                 int                  const leftcol,
                 unsigned int         const cols,
@@ -526,19 +539,20 @@ insertCharacter(const struct glyph * const glyph,
 -----------------------------------------------------------------------------*/
     unsigned int glyph_y;  /* Y position within the glyph */
 
-    if (leftcol + glyph->x < 0 ||
-        leftcol + glyph->x + glyph->width > cols ||
+    if (leftcol + glyphP->x < 0 ||
+        leftcol + glyphP->x + glyphP->width > cols ||
         toprow < 0 ||
-        toprow + glyph->height >rows )
+        toprow + glyphP->height >rows )
         pm_error("internal error.  Rendering out of bounds");
 
-    for (glyph_y = 0; glyph_y < glyph->height; ++glyph_y) {
+    for (glyph_y = 0; glyph_y < glyphP->height; ++glyph_y) {
         unsigned int glyph_x;  /* position within the glyph */
 
-        for (glyph_x = 0; glyph_x < glyph->width; ++glyph_x) {
-            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
-                bits[toprow + glyph_y][leftcol + glyph->x + glyph_x] = 
-                    PBM_BLACK;
+        for (glyph_x = 0; glyph_x < glyphP->width; ++glyph_x) {
+            if (glyphP->bmap[glyph_y * glyphP->width + glyph_x]) {
+                unsigned int const col = leftcol + glyphP->x + glyph_x;
+                bits[toprow+glyph_y][col/8] |= PBM_BLACK << (7-col%8);
+        }
         }
     }
 }    
@@ -547,14 +561,15 @@ insertCharacter(const struct glyph * const glyph,
 
 static void
 insertCharacters(bit **        const bits, 
-                 struct text   const lp,
+                 struct Text   const lp,
                  struct font * const fontP, 
                  int           const topmargin, 
                  int           const leftmargin,
                  float         const intercharacter_space,
                  unsigned int  const cols,
                  unsigned int  const rows,
-                 int           const lspace) {
+                 int           const lspace,
+                 bool          const fixedAdvance) {
 /*----------------------------------------------------------------------------
    Render the text 'lp' into the image 'bits' using font *fontP and
    putting 'intercharacter_space' pixels between characters and
@@ -581,14 +596,17 @@ insertCharacters(bit **        const bits,
             char const currentChar = lp.textArray[line][cursor];
             unsigned int const glyphIndex = (unsigned char) currentChar;
             struct glyph * const glyphP = fontP->glyph[glyphIndex];
-            int const toprow = row + fontP->maxheight + fontP->y 
-                - glyphP->height - glyphP->y;
+            int const toprow =
+                row + fontP->maxheight + fontP->y - glyphP->height - glyphP->y;
                 /* row number in image of top row in glyph */
 
             assert(glyphP != NULL);
             
             insertCharacter(glyphP, toprow, leftcol, cols, rows, bits);
 
+        if (fixedAdvance)
+            leftcol += fontP->maxwidth;
+        else
             advancePosition(leftcol, currentChar, glyphP,
                             intercharacter_space, accumulatedIcs,
                             &leftcol, &accumulatedIcs);
@@ -599,11 +617,11 @@ insertCharacters(bit **        const bits,
 
 
 static void
-flowText(struct text    const inputText,
+flowText(struct Text    const inputText,
          int            const targetWidth, 
          struct font  * const fontP, 
          float          const intercharacterSpace,
-         struct text  * const outputTextP,
+         struct Text  * const outputTextP,
          unsigned int * const maxleftbP) {
     
     unsigned int outputLineNum;
@@ -650,7 +668,7 @@ flowText(struct text    const inputText,
 
 
 static void
-truncateText(struct text    const inputText, 
+truncateText(struct Text    const inputText, 
              unsigned int   const targetWidth, 
              struct font  * const fontP, 
              float          const intercharacterSpace,
@@ -685,16 +703,17 @@ truncateText(struct text    const inputText,
 static void
 getText(char          const cmdlineText[], 
         struct font * const fontP,
-        struct text * const inputTextP) {
+        struct Text * const inputTextP,
+        enum FixMode  const fixMode) {
 
-    struct text inputText;
+    struct Text inputText;
 
     if (cmdlineText) {
         MALLOCARRAY_NOFAIL(inputText.textArray, 1);
         inputText.allocatedLineCount = 1;
         inputText.lineCount = 1;
         fixControlChars(cmdlineText, fontP,
-                        (const char**)&inputText.textArray[0]);
+                        (const char**)&inputText.textArray[0], fixMode);
     } else {
         /* Read text from stdin. */
 
@@ -730,7 +749,8 @@ getText(char          const cmdlineText[],
                 if (textArray == NULL)
                     pm_error("out of memory");
             }
-            fixControlChars(buf, fontP, (const char **)&textArray[lineCount]);
+            fixControlChars(buf, fontP,
+                            (const char **)&textArray[lineCount], fixMode);
             if (textArray[lineCount] == NULL)
                 pm_error("out of memory");
             ++lineCount;
@@ -745,7 +765,62 @@ getText(char          const cmdlineText[],
 
 
 static void
-computeImageHeight(struct text         const formattedText, 
+computeMargins(struct CmdlineInfo const cmdline,
+               struct Text        const inputText,
+               struct font *      const fontP,
+               unsigned int *     const vmarginP,
+               unsigned int *     const hmarginP) {
+       
+    if (cmdline.nomargins) {
+        *vmarginP = 0;
+        *hmarginP = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            *vmarginP = fontP->maxheight / 2;
+            *hmarginP = fontP->maxwidth;
+        } else {
+            *vmarginP = fontP->maxheight;
+            *hmarginP = 2 * fontP->maxwidth;
+        }
+    }
+}
+
+    
+
+static void
+formatText(struct CmdlineInfo const cmdline,
+           struct Text        const inputText,
+           struct font *      const fontP,
+           unsigned int       const hmargin,
+           struct Text *      const formattedTextP,
+           unsigned int *     const maxleftb0P) {
+/*----------------------------------------------------------------------------
+  Flow or truncate lines to meet user's width request.
+-----------------------------------------------------------------------------*/
+    if (cmdline.width > 0) {
+        unsigned int const fontMargin = fontP->x < 0 ? -fontP->x : 0;
+
+        if (cmdline.width > INT_MAX -10)
+            pm_error("-width value too large: %u", cmdline.width);
+        else if (cmdline.width < 2 * hmargin)
+            pm_error("-width value too small: %u", cmdline.width);
+        else if (inputText.lineCount == 1) {
+            flowText(inputText, cmdline.width - fontMargin,
+                     fontP, cmdline.space, formattedTextP, maxleftb0P);
+            freeTextArray(inputText);
+        } else {
+            truncateText(inputText, cmdline.width - fontMargin,
+                         fontP, cmdline.space, maxleftb0P);
+            *formattedTextP = inputText;
+        }
+    } else
+        *formattedTextP = inputText;
+}
+
+
+
+static void
+computeImageHeight(struct Text         const formattedText, 
                    const struct font * const fontP,
                    int                 const interlineSpace,
                    unsigned int        const vmargin,
@@ -769,7 +844,7 @@ computeImageHeight(struct text         const formattedText,
 
 
 static void
-computeImageWidth(struct text         const formattedText, 
+computeImageWidth(struct Text         const formattedText, 
                   const struct font * const fontP,
                   float               const intercharacterSpace,
                   unsigned int        const hmargin,
@@ -817,98 +892,180 @@ computeImageWidth(struct text         const formattedText,
 
 
 
-int
-main(int argc, const char *argv[]) {
+static void
+renderText(unsigned int  const cols,
+           unsigned int  const rows,
+           struct font * const fontP,
+           unsigned int  const hmargin,
+           unsigned int  const vmargin,
+           struct Text   const formattedText,
+           unsigned int  const maxleftb,
+           float         const space,
+           int           const lspace,
+           bool          const fixedAdvance,
+           FILE *        const ofP) {
+
+    bit ** const bits = pbm_allocarray(pbm_packed_bytes(cols), rows);
+
+    /* Fill background with white */
+    clearBackground(bits, cols, rows);
+
+    /* Put the text in  */
+    insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
+                     space, cols, rows, lspace, fixedAdvance);
+
+    {
+        unsigned int row;
+
+        pbm_writepbminit(ofP, cols, rows, 0);
+
+        for (row = 0; row < rows; ++row)
+            pbm_writepbmrow_packed(ofP, bits[row], cols, 0);
+    }
+
+    pbm_freearray(bits, rows);
+}
+
+
+
+static char const * sheetTextArray[] = { 
+"M \",/^_[`jpqy| M",
+"                ",
+"/  !\"#$%&'()*+ /",
+"< ,-./01234567 <",
+"> 89:;<=>?@ABC >",
+"@ DEFGHIJKLMNO @",
+"_ PQRSTUVWXYZ[ _",
+"{ \\]^_`abcdefg {",
+"} hijklmnopqrs }",
+"~ tuvwxyz{|}~  ~",
+"                ",
+"M \",/^_[`jpqy| M" };
+
+
+
+static void
+validateText(const char ** const textArray,
+             struct font * const fontP) {
+/*----------------------------------------------------------------------------
+   Abort the program if there are characters in 'textArray' which cannot be
+   rendered in font *fontP.
+-----------------------------------------------------------------------------*/
+    const char * output;
+    unsigned int textRow;
+
+    for (textRow = 0; textRow < 12; ++textRow)
+        fixControlChars(textArray[textRow], fontP, &output, QUIT);
+
+    pm_strfree(output);
+}
+
+
+
+static void
+renderSheet(struct font * const fontP,
+            FILE *        const ofP) {
+
+    int const cols  = fontP->maxwidth  * 16;
+    int const rows  = fontP->maxheight * 12;
+    struct Text const sheetText = { (char ** const) sheetTextArray, 12, 12};
+
+    validateText(sheetTextArray, fontP);
+
+    renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0),
+               0.0, 0, TRUE, ofP);
+}
+
+
+
+static void
+dryrunOutput(unsigned int const cols,
+             unsigned int const rows,
+             FILE *       const ofP) {
+ 
+    fprintf(ofP, "%u %u\n", cols, rows); 
+}
+
+
+
+static void
+pbmtext(struct CmdlineInfo const cmdline,
+        struct font *      const fontP,
+        FILE *             const ofP) {
 
-    struct CmdlineInfo cmdline;
-    bit ** bits;
     unsigned int rows, cols;
-    struct font * fontP;
-    unsigned int vmargin, hmargin, fontMargin;
-    struct text inputText;
-    struct text formattedText;
+        /* Dimensions in pixels of the output image */
+    unsigned int cols0;
+    unsigned int vmargin, hmargin;
+        /* Margins in pixels we add to the output image */
+    unsigned int hmargin0;
+    struct Text inputText;
+    struct Text formattedText;
     unsigned int maxleftb, maxleftb0;
 
-    pm_proginit(&argc, argv);
+    getText(cmdline.text, fontP, &inputText,
+            cmdline.verbose ? WARN : SILENT);
 
-    parseCommandLine(argc, argv, &cmdline);
-    
-    computeFont(cmdline, &fontP);
+    computeMargins(cmdline, inputText, fontP, &vmargin, &hmargin0);
 
-    getText(cmdline.text, fontP, &inputText);
-       
-    if (cmdline.nomargins) {
-        vmargin = 0;
-        hmargin = 0;
-    } else {
-        if (inputText.lineCount == 1) {
-            vmargin = fontP->maxheight / 2;
-            hmargin = fontP->maxwidth;
-        } else {
-            vmargin = fontP->maxheight;
-            hmargin = 2 * fontP->maxwidth;
-        }
-    }
-    
-    if (cmdline.width > 0) {
-        fontMargin = fontP->x < 0 ? -fontP->x : 0;
+    formatText(cmdline, inputText, fontP, hmargin0,
+               &formattedText, &maxleftb0);
 
-        if (cmdline.width > INT_MAX -10)
-            pm_error("-width value too large: %u", cmdline.width);
-        else if (cmdline.width < 2 * hmargin)
-            pm_error("-width value too small: %u", cmdline.width);
-        /* Flow or truncate lines to meet user's width request */
-        else if (inputText.lineCount == 1) {
-            flowText(inputText, cmdline.width - fontMargin,
-                     fontP, cmdline.space, &formattedText, &maxleftb0);
-            freeTextArray(inputText);
-        } else {
-            truncateText(inputText, cmdline.width - fontMargin,
-                         fontP, cmdline.space, &maxleftb0);
-            formattedText = inputText;
-        }
-    } else
-        formattedText = inputText;
-        
     if (formattedText.lineCount == 0)
         pm_error("No input text");
     
-    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin,
-                       &rows);
+    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, &rows);
 
     computeImageWidth(formattedText, fontP, cmdline.space,
-              cmdline.width > 0 ? 0 : hmargin, &cols, &maxleftb);
+                      cmdline.width > 0 ? 0 : hmargin0, &cols0, &maxleftb);
 
-    if (cols == 0 || rows == 0)
+    if (cols0 == 0 || rows == 0)
         pm_error("Input is all whitespace and/or non-renderable characters.");
 
-    if (cmdline.width > 0) {
-      if(cmdline.width < cols)
-      pm_error("internal error: calculated image width (%u) exceeds "
-                   "specified -width value: %u",
-                   cols, cmdline.width);
-      else if(maxleftb0 != maxleftb)
-      pm_error("internal error: contradicting backup values");
-      else {
-      hmargin = MIN(hmargin, (cmdline.width - cols) / 2);
-          cols = cmdline.width;
-      }
+    if (cmdline.width == 0) {
+        cols    = cols0;
+        hmargin = hmargin0;
+    } else {
+        if (cmdline.width < cols0)
+            pm_error("internal error: calculated image width (%u) exceeds "
+                     "specified -width value: %u",
+                     cols0, cmdline.width);
+        else if (maxleftb0 != maxleftb)
+            pm_error("internal error: contradicting backup values");
+        else {
+            hmargin = MIN(hmargin0, (cmdline.width - cols0) / 2);
+            cols = cmdline.width;
+        }
     }
 
-    bits = pbm_allocarray(cols, rows);
+    if (cmdline.dryrun)
+        dryrunOutput(cols, rows, ofP);
+    else 
+        renderText(cols, rows, fontP, hmargin, vmargin, formattedText,
+                   maxleftb, cmdline.space, cmdline.lspace, FALSE, ofP);
 
-    /* Fill background with white */
-    fillRect(bits, rows, cols, PBM_WHITE);
+    freeTextArray(formattedText);
+}
 
-    /* Put the text in  */
-    insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
-                     cmdline.space, cols, rows, cmdline.lspace);
 
-    pbm_writepbm(stdout, bits, cols, rows, 0);
 
-    pbm_freearray(bits, rows);
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+    struct font * fontP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    computeFont(cmdline, &fontP);
+
+    if (cmdline.dumpsheet)
+        renderSheet(fontP, stdout);
+    else
+        pbmtext(cmdline, fontP, stdout);
 
-    freeTextArray(formattedText);
     pm_close(stdout);
 
     return 0;
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index e6367530..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.
@@ -14,7 +14,17 @@
  *
  * 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 */
 #include <unistd.h>
@@ -22,60 +32,15 @@
 #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) {
 /*-----------------------------------------------------------------------------
@@ -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
@@ -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) {
 
-    const char * const which = "which gs";
+    unsigned int const maxArgCt = 50;
+    
+    const char ** retval;
+    unsigned int argCt;  /* Number of arguments in 'retval' so far */
 
-    static char buffer[BUFFER_SIZE];
+    if (cmdline.res <= 0)
+         pm_error("Resolution (dpi) must be positive.");
+    
+    if (cmdline.fontsize <= 0)
+         pm_error("Font size must be positive.");
+  
+    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 */
+    
+    MALLOCARRAY_NOFAIL(lineBuff, lineBuffSize);
 
-    fclose(psfile);
+    while (fgets(lineBuff, lineBuffSize, inFileP) != NULL) {
+        unsigned int rWidthHeight, rAscentDescent, rBBox, rFontname;
 
-    pm_strfree(ps);
+        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);
 
-    rc = system(com);
-    if (rc != 0)
-        pm_error("Failed to run Ghostscript process.  rc=%d", rc);
+    pm_system2_vp(executableNm,
+                  argList,
+                  &pm_feed_from_memory, &feedBuffer,
+                  cmdline.verbose ? &acceptGSoutput : &pm_accept_null, NULL,
+                  &termStatus);
 
-    pm_strfree(com);
-}
+    if (termStatus != 0) {
+        const char * const msg = pm_termStatusDesc(termStatus);
 
+        pm_error("Failed to run Ghostscript process.  %s", msg);
 
+        pm_strfree(msg);
+    }
+    freeArgList(argList);
+}
 
-static void
-cropToStdout(const char * const inputFileName,
-             bool         const verbose) {
 
-    const char * com;
 
-    com = cropCommand(inputFileName);
+static void
+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.
 
-    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;
+  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; 
+    
+    ifP = pm_openr(fileName);
+    pbm_readpbminit(ifP, &cols, &rows, &format);
 
-        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);
+    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(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);
+static void
+dumpPsProgram(struct CmdlineInfo const cmdline) {
 
-    pm_asprintf(&uncroppedPbmFname, template, getpid(), "pbm");
-    if (uncroppedPbmFname == NULL)
-        pm_error("Unable to allocate memory");
- 
-    executeProgram(psFname, uncroppedPbmFname, 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[]) {
+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;
 }
+
+
+
diff --git a/generator/pgmcrater b/generator/pgmcrater
index 1c22ed70..c66f5576 100755
--- a/generator/pgmcrater
+++ b/generator/pgmcrater
@@ -37,6 +37,26 @@ use strict;
 
 use Getopt::Long;
 
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pamcrater', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+##############################################################################
+#
+#  MAINLINE
+#
+##############################################################################
+
+doVersionHack(\@ARGV);
+
 my @pgmcraterArgv = @ARGV;
 
 my $validOptions = GetOptions(
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
index c0568d9b..a4f90a09 100755
--- a/generator/ppmrainbow
+++ b/generator/ppmrainbow
@@ -25,11 +25,27 @@ exec perl -w -x -S -- "$0" "$@"
 #!/usr/bin/perl
 use strict;
 use Getopt::Long;
+use File::Temp;
 
 my ($FALSE, $TRUE) = (0,1);
 
 (my $myname = $0) =~ s#\A.*/##;
 
+
+
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pgmramp', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
 sub fatal($) {
     my ($msg) = @_;
 
@@ -37,19 +53,29 @@ sub fatal($) {
     exit(1);
 }
 
-my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose);
+
+
+##############################################################################
+#
+#                                 MAINLINE
+#
+##############################################################################
+
+doVersionHack(\@ARGV);
+
+my ($Twid, $Thgt, $tmpdir, $repeat, $verbose);
 
 # set defaults
 $Twid = 600;
 $Thgt = 8;
 $tmpdir = $ENV{"TMPDIR"} || "/tmp";
-$norepeat = $FALSE;
+$repeat = $TRUE;
 $verbose = $FALSE;
 
 GetOptions("width=i"   => \$Twid,
            "height=i"  => \$Thgt,
            "tmpdir=s"  => \$tmpdir,
-           "norepeat!" => \$norepeat,
+           "repeat!"   => \$repeat,
            "verbose!"  => \$verbose);
 
 if ($Twid < 1 || $Thgt < 1) {
@@ -59,7 +85,7 @@ my $verboseCommand = $verbose ? "set -x;" : "";
 
 if (@ARGV < 1) {
     fatal("You must specify at least one color as an argument");
-} elsif (@ARGV < 2 && $norepeat) {
+} elsif (@ARGV < 2 && ! $repeat) {
     fatal("With the -norepeat option, you must specify at least two colors " .
           "as arguments.");
 }
@@ -67,14 +93,11 @@ if (@ARGV < 1) {
 my @colorlist;
 
 @colorlist = @ARGV;
-if (!$norepeat) {
+if ($repeat) {
     push @colorlist, $ARGV[0];
 }
 
-my $ourtmp = "$tmpdir/ppmrainbow$$";
-mkdir($ourtmp, 0777) or
-    die("Unable to create directory for temporary files '$ourtmp");
-
+my $ourtmp = File::Temp::tempdir("$tmpdir/ppmrainbowXXXX", UNLINK=>1);
 
 my $widthRemaining;
 my $n;