about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2022-09-28 02:22:32 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2022-09-28 02:22:32 +0000
commit0d782c278caeb9e70b1a5635d018168b09ecf2ba (patch)
treeb807f4ee6b4ce6b9cbafb66768d09c106a71040d
parent4616d7d06bc2ee200a7f40cee107fbbbf5d0cae9 (diff)
downloadnetpbm-mirror-0d782c278caeb9e70b1a5635d018168b09ecf2ba.tar.gz
netpbm-mirror-0d782c278caeb9e70b1a5635d018168b09ecf2ba.tar.xz
netpbm-mirror-0d782c278caeb9e70b1a5635d018168b09ecf2ba.zip
Copy current Development release as Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4444 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile12
-rw-r--r--analyzer/pamtable.c85
-rw-r--r--converter/other/fitstopnm.c201
-rw-r--r--converter/other/pamtofits.c19
-rwxr-xr-xconverter/ppm/hpcdtoppm/pcdovtoppm25
-rw-r--r--converter/ppm/ppmtompeg/specifics.c48
-rw-r--r--doc/HISTORY30
-rw-r--r--editor/Makefile17
-rw-r--r--editor/pamcat.c1303
-rw-r--r--editor/pamlevels.c1
-rw-r--r--editor/pnmcat.c879
-rwxr-xr-xeditor/pnmindex.csh189
-rwxr-xr-xeditor/pnmindex.sh214
-rwxr-xr-xeditor/pnmmargin4
-rwxr-xr-xeditor/pnmquantall4
-rw-r--r--editor/specialty/pnmindex.c106
-rw-r--r--generator/pbmtextps.c12
-rw-r--r--generator/ppmforge.c60
-rwxr-xr-xgenerator/ppmrainbow7
-rw-r--r--lib/libpam.c86
-rw-r--r--lib/libpnm3.c6
-rw-r--r--lib/pam.h7
-rw-r--r--lib/util/nstring.c738
-rw-r--r--lib/util/nstring.h13
-rw-r--r--lib/util/shhopt.h6
-rw-r--r--other/pamunlookup.c1
-rwxr-xr-xtest/Execute-Tests2
-rw-r--r--test/Test-Order5
-rw-r--r--test/all-in-place.ok2
-rwxr-xr-xtest/all-in-place.test4
-rwxr-xr-xtest/gif-roundtrip.test72
-rwxr-xr-xtest/gif-transparent1.test2
-rw-r--r--test/legacy-names.ok1
-rwxr-xr-xtest/legacy-names.test1
-rw-r--r--test/pamarith.ok4
-rwxr-xr-xtest/pamarith.test90
-rw-r--r--test/pamcat1.ok150
-rwxr-xr-xtest/pamcat1.test271
-rw-r--r--test/pamcat2.ok93
-rwxr-xr-xtest/pamcat2.test56
-rwxr-xr-xtest/pamchannel.test6
-rwxr-xr-xtest/pamcrater.test8
-rwxr-xr-xtest/pamcut.test30
-rwxr-xr-xtest/pamdepth.test4
-rwxr-xr-xtest/pamditherbw.test24
-rwxr-xr-xtest/pamenlarge-pbm.test8
-rwxr-xr-xtest/pamfile.test6
-rwxr-xr-xtest/pamfind.test8
-rwxr-xr-xtest/pamfix.test6
-rwxr-xr-xtest/pamfunc.test48
-rwxr-xr-xtest/pamgauss.test10
-rwxr-xr-xtest/pamhue.test6
-rwxr-xr-xtest/pamrecolor.test14
-rwxr-xr-xtest/pamrestack.test10
-rwxr-xr-xtest/pamscale-reportonly.test12
-rwxr-xr-xtest/pamseq.test8
-rwxr-xr-xtest/pamshuffle.test6
-rwxr-xr-xtest/pamsumm.test8
-rw-r--r--test/pamtable.ok60
-rwxr-xr-xtest/pamtable.test43
-rwxr-xr-xtest/pamundice.test30
-rwxr-xr-xtest/pbmclean.test2
-rwxr-xr-xtest/pbmmake.test16
-rwxr-xr-xtest/pbmnoise-parameters.test50
-rwxr-xr-xtest/pbmpage.test6
-rwxr-xr-xtest/pbmpscale.test6
-rwxr-xr-xtest/pbmtext-utf8.test18
-rwxr-xr-xtest/pbmtext.test28
-rw-r--r--test/pbmtextps-dump.ok65
-rwxr-xr-xtest/pbmtextps-dump.test84
-rw-r--r--test/pbmtextps.ok16
-rwxr-xr-xtest/pbmtextps.test53
-rwxr-xr-xtest/pbmtopgm.test8
-rwxr-xr-xtest/pbmupc.test18
-rwxr-xr-xtest/pgmhist.test8
-rwxr-xr-xtest/pgmmake.test16
-rwxr-xr-xtest/pgmnoise-parameters.test10
-rwxr-xr-xtest/pgmnoise.test22
-rwxr-xr-xtest/pgmramp.test6
-rwxr-xr-xtest/pgmtoppm.test24
-rw-r--r--test/pnmcat.ok6
-rwxr-xr-xtest/pnmcat.test52
-rwxr-xr-xtest/pnmcolormap.test14
-rwxr-xr-xtest/pnmcrop1.test2
-rwxr-xr-xtest/pnmcrop3.test4
-rwxr-xr-xtest/pnmpsnr.test8
-rwxr-xr-xtest/pnmquant.test16
-rwxr-xr-xtest/pnmquantall.test6
-rwxr-xr-xtest/pnmremap1.test12
-rw-r--r--test/pnmtile.ok2
-rwxr-xr-xtest/pnmtile.test16
-rwxr-xr-xtest/ppmforge-parameters.test14
-rwxr-xr-xtest/ppmforge.test2
-rwxr-xr-xtest/ppmhist.test6
-rwxr-xr-xtest/ppmmake.test20
-rwxr-xr-xtest/ppmpat.test52
-rwxr-xr-xtest/ppmtoapplevol.test6
-rwxr-xr-xtest/ppmwheel.test10
-rwxr-xr-xtest/ps-roundtrip.test4
-rw-r--r--test/qoi-roundtrip.ok34
-rwxr-xr-xtest/qoi-roundtrip.test254
-rw-r--r--test/stdin-pam1.ok1
-rwxr-xr-xtest/stdin-pam1.test3
-rw-r--r--test/stdin-pam3.ok2
-rwxr-xr-xtest/stdin-pam3.test2
-rw-r--r--test/stdin-pnm1.ok1
-rwxr-xr-xtest/stdin-pnm1.test5
-rw-r--r--test/stdin-ppm2.ok2
-rwxr-xr-xtest/stdin-ppm2.test12
-rw-r--r--test/stdin-ppm3.ok2
-rwxr-xr-xtest/stdin-ppm3.test18
-rw-r--r--version.mk6
112 files changed, 3530 insertions, 2701 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 7389c16a..2c172da6 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -24,6 +24,18 @@
 #   The default target is either "merge" or "nonmerge", as determined by
 #   the DEFAULT_TARGET variable set by config.mk.
 
+# Debugging techniques:
+#
+#   To build so you can easily debug with a debugger such as Gdb:
+#
+#     make CFLAGS="-g -O0"
+#
+#   To debug libnetpbm, link it statically into the using program:
+#
+#     make NETPBMLIBTYPE=unixstatic pamcut
+#
+#     (Then 'ldd ./pamcut' to make sure it worked)
+#
 # About the "merge" target: Normally the Makefiles build separate
 # executables for each program.  However, on some systems (especially
 # those without shared libraries) this can mean a lot of space.  In
diff --git a/analyzer/pamtable.c b/analyzer/pamtable.c
index 2835469a..acd027a6 100644
--- a/analyzer/pamtable.c
+++ b/analyzer/pamtable.c
@@ -15,12 +15,16 @@
 #include "mallocvar.h"
 #include "nstring.h"
 
+enum Style {STYLE_BASIC, STYLE_TUPLE};
+
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char * inputFileName;  /* Name of input file */
-    unsigned int  verbose;
+    enum Style   outputStyle;
+    unsigned int hex;
+    unsigned int verbose;
 };
 
 
@@ -34,11 +38,14 @@ parseCommandLine(int argc, const char ** const argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    unsigned int tuple;
 
     MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
 
+    OPTENT3(0,   "tuple",     OPT_FLAG,  NULL, &tuple,               0);
+    OPTENT3(0,   "hex",       OPT_FLAG,  NULL, &cmdlineP->hex,       0);
     OPTENT3(0,   "verbose",   OPT_FLAG,  NULL, &cmdlineP->verbose,   0);
         /* For future expansion */
 
@@ -49,6 +56,14 @@ parseCommandLine(int argc, const char ** const argv,
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
+    if (tuple && cmdlineP->hex)
+        pm_error("-hex is invalid with -tuple");
+
+    if (tuple)
+        cmdlineP->outputStyle = STYLE_TUPLE;
+    else
+        cmdlineP->outputStyle = STYLE_BASIC;
+
     if (argc-1 > 1)
         pm_error("Too many arguments (%d).  File name is the only argument.",
                  argc-1);
@@ -74,12 +89,27 @@ typedef struct {
     const char * interTupleGutter;
        /* What we print between tuples within a row */
 
+    const char * rowStartString;
+       /* What we print at the beginning of each row */
+
+    const char * rowEndString;
+       /* What we print at the end of each row */
+
 } Format;
 
 
 
+static double const
+log16(double const arg) {
+
+    return log(arg)/log(16);
+}
+
+
+
 static const char *
-sampleFormat(const struct pam * const pamP) {
+basicSampleFormat(const struct pam * const pamP,
+                  bool               const wantHex) {
 /*----------------------------------------------------------------------------
    The printf format string for a single sample in the output table.
 
@@ -87,11 +117,21 @@ sampleFormat(const struct pam * const pamP) {
 
    This format does not include any spacing between samples.
 -----------------------------------------------------------------------------*/
-    unsigned int const decimalWidth = ROUNDU(ceil(log10(pamP->maxval + 1)));
-
+    unsigned int cipherWidth;
+    char         formatSpecifier;
+    const char * flag;
     const char * retval;
 
-    pm_asprintf(&retval, "%%%uu", decimalWidth);
+    if (wantHex) {
+        formatSpecifier = 'x';
+        cipherWidth     = ROUNDU(ceil(log16(pamP->maxval + 1)));
+        flag            = "0";
+    } else {
+        formatSpecifier = 'u';
+        cipherWidth     = ROUNDU(ceil(log10(pamP->maxval + 1)));
+        flag            = "";
+    }
+    pm_asprintf(&retval, "%%%s%u%c", flag, cipherWidth, formatSpecifier);
 
     return retval;
 }
@@ -100,13 +140,26 @@ sampleFormat(const struct pam * const pamP) {
 
 static void
 makeFormat(const struct pam * const pamP,
+           enum Style         const outputStyle,
+           bool               const wantHex,
            Format *           const formatP) {
 
-    formatP->sampleFmt = sampleFormat(pamP);
-
-    formatP->interSampleGutter = " ";
-
-    formatP->interTupleGutter = pamP->depth > 1 ? "|" : " ";
+    switch (outputStyle) {
+      case STYLE_BASIC:
+          formatP->sampleFmt         = basicSampleFormat(pamP, wantHex);
+          formatP->interSampleGutter = " ";
+          formatP->interTupleGutter  = pamP->depth > 1 ? "|" : " ";
+          formatP->rowStartString    = "";
+          formatP->rowEndString      = "\n";
+          break;
+      case STYLE_TUPLE:
+          formatP->sampleFmt         = pm_strdup("%u");
+          formatP->interSampleGutter = ",";
+          formatP->interTupleGutter  = ") (";
+          formatP->rowStartString    = "(";
+          formatP->rowEndString      = ")\n";
+          break;
+    }
 }
 
 
@@ -127,6 +180,8 @@ printRow(const struct pam * const pamP,
 
     unsigned int col;
 
+    fputs (format.rowStartString, ofP);
+
     for (col = 0; col < pamP->width; ++col) {
         unsigned int plane;
 
@@ -142,7 +197,7 @@ printRow(const struct pam * const pamP,
         }
     }
 
-    fputs("\n", ofP);
+    fputs (format.rowEndString, ofP);
 }
 
 
@@ -150,14 +205,16 @@ printRow(const struct pam * const pamP,
 static void
 printRaster(FILE *             const ifP,
             const struct pam * const pamP,
-            FILE *             const ofP) {
+            FILE *             const ofP,
+            enum Style         const outputStyle,
+            bool               const wantHex) {
 
     Format format;
 
     tuple * inputRow;   /* Row from input image */
     unsigned int row;
 
-    makeFormat(pamP, &format);
+    makeFormat(pamP, outputStyle, wantHex, &format);
 
     inputRow = pnm_allocpamrow(pamP);
 
@@ -189,7 +246,7 @@ main(int argc, const char *argv[]) {
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-    printRaster(ifP, &inpam, stdout);
+    printRaster(ifP, &inpam, stdout, cmdline.outputStyle, cmdline.hex);
 
     pm_close(inpam.file);
 
diff --git a/converter/other/fitstopnm.c b/converter/other/fitstopnm.c
index bdf5c78a..82c19a69 100644
--- a/converter/other/fitstopnm.c
+++ b/converter/other/fitstopnm.c
@@ -44,6 +44,7 @@
 #include <string.h>
 #include <float.h>
 #include <assert.h>
+#include <stdbool.h>
 
 #include "pm_config.h"
 #include "pm_c_util.h"
@@ -74,12 +75,12 @@ struct CmdlineInfo {
 
 
 
-static void 
-parseCommandLine(int argc, const char ** argv, 
+static void
+parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
 /* --------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -140,7 +141,7 @@ parseCommandLine(int argc, const char ** argv,
         cmdlineP->inputFileName = "-";
     else {
         cmdlineP->inputFileName = argv[1];
-        
+
         if (argc-1 > 1)
             pm_error("Too many arguments (%u).  The only non-option argument "
                      "is the input file name.", argc-1);
@@ -150,29 +151,25 @@ parseCommandLine(int argc, const char ** argv,
 
 
 
-struct FITS_Header {
-  int simple;       /* basic format or not */
-  int bitpix;
-      /* number of bits per pixel, positive for integer, negative 
-         for floating point
-      */
-  int naxis;        /* number of axes */
-  int naxis1;       /* number of points on axis 1 */
-  int naxis2;       /* number of points on axis 2 */
-  int naxis3;       /* number of points on axis 3 */
-  double datamin;   /* min # (Physical value!) */
-  double datamax;   /* max #     "       "     */
-  double bzer;      /* Physical value = Array value*bscale + bzero */
-  double bscale;
-};
-
-
 typedef enum {
     VF_CHAR, VF_SHORT, VF_LONG, VF_FLOAT, VF_DOUBLE
-} valFmt;
+} ValFmt;
 
-struct fitsRasterInfo {
-    valFmt valFmt;
+struct FITS_Header {
+    bool simple;      /* basic format */
+    ValFmt valFmt;    /* format of values -- bits per pixel, integer/float */
+    unsigned int naxis;  /* number of axes */
+    unsigned int naxis1;       /* number of points on axis 1 */
+    unsigned int naxis2;       /* number of points on axis 2 */
+    unsigned int naxis3;       /* number of points on axis 3 */
+    double datamin;   /* min # (Physical value!) */
+    double datamax;   /* max #     "       "     */
+    double bzer;      /* Physical value = Array value*bscale + bzero */
+    double bscale;
+};
+
+struct FitsRasterInfo {
+    ValFmt valFmt;
     double bzer;
     double bscale;
 };
@@ -296,7 +293,7 @@ readFitsDouble(FILE *   const ifP,
 
 
 
-static valFmt
+static ValFmt
 valFmtFromBitpix(int const bitpix) {
 /*----------------------------------------------------------------------------
    Return the format of a "value" in the FITS file, given the value
@@ -323,7 +320,7 @@ valFmtFromBitpix(int const bitpix) {
 
 static void
 readVal(FILE *   const ifP,
-        valFmt   const fmt,
+        ValFmt   const fmt,
         double * const vP) {
 
     switch (fmt) {
@@ -334,15 +331,15 @@ readVal(FILE *   const ifP,
     case VF_SHORT:
         readFitsShort(ifP, vP);
         break;
-      
+
     case VF_LONG:
         readFitsLong(ifP, vP);
         break;
-      
+
     case VF_FLOAT:
         readFitsFloat(ifP, vP);
         break;
-      
+
     case VF_DOUBLE:
         readFitsDouble(ifP, vP);
         break;
@@ -364,43 +361,119 @@ readCard(FILE * const ifP,
 
 
 
+
+static void
+processNaxisN(unsigned int   const n,
+              int            const value,
+              bool           const gotNaxis,
+              unsigned int   const naxis,
+              bool *         const gotNaxisNP,
+              unsigned int * const naxisNP) {
+
+    if (*gotNaxisNP)
+        pm_error("Invalid FITS header: two NAXIS%u keywords", n);
+    else {
+        *gotNaxisNP = true;
+
+        if (!gotNaxis)
+            pm_error("Invalid FITS header: NAXIS must precede NAXIS%u", n);
+        else if (naxis < n)
+            pm_error("Invalid FITS header: NAXIS%u for image with "
+                     "only %u axes", n, naxis);
+        else if (value < 0)
+            pm_error("Invalid NAXIS%u value %d in FITS header:  "
+                     "Must not be negative", n, value);
+        else
+            *naxisNP = value;
+    }
+}
+
+
+
 static void
 readFitsHeader(FILE *               const ifP,
                struct FITS_Header * const hP) {
 
-    int seenEnd;
-  
-    seenEnd = 0;
+    bool gotSimple, gotNaxis, gotN1, gotN2, gotN3, gotBitpix, gotEnd;
+
+    gotSimple = false;  /* initial value */
+    gotNaxis  = false;  /* initial value */
+    gotN1     = false;  /* initial value */
+    gotN2     = false;  /* initial value */
+    gotN3     = false;  /* initial value */
+    gotBitpix = false;  /* initial value */
+    gotEnd    = false;  /* initial value */
+
     /* Set defaults */
-    hP->simple  = 0;
     hP->bzer    = 0.0;
     hP->bscale  = 1.0;
     hP->datamin = - DBL_MAX;
     hP->datamax = DBL_MAX;
-  
-    while (!seenEnd) {
+
+    while (!gotEnd) {
         unsigned int i;
         for (i = 0; i < 36; ++i) {
             char buf[80];
             char c;
+            int n;
 
             readCard(ifP, buf);
-    
+
             if (sscanf(buf, "SIMPLE = %c", &c) == 1) {
+                if (gotSimple)
+                    pm_error("FITS header has two SIMPLE keywords");
+                gotSimple = true;
                 if (c == 'T' || c == 't')
-                    hP->simple = 1;
-            } else if (sscanf(buf, "BITPIX = %d", &(hP->bitpix)) == 1);
-            else if (sscanf(buf, "NAXIS = %d", &(hP->naxis)) == 1);
-            else if (sscanf(buf, "NAXIS1 = %d", &(hP->naxis1)) == 1);
-            else if (sscanf(buf, "NAXIS2 = %d", &(hP->naxis2)) == 1);
-            else if (sscanf(buf, "NAXIS3 = %d", &(hP->naxis3)) == 1);
-            else if (sscanf(buf, "DATAMIN = %lf", &(hP->datamin)) == 1);
-            else if (sscanf(buf, "DATAMAX = %lf", &(hP->datamax)) == 1);
-            else if (sscanf(buf, "BZERO = %lf", &(hP->bzer)) == 1);
-            else if (sscanf(buf, "BSCALE = %lf", &(hP->bscale)) == 1);
-            else if (strncmp(buf, "END ", 4 ) == 0) seenEnd = 1;
+                    hP->simple = true;
+                else if (c == 'F' || c == 'f')
+                    hP->simple = false;
+                else
+                    pm_error("Invalid SIMPLE value '%c'.  Only 'T' and 'F' "
+                             "are recognized", c);
+            } else if (sscanf(buf, "BITPIX = %d", &n) == 1) {
+                if (gotBitpix)
+                    pm_error("FITS header has two NAXIS keywords");
+                gotBitpix = true;
+                hP->valFmt = valFmtFromBitpix(n);
+            } else if (sscanf(buf, "NAXIS = %d", &n) == 1) {
+                gotNaxis = true;
+                if (n < 0)
+                    pm_error("Invalid value %d for NAXIS in FITS header.  "
+                             "Value must not be negative", n);
+                else
+                    hP->naxis = n;
+            } else if (sscanf(buf, "NAXIS1 = %d", &n) == 1) {
+                processNaxisN(1, n, gotNaxis, hP->naxis, &gotN1, &hP->naxis1);
+            } else if (sscanf(buf, "NAXIS2 = %d", &n) == 1) {
+                processNaxisN(2, n, gotNaxis, hP->naxis, &gotN2, &hP->naxis2);
+            } else if (sscanf(buf, "NAXIS3 = %d", &n) == 1) {
+                processNaxisN(3, n, gotNaxis, hP->naxis, &gotN3, &hP->naxis3);
+            } else if (sscanf(buf, "DATAMIN = %lf", &(hP->datamin)) == 1) {
+            } else if (sscanf(buf, "DATAMAX = %lf", &(hP->datamax)) == 1) {
+            } else if (sscanf(buf, "BZERO = %lf", &(hP->bzer)) == 1) {
+            } else if (sscanf(buf, "BSCALE = %lf", &(hP->bscale)) == 1) {
+            } else if (strncmp(buf, "END ", 4 ) == 0) {
+                gotEnd = true;
+            }
         }
     }
+    if (!gotSimple)
+        pm_error("FITS header missing the SIMPLE keyword");
+    if (!gotBitpix)
+        pm_error("FITS header missing the BITPIX keyword");
+    if (!gotNaxis)
+        pm_error("FITS header missing the NAXIS keyword");
+
+    if (hP->naxis > 3)
+        pm_error("FITS file has %u axes; this program can handle "
+                 "no more than 3", hP->naxis);
+
+    if (hP->naxis > 0 && !gotN1)
+        pm_error("FITS header missing NAXIS1 keyword");
+    if (hP->naxis > 1 && !gotN2)
+        pm_error("FITS header missing NAXIS1 keyword");
+    if (hP->naxis > 2 && !gotN3)
+        pm_error("FITS header missing NAXIS3 keyword");
 }
 
 
@@ -421,7 +494,7 @@ interpretPlanes(struct FITS_Header const fitsHeader,
         if (imageRequest) {
             if (imageRequest > fitsHeader.naxis3)
                 pm_error("Only %u plane%s in this file.  "
-                         "You requested image %u", 
+                         "You requested image %u",
                          fitsHeader.naxis3, fitsHeader.naxis3 > 1 ? "s" : "",
                          imageRequest);
             else {
@@ -446,7 +519,7 @@ interpretPlanes(struct FITS_Header const fitsHeader,
         }
     }
     if (verbose) {
-        
+
         pm_message("FITS stream is %smultiplane", *multiplaneP ? "" : "not ");
         pm_message("We will take image %u (1 is first) of %u "
                    "in the FITS stream",
@@ -461,7 +534,7 @@ scanImageForMinMax(FILE *       const ifP,
                    unsigned int const images,
                    int          const cols,
                    int          const rows,
-                   valFmt       const valFmt,
+                   ValFmt       const valFmt,
                    double       const bscale,
                    double       const bzer,
                    unsigned int const imagenum,
@@ -476,7 +549,7 @@ scanImageForMinMax(FILE *       const ifP,
     unsigned int image;
     pm_filepos rasterPos;
     double fmaxval;
-    
+
     pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
 
     pm_message("Scanning file for scaling parameters");
@@ -554,7 +627,7 @@ computeMinMax(FILE *             const ifP,
     if (datamin == -DBL_MAX || datamax == DBL_MAX) {
         double scannedDatamin, scannedDatamax;
         scanImageForMinMax(ifP, images, cols, rows,
-                           valFmtFromBitpix(h.bitpix), h.bscale, h.bzer,
+                           h.valFmt, h.bscale, h.bzer,
                            imagenum, multiplane,
                            &scannedDatamin, &scannedDatamax);
 
@@ -571,12 +644,12 @@ computeMinMax(FILE *             const ifP,
 
 static xelval
 determineMaxval(struct CmdlineInfo const cmdline,
-                valFmt             const valFmt,
+                ValFmt             const valFmt,
                 double             const datamax,
                 double             const datamin) {
 
     xelval retval;
-                
+
     if (cmdline.omaxvalSpec)
         retval = cmdline.omaxval;
     else {
@@ -612,11 +685,11 @@ convertPgmRaster(FILE *                const ifP,
                  xelval                const maxval,
                  unsigned int          const desiredImage,
                  unsigned int          const imageCount,
-                 struct fitsRasterInfo const rasterInfo,
+                 struct FitsRasterInfo const rasterInfo,
                  double                const scale,
                  double                const datamin,
                  xel **                const xels) {
-        
+
     /* Note: the FITS specification does not give the association between
        file position and image position (i.e. is the first pixel in the
        file the top left, bottom left, etc.).  We use the common sense,
@@ -648,7 +721,7 @@ convertPgmRaster(FILE *                const ifP,
                 }
             }
         }
-    } 
+    }
 }
 
 
@@ -658,7 +731,7 @@ convertPpmRaster(FILE *                const ifP,
                  unsigned int          const cols,
                  unsigned int          const rows,
                  xelval                const maxval,
-                 struct fitsRasterInfo const rasterInfo,
+                 struct FitsRasterInfo const rasterInfo,
                  double                const scale,
                  double                const datamin,
                  xel **                const xels) {
@@ -708,7 +781,7 @@ convertRaster(FILE *                const ifP,
               bool                  const multiplane,
               unsigned int          const desiredImage,
               unsigned int          const imageCount,
-              struct fitsRasterInfo const rasterInfo,
+              struct FitsRasterInfo const rasterInfo,
               double                const scale,
               double                const datamin) {
 
@@ -743,7 +816,7 @@ main(int argc, const char * argv[]) {
     double scale;
     double datamin, datamax;
     struct FITS_Header fitsHeader;
-    struct fitsRasterInfo rasterInfo;
+    struct FitsRasterInfo rasterInfo;
 
     unsigned int imageCount;
     unsigned int desiredImage;
@@ -754,15 +827,15 @@ main(int argc, const char * argv[]) {
         /* This is a one-image multiplane stream; 'desiredImage'
            is undefined
         */
-  
+
     pm_proginit(&argc, argv);
-  
+
     parseCommandLine(argc, argv, &cmdline);
 
     ifP = pm_openr(cmdline.inputFileName);
 
     readFitsHeader(ifP, &fitsHeader);
-  
+
     if (!fitsHeader.simple)
         pm_error("FITS file is not in simple format, can't read");
 
@@ -774,7 +847,7 @@ main(int argc, const char * argv[]) {
 
     rasterInfo.bscale = fitsHeader.bscale;
     rasterInfo.bzer   = fitsHeader.bzer;
-    rasterInfo.valFmt = valFmtFromBitpix(fitsHeader.bitpix);
+    rasterInfo.valFmt = fitsHeader.valFmt;
 
     interpretPlanes(fitsHeader, cmdline.image, cmdline.verbose,
                     &imageCount, &multiplane, &desiredImage);
diff --git a/converter/other/pamtofits.c b/converter/other/pamtofits.c
index 92e29c7e..fe81d94d 100644
--- a/converter/other/pamtofits.c
+++ b/converter/other/pamtofits.c
@@ -45,12 +45,12 @@ struct cmdlineInfo {
 
 
 
-static void 
+static void
 parseCommandLine(int argc, char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*--------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -95,7 +95,7 @@ parseCommandLine(int argc, char ** argv,
         cmdlineP->inputFileName = "-";
     else {
         cmdlineP->inputFileName = argv[1];
-        
+
         if (argc-1 > 1)
             pm_error("Too many arguments (%u).  The only non-option argument "
                      "is the input file name.", argc-1);
@@ -127,7 +127,7 @@ padToMultipleOf36Cards(unsigned int const nCardsAlreadyWritten) {
 
     unsigned int const npadCard = 36 - (nCardsAlreadyWritten % 36);
     unsigned int i;
-    
+
     for (i = 0; i < npadCard; ++i)
         writeHeaderCard("");
 }
@@ -146,7 +146,7 @@ writeFitsHeader(int    const bitpix,
 
     char buffer[80+1];
     unsigned int cardsWritten;
-                
+
     cardsWritten = 0;  /* initial value */
 
     sprintf(buffer, "%-20.20s%10.10s", "SIMPLE  =", "T");
@@ -178,7 +178,7 @@ writeFitsHeader(int    const bitpix,
     sprintf(buffer, "%-18.18s%12.5E", "BSCALE  =", bscale);
     writeHeaderCard(buffer);
     ++cardsWritten;
-    
+
     sprintf(buffer, "%-18.18s%12.5E", "BZERO   =", fitsBzero);
     writeHeaderCard(buffer);
     ++cardsWritten;
@@ -263,7 +263,7 @@ main(int argc, char * argv[]) {
            the default case, that PNM sample value is also the FITS "physical"
            value, but user options can change that.
         */
-    
+
     pnm_init(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
@@ -287,7 +287,7 @@ main(int argc, char * argv[]) {
     assert(datamax > datamin);
 
     bscale = (datamax - datamin) / pam.maxval;
-    
+
     if (pam.maxval > 255) {
         bitpix = 16;
         /* Because 16 bit FITS samples are signed, we have to do a 2**15
@@ -315,3 +315,6 @@ main(int argc, char * argv[]) {
 
     return 0;
 }
+
+
+
diff --git a/converter/ppm/hpcdtoppm/pcdovtoppm b/converter/ppm/hpcdtoppm/pcdovtoppm
index b86048cc..1c6cb430 100755
--- a/converter/ppm/hpcdtoppm/pcdovtoppm
+++ b/converter/ppm/hpcdtoppm/pcdovtoppm
@@ -144,7 +144,7 @@ tempdir=$(mktemp -d "${TMPDIR:-/tmp}/pcdovtoppm.XXXXXXXX") ||
     { echo "Could not create temporary file. Exiting." 1>&2; exit 1; }
 trap 'rm -rf $tempdir' 0
 
-tmpfile=`tempfile -p pi -m 600`
+tmpfile=$(tempfile -p pi -m 600)
 
 # Convert the PCD overview file to many PPM images
 if [ -f $1 ] ; then
@@ -183,11 +183,12 @@ do
     ttext="$i:t"
 
     if [ "$back" = "-white" ] ; then
-        pbmtext $font "$ttext" | pnmcrop -quiet | pnmmargin -white 2| \
-        pnmcat $back -tb $tmpfile - > $imagefile
+        pbmtext $font "$ttext" | pnmcrop -quiet | pnmmargin -white 2 | \
+        pamcat $back -topbottom $tmpfile - > $imagefile
     else
         pbmtext $font "$ttext" | pnmcrop -quiet | pnmmargin -white 2 | \
-        pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
+          pnminvert | \
+          pamcat $back -topbottom $tmpfile - > $imagefile
     fi
 
     rm -f $tmpfile
@@ -199,9 +200,9 @@ do
         rowfile=${tempdir}/pi.${row}
         rm -f $rowfile
     if [ "$colors" = "n" ] ; then
-        pnmcat $back -lr -jbottom $imagefiles > $rowfile
+        pamcat $back -leftright -jbottom $imagefiles > $rowfile
     else
-        pnmcat $back -lr -jbottom $imagefiles | \
+        pamcat $back -leftright -jbottom $imagefiles | \
         ppmquant -quiet $colors > $rowfile
     fi
     rm -f $imagefiles
@@ -219,10 +220,10 @@ if [ -n $imagefiles ] ; then
     rowfile=${tempdir}/pi.${row}
     rm -f $rowfile
     if [ "$colors" = "n" ] ; then
-        pnmcat $back -lr -jbottom $imagefiles > $rowfile
+        pamcat $back -leftright -jbottom $imagefiles > $rowfile
     else
-        pnmcat $back -lr -jbottom $imagefiles | \
-        ppmquant -quiet $colors > $rowfile
+        pamcat $back -leftright -jbottom $imagefiles | \
+          ppmquant -quiet $colors > $rowfile
     fi
     rm -f $imagefiles
     rowfiles="$rowfiles $rowfile"
@@ -232,9 +233,11 @@ if [ $(echo $rowfiles|wc -w) -eq 1 ] ; then
     ppmtoppm $plainopt < $rowfiles
 else
     if [ "$colors" = "n" ] ; then
-        pnmcat $back -tb $plainopt $rowfiles
+        pamcat $back -topbottom $rowfiles | \
+          ppmtoppm $plainopt
     else
-        pnmcat $back -tb $rowfiles | ppmquant $plainopt -quiet $colors
+        pamcat $back -tb $rowfiles | \
+          ppmquant $plainopt -quiet $colors
     fi
 fi
 
diff --git a/converter/ppm/ppmtompeg/specifics.c b/converter/ppm/ppmtompeg/specifics.c
index aa3d7b18..bd74e805 100644
--- a/converter/ppm/ppmtompeg/specifics.c
+++ b/converter/ppm/ppmtompeg/specifics.c
@@ -41,6 +41,7 @@
 
 #include "netpbm/mallocvar.h"
 #include "netpbm/nstring.h"
+#include "netpbm/pm_system.h"
 
 #include "all.h"
 #include "mtypes.h"
@@ -152,38 +153,37 @@ static char version = -1;
 void
 Specifics_Init() {
 
-    FILE *specificsFP;
+    /* 'specificsFile' is a global variable whose value is the name of the
+       specifics file, given by the parameter file.
+    */
+
+    FILE *       specificsFP;
+    const char * preprocessedFileNm;
+
+    pm_message("Specifics file: %s", specificsFile);
+
+    pm_asprintf(&preprocessedFileNm, "%s.cpp", specificsFile);
+
+    pm_system_lp("rm", NULL, NULL, NULL, NULL, "-f", preprocessedFileNm);
 
     {
         const char * command;
-        pm_asprintf(&command, "rm -f %s.cpp", specificsFile);
-        system(command);
-        pm_strfree(command);
-    }
-    {
-        const char * command;
-        pm_asprintf(&command, "cpp -P %s %s %s.cpp",
-                    specificsDefines, specificsFile, specificsFile);
-        system(command);
+        pm_asprintf(&command, "cpp -P %s '%s' -o '%s'",
+                    specificsDefines, specificsFile, preprocessedFileNm);
+        pm_system(NULL, NULL, NULL, NULL, command);
         pm_strfree(command);
     }
-    strcat(specificsFile, ".cpp");
-    if ((specificsFP = fopen(specificsFile, "r")) == NULL) {
-        fprintf(stderr, "Error with specifics file, cannot open %s\n",
-                specificsFile);
-        exit(1);
-    }
-    printf("Specifics file: %s\n", specificsFile);
+
+    specificsFP = pm_openr(preprocessedFileNm);
+
+    pm_system_lp("rm", NULL, NULL, NULL, NULL, "-f", preprocessedFileNm);
 
     Parse_Specifics_File(specificsFP);
-    {
-        const char * command;
-        pm_asprintf(&command, "rm -f %s.cpp", specificsFile);
-        system(command);
-        pm_strfree(command);
-    }
-}
 
+    pm_close(specificsFP);
+
+    pm_strfree(preprocessedFileNm);
+}
 
 
 
diff --git a/doc/HISTORY b/doc/HISTORY
index 45f88a3a..54c9150d 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,17 +4,22 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-22.09.24 BJH  Release 10.99.03
+22.09.28 BJH  Release 11.00.00
 
-              pnmmargin: fix shell injection vulnerability.  Always broken
-              (Program was added in primordial Netpbm in 1990).
+              (No significance to new major number; just ran out of 2-digit
+              minor numbers).
 
-22.08.03 BJH  Release 10.99.02
+              Add pamcat.
 
-              pnmindex: fix shell injection vulnerabilities.  Broken since
-              Netpbm 10.28 (June 2005).
+              pamtable: add -tuple.
 
-22.07.17 BJH  Release 10.99.01
+              pamtable: add -hex.
+
+              pbmtextps: improve error messages.
+
+              pnmtofits: fix arbitrary behavior when FITS input lacks
+              required fields in header.  Always broken.  Pnmtofits'
+              predecessor was in primordial Netpbm (1989).
 
               ppmtoicr: Fix bug: all options cause bogus command line parsing
               errors.  Always broken.  Ppmtoicr was new in 1991.
@@ -24,6 +29,17 @@ CHANGE HISTORY
               ppmtoicr: make -rle option issue an error message saying it no
               longer exists (it did, sort of, before 2015).
 
+              ppmforge: Fix arbitrary output with really large -power.
+              
+              pnmindex: fix shell injection vulnerabilities.  Broken since
+              Netpbm 10.28 (June 2005).
+
+              pnmmargin: fix shell injection vulnerability.  Always broken
+              (Program was added in primordial Netpbm in 1990).
+
+              build: Create backward compatibility symbolic link for
+              pnminterp-gen, missing for over 20 years.
+
 22.06.24 BJH  Release 10.99.00
 
               Add pamrestack.
diff --git a/editor/Makefile b/editor/Makefile
index 8798cf6e..0027832c 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -16,7 +16,8 @@ SUBDIRS = pamflip specialty
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \
+PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten \
+	       pamcat pamcomp pamcut \
 	       pamdice pamditherbw pamedge pamenlarge \
 	       pamfunc pamhomography pamhue pamlevels \
 	       pammasksharpen pammixmulti \
@@ -26,7 +27,7 @@ PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \
 	       pbmclean pbmmask pbmpscale pbmreduce \
 	       pgmdeshadow pgmenhance \
 	       pgmmedian \
-	       pnmalias pnmcat pnmconvol pnmcrop \
+	       pnmalias pnmconvol pnmcrop \
 	       pnmgamma \
 	       pnmhisteq pnminvert pnmmontage \
 	       pnmnlfilt pnmnorm pnmpad pnmpaste \
@@ -63,10 +64,15 @@ install.bin install.merge: install.bin.local
 .PHONY: install.bin.local
 install.bin.local: $(PKGDIR)/bin
 # Remember that $(SYMLINK) might just be a copy command.
-# backward compatibility: program used to be pnminterp
+
+# In December 2001, pamstretch replaced pnminterp and pamstretch-getn
+# replaced pnminterp-gen
 	cd $(PKGDIR)/bin ; \
 	rm -f pnminterp$(EXE); \
 	$(SYMLINK) pamstretch$(EXE) pnminterp$(EXE)
+	cd $(PKGDIR)/bin ; \
+	rm -f pnminterp-gen$(EXE); \
+	$(SYMLINK) pamstretch-gen$(EXE) pnminterp-gen$(EXE)
 # In March 2002, pnmnorm replaced ppmnorm and pgmnorm
 	cd $(PKGDIR)/bin ; \
 	rm -f ppmnorm$(EXE) ; \
@@ -100,6 +106,10 @@ install.bin.local: $(PKGDIR)/bin
 	cd $(PKGDIR)/bin ; \
 	rm -f pnmcomp$(EXE) ; \
 	$(SYMLINK) pamcomp$(EXE) pnmcomp$(EXE)
+# In August 2022, pamcat replaced pnmcat
+	cd $(PKGDIR)/bin ; \
+	rm -f pnmcat$(EXE) ; \
+	$(SYMLINK) pamcat$(EXE) pnmcat$(EXE)
 
 mergecomptrylist:
 	cat /dev/null >$@
@@ -111,4 +121,5 @@ mergecomptrylist:
 	echo "TRY(\"pnmcut\",     main_pamcut);"     >>$@
 	echo "TRY(\"pnmscale\",   main_pamscale);"   >>$@
 	echo "TRY(\"pnmcomp\",    main_pamcomp);"    >>$@
+	echo "TRY(\"pnmcat\",     main_pamcomp);"    >>$@
 
diff --git a/editor/pamcat.c b/editor/pamcat.c
new file mode 100644
index 00000000..c7f0482d
--- /dev/null
+++ b/editor/pamcat.c
@@ -0,0 +1,1303 @@
+/*=============================================================================
+                                   pamcat
+===============================================================================
+
+  Concatenate images.
+
+  By Bryan Henderson and Akira Urushibata.  Contributed to the public domain
+  by its authors.
+
+=============================================================================*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "bitarith.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pbm.h"
+
+#define LEFTBITS pm_byteLeftBits
+#define RIGHTBITS pm_byteRightBits
+
+enum PadColorMethod {PAD_BLACK, PAD_WHITE, PAD_AUTO};
+  /* The method of determining the color of padding when images are not the
+     same height or width.  Always white (maxval samples) always black (zero
+     samples) or determined from what looks like background for the image in
+     question.
+  */
+
+
+enum Orientation {TOPBOTTOM, LEFTRIGHT};
+  /* Direction of concatenation */
+
+enum Justification {JUST_CENTER, JUST_MIN, JUST_MAX};
+  /* Justification of images in concatenation */
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char **       inputFileName;
+    unsigned int        fileCt;
+    enum PadColorMethod padColorMethod;
+    enum Orientation    orientation;
+    enum Justification  justification;
+    unsigned int        verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3() on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int leftright, topbottom;
+    unsigned int black, white;
+    unsigned int jtop, jbottom, jleft, jright, jcenter;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "leftright",   OPT_FLAG,   NULL, &leftright,         0);
+    OPTENT3(0, "lr",          OPT_FLAG,   NULL, &leftright,         0);
+    OPTENT3(0, "topbottom",   OPT_FLAG,   NULL, &topbottom,         0);
+    OPTENT3(0, "tb",          OPT_FLAG,   NULL, &topbottom,         0);
+    OPTENT3(0, "black",       OPT_FLAG,   NULL, &black,             0);
+    OPTENT3(0, "white",       OPT_FLAG,   NULL, &white,             0);
+    OPTENT3(0, "jtop",        OPT_FLAG,   NULL, &jtop,              0);
+    OPTENT3(0, "jbottom",     OPT_FLAG,   NULL, &jbottom,           0);
+    OPTENT3(0, "jleft",       OPT_FLAG,   NULL, &jleft,             0);
+    OPTENT3(0, "jright",      OPT_FLAG,   NULL, &jright,            0);
+    OPTENT3(0, "jcenter",     OPT_FLAG,   NULL, &jcenter,           0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    free(option_def);
+
+    if (leftright + topbottom > 1)
+        pm_error("You may specify only one of -topbottom (-tb) and "
+                 "-leftright (-lr)");
+    else if (leftright)
+        cmdlineP->orientation = LEFTRIGHT;
+    else if (topbottom)
+        cmdlineP->orientation = TOPBOTTOM;
+    else
+        pm_error("You must specify either -leftright or -topbottom");
+
+    if (black + white > 1)
+        pm_error("You may specify only one of -black and -white");
+    else if (black)
+        cmdlineP->padColorMethod = PAD_BLACK;
+    else if (white)
+        cmdlineP->padColorMethod = PAD_WHITE;
+    else
+        cmdlineP->padColorMethod = PAD_AUTO;
+
+    if (jtop + jbottom + jleft + jright + jcenter > 1)
+        pm_error("You may specify only one of -jtop, -jbottom, "
+                 "-jleft, and -jright");
+    else {
+        switch (cmdlineP->orientation) {
+        case LEFTRIGHT:
+            if (jleft)
+                pm_error("-jleft is invalid with -leftright");
+            if (jright)
+                pm_error("-jright is invalid with -leftright");
+            if (jtop)
+                cmdlineP->justification = JUST_MIN;
+            else if (jbottom)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        case TOPBOTTOM:
+            if (jtop)
+                pm_error("-jtop is invalid with -topbottom");
+            if (jbottom)
+                pm_error("-jbottom is invalid with -topbottom");
+            if (jleft)
+                cmdlineP->justification = JUST_MIN;
+            else if (jright)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        }
+    }
+
+    if (argc-1 < 1) {
+        MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, 1);
+        cmdlineP->inputFileName[0] = "-";
+        cmdlineP->fileCt = 1;
+    } else {
+        unsigned int i;
+        unsigned int stdinCt;
+            /* Number of input files user specified as Standard Input */
+
+        MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, argc-1);
+
+        for (i = 0, stdinCt = 0; i < argc-1; ++i) {
+            cmdlineP->inputFileName[i] = argv[1+i];
+            if (streq(argv[1+i], "-"))
+                ++stdinCt;
+        }
+        cmdlineP->fileCt = argc-1;
+        if (stdinCt > 1)
+            pm_error("At most one input image can come from Standard Input.  "
+                     "You specified %u", stdinCt);
+    }
+}
+
+
+
+static const char *
+tupletypeX(bool         const allVisual,
+           unsigned int const colorDepth,
+           sample       const maxMaxval,
+           bool         const haveOpacity) {
+
+    const char * retval;
+
+    if (allVisual) {
+        switch (colorDepth) {
+        case 1:
+            if (maxMaxval == 1)
+                retval = haveOpacity ? "BLACKANDWHITE_ALPHA" : "BLACKANDWHITE";
+            else
+                retval = haveOpacity ? "GRAYSCALE_ALPHA"     : "GRAYSCALE";
+            break;
+        case 3:
+            retval = haveOpacity ? "RGB_ALPHA"           : "RGB";
+            break;
+        default:
+            assert(false);
+        }
+    } else
+        retval = "";
+
+    return retval;
+}
+
+
+
+typedef struct {
+    /* This describes a transformation from one tuple type to another,
+       e.g. from BLACKANDWHITE to GRAY_ALPHA.
+
+       For transformations bewteen the defined ones for visual images,
+       only the "up" transformations are covered.
+    */
+    bool mustPromoteColor;
+        /* Plane 0, which is the black/white or grayscale plane and also
+           the red plane must be copied as the red, green, and blue planes
+           (0, 1, and 2).
+        */
+    bool mustPromoteOpacity;
+        /* Plane 1, which is the opacity plane for black and white or
+           grayscale tuples, must be copied as the RGB opacity plane (3).
+        */
+    bool mustCreateOpacity;
+        /* The opacity plane value must be set to opaque */
+
+    bool mustPadZero;
+        /* Where the target tuple type is deeper than the source tuple
+           type, all higher numbered planes must be cleared to zero.
+
+           This is mutually exclusive with the rest of the musts.
+        */
+
+} TtTransform;
+
+
+
+static TtTransform
+ttXformForImg(const struct pam * const inpamP,
+              const struct pam * const outpamP) {
+/*----------------------------------------------------------------------------
+  The transform required to transform tuples of the kind described by *inpamP
+  to tuples of the kind described by *outpamP (e.g. from grayscale to RGB,
+  which involves replicating one plane into three).
+
+  We assume *outpamP tuples are of a type that is at least as expressive as
+  *inpamP tuples.  So e.g. outpamP->tuple_type cannot be "GRAYSCALE" if
+  inpamP->tuple_type is "RGB".
+-----------------------------------------------------------------------------*/
+    TtTransform retval;
+
+    if (inpamP->visual && outpamP->visual) {
+        retval.mustPromoteColor   =
+            (outpamP->color_depth > inpamP->color_depth);
+        retval.mustPromoteOpacity =
+            (outpamP->color_depth > inpamP->color_depth &&
+             (outpamP->have_opacity && inpamP->have_opacity));
+        retval.mustCreateOpacity  =
+            (outpamP->have_opacity && !inpamP->have_opacity);
+        retval.mustPadZero = false;
+    } else {
+        retval.mustPromoteColor   = false;
+        retval.mustPromoteOpacity = false;
+        retval.mustCreateOpacity  = false;
+        retval.mustPadZero        = true;
+    }
+    return retval;
+}
+
+
+
+static void
+reportPlans(unsigned int       const fileCt,
+            const struct pam * const outpamP) {
+
+    pm_message("Concatenating %u input images", fileCt);
+
+    pm_message("Output width, height, depth: %u x %u x %u",
+               outpamP->width, outpamP->height, outpamP->depth);
+
+    if (outpamP->format == RPBM_FORMAT)
+        pm_message("Using PBM fast path and producing raw PBM output");
+    else if (outpamP->format == PBM_FORMAT)
+        pm_message("Output format: Plain PBM");
+    else {
+        pm_message("Output maxval (max of all inputs): %lu", outpamP->maxval);
+
+        switch (outpamP->format) {
+        case PGM_FORMAT:
+            pm_message("Output format: Plain PGM");
+            break;
+        case RPGM_FORMAT:
+            pm_message("Output format: Raw PGM");
+            break;
+        case PPM_FORMAT:
+            pm_message("Output format: Plain PPM");
+            break;
+        case RPPM_FORMAT:
+            pm_message("Output format: Raw PPM");
+            break;
+        case PAM_FORMAT:
+            pm_message("Output format: PAM");
+
+            if (strlen(outpamP->tuple_type) > 0)
+                pm_message("Output tuple type: '%s'", outpamP->tuple_type);
+            else
+                pm_message("Output tuple type is null string because "
+                           "input images have various non-visual tuple types");
+            break;
+        }
+    }
+}
+
+
+
+static void
+computeOutputParms(unsigned int       const fileCt,
+                   enum Orientation   const orientation,
+                   const struct pam * const inpam,  /* array */
+                   bool               const verbose,
+                   struct pam *       const outpamP) {
+
+    double newCols, newRows;
+    unsigned int maxDepth;
+    sample maxMaxval;
+    int newFormat;
+    const char * firstTupletype;
+    bool allSameTt;
+    bool allVisual;
+    unsigned int maxColorDepth;
+    bool haveOpacity;
+    unsigned int fileSeq;
+
+    for (fileSeq = 0, newCols = 0, newRows = 0, maxDepth = 0, maxMaxval = 0,
+             newFormat = 0,
+             allVisual = true, maxColorDepth = 0, haveOpacity = false,
+             firstTupletype = NULL, allSameTt = true;
+         fileSeq < fileCt;
+         ++fileSeq) {
+
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        switch (orientation) {
+        case LEFTRIGHT:
+            newCols += inpamP->width;
+            newRows = MAX(newRows, inpamP->height);
+            break;
+        case TOPBOTTOM:
+            newRows += inpamP->height;
+            newCols = MAX(newCols, inpamP->width);
+            break;
+        }
+
+        if (!firstTupletype)
+            firstTupletype = inpamP->tuple_type;
+        if (inpamP->tuple_type != firstTupletype)
+            allSameTt = false;
+
+        if (inpamP->visual) {
+            maxColorDepth = MAX(maxColorDepth, inpamP->color_depth);
+
+            if (inpamP->have_opacity)
+                haveOpacity = true;
+        } else
+            allVisual = false;
+
+        maxDepth      = MAX(maxDepth,      inpamP->depth);
+        maxMaxval     = MAX(maxMaxval,     inpamP->maxval);
+
+        if (PAM_FORMAT_TYPE(inpamP->format) > PAM_FORMAT_TYPE(newFormat))
+            newFormat = inpamP->format;
+    }
+    assert(newCols       > 0);
+    assert(newRows       > 0);
+    assert(maxMaxval     > 0);
+    assert(newFormat     > 0);
+
+    if (newCols > INT_MAX)
+       pm_error("Output width too large: %.0f.", newCols);
+    if (newRows > INT_MAX)
+       pm_error("Output height too large: %.0f.", newRows);
+
+    outpamP->size = sizeof(*outpamP);
+    outpamP->len  = PAM_STRUCT_SIZE(tuple_type);
+
+    /* Note that while 'double' is not in general a precise numerical type,
+       in the case of a sum of integers which is less than INT_MAX, it
+       is exact, because double's precision is greater than int's.
+    */
+    outpamP->height           = (unsigned int)newRows;
+    outpamP->width            = (unsigned int)newCols;
+    if (allVisual)
+        outpamP->depth        = MAX(maxDepth,
+                                    maxColorDepth + (haveOpacity ? 1 : 0));
+    else
+        outpamP->depth        = maxDepth;
+    outpamP->allocation_depth = 0;  /* This means same as depth */
+    outpamP->maxval           = maxMaxval;
+    outpamP->format           = newFormat;
+    if (allSameTt)
+        STRSCPY(outpamP->tuple_type, firstTupletype);
+    else
+        STRSCPY(outpamP->tuple_type,
+                tupletypeX(allVisual, maxColorDepth, maxMaxval, haveOpacity));
+    outpamP->comment_p        = NULL;
+    outpamP->plainformat      = false;
+
+    if (verbose)
+        reportPlans(fileCt, outpamP);
+}
+
+
+
+static void
+copyBitrow(const unsigned char * const source,
+           unsigned char *       const destBitrow,
+           unsigned int          const cols,
+           unsigned int          const offset) {
+/*----------------------------------------------------------------------------
+  Copy from source to destBitrow, without shifting.  Preserve
+  surrounding image data.
+-----------------------------------------------------------------------------*/
+    unsigned char * const dest = & destBitrow[ offset/8 ];
+        /* Copy destination, with leading full bytes ignored. */
+    unsigned int const rs = offset % 8;
+        /* The "little offset", as measured from start of dest.  Source
+           is already shifted by this value.
+        */
+    unsigned int const trs = (cols + rs) % 8;
+        /* The number of partial bits in the final char. */
+    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
+        /* # bytes to process, including partial ones on both ends. */
+    unsigned int const last = colByteCnt - 1;
+
+    unsigned char const origHead = dest[0];
+    unsigned char const origEnd  = dest[last];
+
+    unsigned int i;
+
+    assert(colByteCnt >= 1);
+
+    for (i = 0; i < colByteCnt; ++i)
+        dest[i] = source[i];
+
+    if (rs > 0)
+        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
+
+    if (trs > 0)
+        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
+}
+
+
+
+static void
+padFillBitrow(unsigned char * const destBitrow,
+              unsigned char   const padColor,
+              unsigned int    const cols,
+              unsigned int    const offset) {
+/*----------------------------------------------------------------------------
+   Fill destBitrow, starting at offset, with padColor.  padColor is a
+   byte -- 0x00 or 0xff -- not a single bit.
+-----------------------------------------------------------------------------*/
+    unsigned char * const dest = &destBitrow[offset/8];
+    unsigned int const rs = offset % 8;
+    unsigned int const trs = (cols + rs) % 8;
+    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
+    unsigned int const last = colByteCnt - 1;
+
+    unsigned char const origHead = dest[0];
+    unsigned char const origEnd  = dest[last];
+
+    unsigned int i;
+
+    assert(colByteCnt > 0);
+
+    for (i = 0; i < colByteCnt; ++i)
+        dest[i] = padColor;
+
+    if (rs > 0)
+        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
+
+    if (trs > 0)
+        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
+}
+
+
+
+/* concatenateLeftRightPbm() and concatenateLeftRightGen()
+   employ almost identical algorithms.
+   The difference is in the data types and functions.
+
+   Same for concatenateTopBottomPbm() and concatenateTopBottomGen().
+*/
+
+
+typedef struct {
+    /* Information about one image */
+    unsigned char * proberow;
+        /* Top row of image, when background color is
+           auto-determined.
+        */
+    unsigned int offset;
+        /* start position of image, in bits, counting from left
+           edge
+        */
+    unsigned char background;
+        /* Background color.  0x00 means white; 0xff means black */
+    unsigned int padtop;
+        /* Top padding amount */
+} LrImgCtlPbm;
+
+
+
+static void
+createLrImgCtlPbm(const struct pam *  const inpam,  /* array */
+                  unsigned int        const fileCt,
+                  unsigned int        const outHeight,
+                  enum Justification  const justification,
+                  enum PadColorMethod const padColorMethod,
+                  LrImgCtlPbm **      const imgCtlP) {
+/*----------------------------------------------------------------------------
+   Read the first row of each image in inpam[] and return that and additional
+   information about images as *imgCtlP.
+-----------------------------------------------------------------------------*/
+    LrImgCtlPbm * imgCtl;  /* array, size 'fileCt' */
+    unsigned int fileSeq;
+
+    MALLOCARRAY_NOFAIL(imgCtl, fileCt);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtlPbm *      const imgCtlP = &imgCtl[fileSeq];
+        const struct pam * const inpamP  = &inpam[fileSeq];
+
+        switch (justification) {
+        case JUST_MIN:
+            imgCtlP->padtop = 0;
+            break;
+        case JUST_MAX:
+            imgCtlP->padtop = outHeight - inpam[fileSeq].height;
+            break;
+        case JUST_CENTER:
+            imgCtlP->padtop = (outHeight - inpamP->height) / 2;
+            break;
+        }
+
+        imgCtlP->offset =
+            (fileSeq == 0) ?
+                0 : imgCtl[fileSeq-1].offset + inpam[fileSeq-1].width;
+
+        if (inpamP->height == outHeight)  /* no padding */
+            imgCtlP->proberow = NULL;
+        else {                   /* determine pad color for image i */
+            switch (padColorMethod) {
+            case PAD_AUTO: {
+                bit bgBit;
+                imgCtlP->proberow =
+                    pbm_allocrow_packed((unsigned int)inpamP->width + 7);
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, imgCtlP->proberow,
+                    inpamP->width, inpamP->format, imgCtlP->offset % 8);
+
+                bgBit = pbm_backgroundbitrow(
+                    imgCtlP->proberow, inpamP->width,
+                    imgCtlP->offset % 8);
+
+                imgCtlP->background = bgBit == PBM_BLACK ? 0xff : 0x00;
+            } break;
+            case PAD_BLACK:
+                imgCtlP->proberow   = NULL;
+                imgCtlP->background = 0xff;
+                break;
+            case PAD_WHITE:
+                imgCtlP->proberow   = NULL;
+                imgCtlP->background = 0x00;
+                break;
+            }
+        }
+    }
+    *imgCtlP = imgCtl;
+}
+
+
+
+static void
+destroyPbmImgCtl(LrImgCtlPbm * const imgCtl,  /* array */
+                 unsigned int  const fileCt) {
+
+    unsigned int i;
+
+    for (i = 0; i < fileCt; ++i) {
+        if (imgCtl[i].proberow)
+            free(imgCtl[i].proberow);
+    }
+    free(imgCtl);
+}
+
+
+
+static void
+concatenateLeftRightPbm(struct pam *        const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    unsigned char * const outrow = pbm_allocrow_packed(outpamP->width);
+        /* We use just one outrow.  All padding and image data (with the
+           exception of following imgCtl.proberow) goes directly into this
+           packed PBM row.
+        */
+
+    LrImgCtlPbm * imgCtl;
+        /* malloc'ed array, one element per image.  Shadows inpam[] */
+    unsigned int row;
+
+    createLrImgCtlPbm(inpam, fileCt, outpamP->height,
+                      justification, padColorMethod,
+                      &imgCtl);
+
+    outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int fileSeq;
+
+        for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+            const LrImgCtlPbm * const imgCtlP = &imgCtl[fileSeq];
+            const struct pam *  const inpamP  = &inpam[fileSeq];
+
+            if ((row == 0 && imgCtlP->padtop > 0) ||
+                row == imgCtlP->padtop + inpamP->height) {
+
+                /* This row begins a run of padding, either above or below
+                   file 'i', so set 'outrow' to padding.
+                */
+                padFillBitrow(outrow, imgCtlP->background, inpamP->width,
+                              imgCtlP->offset);
+            }
+
+            if (row == imgCtlP->padtop && imgCtlP->proberow != NULL) {
+                /* Top row has been read to proberow[] to determine
+                   background.  Copy it to outrow[].
+                */
+                copyBitrow(imgCtlP->proberow, outrow,
+                           inpamP->width, imgCtlP->offset);
+            } else if (row >= imgCtlP->padtop &&
+                       row < imgCtlP->padtop + inpamP->height) {
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, outrow, inpamP->width, inpamP->format,
+                    imgCtlP->offset);
+            } else {
+                /* It's a row of padding, so outrow[] is already set
+                   appropriately.
+                */
+            }
+        }
+        pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+    }
+
+    destroyPbmImgCtl(imgCtl, fileCt);
+
+    pbm_freerow_packed(outrow);
+}
+
+
+
+static void
+concatenateTopBottomPbm(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    unsigned char * const outrow = pbm_allocrow_packed(outpamP->width);
+        /* Like the left-right PBM case, all padding and image data
+           goes directly into outrow.  There is no proberow.
+        */
+    unsigned char background, backgroundPrev;
+        /* 0x00 means white; 0xff means black */
+    unsigned int  padleft;
+    bool          backChange;
+        /* Background color is different from that of the previous
+           input image.
+        */
+
+    unsigned int fileSeq;
+    unsigned int row, startRow;
+
+    outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+
+    switch (padColorMethod){
+    case PAD_AUTO:   /* do nothing */    break;
+    case PAD_BLACK:  background = 0xff;  break;
+    case PAD_WHITE:  background = 0x00;  break;
+    }
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        if (inpamP->width == outpamP->width) {
+            /* No padding */
+            startRow   = 0;
+            backChange = FALSE;
+            padleft    = 0;
+            outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+        } else {
+            /* Determine amount of padding and color */
+            switch (justification) {
+            case JUST_MIN:
+                padleft = 0;
+                break;
+            case JUST_MAX:
+                padleft = outpamP->width - inpamP->width;
+                break;
+            case JUST_CENTER:
+                padleft = (outpamP->width - inpamP->width) / 2;
+                break;
+            }
+
+            switch (padColorMethod) {
+            case PAD_AUTO: {
+                bit bgBit;
+
+                startRow = 1;
+
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, outrow, inpamP->width, inpamP->format,
+                    padleft);
+
+                bgBit = pbm_backgroundbitrow(outrow, inpamP->width, padleft);
+                background = bgBit == PBM_BLACK ? 0xff : 0x00;
+
+                backChange = (fileSeq == 0 || background != backgroundPrev);
+            } break;
+            case PAD_WHITE:
+            case PAD_BLACK:
+                startRow = 0;
+                backChange = (fileSeq == 0);
+                break;
+            }
+
+            if (backChange ||
+                (fileSeq > 0 && inpam[fileSeq-1].width > inpamP->width)) {
+                unsigned int const padright =
+                    outpamP->width - padleft - inpamP->width;
+
+                if (padleft > 0)
+                    padFillBitrow(outrow, background, padleft, 0);
+
+                if (padright > 0)
+                    padFillBitrow(outrow, background, padright,
+                                  padleft + inpamP->width);
+
+            }
+        }
+
+        if (startRow == 1)
+            /* Top row already read for auto background color
+               determination.  Write it out.
+            */
+            pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+
+        for (row = startRow; row < inpamP->height; ++row) {
+            pbm_readpbmrow_bitoffset(inpamP->file, outrow, inpamP->width,
+                                     inpamP->format, padleft);
+            pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+        }
+
+        backgroundPrev = background;
+    }
+    pbm_freerow_packed(outrow);
+}
+
+
+
+static void
+padPlanesRow(const struct pam *  const inpamP,
+             tuple *             const outrow,
+             const struct pam *  const outpamP) {
+/*----------------------------------------------------------------------------
+  Rearrange the planes of *outrow as needed to transform them into tuples
+  as described by *outpamP from tuples as described by *inpamP.
+-----------------------------------------------------------------------------*/
+    TtTransform const ttTransform = ttXformForImg(inpamP, outpamP);
+
+    assert(inpamP->allocation_depth >= outpamP->depth);
+
+    if (ttTransform.mustPromoteOpacity) {
+        unsigned int col;
+
+        assert(outpamP->depth >= PAM_TRN_PLANE);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            outrow[col][outpamP->opacity_plane] =
+                outrow[col][inpamP->opacity_plane];
+        }
+    }
+    if (ttTransform.mustPromoteColor) {
+        unsigned int col;
+
+        assert(outpamP->depth >= PAM_GRN_PLANE);
+        assert(outpamP->depth >= PAM_BLU_PLANE);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            assert(PAM_RED_PLANE == 0);
+            outrow[col][PAM_GRN_PLANE] = outrow[col][0];
+            outrow[col][PAM_BLU_PLANE] = outrow[col][0];
+        }
+    }
+
+    if (ttTransform.mustCreateOpacity) {
+        unsigned int col;
+
+        for (col = 0; col < inpamP->width; ++col)
+            outrow[col][outpamP->opacity_plane] = outpamP->maxval;
+    }
+
+    if (ttTransform.mustPadZero) {
+        unsigned int plane;
+
+        for (plane = inpamP->depth; plane < outpamP->depth; ++plane) {
+            unsigned int col;
+
+            for (col = 0; col < inpamP->width; ++col)
+                outrow[col][plane] = 0;
+        }
+    }
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   Parameters and state for placing a row of a particular input image in
+   the output in a left-right concatenation.
+-----------------------------------------------------------------------------*/
+    tuple *      cachedRow;
+        /* Contents of the current row of the image, with depth and maxval
+           adjusted for output, in malloc'ed space belonging to this object.
+           Input file is positioned past this row.  Null if data not present
+           and input file is positioned to the current row.
+        */
+    tuple *      out;
+        /* Point in output row buffer where the row from this image goes */
+    tuple        background;
+    unsigned int padtop;
+        /* Number of rows of padding that go above this image in the output */
+} LrImgCtl;
+
+
+
+static void
+createLrImgCtlArray(const struct pam *  const inpam,  /* array */
+                    unsigned int        const fileCt,
+                    tuple *             const newTuplerow,
+                    const struct pam *  const outpamP,
+                    enum Justification  const justification,
+                    enum PadColorMethod const padColorMethod,
+                    LrImgCtl **         const imgCtlP) {
+
+    LrImgCtl * imgCtl;  /* array */
+    unsigned int fileSeq;
+
+    MALLOCARRAY_NOFAIL(imgCtl, fileCt);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtl *         const thisEntryP = &imgCtl[fileSeq];
+        const struct pam * const inpamP     = &inpam[fileSeq];
+
+        switch (justification) {  /* Determine top padding */
+            case JUST_MIN:
+                thisEntryP->padtop = 0;
+                break;
+            case JUST_MAX:
+                thisEntryP->padtop = outpamP->height - inpamP->height;
+                break;
+            case JUST_CENTER:
+                thisEntryP->padtop = (outpamP->height - inpamP->height) / 2;
+                break;
+        }
+
+        thisEntryP->out =
+            (fileSeq == 0 ?
+             &newTuplerow[0] : imgCtl[fileSeq-1].out + inpam[fileSeq-1].width);
+
+        if (inpamP->height == outpamP->height) { /* no vertical padding */
+            thisEntryP->cachedRow  = NULL;
+            pnm_createBlackTuple(outpamP, &thisEntryP->background);
+                /* Meaningless because no padding */
+        } else {
+            /* Determine pad color */
+            switch (padColorMethod){
+            case PAD_AUTO:
+                thisEntryP->cachedRow = pnm_allocpamrow(inpamP);
+                pnm_readpamrow(inpamP, thisEntryP->cachedRow);
+                pnm_scaletuplerow(inpamP, thisEntryP->cachedRow,
+                                  thisEntryP->cachedRow, outpamP->maxval);
+                padPlanesRow(inpamP, thisEntryP->cachedRow, outpamP);
+                {
+                    struct pam cachedRowPam;
+                    cachedRowPam = *outpamP;
+                    cachedRowPam.width = inpamP->width;
+                    thisEntryP->background = pnm_backgroundtuplerow(
+                        &cachedRowPam, thisEntryP->cachedRow);
+                }
+                break;
+            case PAD_BLACK:
+                thisEntryP->cachedRow = NULL;
+                pnm_createBlackTuple(outpamP, &thisEntryP->background);
+                break;
+            case PAD_WHITE:
+                thisEntryP->cachedRow = NULL;
+                pnm_createWhiteTuple(outpamP, &thisEntryP->background);
+                break;
+            }
+        }
+        if (outpamP->visual) {
+            /* Any opacity sample in background color tuple is meaningless at
+               this point; make it opaque.
+            */
+            if (outpamP->have_opacity) {
+                thisEntryP->background[outpamP->opacity_plane] =
+                    outpamP->maxval;
+            }
+        }
+
+    }
+    *imgCtlP = imgCtl;
+}
+
+
+
+static void
+destroyLrImgCtlArray(LrImgCtl *   const imgCtl,  /* array */
+                     unsigned int const fileCt) {
+
+    unsigned int fileSeq;
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtl * const thisEntryP = &imgCtl[fileSeq];
+
+        pnm_freepamtuple(thisEntryP->background);
+        pnm_freepamrow(thisEntryP->cachedRow);
+    }
+
+    free(imgCtl);
+}
+
+
+
+static void
+concatenateLeftRightGen(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    tuple * const outrow = pnm_allocpamrow(outpamP);
+
+    LrImgCtl *   imgCtl;
+    unsigned int row;
+
+    createLrImgCtlArray(inpam, fileCt, outrow, outpamP,
+                        justification, padColorMethod,
+                        &imgCtl);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int fileSeq;
+
+        for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+            LrImgCtl *   const thisEntryP   = &imgCtl[fileSeq];
+            const struct pam * const inpamP = &inpam[fileSeq];
+
+            if ((row == 0 && thisEntryP->padtop > 0) ||
+                row == thisEntryP->padtop + inpamP->height) {
+                /* This row begins a run of padding, either above or below
+                   image 'fileSeq', so set its part of outrow[] to padding.
+                */
+                unsigned int col;
+                for (col = 0; col < inpamP->width; ++col) {
+                    pnm_assigntuple(outpamP, thisEntryP->out[col],
+                                    thisEntryP->background);
+                }
+            }
+            if (row == thisEntryP->padtop && thisEntryP->cachedRow) {
+                /* We're at the top row of image 'fileSeq', and that row
+                   has already been read to cachedRow[] to determine
+                   background.  Copy it to image fileseq's part of outrow[].
+                */
+                unsigned int col;
+                for (col = 0; col < inpamP->width; ++col) {
+                    pnm_assigntuple(outpamP, thisEntryP->out[col],
+                                    thisEntryP->cachedRow[col]);
+                }
+                free(thisEntryP->cachedRow);
+                thisEntryP->cachedRow = NULL;
+            } else if (row >= thisEntryP->padtop &&
+                       row < thisEntryP->padtop + inpamP->height) {
+                pnm_readpamrow(inpamP, thisEntryP->out);
+                pnm_scaletuplerow(inpamP, thisEntryP->out,
+                                  thisEntryP->out, outpamP->maxval);
+                padPlanesRow(inpamP, thisEntryP->out, outpamP);
+            } else {
+                /* It's a row of padding, so image filesSeq's part of outrow[]
+                   is already set appropriately.
+                */
+            }
+        }
+        /* Note that imgCtl[N].out[] is an alias to part of outrow[], so
+           outrow[] has been set.
+        */
+        pnm_writepamrow(outpamP, outrow);
+    }
+    destroyLrImgCtlArray(imgCtl, fileCt);
+
+    pnm_freepamrow(outrow);
+}
+
+
+
+static tuple
+initialBackgroundColor(const struct pam *  const outpamP,
+                       enum PadColorMethod const padColorMethod) {
+
+    tuple retval;
+
+    switch (padColorMethod) {
+    case PAD_AUTO:
+        /* Background is different for each input image */
+        retval = pnm_allocpamtuple(outpamP);
+            /* Dummy value; just need something to free */
+        break;
+    case PAD_BLACK:
+        pnm_createBlackTuple(outpamP, &retval);
+        break;
+    case PAD_WHITE:
+        pnm_createWhiteTuple(outpamP, &retval);
+        break;
+    }
+
+    if (outpamP->visual) {
+        /* Any opacity sample in background color tuple is meaningless at this
+           point; make it opaque.
+        */
+        if (outpamP->have_opacity)
+            retval[outpamP->opacity_plane] = outpamP->maxval;
+    }
+
+    return retval;
+}
+
+
+
+static unsigned int
+leftPadAmount(const struct pam * const outpamP,
+              const struct pam * const inpamP,
+              enum Justification const justification) {
+
+    switch (justification) {
+    case JUST_MIN:    return 0;
+    case JUST_MAX:    return outpamP->width - inpamP->width;
+    case JUST_CENTER: return (outpamP->width - inpamP->width) / 2;
+    }
+    assert(false);
+}
+
+
+
+static void
+setHorizPadding(tuple *            const newTuplerow,
+                const struct pam * const outpamP,
+                bool               const backChanged,
+                const struct pam * const inpam,  /* array */
+                unsigned int       const imageSeq,
+                unsigned int       const padLeft,
+                tuple              const background) {
+/*----------------------------------------------------------------------------
+   Set the left and right padding for an output row in a top-bottom
+   concatenation.
+
+   'newTuplerow' is where we set the padding (and also assumed to hold the
+   contents of the previous output row).  *outpamP describes it.
+
+   'backChanged' means the background color is different for the current row
+   from that of the previous row.
+
+   inpam[] is the array of descriptors for all the input images.  'imageSeq'
+   is the index into this array for the current image.
+
+   'background' is the background color to set.
+-----------------------------------------------------------------------------*/
+    if (backChanged ||
+        (imageSeq > 0 && inpam[imageSeq-1].width > inpam[imageSeq].width)) {
+        unsigned int col;
+
+        for (col = 0; col < padLeft; ++col)
+            pnm_assigntuple(outpamP, newTuplerow[col], background);
+        for (col = padLeft + inpam[imageSeq].width;
+             col < outpamP->width;
+             ++col) {
+            pnm_assigntuple(outpamP, newTuplerow[col], background);
+        }
+    } else {
+        /* No need to pad because newTuplerow[] already contains the
+           correct padding from the previous row.
+        */
+    }
+}
+
+
+
+static void
+readFirstTBRowAndDetermineBackground(const struct pam *  const inpamP,
+                                     const struct pam *  const outpamP,
+                                     tuple *             const out,
+                                     tuple *             const backgroundP) {
+/*----------------------------------------------------------------------------
+   Read the first row of an input image into 'out', adjusting it to conform
+   to the output depth and maxval described by *outpamP.
+
+   The image is positioned to the first row at entry.
+
+   From this row, determine the background color for the input image and
+   return it as *backgroundP (a newly malloced tuple).
+-----------------------------------------------------------------------------*/
+    pnm_readpamrow(inpamP, out);
+
+    pnm_scaletuplerow(inpamP, out, out, outpamP->maxval);
+
+    padPlanesRow(inpamP, out, outpamP);
+
+    {
+        struct pam partialOutpam;
+            /* Descriptor for the input image with depth and maxval adjusted to
+               that of the output image.
+            */
+        tuple background;
+
+        partialOutpam = *outpamP;
+        partialOutpam.width = inpamP->width;
+
+        background = pnm_backgroundtuplerow(&partialOutpam, out);
+
+        if (outpamP->visual) {
+            /* Make the background opaque. */
+            if (outpamP->have_opacity)
+                background[outpamP->opacity_plane] = outpamP->maxval;
+        }
+
+        *backgroundP = background;
+    }
+}
+
+
+
+static void
+concatenateTopBottomGen(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    tuple * const newTuplerow = pnm_allocpamrow(outpamP);
+    tuple * out;
+        /* The location in newTuplerow[] that the row from the current
+           input image goes.
+        */
+    unsigned int fileSeq;
+    tuple background;
+    tuple backgroundPrev;
+
+    background = initialBackgroundColor(outpamP, padColorMethod);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        unsigned int row;
+        unsigned int startRow;
+        bool backChanged;
+            /* The background color is different from that of the previous
+               input image.
+            */
+
+        if (inpamP->width == outpamP->width) {
+            /* no padding */
+            startRow = 0;
+            backChanged = false;
+            out = &newTuplerow[0];
+        } else {
+            unsigned int const padLeft =
+                leftPadAmount(outpamP, inpamP, justification);
+
+            if (padColorMethod == PAD_AUTO) {
+                out = &newTuplerow[padLeft];
+                backgroundPrev = background;
+                readFirstTBRowAndDetermineBackground(
+                    inpamP, outpamP, out, &background);
+
+                backChanged =
+                    fileSeq == 0 ||
+                        pnm_tupleequal(outpamP, background, backgroundPrev);
+                pnm_freepamtuple(backgroundPrev);
+
+                startRow = 1;
+            } else {
+                /* Background color is constant: black or white */
+                startRow = 0;
+                out = &newTuplerow[padLeft];
+                backChanged = (fileSeq == 0);
+            }
+
+            setHorizPadding(newTuplerow, outpamP, backChanged, inpam, fileSeq,
+                            padLeft, background);
+        }
+
+        if (startRow == 1)
+            /* Top row was already read for auto background color
+               determination.  Write it out.
+            */
+            pnm_writepamrow(outpamP, newTuplerow);
+
+        for (row = startRow; row < inpamP->height; ++row) {
+            pnm_readpamrow(inpamP, out);
+
+            pnm_scaletuplerow(inpamP, out, out, outpamP->maxval);
+
+            padPlanesRow(inpamP, out, outpamP);
+
+            pnm_writepamrow(outpamP, newTuplerow);
+        }
+    }
+    pnm_freepamtuple(background);
+    pnm_freepamrow(newTuplerow);
+}
+
+
+
+int
+main(int           argc,
+     const char ** argv) {
+
+    struct CmdlineInfo cmdline;
+    struct pam * inpam;  /* malloc'ed array */
+    struct pam outpam;
+    unsigned int i;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    MALLOCARRAY_NOFAIL(inpam, cmdline.fileCt);
+
+    for (i = 0; i < cmdline.fileCt; ++i) {
+        FILE * const ifP = pm_openr(cmdline.inputFileName[i]);
+        inpam[i].comment_p = NULL;  /* Don't want to see the comments */
+        pnm_readpaminit(ifP, &inpam[i], PAM_STRUCT_SIZE(opacity_plane));
+    }
+
+    computeOutputParms(cmdline.fileCt, cmdline.orientation, inpam,
+                       cmdline.verbose, &outpam);
+
+    outpam.file = stdout;
+
+    for (i = 0; i < cmdline.fileCt; ++i)
+        pnm_setminallocationdepth(&inpam[i], outpam.depth);
+
+    pnm_writepaminit(&outpam);
+
+    if (outpam.format == RPBM_FORMAT) {
+        switch (cmdline.orientation) {
+        case LEFTRIGHT:
+            concatenateLeftRightPbm(&outpam, inpam, cmdline.fileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        case TOPBOTTOM:
+            concatenateTopBottomPbm(&outpam, inpam, cmdline.fileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        }
+    } else {
+        switch (cmdline.orientation) {
+        case LEFTRIGHT:
+            concatenateLeftRightGen(&outpam, inpam, cmdline.fileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        case TOPBOTTOM:
+            concatenateTopBottomGen(&outpam, inpam, cmdline.fileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        }
+    }
+    for (i = 0; i < cmdline.fileCt; ++i)
+        pm_close(inpam[i].file);
+    free(cmdline.inputFileName);
+    free(inpam);
+    pm_close(stdout);
+
+    return 0;
+}
+
+
+
diff --git a/editor/pamlevels.c b/editor/pamlevels.c
index a282751a..de2afc45 100644
--- a/editor/pamlevels.c
+++ b/editor/pamlevels.c
@@ -5,7 +5,6 @@
 #include <stdlib.h>
 
 #include "netpbm/pam.h"
-#include "netpbm/pm_system.h"
 #include "netpbm/pm_gamma.h"
 #include "netpbm/nstring.h"
 #include "netpbm/ppm.h"
diff --git a/editor/pnmcat.c b/editor/pnmcat.c
deleted file mode 100644
index fea80181..00000000
--- a/editor/pnmcat.c
+++ /dev/null
@@ -1,879 +0,0 @@
-/* pnmcat.c - concatenate PNM images
-**
-** Copyright (C) 1989, 1991 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-*/
-
-#include <assert.h>
-
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "shhopt.h"
-#include "bitarith.h"
-#include "nstring.h"
-#include "pnm.h"
-
-#define LEFTBITS pm_byteLeftBits
-#define RIGHTBITS pm_byteRightBits
-
-enum backcolor {BACK_WHITE, BACK_BLACK, BACK_AUTO};
-
-enum orientation {TOPBOTTOM, LEFTRIGHT};
-
-enum justification {JUST_CENTER, JUST_MIN, JUST_MAX};
-
-typedef struct {
-    /* This obviously should be a struct pam.  We should convert this
-       to 'pamcat'.
-    */
-    FILE * ifP;
-    int    cols;
-    int    rows;
-    int    format;
-    xelval maxval;
-} ImgInfo;
-
-
-
-struct CmdlineInfo {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    const char ** inputFilespec;
-    unsigned int nfiles;
-    enum backcolor backcolor;
-    enum orientation orientation;
-    enum justification justification;
-};
-
-
-
-static void
-parseCommandLine(int argc, const char ** const argv,
-                 struct CmdlineInfo * const cmdlineP) {
-/*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
-   was passed to us as the argv array.
------------------------------------------------------------------------------*/
-    optEntry * option_def;
-        /* Instructions to OptParseOptions3() on how to parse our options.
-         */
-    optStruct3 opt;
-
-    unsigned int option_def_index;
-
-    unsigned int leftright, topbottom, black, white, jtop, jbottom,
-        jleft, jright, jcenter;
-
-    MALLOCARRAY_NOFAIL(option_def, 100);
-
-    option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "leftright",  OPT_FLAG,   NULL, &leftright,   0);
-    OPTENT3(0, "lr",         OPT_FLAG,   NULL, &leftright,   0);
-    OPTENT3(0, "topbottom",  OPT_FLAG,   NULL, &topbottom,   0);
-    OPTENT3(0, "tb",         OPT_FLAG,   NULL, &topbottom,   0);
-    OPTENT3(0, "black",      OPT_FLAG,   NULL, &black,       0);
-    OPTENT3(0, "white",      OPT_FLAG,   NULL, &white,       0);
-    OPTENT3(0, "jtop",       OPT_FLAG,   NULL, &jtop,        0);
-    OPTENT3(0, "jbottom",    OPT_FLAG,   NULL, &jbottom,     0);
-    OPTENT3(0, "jleft",      OPT_FLAG,   NULL, &jleft,       0);
-    OPTENT3(0, "jright",     OPT_FLAG,   NULL, &jright,      0);
-    OPTENT3(0, "jcenter",    OPT_FLAG,   NULL, &jcenter,     0);
-
-    opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
-
-    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
-
-    free(option_def);
-
-    if (leftright + topbottom > 1)
-        pm_error("You may specify only one of -topbottom (-tb) and "
-                 "-leftright (-lr)");
-    else if (leftright)
-        cmdlineP->orientation = LEFTRIGHT;
-    else if (topbottom)
-        cmdlineP->orientation = TOPBOTTOM;
-    else
-        pm_error("You must specify either -leftright or -topbottom");
-
-    if (black + white > 1)
-        pm_error("You may specify only one of -black and -white");
-    else if (black)
-        cmdlineP->backcolor = BACK_BLACK;
-    else if (white)
-        cmdlineP->backcolor = BACK_WHITE;
-    else
-        cmdlineP->backcolor = BACK_AUTO;
-
-    if (jtop + jbottom + jleft + jright + jcenter > 1)
-        pm_error("You may specify only one of -jtop, -jbottom, "
-                 "-jleft, and -jright");
-    else {
-        switch (cmdlineP->orientation) {
-        case LEFTRIGHT:
-            if (jleft)
-                pm_error("-jleft is invalid with -leftright");
-            if (jright)
-                pm_error("-jright is invalid with -leftright");
-            if (jtop)
-                cmdlineP->justification = JUST_MIN;
-            else if (jbottom)
-                cmdlineP->justification = JUST_MAX;
-            else if (jcenter)
-                cmdlineP->justification = JUST_CENTER;
-            else
-                cmdlineP->justification = JUST_CENTER;
-            break;
-        case TOPBOTTOM:
-            if (jtop)
-                pm_error("-jtop is invalid with -topbottom");
-            if (jbottom)
-                pm_error("-jbottom is invalid with -topbottom");
-            if (jleft)
-                cmdlineP->justification = JUST_MIN;
-            else if (jright)
-                cmdlineP->justification = JUST_MAX;
-            else if (jcenter)
-                cmdlineP->justification = JUST_CENTER;
-            else
-                cmdlineP->justification = JUST_CENTER;
-            break;
-        }
-    }
-
-    if (argc-1 < 1) {
-        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, 1);
-        cmdlineP->inputFilespec[0] = "-";
-        cmdlineP->nfiles = 1;
-    } else {
-        unsigned int i;
-        unsigned int stdinCt;
-            /* Number of input files user specified as Standard Input */
-
-        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, argc-1);
-
-        for (i = 0, stdinCt = 0; i < argc-1; ++i) {
-            cmdlineP->inputFilespec[i] = argv[1+i];
-            if (streq(argv[1+i], "-"))
-                ++stdinCt;
-        }
-        cmdlineP->nfiles = argc-1;
-        if (stdinCt > 1)
-            pm_error("At most one input image can come from Standard Input.  "
-                     "You specified %u", stdinCt);
-    }
-}
-
-
-
-static void
-computeOutputParms(unsigned int     const nfiles,
-                   enum orientation const orientation,
-                   ImgInfo          const img[],
-                   unsigned int *   const newcolsP,
-                   unsigned int *   const newrowsP,
-                   xelval *         const newmaxvalP,
-                   int *            const newformatP) {
-
-    double newcols, newrows;
-    int newformat;
-    xelval newmaxval;
-
-    unsigned int i;
-
-    newcols = 0;
-    newrows = 0;
-
-    for (i = 0; i < nfiles; ++i) {
-        const ImgInfo * const imgP = &img[i];
-
-        if (i == 0) {
-            newmaxval = imgP->maxval;
-            newformat = imgP->format;
-        } else {
-            if (PNM_FORMAT_TYPE(imgP->format) > PNM_FORMAT_TYPE(newformat))
-                newformat = imgP->format;
-            if (imgP->maxval > newmaxval)
-                newmaxval = imgP->maxval;
-        }
-        switch (orientation) {
-        case LEFTRIGHT:
-            newcols += imgP->cols;
-            if (imgP->rows > newrows)
-                newrows = imgP->rows;
-            break;
-        case TOPBOTTOM:
-            newrows += imgP->rows;
-            if (imgP->cols > newcols)
-                newcols = imgP->cols;
-            break;
-        }
-    }
-
-    /* Note that while 'double' is not in general a precise numerical type,
-       in the case of a sum of integers which is less than INT_MAX, it
-       is exact, because double's precision is greater than int's.
-    */
-    if (newcols > INT_MAX)
-       pm_error("Output width too large: %.0f.", newcols);
-    if (newrows > INT_MAX)
-       pm_error("Output height too large: %.0f.", newrows);
-
-    *newrowsP   = (unsigned int)newrows;
-    *newcolsP   = (unsigned int)newcols;
-    *newmaxvalP = newmaxval;
-    *newformatP = newformat;
-}
-
-
-
-static void
-copyBitrow(const unsigned char * const source,
-           unsigned char *       const destBitrow,
-           unsigned int          const cols,
-           unsigned int          const offset) {
-/*----------------------------------------------------------------------------
-  Copy from source to destBitrow, without shifting.  Preserve
-  surrounding image data.
------------------------------------------------------------------------------*/
-    unsigned char * const dest = & destBitrow[ offset/8 ];
-        /* Copy destination, with leading full bytes ignored. */
-    unsigned int const rs = offset % 8;
-        /* The "little offset", as measured from start of dest.  Source
-           is already shifted by this value.
-        */
-    unsigned int const trs = (cols + rs) % 8;
-        /* The number of partial bits in the final char. */
-    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
-        /* # bytes to process, including partial ones on both ends. */
-    unsigned int const last = colByteCnt - 1;
-
-    unsigned char const origHead = dest[0];
-    unsigned char const origEnd  = dest[last];
-
-    unsigned int i;
-
-    assert(colByteCnt >= 1);
-
-    for (i = 0; i < colByteCnt; ++i)
-        dest[i] = source[i];
-
-    if (rs > 0)
-        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
-
-    if (trs > 0)
-        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
-}
-
-
-
-static void
-padFillBitrow(unsigned char * const destBitrow,
-              unsigned char   const padColor,
-              unsigned int    const cols,
-              unsigned int    const offset) {
-/*----------------------------------------------------------------------------
-   Fill destBitrow, starting at offset, with padColor.  padColor is a
-   byte -- 0x00 or 0xff -- not a single bit.
------------------------------------------------------------------------------*/
-    unsigned char * const dest = &destBitrow[offset/8];
-    unsigned int const rs = offset % 8;
-    unsigned int const trs = (cols + rs) % 8;
-    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
-    unsigned int const last = colByteCnt - 1;
-
-    unsigned char const origHead = dest[0];
-    unsigned char const origEnd  = dest[last];
-
-    unsigned int i;
-
-    assert(colByteCnt > 0);
-
-    for (i = 0; i < colByteCnt; ++i)
-        dest[i] = padColor;
-
-    if (rs > 0)
-        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
-
-    if (trs > 0)
-        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
-}
-
-
-
-/* concatenateLeftRightPBM() and concatenateLeftRightGen()
-   employ almost identical algorithms.
-   The difference is in the data types and functions.
-
-   Same for concatenateTopBottomPBM() and concatenateTopBottomGen().
-*/
-
-
-typedef struct {
-    /* Information about one image */
-    unsigned char * proberow;
-        /* Top row of image, when background color is
-           auto-determined.
-        */
-    unsigned int offset;
-        /* start position of image, in bits, counting from left
-           edge
-        */
-    unsigned char background;
-        /* Background color.  0x00 means white; 0xff means black */
-    unsigned int padtop;
-        /* Top padding amount */
-} ImgInfoPbm2;
-
-
-
-static void
-getPbmImageInfo(ImgInfo               const img[],
-                unsigned int          const nfiles,
-                unsigned int          const newrows,
-                enum justification    const justification,
-                enum backcolor        const backcolor,
-                ImgInfoPbm2 **        const img2P) {
-/*----------------------------------------------------------------------------
-   Read the first row of each image in img[] and return that and additional
-   information about images as *img2P.
------------------------------------------------------------------------------*/
-    ImgInfoPbm2 * img2;
-    unsigned int i;
-
-    MALLOCARRAY_NOFAIL(img2, nfiles);
-
-    for (i = 0; i < nfiles; ++i) {
-        switch (justification) {
-        case JUST_MIN:    img2[i].padtop = 0;                           break;
-        case JUST_MAX:    img2[i].padtop = newrows - img[i].rows;       break;
-        case JUST_CENTER: img2[i].padtop = (newrows - img[i].rows) / 2; break;
-        }
-
-        img2[i].offset = (i == 0) ? 0 : img2[i-1].offset + img[i-1].cols;
-
-        if (img[i].rows == newrows)  /* no padding */
-            img2[i].proberow = NULL;
-        else {                   /* determine pad color for image i */
-            switch (backcolor) {
-            case BACK_AUTO: {
-                bit bgBit;
-                img2[i].proberow =
-                    pbm_allocrow_packed((unsigned int)img[i].cols + 7);
-                pbm_readpbmrow_bitoffset(
-                    img[i].ifP, img2[i].proberow,
-                    img[i].cols, img[i].format, img2[i].offset % 8);
-
-                bgBit = pbm_backgroundbitrow(
-                    img2[i].proberow, img[i].cols, img2[i].offset % 8);
-
-                img2[i].background = bgBit == PBM_BLACK ? 0xff : 0x00;
-            } break;
-            case BACK_BLACK:
-                img2[i].proberow   = NULL;
-                img2[i].background = 0xff;
-                break;
-            case BACK_WHITE:
-                img2[i].proberow   = NULL;
-                img2[i].background = 0x00;
-                break;
-            }
-        }
-    }
-    *img2P = img2;
-}
-
-
-
-static void
-destroyPbmImg2(ImgInfoPbm2 * const img2,
-               unsigned int  const nfiles) {
-
-    unsigned int i;
-
-    for (i = 0; i < nfiles; ++i) {
-        if (img2[i].proberow)
-            free(img2[i].proberow);
-    }
-    free(img2);
-}
-
-
-
-static void
-concatenateLeftRightPbm(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        unsigned int       const newcols,
-                        unsigned int       const newrows,
-                        enum justification const justification,
-                        ImgInfo            const img[],
-                        enum backcolor     const backcolor) {
-
-    unsigned char * const outrow = pbm_allocrow_packed(newcols);
-        /* We use just one outrow.  All padding and image data (with the
-           exception of following img2.proberow) goes directly into this
-           packed PBM row.
-        */
-
-    ImgInfoPbm2 * img2;
-        /* malloc'ed array, one element per image.  Shadows img[] */
-    unsigned int row;
-
-    getPbmImageInfo(img, nfiles, newrows, justification, backcolor, &img2);
-
-    outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-
-    for (row = 0; row < newrows; ++row) {
-        unsigned int i;
-
-        for (i = 0; i < nfiles; ++i) {
-
-            if ((row == 0 && img2[i].padtop > 0) ||
-                row == img2[i].padtop + img[i].rows) {
-
-                /* This row begins a run of padding, either above or below
-                   file 'i', so set 'outrow' to padding.
-                */
-                padFillBitrow(outrow, img2[i].background, img[i].cols,
-                              img2[i].offset);
-            }
-
-            if (row == img2[i].padtop && img2[i].proberow != NULL) {
-                /* Top row has been read to proberow[] to determine
-                   background.  Copy it to outrow[].
-                */
-                copyBitrow(img2[i].proberow, outrow,
-                           img[i].cols, img2[i].offset);
-            } else if (row >= img2[i].padtop &&
-                       row < img2[i].padtop + img[i].rows) {
-                pbm_readpbmrow_bitoffset(
-                    img[i].ifP, outrow, img[i].cols, img[i].format,
-                    img2[i].offset);
-            } else {
-                /* It's a row of padding, so outrow[] is already set
-                   appropriately.
-                */
-            }
-        }
-        pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-    }
-
-    destroyPbmImg2(img2, nfiles);
-
-    pbm_freerow_packed(outrow);
-}
-
-
-
-static void
-concatenateTopBottomPbm(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        int                const newcols,
-                        int                const newrows,
-                        enum justification const justification,
-                        ImgInfo            const img[],
-                        enum backcolor     const backcolor) {
-
-    unsigned char * const outrow = pbm_allocrow_packed(newcols);
-        /* Like the left-right PBM case, all padding and image data
-           goes directly into outrow.  There is no proberow.
-        */
-    unsigned char background, backgroundPrev;
-        /* 0x00 means white; 0xff means black */
-    unsigned int  padleft;
-    bool          backChange;
-        /* Background color is different from that of the previous
-           input image.
-        */
-
-    unsigned int i;
-    unsigned int row, startRow;
-
-    outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-
-    switch (backcolor){
-    case BACK_AUTO:   /* do nothing */    break;
-    case BACK_BLACK:  background = 0xff;  break;
-    case BACK_WHITE:  background = 0x00;  break;
-    }
-
-    for (i = 0; i < nfiles; ++i) {
-        if (img[i].cols == newcols) {
-            /* No padding */
-            startRow = 0;
-            backChange = FALSE;
-            padleft = 0;
-            outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-        } else {
-            /* Determine amount of padding and color */
-            switch (justification) {
-            case JUST_MIN:     padleft = 0;                           break;
-            case JUST_MAX:     padleft = newcols - img[i].cols;       break;
-            case JUST_CENTER:  padleft = (newcols - img[i].cols) / 2; break;
-            }
-
-            switch (backcolor) {
-            case BACK_AUTO: {
-                bit bgBit;
-
-                startRow = 1;
-
-                pbm_readpbmrow_bitoffset(img[i].ifP,
-                                         outrow, img[i].cols, img[i].format,
-                                         padleft);
-
-                bgBit = pbm_backgroundbitrow(outrow, img[i].cols, padleft);
-                background = bgBit == PBM_BLACK ? 0xff : 0x00;
-
-                backChange = (i == 0 || background != backgroundPrev);
-            } break;
-            case BACK_WHITE:
-            case BACK_BLACK:
-                startRow = 0;
-                backChange = (i==0);
-                break;
-            }
-
-            if (backChange || (i > 0 && img[i-1].cols > img[i].cols)) {
-                unsigned int const padright = newcols - padleft - img[i].cols;
-
-                if (padleft > 0)
-                    padFillBitrow(outrow, background, padleft, 0);
-
-                if (padright > 0)
-                    padFillBitrow(outrow, background, padright,
-                                  padleft + img[i].cols);
-
-            }
-        }
-
-        if (startRow == 1)
-            /* Top row already read for auto background color
-               determination.  Write it out.
-            */
-            pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-
-        for (row = startRow; row < img[i].rows; ++row) {
-            pbm_readpbmrow_bitoffset(img[i].ifP, outrow, img[i].cols,
-                                     img[i].format, padleft);
-            pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-        }
-
-        backgroundPrev = background;
-    }
-    pbm_freerow_packed(outrow);
-}
-
-
-
-typedef struct {
-    xel * xelrow;
-    xel * inrow;
-    xel   background;
-    int   padtop;
-} ImgGen2;
-
-
-
-static void
-getGenImgInfo(ImgInfo            const img[],
-              unsigned int       const nfiles,
-              xel *              const newxelrow,
-              unsigned int       const newrows,
-              xelval             const newmaxval,
-              int                const newformat,
-              enum justification const justification,
-              enum backcolor     const backcolor,
-              ImgGen2 **         const img2P) {
-
-    ImgGen2 * img2;
-    unsigned int i;
-
-    MALLOCARRAY_NOFAIL(img2, nfiles);
-
-    for (i = 0; i < nfiles; ++i) {
-        switch (justification) {  /* Determine top padding */
-            case JUST_MIN:
-                img2[i].padtop = 0;
-                break;
-            case JUST_MAX:
-                img2[i].padtop = newrows - img[i].rows;
-                break;
-            case JUST_CENTER:
-                img2[i].padtop = (newrows - img[i].rows) / 2;
-                break;
-        }
-
-        img2[i].inrow =
-            (i == 0 ? &newxelrow[0] : img2[i-1].inrow + img[i-1].cols);
-
-        if (img[i].rows == newrows)  /* no padding */
-            img2[i].xelrow = NULL;
-        else {
-            /* Determine pad color */
-            switch (backcolor){
-            case BACK_AUTO:
-                img2[i].xelrow = pnm_allocrow(img[i].cols);
-                pnm_readpnmrow(img[i].ifP, img2[i].xelrow,
-                               img[i].cols, img[i].maxval, img[i].format);
-                pnm_promoteformatrow(img2[i].xelrow, img[i].cols,
-                                     img[i].maxval, img[i].format,
-                                     newmaxval, newformat);
-                img2[i].background = pnm_backgroundxelrow(
-                    img2[i].xelrow, img[i].cols, newmaxval, newformat);
-                break;
-            case BACK_BLACK:
-                img2[i].xelrow = NULL;
-                img2[i].background = pnm_blackxel(newmaxval, newformat);
-                break;
-            case BACK_WHITE:
-                img2[i].xelrow = NULL;
-                img2[i].background = pnm_whitexel(newmaxval, newformat);
-                break;
-            }
-        }
-    }
-    *img2P = img2;
-}
-
-
-
-static void
-concatenateLeftRightGen(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        unsigned int       const newcols,
-                        unsigned int       const newrows,
-                        xelval             const newmaxval,
-                        int                const newformat,
-                        enum justification const justification,
-                        ImgInfo            const img[],
-                        enum backcolor     const backcolor) {
-
-    xel * const outrow = pnm_allocrow(newcols);
-
-    ImgGen2 *    img2;
-    unsigned int row;
-
-    getGenImgInfo(img, nfiles, outrow, newrows,
-                  newmaxval, newformat, justification, backcolor, &img2);
-
-    for (row = 0; row < newrows; ++row) {
-        unsigned int i;
-
-        for (i = 0; i < nfiles; ++i) {
-            if ((row == 0 && img2[i].padtop > 0) ||
-                row == img2[i].padtop + img[i].rows) {
-                /* This row begins a run of padding, either above or below
-                   file 'i', so set 'outrow' to padding.
-                */
-                unsigned int col;
-                for (col = 0; col < img[i].cols; ++col)
-                    img2[i].inrow[col] = img2[i].background;
-            }
-            if (row == img2[i].padtop && img2[i].xelrow) {
-                /* We're at the top row of file 'i', and that row
-                   has already been read to xelrow[] to determine
-                   background.  Copy it to 'outrow'.
-                */
-                unsigned int col;
-                for (col = 0; col < img[i].cols; ++col)
-                    img2[i].inrow[col] = img2[i].xelrow[col];
-
-                free(img2[i].xelrow);
-            } else if (row >= img2[i].padtop &&
-                       row < img2[i].padtop + img[i].rows) {
-                pnm_readpnmrow(
-                    img[i].ifP, img2[i].inrow, img[i].cols, img[i].maxval,
-                    img[i].format);
-                pnm_promoteformatrow(
-                    img2[i].inrow, img[i].cols, img[i].maxval,
-                    img[i].format, newmaxval, newformat);
-            } else {
-                /* It's a row of padding, so outrow[] is already set
-                   appropriately.
-                */
-            }
-        }
-        /* Note that img2[N].inrow{] is an alias to part of outrow[], so
-           outrow[] has been set.
-        */
-        pnm_writepnmrow(ofP, outrow, newcols, newmaxval, newformat, 0);
-    }
-    pnm_freerow(outrow);
-}
-
-
-
-static void
-concatenateTopBottomGen(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        int                const newcols,
-                        int                const newrows,
-                        xelval             const newmaxval,
-                        int                const newformat,
-                        enum justification const justification,
-                        ImgInfo            const img[],
-                        enum backcolor     const backcolor) {
-
-    xel * const newxelrow = pnm_allocrow(newcols);
-    xel * inrow;
-    unsigned int padleft;
-    unsigned int i;
-    unsigned int row, startRow;
-    xel background, backgroundPrev;
-    bool backChange;
-        /* The background color is different from that of the previous
-           input image.
-        */
-
-    switch (backcolor) {
-    case BACK_AUTO: /* do nothing now, determine at start of each image */
-                     break;
-    case BACK_BLACK: background = pnm_blackxel(newmaxval, newformat);
-                     break;
-    case BACK_WHITE: background = pnm_whitexel(newmaxval, newformat);
-                     break;
-    }
-
-    for ( i = 0; i < nfiles; ++i, backgroundPrev = background) {
-        if (img[i].cols == newcols) {
-            /* no padding */
-            startRow = 0;
-            backChange = FALSE;
-            inrow = newxelrow;
-        } else { /* Calculate left padding amount */
-            switch (justification) {
-            case JUST_MIN:    padleft = 0;                            break;
-            case JUST_MAX:    padleft = newcols - img[i].cols;        break;
-            case JUST_CENTER: padleft = (newcols - img[i].cols) / 2;  break;
-            }
-
-            if (backcolor == BACK_AUTO) {
-                /* Determine background color */
-
-                startRow = 1;
-                inrow = &newxelrow[padleft];
-
-                pnm_readpnmrow(img[i].ifP, inrow,
-                               img[i].cols, img[i].maxval, img[i].format);
-                pnm_promoteformatrow(inrow, img[i].cols, img[i].maxval,
-                                     img[i].format,
-                                     newmaxval, newformat);
-                background = pnm_backgroundxelrow(
-                    inrow, img[i].cols, newmaxval, newformat);
-
-                backChange = i==0 || !PNM_EQUAL(background, backgroundPrev);
-            } else {
-                /* background color is constant: black or white */
-                startRow = 0;
-                inrow = &newxelrow[padleft];
-                backChange = (i==0);
-            }
-
-            if (backChange || (i > 0 && img[i-1].cols > img[i].cols)) {
-                unsigned int col;
-
-                for (col = 0; col < padleft; ++col)
-                    newxelrow[col] = background;
-                for (col = padleft + img[i].cols; col < newcols; ++col)
-                    newxelrow[col] = background;
-            }
-        }
-
-        if (startRow == 1)
-            /* Top row already read for auto background
-               color determination.  Write it out. */
-            pnm_writepnmrow(ofP, newxelrow, newcols, newmaxval, newformat, 0);
-
-        for (row = startRow; row < img[i].rows; ++row) {
-            pnm_readpnmrow(img[i].ifP,
-                           inrow, img[i].cols, img[i].maxval, img[i].format);
-            pnm_promoteformatrow(
-                inrow, img[i].cols, img[i].maxval, img[i].format,
-                newmaxval, newformat);
-
-            pnm_writepnmrow(ofP, newxelrow, newcols, newmaxval, newformat, 0);
-        }
-    }
-    pnm_freerow(newxelrow);
-}
-
-
-
-int
-main(int           argc,
-     const char ** argv) {
-
-    struct CmdlineInfo cmdline;
-    ImgInfo * img;  /* malloc'ed array */
-    xelval newmaxval;
-    int newformat;
-    unsigned int i;
-    unsigned int newrows, newcols;
-
-    pm_proginit(&argc, argv);
-
-    parseCommandLine(argc, argv, &cmdline);
-
-    MALLOCARRAY_NOFAIL(img, cmdline.nfiles);
-
-    for (i = 0; i < cmdline.nfiles; ++i) {
-        img[i].ifP = pm_openr(cmdline.inputFilespec[i]);
-        pnm_readpnminit(img[i].ifP, &img[i].cols, &img[i].rows,
-                        &img[i].maxval, &img[i].format);
-    }
-
-    computeOutputParms(cmdline.nfiles, cmdline.orientation, img,
-                       &newcols, &newrows, &newmaxval, &newformat);
-
-    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
-
-    if (PNM_FORMAT_TYPE(newformat) == PBM_TYPE) {
-        switch (cmdline.orientation) {
-        case LEFTRIGHT:
-            concatenateLeftRightPbm(stdout, cmdline.nfiles,
-                                    newcols, newrows, cmdline.justification,
-                                    img, cmdline.backcolor);
-            break;
-        case TOPBOTTOM:
-            concatenateTopBottomPbm(stdout, cmdline.nfiles,
-                                    newcols, newrows, cmdline.justification,
-                                    img, cmdline.backcolor);
-            break;
-        }
-    } else {
-        switch (cmdline.orientation) {
-        case LEFTRIGHT:
-            concatenateLeftRightGen(stdout, cmdline.nfiles,
-                                    newcols, newrows, newmaxval, newformat,
-                                    cmdline.justification, img,
-                                    cmdline.backcolor);
-            break;
-        case TOPBOTTOM:
-            concatenateTopBottomGen(stdout, cmdline.nfiles,
-                                    newcols, newrows, newmaxval, newformat,
-                                    cmdline.justification, img,
-                                    cmdline.backcolor);
-            break;
-        }
-    }
-    for (i = 0; i < cmdline.nfiles; ++i)
-        pm_close(img[i].ifP);
-    free(cmdline.inputFilespec);
-    free(img);
-    pm_close(stdout);
-
-    return 0;
-}
-
-
-
diff --git a/editor/pnmindex.csh b/editor/pnmindex.csh
deleted file mode 100755
index c6f1e844..00000000
--- a/editor/pnmindex.csh
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/bin/csh -f
-#
-# pnmindex - build a visual index of a bunch of anymaps
-#
-# Copyright (C) 1991 by Jef Poskanzer.
-#
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted, provided
-# that the above copyright notice appear in all copies and that both that
-# copyright notice and this permission notice appear in supporting
-# documentation.  This software is provided "as is" without express or
-# implied warranty.
-
-# -title and -quant added by John Heidemann 13-Sep-00.
-
-set size=100		# make the images about this big
-set across=6		# show this many images per row
-set colors=256		# quantize results to this many colors
-set back="-white"	# default background color
-set doquant=true	# quantize or not
-set title=""		# default title (none)
-
-while ( 1 )
-    switch ( "$1" )
-
-	case -s*:
-	if ( $#argv < 2 ) goto usage
-	set size="$2"
-	shift
-	shift
-	breaksw
-
-	case -a*:
-	if ( $#argv < 2 ) goto usage
-	set across="$2"
-	shift
-	shift
-	breaksw
-
-	case -t*:
-	if ( $#argv < 2 ) goto usage
-	set title="$2"
-	shift
-	shift
-	breaksw
-
-	case -c*:
-	set colors="$2"
-	shift
-	shift
-	breaksw
-
-	case -noq*:
-	set doquant=false
-	shift
-	breaksw
-
-	case -q*:
-	set doquant=true
-	shift
-	breaksw
-
-	case -b*:
-	set back="-black"
-	shift
-	breaksw
-
-	case -w*:
-	set back="-white"
-	shift
-	breaksw
-
-	case -*:
-	goto usage
-	breaksw
-
-	default:
-	break
-	breaksw
-
-    endsw
-end
-
-if ( $#argv == 0 ) then
-    goto usage
-endif
-
-set tmpfile=/tmp/pi.tmp.$$
-rm -f $tmpfile
-set maxformat=PBM
-
-set rowfiles=()
-set imagefiles=()
-@ row = 1
-@ col = 1
-
-if ( "$title" != "" ) then
-    set rowfile=/tmp/pi.${row}.$$
-    rm -f $rowfile
-    pbmtext "$title" > $rowfile
-    set rowfiles=( $rowfiles $rowfile )
-    @ row += 1
-endif
-
-foreach i ( $argv )
-
-    set description=`pnmfile $i`
-    if ( $description[4] <= $size && $description[6] <= $size ) then
-	cat $i > $tmpfile
-    else
-	switch ( $description[2] )
-	    case PBM:
-	    pnmscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
-	    breaksw
-
-	    case PGM:
-	    pnmscale -quiet -xysize $size $size $i > $tmpfile
-	    if ( $maxformat == PBM ) then
-		set maxformat=PGM
-	    endif
-	    breaksw
-
-	    default:
-	    if ( $doquant == false ) then
-	        pnmscale -quiet -xysize $size $size $i > $tmpfile
-	    else
-	        pnmscale -quiet -xysize $size $size $i | ppmquant -quiet $colors > $tmpfile
-	    endif
-	    set maxformat=PPM
-	    breaksw
-	endsw
-    endif
-    set imagefile=/tmp/pi.${row}.${col}.$$
-    rm -f $imagefile
-    if ( "$back" == "-white" ) then
-	pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
-    else
-	pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
-    endif
-    rm -f $tmpfile
-    set imagefiles=( $imagefiles $imagefile )
-
-    if ( $col >= $across ) then
-	set rowfile=/tmp/pi.${row}.$$
-	rm -f $rowfile
-	if ( $maxformat != PPM || $doquant == false ) then
-	    pnmcat $back -lr -jbottom $imagefiles > $rowfile
-	else
-	    pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
-	endif
-	rm -f $imagefiles
-	set imagefiles=()
-	set rowfiles=( $rowfiles $rowfile )
-	@ col = 1
-	@ row += 1
-    else
-	@ col += 1
-    endif
-
-end
-
-if ( $#imagefiles > 0 ) then
-    set rowfile=/tmp/pi.${row}.$$
-    rm -f $rowfile
-    if ( $maxformat != PPM || $doquant == false ) then
-	pnmcat $back -lr -jbottom $imagefiles > $rowfile
-    else
-	pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
-    endif
-    rm -f $imagefiles
-    set rowfiles=( $rowfiles $rowfile )
-endif
-
-if ( $#rowfiles == 1 ) then
-    cat $rowfiles
-else
-    if ( $maxformat != PPM || $doquant == false ) then
-	pnmcat $back -tb $rowfiles
-    else
-	pnmcat $back -tb $rowfiles | ppmquant -quiet $colors
-    endif
-endif
-rm -f $rowfiles
-
-exit 0
-
-usage:
-echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
-exit 1
diff --git a/editor/pnmindex.sh b/editor/pnmindex.sh
deleted file mode 100755
index dfc5b82a..00000000
--- a/editor/pnmindex.sh
+++ /dev/null
@@ -1,214 +0,0 @@
-#!/bin/sh
-#
-# pnmindex - build a visual index of a bunch of PNM images
-#
-# Copyright (C) 1991 by Jef Poskanzer.
-#
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted, provided
-# that the above copyright notice appear in all copies and that both that
-# copyright notice and this permission notice appear in supporting
-# documentation.  This software is provided "as is" without express or
-# implied warranty.
-
-size=100        # make the images about this big
-across=6        # show this many images per row
-colors=256      # quantize results to this many colors
-back="-white"   # default background color
-doquant=true    # quantize or not
-title=""        # default title (none)
-
-usage ()
-{
-  echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
-  exit 1
-}
-
-while :; do
-    case "$1" in
-
-    -s*)
-        if [ $# -lt 2 ]; then usage; fi
-        size="$2"
-        shift
-        shift
-    ;;
-
-    -a*)
-        if [ $# -lt 2 ]; then usage; fi
-        across="$2"
-        shift
-        shift
-    ;;
-
-    -t*)
-        if [ $# -lt 2 ]; then usage; fi
-        title="$2"
-        shift
-        shift
-    ;;
-
-    -c*)
-        if [ $# -lt 2 ]; then usage; fi
-        colors="$2"
-        shift
-        shift
-    ;;
-
-    -b*)
-        back="-black"
-        shift
-    ;;
-
-    -w*)
-        back="-white"
-        shift
-    ;;
-
-    -noq*)
-        doquant=false
-        shift
-    ;;
-
-    -q*)
-        doquant=true
-        shift
-    ;;
-
-    -*)
-        usage
-    ;;
-
-    *)
-        break
-    ;;
-    esac
-done
-
-if [ $# -eq 0 ]; then
-    usage
-fi
-
-tempdir="${TMPDIR-/tmp}/pnmindex.$$"
-mkdir -m 0700 $tempdir || \
-  { echo "Could not create temporary file. Exiting."; exit 1;}
-trap 'rm -rf $tempdir' 0 1 3 15
-
-tmpfile=$tempdir/pi.tmp
-maxformat=PBM
-
-rowfiles=()
-imagefiles=()
-row=1
-col=1
-
-if [ "$title"x != ""x ] ; then
-#    rowfile=`tempfile -p pirow -m 600`
-    rowfile=$tempdir/pi.${row}
-    pbmtext "$title" > $rowfile
-    rowfiles=(${rowfiles[*]} $rowfile )
-    row=$(($row + 1))
-fi
-
-for i in "$@"; do
-
-    description=(`pnmfile $i`)
-
-    format=${description[1]}
-    width=${description[3]}
-    height=${description[5]}
-
-    if [ $? -ne 0 ]; then
-        echo pnmfile returned an error
-        exit $?
-    fi
-
-    if [ $width -le $size ] && \
-       [ $height -le $size ]; then
-        cat $i > $tmpfile
-    else
-        case $format in
-
-        PBM) 
-            pamscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
-        ;;
-
-        PGM)
-            pamscale -quiet -xysize $size $size $i > $tmpfile
-            if [ $maxformat = PBM ]; then
-                maxformat=PGM
-            fi
-        ;;
-
-        *) 
-            if [ "$doquant" = "true" ] ; then
-                pamscale -quiet -xysize $size $size $i | \
-                pnmquant -quiet $colors > $tmpfile
-            else
-                pamscale -quiet -xysize $size $size $i > $tmpfile
-            fi
-            maxformat=PPM
-        ;;
-        esac
-    fi
-
-    imagefile=$tempdir/pi.${row}.${col}
-    rm -f $imagefile
-    if [ "$back" = "-white" ]; then
-        pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
-    else
-        pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
-    fi
-    imagefiles=( ${imagefiles[*]} $imagefile )
-
-    if [ $col -ge $across ]; then
-        rowfile=$tempdir/pi.${row}
-        rm -f $rowfile
-
-        if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-            pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
-        else
-            pnmcat $back -lr -jbottom ${imagefiles[*]} | \
-            pnmquant -quiet $colors > $rowfile
-        fi
-
-        rm -f ${imagefiles[*]}
-        unset imagefiles
-        imagefiles=()
-        rowfiles=( ${rowfiles[*]} $rowfile )
-        col=1
-        row=$(($row + 1))
-    else
-        col=$(($col + 1))
-    fi
-done
-
-# All the full rows have been put in row files.  
-# Now put the final partial row in its row file.
-
-if [ ${#imagefiles[*]} -gt 0 ]; then
-    rowfile=$tempdir/pi.${row}
-    rm -f $rowfile
-    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-        pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
-    else
-        pnmcat $back -lr -jbottom ${imagefiles[*]} | \
-        pnmquant -quiet $colors > $rowfile
-    fi
-    rm -f ${imagefiles[*]}
-    rowfiles=( ${rowfiles[*]} $rowfile )
-fi
-
-if [ ${#rowfiles[*]} -eq 1 ]; then
-    cat $rowfiles
-else
-    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-        pnmcat $back -tb ${rowfiles[*]}
-    else
-        pnmcat $back -tb ${rowfiles[*]} | pnmquant -quiet $colors
-    fi
-fi
-rm -f ${rowfiles[*]}
-
-exit 0
-
diff --git a/editor/pnmmargin b/editor/pnmmargin
index a62e5e44..94321c7f 100755
--- a/editor/pnmmargin
+++ b/editor/pnmmargin
@@ -108,8 +108,8 @@ else
     pamflip -rotate90 $tmp2 > $tmp3
 
     # Cat things together.
-    pnmcat -lr $tmp2 $tmp1 $tmp2 > $tmp4
-    pnmcat -tb $plainopt $tmp3 $tmp4 $tmp3
+    pamcat -leftright $tmp2 $tmp1 $tmp2 > $tmp4
+    pamcat -topbottom $plainopt $tmp3 $tmp4 $tmp3
 fi
 
 
diff --git a/editor/pnmquantall b/editor/pnmquantall
index 5f434fc2..594e8f7b 100755
--- a/editor/pnmquantall
+++ b/editor/pnmquantall
@@ -145,11 +145,11 @@ sub tempFile($) {
 sub makeColorMap($$$$) {
     my ($fileNamesR, $newColorCt, $colorMapFileName, $errorR) = @_;
 
-    my $pnmcatCmd = "pnmcat -topbottom -white -jleft @{$fileNamesR}";
+    my $pamcatCmd = "pamcat -topbottom -white -jleft @{$fileNamesR}";
 
     my $pnmcolormapCmd = "pnmcolormap $newColorCt";
 
-    my $makeMapCmd = "$pnmcatCmd | $pnmcolormapCmd >$colorMapFileName";
+    my $makeMapCmd = "$pamcatCmd | $pnmcolormapCmd >$colorMapFileName";
 
     my $termStatus = system($makeMapCmd);
 
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index 438fe058..2b39e4ec 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -1,5 +1,5 @@
 /*============================================================================
-                              pnmindex   
+                                pnmindex
 ==============================================================================
 
   build a visual index of a bunch of PNM images
@@ -32,7 +32,7 @@
 #include "nstring.h"
 #include "pnm.h"
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -56,11 +56,11 @@ systemf(const char * const fmt,
         ...) {
 
     va_list varargs;
-    
+
     size_t dryRunLen;
-    
+
     va_start(varargs, fmt);
-    
+
     pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen);
 
     va_end(varargs);
@@ -83,7 +83,7 @@ systemf(const char * const fmt,
             va_start(varargs, fmt);
 
             pm_vsnprintf(shellCommand, allocSize, fmt, varargs, &realLen);
-                
+
             assert(realLen == dryRunLen);
             va_end(varargs);
 
@@ -94,12 +94,12 @@ systemf(const char * const fmt,
             if (rc != 0)
                 pm_error("shell command '%s' failed.  rc %d",
                          shellCommand, rc);
-            
+
             pm_strfree(shellCommand);
         }
     }
 }
-        
+
 
 
 static const char *
@@ -168,8 +168,8 @@ shellQuote(const char * const arg) {
 
 
 static void
-parseCommandLine(int argc, char ** argv, 
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 
     unsigned int option_def_index;
     optEntry *option_def;
@@ -183,13 +183,13 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "black",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "black",       OPT_FLAG,   NULL,
             &cmdlineP->black,         0);
-    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,
             &cmdlineP->noquant,       0);
-    OPTENT3(0, "quant",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "quant",       OPT_FLAG,   NULL,
             &quant,                   0);
-    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,
             &cmdlineP->verbose,       0);
     OPTENT3(0, "size",        OPT_UINT,   &cmdlineP->size,
             &sizeSpec,                0);
@@ -202,7 +202,7 @@ parseCommandLine(int argc, char ** argv,
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE; 
+    opt.allowNegNum = FALSE;
 
     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
@@ -212,7 +212,7 @@ parseCommandLine(int argc, char ** argv,
 
     if (!colorsSpec)
         cmdlineP->colors = 256;
-    
+
     if (!sizeSpec)
         cmdlineP->size = 100;
 
@@ -246,7 +246,7 @@ parseCommandLine(int argc, char ** argv,
 
 
 static void
-freeCmdline(struct cmdlineInfo const cmdline) {
+freeCmdline(struct CmdlineInfo const cmdline) {
 
     unsigned int i;
 
@@ -326,9 +326,9 @@ rowFileName(const char * const dirName,
             unsigned int const row) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u", dirName, row);
-    
+
     return fileName;
 }
 
@@ -368,7 +368,7 @@ copyImage(const char * const inputFileName,
     systemf("cat %s > %s", inputFileNmToken, outputFileName);
 
     pm_strfree(inputFileNmToken);
-} 
+}
 
 
 
@@ -386,26 +386,26 @@ copyScaleQuantImage(const char * const inputFileName,
 
     switch (PNM_FORMAT_TYPE(format)) {
     case PBM_TYPE:
-        pm_asprintf(&scaleCommand, 
+        pm_asprintf(&scaleCommand,
                     "pamscale -quiet -xysize %u %u %s "
                     "| pgmtopbm > %s",
                     size, size, inputFileNmToken, outputFileName);
         break;
-        
+
     case PGM_TYPE:
-        pm_asprintf(&scaleCommand, 
+        pm_asprintf(&scaleCommand,
                     "pamscale -quiet -xysize %u %u %s >%s",
                     size, size, inputFileNmToken, outputFileName);
         break;
-        
+
     case PPM_TYPE:
         if (quant)
-            pm_asprintf(&scaleCommand, 
+            pm_asprintf(&scaleCommand,
                         "pamscale -quiet -xysize %u %u %s "
                         "| pnmquant -quiet %u > %s",
                         size, size, inputFileNmToken, colors, outputFileName);
         else
-            pm_asprintf(&scaleCommand, 
+            pm_asprintf(&scaleCommand,
                         "pamscale -quiet -xysize %u %u %s >%s",
                         size, size, inputFileNmToken, outputFileName);
         break;
@@ -426,7 +426,7 @@ formatTypeMax(int const typeA,
               int const typeB) {
 
     if (typeA == PPM_TYPE || typeB == PPM_TYPE)
-        return PPM_TYPE; 
+        return PPM_TYPE;
     else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
         return PGM_TYPE;
     else
@@ -441,9 +441,9 @@ thumbnailFileName(const char * const dirName,
                   unsigned int const col) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u.%u", dirName, row, col);
-    
+
     return fileName;
 }
 
@@ -464,7 +464,7 @@ thumbnailFileList(const char * const dirName,
         pm_error("Unable to allocate %u bytes for file list", maxListSize);
 
     list[0] = '\0';
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -487,19 +487,28 @@ makeImageFile(const char * const thumbnailFileName,
               const char * const inputFileName,
               bool         const blackBackground,
               const char * const outputFileName) {
+/*----------------------------------------------------------------------------
+   Create one thumbnail image.  It consists of the image in the file named
+   'thumbnailFileName' with text of that name appended to the bottom.
 
+   Write the result to the file named 'outputFileName'.
+
+   'blackBackground' means give the image a black background where padding
+   is necessary and make the text white on black.  If false, give the image
+   a white background instead.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
     const char * const invertStage   = blackBackground ? "| pnminvert " : "";
     const char * inputFileNmToken    = shellQuote(inputFileName);
 
     systemf("pbmtext %s %s"
-            "| pnmcat %s -topbottom %s - "
+            "| pamcat %s -topbottom %s - "
             "> %s",
             inputFileNmToken, invertStage, blackWhiteOpt,
             thumbnailFileName, outputFileName);
 
     pm_strfree(inputFileNmToken);
-}    
+}
 
 
 
@@ -519,21 +528,21 @@ makeThumbnail(const char *  const inputFileName,
     xelval maxval;
     const char * tmpfile;
     const char * fileName;
-        
+
     ifP = pm_openr(inputFileName);
     pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
     pm_close(ifP);
-    
+
     pm_asprintf(&tmpfile, "%s/pi.tmp", tempDir);
 
     if (imageCols < size && imageRows < size)
         copyImage(inputFileName, tmpfile);
     else
-        copyScaleQuantImage(inputFileName, tmpfile, format, 
+        copyScaleQuantImage(inputFileName, tmpfile, format,
                             size, quant, colors);
 
     fileName = thumbnailFileName(tempDir, row, col);
-        
+
     makeImageFile(tmpfile, inputFileName, black, fileName);
 
     unlink(tmpfile);
@@ -543,7 +552,7 @@ makeThumbnail(const char *  const inputFileName,
 
     *formatP = format;
 }
-        
+
 
 
 static void
@@ -552,7 +561,7 @@ unlinkThumbnailFiles(const char * const dirName,
                      unsigned int const cols) {
 
     unsigned int col;
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -569,7 +578,7 @@ unlinkRowFiles(const char * const dirName,
                unsigned int const rows) {
 
     unsigned int row;
-    
+
     for (row = 0; row < rows; ++row) {
         const char * const fileName = rowFileName(dirName, row);
 
@@ -595,7 +604,7 @@ combineIntoRowAndDelete(unsigned int const row,
     const char * fileName;
     const char * quantStage;
     const char * fileList;
-    
+
     fileName = rowFileName(tempDir, row);
 
     unlink(fileName);
@@ -607,7 +616,7 @@ combineIntoRowAndDelete(unsigned int const row,
 
     fileList = thumbnailFileList(tempDir, row, cols);
 
-    systemf("pnmcat %s -leftright -jbottom %s "
+    systemf("pamcat %s -leftright -jbottom %s "
             "%s"
             ">%s",
             blackWhiteOpt, fileList, quantStage, fileName);
@@ -641,7 +650,7 @@ rowFileList(const char * const dirName,
 
         if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
             pm_error("File name list too long for this program to handle.");
-        
+
         else {
             strcat(list, " ");
             strcat(list, fileName);
@@ -666,7 +675,7 @@ writeRowsAndDelete(unsigned int const rows,
 
     const char * quantStage;
     const char * fileList;
-    
+
     if (maxFormatType == PPM_TYPE && quant)
         pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
     else
@@ -674,7 +683,7 @@ writeRowsAndDelete(unsigned int const rows,
 
     fileList = rowFileList(tempDir, rows);
 
-    systemf("pnmcat %s -topbottom %s %s",
+    systemf("pamcat %s -topbottom %s %s",
             blackWhiteOpt, fileList, quantStage);
 
     pm_strfree(fileList);
@@ -687,7 +696,7 @@ writeRowsAndDelete(unsigned int const rows,
 
 int
 main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     const char * tempDir;
     int maxFormatType;
     unsigned int colsInRow;
@@ -699,7 +708,7 @@ main(int argc, char *argv[]) {
     parseCommandLine(argc, argv, &cmdline);
 
     verbose = cmdline.verbose;
-    
+
     makeTempDir(&tempDir);
 
     maxFormatType = PBM_TYPE;
@@ -714,7 +723,7 @@ main(int argc, char *argv[]) {
 
         int format;
 
-        makeThumbnail(inputFileName, cmdline.size, cmdline.black, 
+        makeThumbnail(inputFileName, cmdline.size, cmdline.black,
                       !cmdline.noquant, cmdline.colors, tempDir,
                       rowsDone, colsInRow, &format);
 
@@ -742,3 +751,6 @@ main(int argc, char *argv[]) {
 
     return 0;
 }
+
+
+
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index 0c3656e7..a3e9124a 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -52,6 +52,9 @@ validateFontName(const char * const name) {
 -----------------------------------------------------------------------------*/
     unsigned int idx;
 
+    if (name[0] == '\0')
+        pm_error("Font name is empty string");
+
     for (idx = 0; name[idx] != '\0'; ++idx) {
         char const c = name[idx];
 
@@ -244,6 +247,8 @@ parseCommandLine(int argc, const char ** argv,
 
     validateFontName(cmdlineP->font);
 
+    if (cmdlineP->res <= 0)
+        pm_error("-resolution must be positive");
     if (cmdlineP->fontsize <= 0)
         pm_error("-fontsize must be positive");
     if (cmdlineP->ascent < 0)
@@ -413,11 +418,8 @@ gsArgList(const char *       const outputFilename,
     const char ** retval;
     unsigned int argCt;  /* Number of arguments in 'retval' so far */
 
-    if (cmdline.res <= 0)
-         pm_error("Resolution (dpi) must be positive.");
-
-    if (cmdline.fontsize <= 0)
-         pm_error("Font size must be positive.");
+    assert(cmdline.res > 0);
+    assert(cmdline.fontsize > 0);
 
     MALLOCARRAY_NOFAIL(retval, maxArgCt+2);
 
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
index 7cef571f..47f9390e 100644
--- a/generator/ppmforge.c
+++ b/generator/ppmforge.c
@@ -241,7 +241,7 @@ parseCommandLine(int argc, const char **argv,
 
 
 static void
-fourn(float *     const data,
+fourn(double *    const data,
       const int * const nn,
       int         const ndim,
       int         const isign) {
@@ -272,7 +272,7 @@ fourn(float *     const data,
     int i1, i2, i3;
     int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2;
     int ibit, idim, k1, k2, n, nprev, nrem, ntot;
-    float tempi, tempr;
+    double tempi, tempr;
     double theta, wi, wpi, wpr, wr, wtemp;
 
 #define SWAP(a,b) tempr=(a); (a) = (b); (b) = tempr
@@ -401,7 +401,7 @@ cast(double         const low,
 
 
 static void
-spectralsynth(float **       const x,
+spectralsynth(double **      const aP,
               unsigned int   const n,
               double         const h,
               struct Gauss * const gaussP) {
@@ -411,18 +411,20 @@ spectralsynth(float **       const x,
   This algorithm is given under the name SpectralSynthesisFM2D on page 108 of
   Peitgen & Saupe.
 -----------------------------------------------------------------------------*/
-    unsigned bl;
+    unsigned int const bl = ((((unsigned long) n) * n) + 1) * 2;
+
     int i, j, i0, j0, nsize[3];
     double rad, phase, rcos, rsin;
-    float *a;
+    double * a;
+
+    MALLOCARRAY(a, bl);
 
-    bl = ((((unsigned long) n) * n) + 1) * 2 * sizeof(float);
-    a = (float *) calloc(bl, 1);
-    if (a == (float *) 0) {
-        pm_error("Cannot allocate %d x %d result array (% d bytes).",
+    if (!a) {
+        pm_error("Cannot allocate %u x %u result array (%u doubles).",
                  n, n, bl);
     }
-    *x = a;
+    for (i = 0; i < bl; ++i)
+        a[i] = 0.0;  /* initial value */
 
     for (i = 0; i <= n / 2; i++) {
         for (j = 0; j <= n / 2; j++) {
@@ -463,6 +465,8 @@ spectralsynth(float **       const x,
     nsize[0] = 0;
     nsize[1] = nsize[2] = n;          /* Dimension of frequency domain array */
     fourn(a, nsize, 2, -1);       /* Take inverse 2D Fourier transform */
+
+    *aP = a;
 }
 
 
@@ -572,9 +576,9 @@ atSat(double const x,
 
 
 static unsigned char *
-makeCp(float *      const a,
-       unsigned int const n,
-       pixval       const maxval) {
+makeCp(const double * const a,
+       unsigned int   const n,
+       pixval         const maxval) {
 
     /* Prescale the grid points into intensities. */
 
@@ -587,7 +591,7 @@ makeCp(float *      const a,
     if (cp == NULL)
         pm_error("Unable to allocate %u bytes for cp array", n);
 
-    ap = cp;
+    ap = cp;  /* initial value */
     {
         unsigned int i;
         for (i = 0; i < n; i++) {
@@ -603,7 +607,7 @@ makeCp(float *      const a,
 
 static void
 createPlanetStuff(bool             const clouds,
-                  float *          const a,
+                  const double *   const a,
                   unsigned int     const n,
                   double **        const uP,
                   double **        const u1P,
@@ -977,7 +981,7 @@ generatePlanetRow(pixel *         const pixelrow,
 static void
 genplanet(bool           const stars,
           bool           const clouds,
-          float *        const a,
+          const double * const a,
           unsigned int   const cols,
           unsigned int   const rows,
           unsigned int   const n,
@@ -1052,9 +1056,9 @@ genplanet(bool           const stars,
 
 
 static void
-applyPowerLawScaling(float * const a,
-                     int     const meshsize,
-                     double  const powscale) {
+applyPowerLawScaling(double * const a,
+                     int      const meshsize,
+                     double   const powscale) {
 
     /* Apply power law scaling if non-unity scale is requested. */
 
@@ -1065,7 +1069,7 @@ applyPowerLawScaling(float * const a,
             for (j = 0; j < meshsize; j++) {
                 double const r = Real(a, i, j);
                 if (r > 0)
-                    Real(a, i, j) = pow(r, powscale);
+                    Real(a, i, j) = MIN(hugeVal, pow(r, powscale));
             }
         }
     }
@@ -1074,10 +1078,10 @@ applyPowerLawScaling(float * const a,
 
 
 static void
-computeExtremeReal(const float * const a,
-                   int           const meshsize,
-                   double *      const rminP,
-                   double *      const rmaxP) {
+computeExtremeReal(const double * const a,
+                   int            const meshsize,
+                   double *       const rminP,
+                   double *       const rmaxP) {
 
     /* Compute extrema for autoscaling. */
 
@@ -1103,8 +1107,8 @@ computeExtremeReal(const float * const a,
 
 
 static void
-replaceWithSpread(float * const a,
-                  int     const meshsize) {
+replaceWithSpread(double * const a,
+                  int      const meshsize) {
 /*----------------------------------------------------------------------------
   Replace the real part of each element of the 'a' array with a
   measure of how far the real is from the middle; sort of a standard
@@ -1138,7 +1142,7 @@ planet(unsigned int const cols,
 /*----------------------------------------------------------------------------
    Make a planet.
 -----------------------------------------------------------------------------*/
-    float * a;
+    double * a;
     bool error;
     struct Gauss gauss;
 
@@ -1149,7 +1153,7 @@ planet(unsigned int const cols,
         error = FALSE;
     } else {
         spectralsynth(&a, meshsize, 3.0 - fracdim, &gauss);
-        if (a == NULL) {
+        if (!a) {
             error = TRUE;
         } else {
             applyPowerLawScaling(a, meshsize, powscale);
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
index a5c2689d..68d519a0 100755
--- a/generator/ppmrainbow
+++ b/generator/ppmrainbow
@@ -126,8 +126,11 @@ while (@colorlist >= 2) {
     shift @colorlist;
 }
 
-0 == system qq{$verboseCommand pnmcat -lr @outlist}
-    or exit 1;
+my $termStat =
+    system("$verboseCommand pamcat -leftright @outlist");
+if ($termStat != 0) {
+    exit 1;
+}
 
 exit 0;
 
diff --git a/lib/libpam.c b/lib/libpam.c
index b24f230e..a01dd596 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -216,6 +216,24 @@ pnm_createBlackTuple(const struct pam * const pamP,
 
 
 
+void
+pnm_createWhiteTuple(const struct pam * const pamP,
+                     tuple *            const whiteTupleP) {
+/*----------------------------------------------------------------------------
+   Create a "white" tuple.  By that we mean a tuple all of whose elements are
+   the maxval.  If it's an RGB, grayscale, or b&w pixel, that means it's
+   white.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    *whiteTupleP = pnm_allocpamtuple(pamP);
+
+    for (i = 0; i < pamP->depth; ++i)
+        (*whiteTupleP)[i] = pamP->maxval;
+}
+
+
+
 static tuple *
 allocPamRow(const struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -434,6 +452,8 @@ setSeekableAndRasterPos(struct pam * const pamP) {
 }
 
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstringop-truncation"
 
 #define MAX_LABEL_LENGTH 8
 #define MAX_VALUE_LENGTH 255
@@ -443,7 +463,7 @@ parseHeaderLine(const char * const buffer,
                 char *       const label,
                 char *       const value) {
 /*----------------------------------------------------------------------------
-   We truncate the labe to MAX_LABEL_LENGTH and the value to
+   We truncate the label to MAX_LABEL_LENGTH and the value to
    MAX_VALUE_LENGTH.  There must be at least that much space (plus space
    for a terminating NUL) at 'label' and 'value', respectively.
 -----------------------------------------------------------------------------*/
@@ -484,6 +504,7 @@ parseHeaderLine(const char * const buffer,
         value[valueCurs] = '\0';
     }
 }
+#pragma GCC diagnostic pop
 
 
 
@@ -1417,6 +1438,66 @@ pnm_backgroundtuple(struct pam *  const pamP,
 
 
 
+tuple
+pnm_backgroundtuplerow(const struct pam * const pamP,
+                       tuple            * const tuplerow) {
+/*-----------------------------------------------------------------------------
+  Guess a good background color for an image that contains row 'tuplerow'
+  (probably top or bottom edge), described by *pamP.
+
+  This function was copied from libpnm3.c's pnm_backgroundxelrow() and
+  modified to use tuples instead of xels.
+-----------------------------------------------------------------------------*/
+    tuple bgtuple;
+
+    bgtuple = pnm_allocpamtuple(pamP);
+
+    assert(pamP->width > 0);
+
+    if (pamP->width == 1)
+        pnm_assigntuple(pamP, bgtuple, tuplerow[0]);
+    else {
+        tuple const l = tuplerow[0];
+        tuple const r = tuplerow[pamP->width-1];
+
+        if (pnm_tupleequal(pamP, l, r)) {
+            /* Both corners are same color, so that's the background color,
+               without any extra computation.
+            */
+            pnm_assigntuple(pamP, bgtuple, l);
+        } else {
+            /* Corners are different */
+
+            if (pamP->depth == 1 && pamP->maxval == 1) {
+                /* It's black and white, with one corner black, the other
+                   white.  We consider whichever color is most prevalent in
+                   the row the background color.
+                */
+                unsigned int col;
+                unsigned int blackCt;
+
+                for (col = 0, blackCt = 0; col < pamP->width; ++col) {
+                    if (tuplerow[col] == 0)
+                        ++blackCt;
+                }
+                if (blackCt > pamP->width / 2)
+                    bgtuple[0] = 0;
+                else
+                    bgtuple[0] = pamP->maxval;
+            } else {
+                /* Use the cartesian mean of the two corner colors */
+                unsigned int plane;
+
+                for (plane = 0; plane < pamP->depth; ++plane)
+                    bgtuple[plane] = (l[plane] + r[plane])/2;
+            }
+        }
+    }
+    return bgtuple;
+}
+
+
+
 /*=============================================================================
    pm_system() Standard Input feeder and Standard Output accepter functions.
 =============================================================================*/
@@ -1456,3 +1537,6 @@ pm_accept_to_pamtuples(int    const pipeToSuckFd,
 
     pm_close(inpamP->file);
 }
+
+
+
diff --git a/lib/libpnm3.c b/lib/libpnm3.c
index 3970c734..ba408bd1 100644
--- a/lib/libpnm3.c
+++ b/lib/libpnm3.c
@@ -106,10 +106,14 @@ pnm_backgroundxelrow(xel *  const xelrow,
                      int    const cols,
                      xelval const maxval,
                      int    const format) {
+/*----------------------------------------------------------------------------
+   Guess a good background color for an image that contains row 'xelrow'
+   (probably top or bottom edge).
 
+   'cols', 'maxval', and 'format' describe 'xelrow'.
+-----------------------------------------------------------------------------*/
     xel bgxel, l, r;
 
-    /* Guess a good background value. */
     l = xelrow[0];
     r = xelrow[cols-1];
 
diff --git a/lib/pam.h b/lib/pam.h
index 88b8c2bd..94c85b4e 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -305,6 +305,9 @@ pnm_getopacity(const struct pam * const pamP,
 void
 pnm_createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
 
+void
+pnm_createWhiteTuple(const struct pam * const pamP, tuple * const whiteTupleP);
+
 tuple
 pnm_allocpamtuple(const struct pam * const pamP);
 
@@ -590,6 +593,10 @@ tuple
 pnm_backgroundtuple(struct pam *  const pamP,
                     tuple      ** const tuples);
 
+tuple
+pnm_backgroundtuplerow(const struct pam * const pamP,
+                       tuple      *       const tuplerow);
+
 /*----------------------------------------------------------------------------
    These are meant for passing to pm_system() as Standard Input feeder
    and Standard Output accepter.
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 1b72d344..4bbb041f 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -1,110 +1,28 @@
-/*
- * snprintf.c - a portable implementation of snprintf
- *
-
-   THIS MODULE WAS ADAPTED FOR NETPBM BY BRYAN HENDERSON ON 2002.03.24.
-   Bryan got the base from
-   http://www.ijs.si/software/snprintf/snprintf-2.2.tar.gz, but made
-   a lot of changes and additions.
-   The code from ww.ijs.si was written by
-
-     Mark Martinec <mark.martinec@ijs.si>
-
-   in April 1999 and June 2000.  Martinec claims copyright as of 1999, 2000,
-   2001, and 2002 and licenses the code to Netpbm maintainers and users (as
-   members of the public) under the GNU General Public License.
-
-   All other authors likewise license this code to the public under the
-   GNU General Public license.
-
- *
- * FEATURES
- * - careful adherence to specs regarding flags, field width and precision;
- * - good performance for large string handling (large format, large
- *   argument or large paddings). Performance is similar to system's sprintf
- *   and in several cases significantly better (make sure you compile with
- *   optimizations turned on, tell the compiler the code is strict ANSI
- *   if necessary to give it more freedom for optimizations);
- * - return value semantics per ISO/IEC 9899:1999 ("ISO C99");
- * - written in standard ISO/ANSI C - requires an ANSI C compiler.
- *
- * IMPLEMENTED CONVERSION SPECIFIERS AND DATA TYPES
- *
- * This snprintf implements only the following conversion specifiers:
- * s, c, d, u, o, x, X, p, f  (and synonyms: i, D, U, O - see below)
- * with flags: '-', '+', ' ', '0' and '#'.
- * An asterisk is acceptable for field width as well as precision.
- *
- * Length modifiers 'h' (short int), 'l' (long int),
- * and 'll' (long long int) are implemented.
- *
- * Conversion of numeric data (conversion specifiers d, u, o, x, X, p)
- * with length modifiers (none or h, l, ll) is left to the system routine
- * sprintf, but all handling of flags, field width and precision as well as
- * c and s conversions is done very carefully by this portable routine.
- * If a string precision (truncation) is specified (e.g. %.8s) it is
- * guaranteed the string beyond the specified precision will not be referenced.
- *
- * Length modifiers h, l and ll are ignored for c and s conversions (you
- * can't use data types wint_t and wchar_t).
- *
- * The following common synonyms for conversion characters are acceptable:
- *   - i is a synonym for d
- *   - D is a synonym for ld, explicit length modifiers are ignored
- *   - U is a synonym for lu, explicit length modifiers are ignored
- *   - O is a synonym for lo, explicit length modifiers are ignored
- * The D, O and U conversion characters are nonstandard, they are accepted
- * for backward compatibility only, and should not be used for new code.
- *
- * The following is specifically NOT implemented:
- *   - flag ' (thousands' grouping character) is recognized but ignored
- *   - numeric conversion specifiers: e, E, g, G and synonym F,
- *     as well as the new a and A conversion specifiers
- *   - length modifier 'L' (long double) and 'q' (quad - use 'll' instead)
- *   - wide character/string conversions: lc, ls, and nonstandard
- *     synonyms C and S
- *   - writeback of converted string length: conversion character n
- *   - the n$ specification for direct reference to n-th argument
- *   - locales
- *
- * It is permitted for str_m to be zero, and it is permitted to specify NULL
- * pointer for resulting string argument if str_m is zero (as per ISO C99).
- *
- * The return value is the number of characters which would be generated
- * for the given input, excluding the trailing null. If this value
- * is greater or equal to str_m, not all characters from the result
- * have been stored in str, output bytes beyond the (str_m-1) -th character
- * are discarded. If str_m is greater than zero it is guaranteed
- * the resulting string will be null-terminated.
- *
- * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1,
- * but is different from some older and vendor implementations,
- * and is also different from XPG, XSH5, SUSv2 specifications.
- * For historical discussion on changes in the semantics and standards
- * of snprintf see printf(3) man page in the Linux programmers manual.
- *
- * Routines asprintf and vasprintf return a pointer (in the ptr argument)
- * to a buffer sufficiently large to hold the resulting string. This pointer
- * should be passed to free(3) to release the allocated storage when it is
- * no longer needed. If sufficient space cannot be allocated, these functions
- * will return -1 and set ptr to be a NULL pointer. These two routines are a
- * GNU C library extensions (glibc).
- *
- * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
- * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
- * characters into the allocated output string, the last character in the
- * allocated buffer then gets the terminating null. If the formatted string
- * length (the return value) is greater than or equal to the str_m argument,
- * the resulting string was truncated and some of the formatted characters
- * were discarded. These routines present a handy way to limit the amount
- * of allocated memory to some sane value.
- *
- * AVAILABILITY
- *   http://www.ijs.si/software/snprintf/
- *
- */
-
-
+/*=============================================================================
+                               nstring.c
+===============================================================================
+
+  pm_snprintf (and pm_vsnprintf) in this file used to be derived from
+  'portable_snprintf' from
+  http://www.ijs.si/software/snprintf/snprintf-2.2.tar.gz, because not all
+  system C libraries had snprintf.  But in 2013, we extended that snprintf to
+  implement %f by calling 'snprintf' in the system C library, just to see if
+  it caused any build failures.  As of August 2022, there had been no
+  complaints of problems caused by this reliance on the system providing
+  snprintf, so we just made pm_snprintf a wrapper of snprintf for everything.
+
+  Eventually we will remove pm_snprintf and pm_vsnprintf altogether and their
+  callers will call 'snprintf' and 'vsnprintf' instead
+
+  Note that snprintf is required by the C99 standard.
+
+  The code from which pm_snprintf was formerly derived was protected by
+  copyright and licensed to the public under GPL.  A user in August 2022 noted
+  that GPL was insufficient for his use of it, making him unable to use
+  libnetpbm.
+
+  Code in this file is contributed to the public domain by its authors.
+=============================================================================*/
 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE  /* Make sure strdup() is in string.h */
@@ -112,9 +30,6 @@
    /* Because of conditional compilation, this is GNU source only if the C
       library is GNU.
    */
-#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
-#define PORTABLE_SNPRINTF_VERSION_MINOR 2
-
 #include <sys/types.h>
 #include <limits.h>
 #include <string.h>
@@ -129,64 +44,6 @@
 
 #include "nstring.h"
 
-#ifdef isdigit
-#undef isdigit
-#endif
-#define isdigit(c) ((c) >= '0' && (c) <= '9')
-
-/* For copying strings longer or equal to 'breakeven_point'
- * it is more efficient to call memcpy() than to do it inline.
- * The value depends mostly on the processor architecture,
- * but also on the compiler and its optimization capabilities.
- * The value is not critical, some small value greater than zero
- * will be just fine if you don't care to squeeze every drop
- * of performance out of the code.
- *
- * Small values favor memcpy, large values favor inline code.
- */
-#if defined(__alpha__) || defined(__alpha)
-#  define breakeven_point   2	/* AXP (DEC Alpha)     - gcc or cc or egcs */
-#endif
-#if defined(__i386__)  || defined(__i386)
-#  define breakeven_point  12	/* Intel Pentium/Linux - gcc 2.96 */
-#endif
-#if defined(__hppa)
-#  define breakeven_point  10	/* HP-PA               - gcc */
-#endif
-#if defined(__sparc__) || defined(__sparc)
-#  define breakeven_point  33	/* Sun Sparc 5         - gcc 2.8.1 */
-#endif
-
-/* some other values of possible interest: */
-/* #define breakeven_point  8 */  /* VAX 4000          - vaxc */
-/* #define breakeven_point 19 */  /* VAX 4000          - gcc 2.7.0 */
-
-#ifndef breakeven_point
-#  define breakeven_point   6	/* some reasonable one-size-fits-all value */
-#endif
-
-#define fast_memcpy(d,s,n) \
-  { register size_t nn = (size_t)(n); \
-    if (nn >= breakeven_point) memcpy((d), (s), nn); \
-    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
-      register char *dd; register const char *ss; \
-      for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
-
-#define fast_memset(d,c,n) \
-  { register size_t nn = (size_t)(n); \
-    if (nn >= breakeven_point) memset((d), (int)(c), nn); \
-    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
-      register char *dd; register const int cc=(int)(c); \
-      for (dd=(d); nn>0; nn--) *dd++ = cc; } }
-
-/* declarations */
-
-static char credits[] = "\n\
-@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
-@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
-@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
-
-
 /* MacOS X before 10.7, for one, does not have strnlen */
 size_t
 pm_strnlen(const char * const s,
@@ -203,556 +60,25 @@ pm_strnlen(const char * const s,
 
 void
 pm_vsnprintf(char *       const str,
-             size_t       const str_m,
+             size_t       const maxSize,
              const char * const fmt,
              va_list            ap,
              size_t *     const sizeP) {
 
-    size_t str_l = 0;
-    const char *p = fmt;
+    int rc;
 
-    /* In contrast with POSIX, the ISO C99 now says that str can be
-       NULL and str_m can be 0.  This is more useful than the old:
-       if (str_m < 1) return -1;
-    */
+    rc = vsnprintf(str, maxSize, fmt, ap);
 
-    if (!p) p = "";
-    while (*p) {
-        if (*p != '%') {
-            /* if (str_l < str_m) str[str_l++] = *p++; -- this would
-               be sufficient but the following code achieves better
-               performance for cases * where format string is long and
-               contains few conversions
-            */
-            const char *q = strchr(p + 1,'%');
-            size_t n = !q ? strlen(p) : (q - p);
-            if (str_l < str_m) {
-                size_t const avail = str_m - str_l;
-                fast_memcpy(str + str_l, p, (MIN(n, avail)));
-            }
-            p += n; str_l += n;
-        } else {
-            size_t min_field_width;
-            size_t precision = 0;
-            bool precision_specified;
-            bool justify_left;
-            bool alternate_form;
-            bool force_sign;
-            bool space_for_positive;
-                /* If both the ' ' and '+' flags appear,
-                   the ' ' flag should be ignored.
-                */
-            char length_modifier = '\0';  /* allowed values: \0, h, l, L */
-            char tmp[32];
-                /* temporary buffer for simple numeric->string conversion */
-
-            const char *str_arg;
-                /* string address in case of string argument */
-            size_t str_arg_l;
-                /* natural field width of arg without padding and sign */
-            unsigned char uchar_arg;
-                /* unsigned char argument value - only defined for c
-                   conversion.  N.B. standard explicitly states the char
-                   argument for the c conversion is unsigned.
-                */
-
-            bool zero_padding;
-
-            size_t number_of_zeros_to_pad;
-                /* number of zeros to be inserted for numeric
-                   conversions as required by the precision or minimal
-                   field width
-                */
-
-            size_t zero_padding_insertion_ind;
-                /* index into tmp where zero padding is to be inserted */
-
-            char fmt_spec;
-                /* current conversion specifier character */
-
-            str_arg = credits;
-                /* just to make compiler happy (defined but not used) */
-            str_arg = NULL;
-            ++p;  /* skip '%' */
-
-            /* parse flags */
-            justify_left = false;  /* initial value */
-            alternate_form = false;  /* initial value */
-            force_sign = false;  /* initial value */
-            space_for_positive = false;  /* initial value */
-            zero_padding = false;  /* initial value */
-            number_of_zeros_to_pad = 0;  /* initial value */
-            zero_padding_insertion_ind = 0;  /* initial value */
-            fmt_spec = '\0';  /* initial value */
-
-            while (*p == '0' || *p == '-' || *p == '+' ||
-                   *p == ' ' || *p == '#' || *p == '\'') {
-                switch (*p) {
-                case '0': zero_padding = true; break;
-                case '-': justify_left = true; break;
-                case '+': force_sign = true; space_for_positive = false; break;
-                case ' ': force_sign = true; break;
-                    /* If both the ' ' and '+' flags appear, the ' '
-                       flag should be ignored
-                    */
-                case '#': alternate_form = true; break;
-                case '\'': break;
-                }
-                ++p;
-            }
-            /* If the '0' and '-' flags both appear, the '0' flag
-               should be ignored.
-            */
-
-            /* parse field width */
-            if (*p == '*') {
-                int j;
-                ++p;
-                j = va_arg(ap, int);
-                if (j >= 0) { min_field_width = j; justify_left = false; }
-                else { min_field_width = -j; justify_left = true; }
-            } else if (isdigit((int)(*p))) {
-                /* size_t could be wider than unsigned int; make sure
-                   we treat argument like common implementations do
-                */
-                unsigned int uj = *p++ - '0';
-                while (isdigit((int)(*p)))
-                    uj = 10*uj + (unsigned int)(*p++ - '0');
-                min_field_width = uj;
-            } else
-                min_field_width = 0;
-
-            /* parse precision */
-            if (*p == '.') {
-                ++p;
-                precision_specified = true;
-                if (*p == '*') {
-                    int j = va_arg(ap, int);
-                    p++;
-                    if (j >= 0) precision = j;
-                    else {
-                        precision_specified = false; precision = 0;
-                        /* NOTE: Solaris 2.6 man page claims that in
-                           this case the precision should be set to 0.
-                           Digital Unix 4.0, HPUX 10 and BSD man page
-                           claim that this case should be treated as
-                           unspecified precision, which is what we do
-                           here.
-                        */
-                    }
-                } else if (isdigit((int)(*p))) {
-                    /* size_t could be wider than unsigned int; make
-                       sure we treat argument like common
-                       implementations do
-                    */
-                    unsigned int uj = *p++ - '0';
-                    while (isdigit((int)(*p)))
-                        uj = 10*uj + (unsigned int)(*p++ - '0');
-                    precision = uj;
-                }
-            } else
-                precision_specified = false;
-
-            /* parse 'h', 'l' and 'll' length modifiers */
-            if (*p == 'h' || *p == 'l') {
-                length_modifier = *p; p++;
-                if (length_modifier == 'l' && *p == 'l') {
-                    /* double l = long long */
-                    length_modifier = 'l';  /* treat it as a single 'l' */
-                    p++;
-                }
-            }
-            fmt_spec = *p;
-
-            /* common synonyms: */
-            switch (fmt_spec) {
-            case 'i': fmt_spec = 'd'; break;
-            case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
-            case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
-            case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
-            default: break;
-            }
-            /* get parameter value, do initial processing */
-            switch (fmt_spec) {
-            case '%':
-                /* % behaves similar to 's' regarding flags and field widths */
-            case 'c':
-                /* c behaves similar to 's' regarding flags and field widths */
-            case 's':
-                /* wint_t and wchar_t not handled */
-                length_modifier = '\0';
-                /* the result of zero padding flag with non-numeric
-                    conversion specifier is undefined. Solaris and
-                    HPUX 10 does zero padding in this case, Digital
-                    Unix and Linux does not.
-                */
-
-                zero_padding = false;
-                    /* turn zero padding off for string conversions */
-                str_arg_l = 1;
-                switch (fmt_spec) {
-                case '%':
-                    str_arg = p; break;
-                case 'c': {
-                    int j = va_arg(ap, int);
-                    uchar_arg = (unsigned char) j;
-                        /* standard demands unsigned char */
-                    str_arg = (const char *) &uchar_arg;
-                    break;
-                }
-                case 's':
-                    str_arg = va_arg(ap, const char *);
-                    if (!str_arg)
-                        /* make sure not to address string beyond the
-                           specified precision !!!
-                        */
-                        str_arg_l = 0;
-                    else if (!precision_specified)
-                        /* truncate string if necessary as requested by
-                           precision
-                        */
-                        str_arg_l = strlen(str_arg);
-                    else if (precision == 0)
-                        str_arg_l = 0;
-                    else {
-                        /* memchr on HP does not like n > 2^31  !!! */
-                        const char * q =
-                            memchr(str_arg, '\0', MIN(precision, 0x7fffffff));
-                        str_arg_l = !q ? precision : (q-str_arg);
-                    }
-                    break;
-                default: break;
-                }
-                break;
-            case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
-                /* NOTE: the u, o, x, X and p conversion specifiers imply
-                   the value is unsigned;  d implies a signed value
-                */
-                int arg_sign = 0;
-                /* 0  if numeric argument is zero (or if pointer is NULL
-                      for 'p'),
-                   +1 if greater than zero (or nonzero for unsigned arguments),
-                   -1 if negative (unsigned argument is never negative)
-                */
-
-                int int_arg = 0;
-                unsigned int uint_arg = 0;
-                   /* defined only for length modifier h, or for no
-                      length modifiers
-                   */
-
-                long int long_arg = 0;  unsigned long int ulong_arg = 0;
-                /* only defined for length modifier l */
-
-                void *ptr_arg = NULL;
-                /* pointer argument value -only defined for p conversion */
-
-                if (fmt_spec == 'p') {
-                    /* HPUX 10: An l, h, ll or L before any other
-                        conversion character (other than d, i, u, o,
-                        x, or X) is ignored.
-
-                      Digital Unix: not specified, but seems to behave
-                      as HPUX does.
-
-                      Solaris: If an h, l, or L appears before any
-                      other conversion specifier (other than d, i, u,
-                      o, x, or X), the behavior is
-                      undefined. (Actually %hp converts only 16-bits
-                      of address and %llp treats address as 64-bit
-                      data which is incompatible with (void *)
-                      argument on a 32-bit system).
-                    */
-
-                    length_modifier = '\0';
-                    ptr_arg = va_arg(ap, void *);
-                    if (ptr_arg != NULL) arg_sign = 1;
-                } else if (fmt_spec == 'd') {  /* signed */
-                    switch (length_modifier) {
-                    case '\0':
-                    case 'h':
-                        /* It is non-portable to specify a second
-                           argument of char or short to va_arg,
-                           because arguments seen by the called
-                           function are not char or short.  C converts
-                           char and short arguments to int before
-                           passing them to a function.
-                        */
-                        int_arg = va_arg(ap, int);
-                        if      (int_arg > 0) arg_sign =  1;
-                        else if (int_arg < 0) arg_sign = -1;
-                        break;
-                    case 'l':
-                        long_arg = va_arg(ap, long int);
-                        if      (long_arg > 0) arg_sign =  1;
-                        else if (long_arg < 0) arg_sign = -1;
-                        break;
-                    }
-                } else {  /* unsigned */
-                    switch (length_modifier) {
-                    case '\0':
-                    case 'h':
-                        uint_arg = va_arg(ap, unsigned int);
-                        if (uint_arg)
-                            arg_sign = 1;
-                        break;
-                    case 'l':
-                        ulong_arg = va_arg(ap, unsigned long int);
-                        if (ulong_arg)
-                            arg_sign = 1;
-                        break;
-                    }
-                }
-                str_arg = tmp; str_arg_l = 0;
-                /* NOTE: For d, i, u, o, x, and X conversions, if
-                   precision is specified, the '0' flag should be
-                   ignored. This is so with Solaris 2.6, Digital UNIX
-                   4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with
-                   Perl.
-                */
-                if (precision_specified)
-                    zero_padding = false;
-                if (fmt_spec == 'd') {
-                    if (force_sign && arg_sign >= 0)
-                        tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
-                    /* leave negative numbers for sprintf to handle,
-                       to avoid handling tricky cases like (short
-                       int)(-32768)
-                    */
-                } else if (alternate_form) {
-                    if (arg_sign != 0 && (fmt_spec == 'x' ||
-                                          fmt_spec == 'X')) {
-                        tmp[str_arg_l++] = '0';
-                        tmp[str_arg_l++] = fmt_spec;
-                    }
-                    /* alternate form should have no effect for p
-                       conversion, but ...
-                    */
-                }
-                zero_padding_insertion_ind = str_arg_l;
-                if (!precision_specified)
-                    precision = 1;   /* default precision is 1 */
-                if (precision == 0 && arg_sign == 0) {
-                    /* converted to null string */
-                    /* When zero value is formatted with an explicit
-                       precision 0, the resulting formatted string is
-                       empty (d, i, u, o, x, X, p).
-                    */
-                } else {
-                    char f[5]; int f_l = 0;
-                    f[f_l++] = '%';
-                        /* construct a simple format string for sprintf */
-                    if (!length_modifier) { }
-                    else if (length_modifier=='2') {
-                        f[f_l++] = 'l'; f[f_l++] = 'l';
-                    }
-                    else
-                        f[f_l++] = length_modifier;
-                    f[f_l++] = fmt_spec; f[f_l++] = '\0';
-                    if (fmt_spec == 'p')
-                        str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
-                    else if (fmt_spec == 'd') {  /* signed */
-                        switch (length_modifier) {
-                        case '\0':
-                        case 'h':
-                            str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg);
-                            break;
-                        case 'l':
-                            str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg);
-                            break;
-                        }
-                    } else {  /* unsigned */
-                        switch (length_modifier) {
-                        case '\0':
-                        case 'h':
-                            str_arg_l += sprintf(tmp+str_arg_l, f, uint_arg);
-                            break;
-                        case 'l':
-                            str_arg_l += sprintf(tmp+str_arg_l, f, ulong_arg);
-                            break;
-                        }
-                    }
-                    /* include the optional minus sign and possible "0x"
-                       in the region before the zero padding insertion point
-                    */
-                    if (zero_padding_insertion_ind < str_arg_l &&
-                        tmp[zero_padding_insertion_ind] == '-') {
-                        zero_padding_insertion_ind += 1;
-                    }
-                    if (zero_padding_insertion_ind + 1 < str_arg_l &&
-                        tmp[zero_padding_insertion_ind]   == '0' &&
-                        (tmp[zero_padding_insertion_ind+1] == 'x' ||
-                         tmp[zero_padding_insertion_ind+1] == 'X') ) {
-                        zero_padding_insertion_ind += 2;
-                    }
-                }
-                {
-                    size_t const num_of_digits =
-                        str_arg_l - zero_padding_insertion_ind;
-                    if (alternate_form && fmt_spec == 'o'
-                        /* unless zero is already the first character */
-                        && !(zero_padding_insertion_ind < str_arg_l
-                             && tmp[zero_padding_insertion_ind] == '0')) {
-                        /* assure leading zero for alternate-form
-                           octal numbers
-                        */
-                        if (!precision_specified ||
-                            precision < num_of_digits+1) {
-                            /* precision is increased to force the
-                               first character to be zero, except if a
-                               zero value is formatted with an
-                               explicit precision of zero
-                            */
-                            precision = num_of_digits+1;
-                            precision_specified = true;
-                        }
-                    }
-                    /* zero padding to specified precision? */
-                    if (num_of_digits < precision)
-                        number_of_zeros_to_pad = precision - num_of_digits;
-                }
-                /* zero padding to specified minimal field width? */
-                if (!justify_left && zero_padding) {
-                    int const n =
-                        min_field_width - (str_arg_l+number_of_zeros_to_pad);
-                    if (n > 0)
-                        number_of_zeros_to_pad += n;
-                }
-            } break;
-            case 'f': {
-                char f[10];
-                if (precision_specified)
-                    snprintf(f, ARRAY_SIZE(f), "%%%u.%uf",
-                             (unsigned)min_field_width, (unsigned)precision);
-                else
-                    snprintf(f, ARRAY_SIZE(f), "%%%uf",
-                             (unsigned)min_field_width);
-
-                str_arg_l = sprintf(tmp, f, va_arg(ap, double));
-                str_arg = &tmp[0];
-
-                min_field_width = 0;
-                zero_padding_insertion_ind = 0;
-            } break;
-            default:
-                /* Unrecognized conversion specifier.  Discard the
-                   unrecognized conversion, just keep the unrecognized
-                   conversion character.
-                */
-                zero_padding = false;
-                    /* turn zero padding off for non-numeric convers. */
-                /* reset flags */
-                justify_left = true;
-                min_field_width = 0;
-                str_arg = p;
-                str_arg_l = 0;
-                if (*p)
-                    /* include invalid conversion specifier unchanged
-                       if not at end-of-string
-                    */
-                    ++str_arg_l;
-                break;
-            }
-            if (*p)
-                p++;  /* step over the just processed conversion specifier */
-            /* insert padding to the left as requested by
-               min_field_width; this does not include the zero padding
-               in case of numerical conversions
-            */
+    assert((size_t)rc == rc);
 
-            if (!justify_left) {
-                /* left padding with blank or zero */
-                int n = min_field_width - (str_arg_l + number_of_zeros_to_pad);
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t const avail = str_m - str_l;
-                        fast_memset(str + str_l, (zero_padding ? '0' : ' '),
-                                    (MIN(n, avail)));
-                    }
-                    str_l += n;
-                }
-            }
-            /* zero padding as requested by the precision or by the
-               minimal field width for numeric conversions required?
-            */
-            if (number_of_zeros_to_pad <= 0) {
-                /* will not copy first part of numeric right now,
-                   force it to be copied later in its entirety
-                */
-                zero_padding_insertion_ind = 0;
-            } else {
-                {
-                    /* insert first part of numerics (sign or '0x') before
-                       zero padding
-                    */
-                    int const n = zero_padding_insertion_ind;
-                    if (n > 0) {
-                        if (str_l < str_m) {
-                            size_t const avail = str_m - str_l;
-                            fast_memcpy(str + str_l, str_arg, (MIN(n, avail)));
-                        }
-                        str_l += n;
-                    }
-                }
-                {
-                    /* insert zero padding as requested by the precision
-                       or min field width
-                    */
-                    int const n = number_of_zeros_to_pad;
-                    if (n > 0) {
-                        if (str_l < str_m) {
-                            size_t const avail = str_m - str_l;
-                            fast_memset(str + str_l, '0', (MIN(n, avail)));
-                        }
-                        str_l += n;
-                    }
-                }
-            }
-            /* insert formatted string (or as-is conversion specifier
-               for unknown conversions)
-            */
-            {
-                int const n = str_arg_l - zero_padding_insertion_ind;
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t const avail = str_m-str_l;
-                        fast_memcpy(str + str_l,
-                                    str_arg + zero_padding_insertion_ind,
-                                    MIN(n, avail));
-                    }
-                    str_l += n;
-                }
-            }
-            /* insert right padding */
-            if (justify_left) {
-                /* right blank padding to the field width */
-                int const n =
-                    min_field_width - (str_arg_l + number_of_zeros_to_pad);
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t const avail = str_m - str_l;
-                        fast_memset(str+str_l, ' ', (MIN(n, avail)));
-                    }
-                    str_l += n;
-                }
-            }
-        }
-    }
-    if (str_m > 0) {
-        /* make sure the string is null-terminated even at the expense
-           of overwriting the last character (shouldn't happen, but
-           just in case)
-        */
-        str[MIN(str_l, str_m - 1)] = '\0';
-    }
-    *sizeP = str_l;
+    *sizeP = (size_t)rc;
 }
 
 
 
 int
 pm_snprintf(char *       const dest,
-            size_t       const str_m,
+            size_t       const maxSize,
             const char * const fmt,
             ...) {
 
@@ -761,7 +87,7 @@ pm_snprintf(char *       const dest,
 
     va_start(ap, fmt);
 
-    pm_vsnprintf(dest, str_m, fmt, ap, &size);
+    pm_vsnprintf(dest, maxSize, fmt, ap, &size);
 
     va_end(ap);
 
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 677e24cb..1f03e4f2 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -124,12 +124,9 @@ strncaseeq(const char * const comparand,
 #define TOUPPER(C) ((char)toupper((unsigned char)(C)))
 
 
-/* These are all private versions of commonly available standard C
-   library subroutines whose names are the same except with the N at
-   the end.  Because not all standard C libraries have them all,
-   Netpbm must include them in its own libraries, and because some
-   standard C libraries have some of them, Netpbm must use different
-   names for them.
+/* Most of these are private versions of commonly available standard C library
+   subroutines whose names are similar.  They're here because not all standard
+   C libraries have them.
 
    The GNU C library has all of them.  All but the oldest standard C libraries
    have snprintf().
@@ -160,13 +157,13 @@ pm_strnlen(const char * const s,
 
 int
 pm_snprintf(char *       const dest,
-            size_t       const str_m,
+            size_t       const maxSize,
             const char * const fmt,
             ...) PM_GNU_PRINTF_ATTR(3,4);
 
 void
 pm_vsnprintf(char *       const str,
-             size_t       const str_m,
+             size_t       const maxSize,
              const char * const fmt,
              va_list            ap,
              size_t *     const sizeP);
diff --git a/lib/util/shhopt.h b/lib/util/shhopt.h
index 9ba072cb..27adc144 100644
--- a/lib/util/shhopt.h
+++ b/lib/util/shhopt.h
@@ -209,7 +209,7 @@ typedef struct {
 /* OPTENT3 is the same as OPTENTRY except that it also sets the "specified"
    element of the table entry (so it assumes OPTION_DEF is a table of optEntry
    instead of optStruct).  This is a pointer to a variable that the parser
-   will set to and indication of whether the option appears in the command
+   will set to an indication of whether the option appears in the command
    line.  1 for yes; 0 for no.
 
    HISTORICAL NOTE: Until 2019, this was the number of times the option was
@@ -224,8 +224,8 @@ typedef struct {
        const char * alpha_filename
        unsigned int alpha_spec;
        MALLOCARRAY_NOFAIL(option_def, 100);
-       OPTENT3('h', "verbose",  OPT_FLAG,   &verbose_flag    NULL);
-       OPTENT3(0,   "alphaout", OPT_STRING, &alpha_filename, &alpha_spec);
+       OPTENT3('h', "verbose",  OPT_FLAG,   &verbose_flag,   NULL,        0);
+       OPTENT3(0,   "alphaout", OPT_STRING, &alpha_filename, &alpha_spec, 0);
 */
 
 #define OPTENT3(shortvalue,longvalue,typevalue,outputvalue,specifiedvalue, \
diff --git a/other/pamunlookup.c b/other/pamunlookup.c
index 624951df..defa7b1f 100644
--- a/other/pamunlookup.c
+++ b/other/pamunlookup.c
@@ -19,7 +19,6 @@
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
-#include "pm_system.h"
 #include "nstring.h"
 #include "pam.h"
 #include "pammap.h"
diff --git a/test/Execute-Tests b/test/Execute-Tests
index 9b51cfd5..3c231d98 100755
--- a/test/Execute-Tests
+++ b/test/Execute-Tests
@@ -186,7 +186,7 @@ elif [ $VALGRIND_TESTS = "on" ]
 
   for i in awk basename cat cksum cmp comm cp cut date dirname \
            egrep fgrep file grep gs head iconv ls mkdir mktemp perl \
-           printf rm sed seq sh sort tee tr uniq wc \
+           printf rm sed seq sh sort tail tee tr uniq wc \
            testrandom Available-Testprog
 
     # Tell valgrind not to probe execution of the above programs.
diff --git a/test/Test-Order b/test/Test-Order
index eafcb974..214cf4ba 100644
--- a/test/Test-Order
+++ b/test/Test-Order
@@ -16,6 +16,8 @@ pbmtext.test
 pbmtext-bdf.test
 pbmtext-iso88591.test
 pbmtext-utf8.test
+pbmtextps-dump.test
+pbmtextps.test
 pbmupc.test
 pgmramp.test
 pamgauss.test
@@ -70,6 +72,8 @@ pamhue.test
 
 pbmclean.test
 pamcut.test
+pamcat1.test
+pamcat2.test
 pnmcat.test
 pamdice.test
 pamundice.test
@@ -232,6 +236,7 @@ pj-roundtrip.test
 ps-roundtrip.test
 ps-flate-roundtrip.test
 ps-alt-roundtrip.test
+qoi-roundtrip.test
 sgi-roundtrip.test
 sbig-roundtrip.test
 st4-roundtrip.test
diff --git a/test/all-in-place.ok b/test/all-in-place.ok
index 0af78737..31854728 100644
--- a/test/all-in-place.ok
+++ b/test/all-in-place.ok
@@ -41,6 +41,7 @@ pamarith: ok
 pambackground: ok
 pambayer: ok
 pambrighten: ok
+pamcat: ok
 pamchannel: ok
 pamcomp: ok
 pamcrater: ok
@@ -202,7 +203,6 @@ pjtoppm: ok
 pktopbm: ok
 pngtopam: ok
 pnmalias: ok
-pnmcat: ok
 pnmcolormap: ok
 pnmconvol: ok
 pnmcrop: ok
diff --git a/test/all-in-place.test b/test/all-in-place.test
index c2b99328..386d2872 100755
--- a/test/all-in-place.test
+++ b/test/all-in-place.test
@@ -83,6 +83,7 @@ ordinary_testprogs="\
   pambackground \
   pambayer \
   pambrighten \
+  pamcat \
   pamchannel \
   pamcomp \
   pamcrater \
@@ -244,7 +245,6 @@ ordinary_testprogs="\
   pktopbm \
   pngtopam \
   pnmalias \
-  pnmcat \
   pnmcolormap \
   pnmconvol \
   pnmcrop \
@@ -438,4 +438,4 @@ testExitStatus anytopnm 0 $?
 manweb --help > /dev/null
     testExitStatus manweb 0 $?
 
-# We do not test vidtoppm.
\ No newline at end of file
+# We do not test vidtoppm.
diff --git a/test/gif-roundtrip.test b/test/gif-roundtrip.test
index a30be1aa..13ff595e 100755
--- a/test/gif-roundtrip.test
+++ b/test/gif-roundtrip.test
@@ -10,40 +10,40 @@ tmpdir=${tmpdir:-/tmp}
 
 echo "Test 1. Should print 1926073387 101484"
 
-rose_ppm=${tmpdir}/rose.ppm
+test_ppm=${tmpdir}/testimg.ppm
 
-cp testimg.ppm ${rose_ppm} &&
-ppmtorgb3 ${rose_ppm}
+cp testimg.ppm ${tmpdir} &&
+ppmtorgb3 ${test_ppm}
 
-rose_red=${tmpdir}/rose.red
-rose_grn=${tmpdir}/rose.grn
-rose_blu=${tmpdir}/rose.blu
+test_red=${tmpdir}/testimg.red
+test_grn=${tmpdir}/testimg.grn
+test_blu=${tmpdir}/testimg.blu
 out_red=${tmpdir}/out.red
 out_grn=${tmpdir}/out.grn
 #out_blu=${tmpdir}/out.blu
 
-pamtogif ${rose_red} | giftopnm > ${out_red} &&
-pamtogif ${rose_grn} | giftopnm > ${out_grn} &&
-pamtogif ${rose_blu} | giftopnm | \
+pamtogif ${test_red} | giftopnm > ${out_red} &&
+pamtogif ${test_grn} | giftopnm > ${out_grn} &&
+pamtogif ${test_blu} | giftopnm | \
   rgb3toppm ${out_red} ${out_grn} - | \
   cksum
 
-rm ${rose_ppm} ${rose_grn} ${rose_blu} ${out_red} ${out_grn}
+rm ${test_ppm} ${test_grn} ${test_blu} ${out_red} ${out_grn}
 
 echo "Test 2. Should produce 1571496937 33838 six times"
 
-rose_gif=${tmpdir}/rose.gif
+test_gif=${tmpdir}/testimg.gif
 
-cat ${rose_red} | cksum
-pamtogif ${rose_red} | giftopnm | cksum
-pamtogif -interlace ${rose_red} | giftopnm | cksum
-pamtogif -noclear ${rose_red} | giftopnm | cksum
-pamtogif -sort ${rose_red} | tee ${rose_gif} | \
+cat ${test_red} | cksum
+pamtogif ${test_red} | giftopnm | cksum
+pamtogif -interlace ${test_red} | giftopnm | cksum
+pamtogif -noclear ${test_red} | giftopnm | cksum
+pamtogif -sort ${test_red} | tee ${test_gif} | \
   giftopnm | cksum
-echo "junk" >> ${rose_gif} && \
-  giftopnm -image=1 -quitearly ${rose_gif} | cksum
+echo "junk" >> ${test_gif} && \
+  giftopnm -image=1 -quitearly ${test_gif} | cksum
 
-rm  ${rose_gif} ${rose_red}
+rm  ${test_gif} ${test_red}
 
 echo "Test 3. Should produce 281226646 481 six times"
 # maze.pbm is too small for -noclear to take effect 
@@ -78,7 +78,7 @@ echo ""
 echo "Test 5. Should produce: N : 0 0 0 0 : 0 , N : 0 0 0 0 : 0"
 echo "(N=238, 239, 240, 241, 255, 256, 257, 4030, 4031, 4097)"
 
-rose_pgm=${tmpdir}/rose.pgm
+test_pgm=${tmpdir}/testimg.pgm
 
 # The following awk scripts produce a PGM file with no repeated
 # sequences.  Obviously this cannot be compressed at all; the codes
@@ -114,14 +114,14 @@ awk -v maxval=${maxval} 'BEGIN \
 
 for size in 238 239 240 241 255 256 257
   do
-  pamcut -height=${size} ${test257_pgm} > ${rose_pgm} &&
-  pamtogif -verbose ${rose_pgm} | giftopnm | pamdepth ${maxval} | \
-    cmp - ${rose_pgm}
-  echo -n ${size} ":" ${PIPESTATUS[@]} ":" $? ", "
-  pamtogif -nolzw -verbose ${rose_pgm} | giftopnm | pamdepth ${maxval} | \
-    cmp - ${rose_pgm}
-  echo ${size} ":" ${PIPESTATUS[@]} ":" $?
-  rm ${rose_pgm}
+  pamcut -height=${size} ${test257_pgm} > ${test_pgm} &&
+  pamtogif -verbose ${test_pgm} | giftopnm | pamdepth ${maxval} | \
+    cmp - ${test_pgm}
+  printf "${size} : ${PIPESTATUS[*]} : $? , "
+  pamtogif -nolzw -verbose ${test_pgm} | giftopnm | pamdepth ${maxval} | \
+    cmp - ${test_pgm}
+  printf "${size} : ${PIPESTATUS[*]} : $?\n"
+  rm ${test_pgm}
   done 
 
 rm ${test257_pgm}
@@ -144,16 +144,16 @@ awk -v maxval=${maxval} 'BEGIN \
 
 for size in 4030 4031 4097
   do
-  pamcut -height ${size} ${test4097_pgm} > ${rose_pgm} &&
-  pamtogif -verbose ${rose_pgm} | giftopnm | pamdepth ${maxval} | \
-    cmp - ${rose_pgm}
+  pamcut -height ${size} ${test4097_pgm} > ${test_pgm} &&
+  pamtogif -verbose ${test_pgm} | giftopnm | pamdepth ${maxval} | \
+    cmp - ${test_pgm}
   # pamdepth ${maxval} is necessary because
   # giftopnm output is maxval 255
-  echo -n ${size} ":" ${PIPESTATUS[@]} ":" $? ", "
-  pamtogif -nolzw ${rose_pgm} | giftopnm | pamdepth ${maxval} | \
-    cmp - ${rose_pgm}
-  echo ${size} ":" ${PIPESTATUS[@]} ":" $?
-  rm ${rose_pgm}
+  printf "${size} : ${PIPESTATUS[*]} : $? , "
+  pamtogif -nolzw ${test_pgm} | giftopnm | pamdepth ${maxval} | \
+    cmp - ${test_pgm}
+  printf "${size} : ${PIPESTATUS[*]} : $?\n"
+  rm ${test_pgm}
   done 
 
 rm ${test4097_pgm}
diff --git a/test/gif-transparent1.test b/test/gif-transparent1.test
index c2da5468..74496787 100755
--- a/test/gif-transparent1.test
+++ b/test/gif-transparent1.test
@@ -52,7 +52,7 @@ i=1
 for test_pnm in ${argyle_ppm} ${gingham_ppm} ${madras_ppm} \
 			      ${alpha0_pbm} ${alpha1_pbm}
   do
-  echo -n "Image $i: "; i=$((i+1));
+  printf "Image $i: "; i=$((i+1));
   pamtogif ${test_pnm} | giftopnm | tee ${out_pnm} | \
     cmp -s - ${test_pnm}; result=$?
 
diff --git a/test/legacy-names.ok b/test/legacy-names.ok
index 27baf672..f491a1ff 100644
--- a/test/legacy-names.ok
+++ b/test/legacy-names.ok
@@ -12,6 +12,7 @@ pgmoil: ok
 pgmslice: ok
 pngtopnm: ok
 pnmarith: ok
+pnmcat: ok
 pnmcomp: ok
 pnmcut: ok
 pnmdepth: ok
diff --git a/test/legacy-names.test b/test/legacy-names.test
index e20dde76..9c0c5697 100755
--- a/test/legacy-names.test
+++ b/test/legacy-names.test
@@ -69,6 +69,7 @@ ordinary_testprogs="\
   pgmslice \
   pngtopnm \
   pnmarith \
+  pnmcat \
   pnmcomp \
   pnmcut \
   pnmdepth \
diff --git a/test/pamarith.ok b/test/pamarith.ok
index c9fc49db..973b8d1b 100644
--- a/test/pamarith.ok
+++ b/test/pamarith.ok
@@ -2,8 +2,8 @@ Test 1
 P2
 16 2
 15
-0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
+0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
+2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 
 -add
 P2 16 1 15 2 3 4 5 6 7 8 9 10 11 12 13 14 15 15 15  
 1927712885 59
diff --git a/test/pamarith.test b/test/pamarith.test
index 9c95db80..34922a90 100755
--- a/test/pamarith.test
+++ b/test/pamarith.test
@@ -1,6 +1,6 @@
 #! /bin/sh
 # This script tests: pamarith
-# Also requires: pamtopnm rgb3toppm pamenlarge pnmcat pamseq pbmmake pgmmake
+# Also requires: pamtopnm rgb3toppm pamenlarge pamcat pamseq pbmmake pgmmake
 # Also requires: ppmpat pamchannel
 
 tmpdir=${tmpdir:-/tmp}
@@ -18,7 +18,7 @@ pgmmake -maxval 15 0.15 16 1 > ${input2_pgm}
 rgb3toppm ${input1_pgm} ${input1_pgm} ${input1_pgm} > ${input1_ppm}
 rgb3toppm ${input2_pgm} ${input2_pgm} ${input2_pgm} > ${input2_ppm}
 
-pnmcat -tb -plain ${input1_pgm} ${input2_pgm}
+pamcat -tb ${input1_pgm} ${input2_pgm} -plain
 
 for fn in "-add" "-subtract" "-multiply" "-divide" "-difference" \
     "-minimum" "-maximum" "-mean" "-compare" "-equal" \
@@ -43,7 +43,7 @@ echo "Test 2 PBM"
 pbmmake -g 8 1 > ${input1_pbm}
 pbmmake -g 2 1 | pamenlarge -xscale=4 > ${input2_pbm}
 
-pnmcat -tb -plain ${input1_pbm} ${input2_pbm}
+pamcat -tb -plain ${input1_pbm} ${input2_pbm}
 
 for fn in "-add" "-subtract" "-multiply" "-divide" "-difference" \
     "-minimum" "-maximum" "-mean" "-compare" "-equal" \
@@ -127,80 +127,80 @@ test_out=${tmpdir}/test_out
 # multiple functions
 
 pamarith -add -subtract testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -multiply -divide testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -difference -minimum testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -maximum -mean testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -compare -and testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -compare -equal testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -or -nand testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -nor -xor testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -shiftleft -shiftright testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -add does not take a value
 
 pamarith -add=1 testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
  test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # No function
 
 pamarith -plain testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # Just one input image file
 
 pamarith -add testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # No input image file
 
 pamarith -add > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -208,48 +208,48 @@ pamarith -add > ${test_out} || \
 
 pamchannel -infile testimg.ppm 0 1 | \
   pamarith -add testimg.ppm - > ${test_out} || \
-  echo -n "Expected failure 15"
+  printf "Expected failure 15"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # Input images with different x/y dimensions
 
 pamarith -add testimg.ppm testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 16"
+  printf "Expected failure 16"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamenlarge -xscale=2 testgrid.pbm | \
   pamarith -add testgrid.pbm - > ${test_out} || \
-  echo -n "Expected failure 17"
+  printf "Expected failure 17"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamenlarge -yscale=3 testgrid.pbm | \
   pamarith -add testgrid.pbm - > ${test_out} || \
-  echo -n "Expected failure 18"
+  printf "Expected failure 18"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # Invalid usage of -closeness
 
 pamarith -equal -closeness=100.1 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 19"
+  printf "Expected failure 19"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -equal -closeness=-10 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 20"
+  printf "Expected failure 20"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -closeness -equal testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 21"
+  printf "Expected failure 21"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -compare -closeness=10 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 22"
+  printf "Expected failure 22"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -267,64 +267,64 @@ pgmmake -maxval=8191 1.0 3 1 > ${input5_pgm}
 # Bit string functions - Maxval must match
 
 pamarith -and ${input3_pgm} ${input5_pgm} > ${test_out} || \
-  echo -n "Expected failure 23"
+  printf "Expected failure 23"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -or ${input3_pgm} ${input5_pgm} > ${test_out} || \
-  echo -n "Expected failure 24"
+  printf "Expected failure 24"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -nand ${input3_pgm} ${input5_pgm} > ${test_out} || \
-  echo -n "Expected failure 25"
+  printf "Expected failure 25"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -nor ${input3_pgm} ${input5_pgm} > ${test_out} || \
-  echo -n "Expected failure 26"
+  printf "Expected failure 26"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -xor ${input3_pgm} ${input5_pgm} > ${test_out} || \
-  echo -n "Expected failure 27"
+  printf "Expected failure 27"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # Bit string functions - Maxval must be 2^n -1
 
 pamarith -and ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 28"
+  printf "Expected failure 28"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -or ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 29"
+  printf "Expected failure 29"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -nand ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 30"
+  printf "Expected failure 30"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -nor ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 31"
+  printf "Expected failure 31"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -xor ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 32"
+  printf "Expected failure 32"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -shiftleft ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 33"
+  printf "Expected failure 33"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -shiftright ${input4_pgm} ${input4_pgm} > ${test_out} || \
-  echo -n "Expected failure 34"
+  printf "Expected failure 34"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -335,32 +335,32 @@ rm ${input3_pgm} ${input4_pgm} ${input5_pgm}
 # three or more inputs.
 
 pamarith -subtract testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 35"
+  printf "Expected failure 35"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -divide testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 36"
+  printf "Expected failure 36"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -compare testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 37"
+  printf "Expected failure 37"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -difference testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 38"
+  printf "Expected failure 38"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -shiftleft testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 39"
+  printf "Expected failure 39"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -shiftright testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 40"
+  printf "Expected failure 40"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -368,11 +368,11 @@ pamarith -shiftright testimg.ppm testimg.ppm testimg.ppm > ${test_out} || \
 # These two cases should be removed once improvements are made.
 
 pamarith -equal testgrid.pbm testgrid.pbm testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 41"
+  printf "Expected failure 41"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamarith -mean testgrid.pbm  testgrid.pbm testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 42"
+  printf "Expected failure 42"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamcat1.ok b/test/pamcat1.ok
new file mode 100644
index 00000000..4e23666f
--- /dev/null
+++ b/test/pamcat1.ok
@@ -0,0 +1,150 @@
+Test 1.  Should print 15135078 29, then 0 fourteen times
+15135078 29
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Test 2.  Should print 1957849403 59, 0 six times, 1 once
+1957849403 59
+0
+0
+0
+0
+0
+0
+1
+Test 3.  Should print 2673197404 69, 0 six times, 1 once
+2673197404 69
+0
+0
+0
+0
+0
+0
+1
+Test 4.  Should print 2285402562 36, then 0 twelve times
+2285402562 36
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Test 5.  Should print 1836757613 62, 0 six times, 1 once
+1836757613 62
+0
+0
+0
+0
+0
+0
+1
+Test 6.  Should print 3601245348 137, 0 six times, 1 once
+3601245348 137
+0
+0
+0
+0
+0
+0
+1
+Test 7.  Should print 1572996771 71, then 0 twelve times
+1572996771 71
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Test 8.  Should print 270413826 252, 0 six times, 1 once
+270413826 252
+0
+0
+0
+0
+0
+0
+1
+Test 9.  Should print 2942772630 192, 0 six times, 1 once
+2942772630 192
+0
+0
+0
+0
+0
+0
+1
+Test 10.  Should print 2700536985 95, then 0 twelve times
+2700536985 95
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Test 11.  Should print 2193235886 195, 0 eight times, 1 once
+2193235886 195
+0
+0
+0
+0
+0
+0
+0
+0
+1
+Test 12.  Should print 2773166369 245, 0 nine times, 1 once
+2773166369 245
+0
+0
+0
+0
+0
+0
+0
+0
+0
+1
+Test Invalid
+Expected failure 1 1
+Expected failure 2 1
+Expected failure 3 1
+Expected failure 4 1
+Expected failure 5 1
+Expected failure 6 1
+Expected failure 7 1
+Expected failure 8 1
+Expected failure 9 1
+Expected failure 10 1
+Expected failure 11 1
+Expected failure 12 1
+Expected failure 13 1
+Expected failure 14 1
diff --git a/test/pamcat1.test b/test/pamcat1.test
new file mode 100755
index 00000000..67d941cc
--- /dev/null
+++ b/test/pamcat1.test
@@ -0,0 +1,271 @@
+#! /bin/sh
+# This script tests: pamcat
+# Also requires: pbmmake pgmmake ppmmake pamseq pamrestack
+
+tmpdir=${tmpdir:-/tmp}
+check10x10_pbm=${tmpdir}/check10x10.pbm
+check40x10_pbm=${tmpdir}/check40x10.pbm
+check10x30_pbm=${tmpdir}/check10x30.pbm
+
+gray5x5_pgm=${tmpdir}/gray5x5.pgm
+gray10x5_pgm=${tmpdir}/gray10x5.pgm
+gray5x25_pgm=${tmpdir}/gray5x25.pgm
+
+yellow4x5_ppm=${tmpdir}/yellow4x5.ppm
+yellow16x5_ppm=${tmpdir}/yellow16x5.ppm
+yellow4x15_ppm=${tmpdir}/yellow4x15.ppm
+
+seq2_4h_pam=${tmpdir}/seq2_4h.pam
+seq2_4h4_pam=${tmpdir}/seq2_4h4.pam
+seq2_4v_pam=${tmpdir}/seq2_4v.pam
+seq2_4v3_pam=${tmpdir}/seq2_4v3.pam
+
+
+echo "Test 1.  Should print 15135078 29, then 0 fourteen times"
+
+pbmmake -g 10 10 | tee ${check10x10_pbm} | cksum
+for opt in "-leftright" "-lr" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-topbottom" "-tb" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black"
+  do
+  pamcat ${opt} ${check10x10_pbm} | cmp -s ${check10x10_pbm} - ; echo $?
+  done
+
+
+echo "Test 2.  Should print 1957849403 59, 0 six times, 1 once"
+pbmmake -g 40 10 | tee ${check40x10_pbm} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-tb"
+  do
+  pamcat ${opt} ${check10x10_pbm} ${check10x10_pbm} \
+         ${check10x10_pbm} ${check10x10_pbm} | \
+  cmp -s ${check40x10_pbm} - ; echo $?
+  done
+
+
+echo "Test 3.  Should print 2673197404 69, 0 six times, 1 once"
+pbmmake -g 10 30 | tee ${check10x30_pbm} | cksum
+for opt in "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black" \
+    "-lr"
+  do
+  pamcat ${opt} ${check10x10_pbm} ${check10x10_pbm} ${check10x10_pbm} |\
+  cmp -s ${check10x30_pbm} - ; echo $? 	
+  done
+
+rm ${check10x10_pbm} ${check40x10_pbm} ${check10x30_pbm}
+
+
+echo "Test 4.  Should print 2285402562 36, then 0 twelve times"
+
+pgmmake 0.125 5 5 | tee ${gray5x5_pgm} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black"
+  do
+  pamcat ${opt} ${gray5x5_pgm} | cmp -s ${gray5x5_pgm} - ; echo $?
+  done
+
+
+echo "Test 5.  Should print 1836757613 62, 0 six times, 1 once"
+pgmmake 0.125 10 5 | tee ${gray10x5_pgm} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-tb"
+  do
+  pamcat ${opt} ${gray5x5_pgm} ${gray5x5_pgm} |\
+  cmp -s ${gray10x5_pgm} - ; echo $?
+  done
+
+
+echo "Test 6.  Should print 3601245348 137, 0 six times, 1 once"
+pgmmake 0.125 5 25 | tee ${gray5x25_pgm} | cksum
+for opt in "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black" \
+    "-lr"
+  do
+  pamcat ${opt} ${gray5x5_pgm} ${gray5x5_pgm} ${gray5x5_pgm} \
+	 ${gray5x5_pgm} ${gray5x5_pgm} |\
+  cmp -s ${gray5x25_pgm} - ; echo $?
+  done
+
+
+rm ${gray5x5_pgm} ${gray10x5_pgm} ${gray5x25_pgm}
+
+
+echo "Test 7.  Should print 1572996771 71, then 0 twelve times"
+ppmmake rgb:255/255/1 4 5 | tee ${yellow4x5_ppm} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black"
+  do
+  pamcat ${opt} ${yellow4x5_ppm} |\
+  cmp -s ${yellow4x5_ppm} - ; echo $?
+  done
+
+
+echo "Test 8.  Should print 270413826 252, 0 six times, 1 once"
+ppmmake rgb:255/255/1 16 5 | tee ${yellow16x5_ppm} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-tb"
+  do
+  pamcat ${opt} ${yellow4x5_ppm} ${yellow4x5_ppm} \
+         ${yellow4x5_ppm} ${yellow4x5_ppm} |\
+  cmp -s ${yellow16x5_ppm} - ; echo $?
+  done
+
+
+echo "Test 9.  Should print 2942772630 192, 0 six times, 1 once"
+ppmmake rgb:255/255/1 4 15 | tee ${yellow4x15_ppm} | cksum
+for opt in "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black" \
+    "-lr"
+  do
+  pamcat ${opt} ${yellow4x5_ppm} ${yellow4x5_ppm} ${yellow4x5_ppm} |\
+  cmp -s ${yellow4x15_ppm} - ; echo $?
+  done
+
+rm ${yellow4x5_ppm} ${yellow16x5_ppm} ${yellow4x15_ppm}
+
+
+echo "Test 10.  Should print 2700536985 95, then 0 twelve times"
+pamseq 2 4 | tee ${seq2_4h_pam} | cksum
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black"
+  do
+  pamcat ${opt} ${seq2_4h_pam}  |\
+  cmp -s ${seq2_4h_pam} - ; echo $?
+  done
+
+
+echo "Test 11.  Should print 2193235886 195, 0 eight times, 1 once"
+pamrestack -width=1 ${seq2_4h_pam} | tee ${seq2_4v_pam} |\
+  pamenlarge -xscale=3 |\
+  tee ${seq2_4v3_pam} | cksum
+pamcat -lr ${seq2_4v_pam} ${seq2_4v_pam} | pamcat -lr - ${seq2_4v_pam} |\
+  cmp -s ${seq2_4v3_pam} - ; echo $?
+pamcat -lr ${seq2_4v_pam} | pamcat -lr - ${seq2_4v_pam}  ${seq2_4v_pam} |\
+  cmp -s ${seq2_4v3_pam} - ; echo $?
+for opt in "-leftright" "-lr -jtop" "-lr -jcenter" "-lr -jbottom" \
+    "-lr -white" "-lr -black" \
+    "-topbottom"
+  do
+  pamcat ${opt} ${seq2_4v_pam} ${seq2_4v_pam} ${seq2_4v_pam} |\
+  cmp -s ${seq2_4v3_pam} - ; echo $?
+  done
+
+
+echo "Test 12.  Should print 2773166369 245, 0 nine times, 1 once"
+pamenlarge -yscale 4 ${seq2_4h_pam} | tee ${seq2_4h4_pam} | cksum
+pamcat -tb ${seq2_4h_pam} |\
+    pamcat -tb - ${seq2_4h_pam} ${seq2_4h_pam} ${seq2_4h_pam} |\
+    cmp -s ${seq2_4h4_pam} - ; echo $?
+pamcat -tb ${seq2_4h_pam} ${seq2_4h_pam} |\
+    pamcat -tb - ${seq2_4h_pam} ${seq2_4h_pam} |\
+    cmp -s ${seq2_4h4_pam} - ; echo $?
+pamcat -tb ${seq2_4h_pam} ${seq2_4h_pam} ${seq2_4h_pam}|\
+    pamcat -tb - ${seq2_4h_pam} |\
+    cmp -s ${seq2_4h4_pam} - ; echo $?
+for opt in "-topbottom" "-tb -jleft"  "-tb -jcenter" "-tb --jright" \
+    "-tb -white" "-tb -black" \
+    "-leftright"
+  do
+  pamcat ${opt} ${seq2_4h_pam} ${seq2_4h_pam} ${seq2_4h_pam} ${seq2_4h_pam} |\
+  cmp -s ${seq2_4h4_pam} - ; echo $?
+  done
+
+rm ${seq2_4h_pam} ${seq2_4v_pam} ${seq2_4v3_pam} ${seq2_4h4_pam}
+
+
+
+echo "Test Invalid"
+
+test_out=${tmpdir}/test_out
+
+echo 1>&2
+echo "Invalid command-line argument combinations." 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+# direction not specified
+pamcat testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 1"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# both directions specified
+pamcat -topbottom -leftright testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 2"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# both pad colors specified
+pamcat -topbottom -white -black testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 3"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# justification parameters overspecified
+pamcat -lr -jtop -jbottom testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 4"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -lr -jtop -jcenter testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 5"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -lr -jcenter -jbottom testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 6"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -tb -jleft -jright testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 7"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -tb -jleft -jcenter testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 8"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -tb -jcenter -jright testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 9"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# justification parameter in the wrong direction
+pamcat -lr -jleft    testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 10"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -lr -jright   testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 11"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -tb -jtop     testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 12"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+pamcat -tb -jbottom  testgrid.pbm testimg.ppm > ${test_out} || \
+  printf "Expected failure 13"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# more than one input image from standard input
+cat testgrid.pbm | pamcat -lr - - testimg.ppm > ${test_out} || \
+  printf "Expected failure 14"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
diff --git a/test/pamcat2.ok b/test/pamcat2.ok
new file mode 100644
index 00000000..549fe97a
--- /dev/null
+++ b/test/pamcat2.ok
@@ -0,0 +1,93 @@
+Test 1.
+P1
+1 1
+0
+P1
+1 1
+0
+P1
+1 1
+1
+P1
+1 1
+0
+Test 2.
+P1
+7 5
+0000000
+1000001
+1000001
+1000001
+1000001
+P1
+7 5
+1000001
+1000001
+0000000
+1000001
+1000001
+P1
+7 5
+1000001
+1000001
+1000001
+1000001
+0000000
+P1
+7 5
+1000001
+1000001
+0000000
+1000001
+1000001
+P1
+5 7
+01111
+00000
+00000
+00000
+00000
+00000
+01111
+P1
+5 7
+11011
+00000
+00000
+00000
+00000
+00000
+11011
+P1
+5 7
+11110
+00000
+00000
+00000
+00000
+00000
+11110
+P1
+5 7
+11011
+00000
+00000
+00000
+00000
+00000
+11011
+Test 3.
+1715060535 12
+1715060535 12
+3621575043 12
+3621575043 12
+Test 4.
+1972597727 14
+1972597727 14
+Test 5.
+2175144709 10456
+2175144709 10456
+2175144709 10456
+609107534 10444
+609107534 10444
+609107534 10444
diff --git a/test/pamcat2.test b/test/pamcat2.test
new file mode 100755
index 00000000..6c76568d
--- /dev/null
+++ b/test/pamcat2.test
@@ -0,0 +1,56 @@
+#! /bin/sh
+# This script tests: pamcat
+# Also requires: pbmmake pamflip
+
+tmpdir=${tmpdir:-/tmp}
+dotw_pbm=${tmpdir}/dotw.pbm
+dotb_pbm=${tmpdir}/dotb.pbm
+check5x5_pbm=${tmpdir}/check5x5.pbm
+dot_ppm=${tmpdir}/dot.ppm
+
+echo "Test 1."
+pbmmake -w 1 1 | tee ${dotw_pbm} | pamcat -tb -plain
+pamcat -lr -plain ${dotw_pbm}
+pbmmake -b 1 1 | tee ${dotb_pbm} | pamcat -tb -plain
+pamcat -tb -plain ${dotw_pbm}
+
+echo "Test 2."
+
+pbmmake -w 5 5 > ${check5x5_pbm}
+
+pamcat -lr -jt -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -lr -jc -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -lr -jb -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -lr     -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+
+pamcat -tb -jl -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -tb -jc -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -tb -jr -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+pamcat -tb     -black ${dotw_pbm} ${check5x5_pbm} ${dotw_pbm} -plain
+
+echo "Test 3."
+pbmmake -b 1 1 > ${dotb_pbm}
+
+pamcat -lr -jt -white ${dotb_pbm} ${check5x5_pbm} ${dotb_pbm} | cksum
+pamcat -lr -jb -white ${dotb_pbm} ${check5x5_pbm} ${dotb_pbm} |\
+  pamflip -tb | cksum
+pamcat -tb -jl -white ${dotb_pbm} ${check5x5_pbm} ${dotb_pbm} |\
+  pamflip -ccw | cksum
+pamcat -tb -jr -white ${dotb_pbm} ${check5x5_pbm} ${dotb_pbm} |\
+  pamflip -cw | cksum
+
+echo "Test 4."
+ppmmake rgb:20/40/d0 1 1 | tee ${dot_ppm} | pamcat -lr | cksum
+pamcat -tb  ${dot_ppm} | cksum
+
+echo "Test 5."
+for just in -jtop -jcenter -jbottom
+do
+pamcat -lr ${just} ${dot_ppm} maze.pbm ${dot_ppm} | cksum
+done
+
+for just in -jleft -jcenter -jright
+do
+pamcat -tb ${just} ${dot_ppm} maze.pbm ${dot_ppm} | cksum
+done
+ 
\ No newline at end of file
diff --git a/test/pamchannel.test b/test/pamchannel.test
index f72725ee..f2662706 100755
--- a/test/pamchannel.test
+++ b/test/pamchannel.test
@@ -42,17 +42,17 @@ test_out=${tmpdir}/test_out
 echo "Test Invalid"
 
 pamchannel  -infile testgrid.pbm 1 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamchannel  -infile testimg.ppm 3 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamtopam testimg.ppm | pamchannel -infile=- 4 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pamcrater.test b/test/pamcrater.test
index 61414480..e0c98afb 100755
--- a/test/pamcrater.test
+++ b/test/pamcrater.test
@@ -64,21 +64,21 @@ echo "Error messages should appear below the line." 1>&2
 echo "------------------------------" 1>&2
 
 pamcrater -width 0 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcrater -height 0 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcrater -number 0 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcrater -test -radius=10 | pamshadedrelief -gamma 0 > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamcut.test b/test/pamcut.test
index 2299b4bc..a489635a 100755
--- a/test/pamcut.test
+++ b/test/pamcut.test
@@ -56,79 +56,79 @@ echo "-----------------------------------------------------------" 1>&2
 
 # overspecification
 pamcut -left=1 -right=1 -width=14 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -top=1 -bottom=1 -height=16 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -right=1 -cropright=1 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -top=1 -croptop=1 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -bottom=1 -cropbottom=1 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -left=1 -cropleft=1 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # excessive cropping
 pamcut -cropleft=7 -cropright=8 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -left=7 -right=6 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -croptop=8 -cropbottom=8 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -top=10 -bottom=9 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # pad absent
 pamcut -cropleft=1 -width=14 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut -croptop=1  -height=16 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # legacy style: insufficient number of positional parameters
 pamcut 5 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut 5 4 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamcut 5 5 30 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamdepth.test b/test/pamdepth.test
index 86f6c952..7ff73f41 100755
--- a/test/pamdepth.test
+++ b/test/pamdepth.test
@@ -30,11 +30,11 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamdepth 0 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamdepth 65536 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamditherbw.test b/test/pamditherbw.test
index 3f37f117..be560089 100755
--- a/test/pamditherbw.test
+++ b/test/pamditherbw.test
@@ -47,62 +47,62 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamditherbw -fs -atkinson       ${test_red} > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -floyd -atkinson    ${test_red} > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -dither8  -cluster3 ${test_red} > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -cluster3 -cluster4 ${test_red} > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -cluster3 -cluster8 ${test_red} > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -cluster4 -cluster8 ${test_red} > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -hilbert -threshold ${test_red} > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -clump=8            ${test_red} > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -fs -clump=8        ${test_red} > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -hilbert -clump=1   ${test_red} > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -th -value=-1       ${test_red} > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamditherbw -th -value=1.1      ${test_red} > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pamenlarge-pbm.test b/test/pamenlarge-pbm.test
index fbb2eced..bea740c6 100755
--- a/test/pamenlarge-pbm.test
+++ b/test/pamenlarge-pbm.test
@@ -18,7 +18,8 @@ LC_ALL=C awk 'BEGIN { print "P4";         # header
 # Test 1.
 echo "test 1"
  
-for xs in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # for xs in `seq 23`
+for xs in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+  # for xs in `seq 23`
   do
   pamenlarge -xscale=${xs} ${complete256_pbm} | cksum
   done
@@ -26,7 +27,8 @@ for xs in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # for xs i
 # Test 2.
 echo "test 2"
 
-for xs1 in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  # for xs in `seq 15`
+for xs1 in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+  # for xs1 in `seq 15`
   do
   xs2=$((30-${xs1}))
   pamenlarge -xscale=${xs1} ${complete256_pbm} | \
@@ -52,7 +54,7 @@ for width in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  # for width in `seq 16`
   done
  
   for xscale in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  # for xscale in `seq 16`
-    do echo -n ${xscale} " "
+    do printf "%d  " ${xscale}
     for width in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  # for width in `seq 16` 
       do pamenlarge -xscale=${xscale} ${test3_pbm}.${width} ; done | cksum
       #
diff --git a/test/pamfile.test b/test/pamfile.test
index a0a5c65b..260d0b27 100755
--- a/test/pamfile.test
+++ b/test/pamfile.test
@@ -31,16 +31,16 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamfile -size -machine  testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfile -count -machine testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 head -n1 testimg.ppm | pamfile > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamfind.test b/test/pamfind.test
index 87ef5f9f..9165fbdf 100755
--- a/test/pamfind.test
+++ b/test/pamfind.test
@@ -44,21 +44,21 @@ echo "-----------------------------------------------------------" 1>&2
 echo "Test Invalid"
 
 pamfind -color=black -target=1,1,1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfind -target=0,0 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfind -target=0,0,0,0 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfind testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamfix.test b/test/pamfix.test
index 89d3beb6..a18cae10 100755
--- a/test/pamfix.test
+++ b/test/pamfix.test
@@ -54,16 +54,16 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 printf "P2\n3 2\n7\n0 1 2\n6 7 8\n" | pamfix -change -clip > ${test_out} || \
-  echo -n "Expected failure 1";
+  printf "Expected failure 1";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 printf "P1\n5 5\n" | pamfix -truncate -plain > ${test_out} || \
-  echo -n "Expected failure 2";
+  printf "Expected failure 2";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 printf "P2\n3 3\255\n" | pamfix -truncate -plain > ${test_out} || \
-  echo -n "Expected failure 3";
+  printf "Expected failure 3";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamfunc.test b/test/pamfunc.test
index 0d1a4f3c..fe142be7 100755
--- a/test/pamfunc.test
+++ b/test/pamfunc.test
@@ -93,121 +93,121 @@ echo "Test Invalid"
 test_out=${tmpdir}/test_out
 
 pamfunc -multiplier testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -multiplier=-1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -divisor testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -divisor=-20 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -adder testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -adder 0.5 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -subtractor testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -subtractor 0.1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -multiplier=1 -divisor=2 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -adder=2 -subtractor=3 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -min testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -max testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -andmask testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -ormask testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -xormask testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 15"
+  printf "Expected failure 15"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -not 1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 16"
+  printf "Expected failure 16"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -min=1 -max=2 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 17"
+  printf "Expected failure 17"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -andmask=1 -ormask=0 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 18"
+  printf "Expected failure 18"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -andmask=0xffff testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 19"
+  printf "Expected failure 19"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -shiftleft testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 20"
+  printf "Expected failure 20"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -shiftright testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 21"
+  printf "Expected failure 21"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -changemaxval testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 22"
+  printf "Expected failure 22"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -shiftleft=1 -shiftright=1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 23"
+  printf "Expected failure 23"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamfunc -multiplier=0.5 -changemaxval=65535 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 24"
+  printf "Expected failure 24"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamgauss.test b/test/pamgauss.test
index 6c3eff78..9400e928 100755
--- a/test/pamgauss.test
+++ b/test/pamgauss.test
@@ -28,26 +28,26 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pamgauss 3 3               > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamgauss 3 3   -sigma=0    > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamgauss 3 3   -sigma=-1.5 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamgauss 3     -sigma=0.5  > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamgauss 3 3 3 -sigma=0.5  > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamhue.test b/test/pamhue.test
index cd5430a8..5979e414 100755
--- a/test/pamhue.test
+++ b/test/pamhue.test
@@ -16,7 +16,7 @@ pamseq -tupletype=RGB 3 1 | pamdepth 255 | pamhue -huechange=60 | \
 
 echo "Test 3"
 # pamhue has no effect on monotone images
-# Should print 0 0 : 0 twice
+# Should print 281226646 481 twice
 
 pamhue -huechange=45  maze.pbm | cmp -s - maze.pbm
   echo ${PIPESTATUS[@]} ":" $?
@@ -25,7 +25,7 @@ pamhue -huechange=180 maze.pbm | cmp -s - maze.pbm
 
 echo "Test 4"
 # spinning the color wheel by multiples of 360 leaves the image unchanged
-# Should print 0 0 : 0 twice
+# Should print 1926073387 101484 twice
 
 pamhue -huechange=0 testimg.ppm   | cmp -s - testimg.ppm
   echo ${PIPESTATUS[@]} ":" $?
@@ -43,6 +43,6 @@ echo "An error message should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamhue testimg.ppm  > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamrecolor.test b/test/pamrecolor.test
index 0ba35b66..a5fecc1f 100755
--- a/test/pamrecolor.test
+++ b/test/pamrecolor.test
@@ -1,6 +1,6 @@
 #! /bin/sh
 # This script tests: pamrecolor
-# Also requires: pgmmake
+# Also requires: ppmtopgm pgmmake
 
 tmpdir=${tmpdir:-/tmp}
 base_pgm=${tmpdir}/base.pgm
@@ -28,34 +28,34 @@ echo "-----------------------------------------------------------" 1>&2
 
 pamrecolor --targetcolor=rgb:00/11/22 \
            --colorfile=${base1_pgm} testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrecolor --rmult=0.3  --gmult=0.3  --bmult=0.3 \
            --colorfile=${base1_pgm} testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrecolor --colorspace=void \
            --targetcolor=rgb:80/80/80 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrecolor --targetcolor=vague testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrecolor --colorfile=${truncated_file} testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrecolor --rmult=0.2989 --gmult=0.5866 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pamrestack.test b/test/pamrestack.test
index 17e42e30..2ebee49f 100755
--- a/test/pamrestack.test
+++ b/test/pamrestack.test
@@ -64,28 +64,28 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamrestack testgrid.pbm maze.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrestack -abort \
   -width=$((pixels * 2 + 1 )) maze.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrestack -crop \
   -width=$((pixels * 2 + 1)) maze.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrestack -width=0 maze.pbm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamrestack -width maze.pbm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamscale-reportonly.test b/test/pamscale-reportonly.test
index f65bcb93..924fb7c2 100755
--- a/test/pamscale-reportonly.test
+++ b/test/pamscale-reportonly.test
@@ -31,36 +31,36 @@ echo "-----------------------------------------------------------" 1>&2
 
 pamscale -reportonly -xsize=640 -ysize=400 -xscale=2 testimg.ppm > \
   ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamscale -reportonly -xsize=640 -xscale=2 -yscale=3 testimg.ppm > \
   ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamscale -reportonly -xsize=640 -ysize=400 -pixels=200000 testimg.ppm \
   > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamscale -reportonly -xsize=640 -ysize=400 -xysize 640 400 testimg.ppm \
   > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamscale -reportonly -xsize=640 -ysize=400 -xyfit  640 400 testimg.ppm \
   > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamscale -reportonly -xsize=640 -ysize=400 -xyfill 640 400 testimg.ppm \
   > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamseq.test b/test/pamseq.test
index 3fe0fc31..ce3d4995 100755
--- a/test/pamseq.test
+++ b/test/pamseq.test
@@ -17,17 +17,17 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pamseq 1 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamseq 0 255 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamseq 3 0   > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -37,6 +37,6 @@ c256=${c64}${c64}${c64}${c64}
 # Tupletype string length=256
 
 pamseq -tupletype="${c256}" 3 15 > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamshuffle.test b/test/pamshuffle.test
index 5582ce3b..2941a9bc 100755
--- a/test/pamshuffle.test
+++ b/test/pamshuffle.test
@@ -52,16 +52,16 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamshuffle testimg.ppm testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamshuffle -randomseed -column testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamshuffle -randomseed=null testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamsumm.test b/test/pamsumm.test
index fb15c0c4..f482911a 100755
--- a/test/pamsumm.test
+++ b/test/pamsumm.test
@@ -35,21 +35,21 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pamsumm -sum -min  testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamsumm -sum -max  testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamsumm -mean -max testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pamsumm            testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pamtable.ok b/test/pamtable.ok
index c8db6002..d63c7720 100644
--- a/test/pamtable.ok
+++ b/test/pamtable.ok
@@ -1,3 +1,4 @@
+Test 1
 0 1 0 1 0 1 0 1 0 1 0 1 0 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 1 0 1 0 1 0 1 0 1 0 1 0 1
@@ -15,10 +16,66 @@
 0 1 0 1 0 1 0 1 0 1 0 1 0 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 
+Test 2
 0 0 0|0 0 1|0 0 2|0 1 0|0 1 1|0 1 2|0 2 0|0 2 1|0 2 2|1 0 0|1 0 1|1 0 2|1 1 0|1 1 1|1 1 2|1 2 0|1 2 1|1 2 2|2 0 0|2 0 1|2 0 2|2 1 0|2 1 1|2 1 2|2 2 0|2 2 1|2 2 2
 
+Test 3
 0 0 0 0|0 0 0 1|0 0 1 0|0 0 1 1|0 1 0 0|0 1 0 1|0 1 1 0|0 1 1 1|1 0 0 0|1 0 0 1|1 0 1 0|1 0 1 1|1 1 0 0|1 1 0 1|1 1 1 0|1 1 1 1
 
+Test 4
+(1) (0) (1) (0) (1)
+
+Test 5
+(1)
+(0)
+(1)
+(0)
+(1)
+(0)
+(1)
+(0)
+(1)
+(0)
+
+Test 6
+(0,0,0) (0,0,1) (0,0,2)
+(0,0,3) (0,1,0) (0,1,1)
+(0,1,2) (0,1,3) (0,2,0)
+(0,2,1) (0,2,2) (0,2,3)
+(0,3,0) (0,3,1) (0,3,2)
+(0,3,3) (1,0,0) (1,0,1)
+(1,0,2) (1,0,3) (1,1,0)
+(1,1,1) (1,1,2) (1,1,3)
+(1,2,0) (1,2,1) (1,2,2)
+(1,2,3) (1,3,0) (1,3,1)
+(1,3,2) (1,3,3) (2,0,0)
+(2,0,1) (2,0,2) (2,0,3)
+(2,1,0) (2,1,1) (2,1,2)
+(2,1,3) (2,2,0) (2,2,1)
+(2,2,2) (2,2,3) (2,3,0)
+(2,3,1) (2,3,2) (2,3,3)
+(3,0,0) (3,0,1) (3,0,2)
+(3,0,3) (3,1,0) (3,1,1)
+(3,1,2) (3,1,3) (3,2,0)
+(3,2,1) (3,2,2) (3,2,3)
+(3,3,0) (3,3,1) (3,3,2)
+(3,3,3) (0,0,0) (0,0,0)
+
+Test 7
+00 00|00 11|00 22|00 33|00 44|00 55|00 66|00 77
+00 88|00 99|00 aa|00 bb|00 cc|00 dd|00 ee|00 ff
+0a 00|0a 11|0a 22|0a 33|0a 44|0a 55|0a 66|0a 77
+0a 88|0a 99|0a aa|0a bb|0a cc|0a dd|0a ee|0a ff
+14 00|14 11|14 22|14 33|14 44|14 55|14 66|14 77
+14 88|14 99|14 aa|14 bb|14 cc|14 dd|14 ee|14 ff
+1e 00|1e 11|1e 22|1e 33|1e 44|1e 55|1e 66|1e 77
+1e 88|1e 99|1e aa|1e bb|1e cc|1e dd|1e ee|1e ff
+28 00|28 11|28 22|28 33|28 44|28 55|28 66|28 77
+28 88|28 99|28 aa|28 bb|28 cc|28 dd|28 ee|28 ff
+32 00|32 11|32 22|32 33|32 44|32 55|32 66|32 77
+32 88|32 99|32 aa|32 bb|32 cc|32 dd|32 ee|32 ff
+
+Test 8
 9 0
 0 9
 
@@ -64,3 +121,6 @@
 65535     0     0|    0     0 65535|    0     0 65535|    0 65535     0
 65535     0     0|    0     0 65535|    0     0 65535|    0 65535     0
 
+Test Invalid
+Should print: Expected failure 1
+Expected failure 1
diff --git a/test/pamtable.test b/test/pamtable.test
index 037d3b07..e948ad0c 100755
--- a/test/pamtable.test
+++ b/test/pamtable.test
@@ -1,13 +1,37 @@
 #! /bin/sh
 # This script tests: pamtable
-# Also requires: pamseq pamdepth pbmmake ppmrainbow
+# Also requires: pamseq pamrestack pamdepth pbmmake ppmrainbow
 
+echo "Test 1"
 pamtable testgrid.pbm
+
 echo
+echo "Test 2"
 pamseq 3 2 -tupletype=RGB | pamtable
+
 echo
+echo "Test 3"
 pamseq 4 1 -tupletype=RGBA | pamtable
+
+echo
+echo "Test 4"
+pbmmake -gray 5 1 | pamtable -tuple
+
+echo
+echo "Test 5"
+pbmmake -gray 1 10 | pamtable -tuple
+
+echo
+echo "Test 6"
+pamseq 3 3 -tupletype=RGB | pamrestack -width=3 | pamtable -tuple
+
+echo
+echo "Test 7"
+pamseq 2 255 -max=50,255 -step=10,17 | pamrestack -width=8 | \
+  pamtable -hex
+
 echo
+echo "Test 8"
 for maxval in 9 10 9999 10000 65535
   do
   pbmmake -g 2 2 | pamdepth ${maxval} | pamtable
@@ -19,3 +43,20 @@ for maxval in 9 10 9999 10000 65535
     pamdepth ${maxval} | pamtable
   echo
   done
+
+echo "Test Invalid"
+
+test_out=${tmpdir}/test.out
+
+echo 1>&2
+echo "Invalid command-line argument combinations." 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+echo "Should print: Expected failure 1"
+
+pamseq -tupletype="void" 1 1 | pamtable -tuple -hex > \
+  ${test_out} || \
+  printf "Expected failure"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
diff --git a/test/pamundice.test b/test/pamundice.test
index 14b43dfb..ba532c30 100755
--- a/test/pamundice.test
+++ b/test/pamundice.test
@@ -1,6 +1,6 @@
 #! /bin/sh
 # This script tests: pamundice
-# Also requires: pamfile pgmmake pnmtile pnmcat
+# Also requires: pamfile pgmmake pnmtile pamcat
 
 tmpdir=${tmpdir:-/tmp}
 fname_stem=${tmpdir}/part
@@ -28,7 +28,7 @@ for y in 0 1 2 3 4
   pamundice -across=3 ${fname_stem}"_"$y"_%1a".pgm > ${tempfile}_"$y"
   done
 
-pnmcat -tb ${tempfile}_[01234] | cksum
+pamcat -tb ${tempfile}_[01234] | cksum
 rm ${tempfile}_[01234]
 
 # Note: the following 2 are valid.  There should not be warning messages.
@@ -38,7 +38,7 @@ for x in 0 1 2
   pamundice -down=5 ${fname_stem}"_%1d_"$x.pgm > ${tempfile}"_"$x
   done
 
-pnmcat -lr ${tempfile}_[012] | cksum
+pamcat -lr ${tempfile}_[012] | cksum
 rm ${tempfile}_[012]
 
 rm ${fname_stem}_?_?.pgm
@@ -123,63 +123,63 @@ echo "-----------------------------------------------------------" 1>&2
 
 # No input file pattern specified
 pamundice -down=5 -across=2 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -down=0
 pamundice -down=0 -across=2 ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -across=0
 pamundice -down=5 -across=0 ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -down too large
 pamundice -down=6 -across=2 ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -across too large
 pamundice -down=5 -across=3 ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # precision does not match
 pamundice -down=5 -across=2 ${fname_stem}_"%2d"_"%2a".pbm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # precision set to zero
 pamundice -down=5 -across=2 ${fname_stem}_"%0d"_"%0a".pbm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # no precision
 pamundice -down=5 -across=2 ${fname_stem}_"%d"_"%a".pbm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -hoverlap too large
 pamundice -down=5 -across=2 -hoverlap=$((${mw}+1)) \
   ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # -voverlap too large
 pamundice -down=5 -across=2 -voverlap=$((${mh}+1)) \
   ${fname_stem}_"%1d"_"%1a".pbm > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -188,14 +188,14 @@ for i in 0 1 2 3 4 5 6 7 8 9
   do
   mktemp -u XXXXXXXXXX.${i} || echo ":::::::::::"${i}":::::::::::"
   done | pamundice -down=5 -across=2 -listfile=- > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # listfile with insufficient lines (insufficient file entries)
 ls ${fname_stem}_*_*.pbm | head -n 9 | \
   pamundice -down=5 -across=2 -listfile=- > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pbmclean.test b/test/pbmclean.test
index 7c0c4d6d..5eba68e0 100755
--- a/test/pbmclean.test
+++ b/test/pbmclean.test
@@ -46,7 +46,7 @@ echo "-----------------------------------------------------------" 1>&2
 
 # overspecification
 pbmclean -black -white -min=1 -extended testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pbmmake.test b/test/pbmmake.test
index c3691b80..1d1c682b 100755
--- a/test/pbmmake.test
+++ b/test/pbmmake.test
@@ -33,41 +33,41 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pbmmake -b -w -plain 1 1 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -b -g -plain 1 1 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -gray -plain 1 1 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -plain   > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -plain 1 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -plain 1 0 > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -plain 0 1 > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmmake -white -plain 1 1 1 > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pbmnoise-parameters.test b/test/pbmnoise-parameters.test
index e467bfd5..17d2c76b 100755
--- a/test/pbmnoise-parameters.test
+++ b/test/pbmnoise-parameters.test
@@ -13,130 +13,130 @@ test_out=${tmpdir}/test_out
 # Invalid -ratio arguments
 
 pbmnoise -ratio       100 100 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio 0     1  100 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=1.1   100 100 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
   
 pbmnoise -ratio=-1    100 100 > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=half  100 100 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=0/1/1 100 100 > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=-1/2  100 100 > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=1/0   100 100 > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=/2    100 100 > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=4/2   100 100 > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=6/    100 100 > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=1.0/2.0 100 100 > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # denominator must be power of 2  
 pbmnoise -ratio=3/9   100 100 > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=a/2   100 100 > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 
 pbmnoise -ratio=2/a  100 100 > ${test_out} || \
-        echo -n "Expected failure 15"
+        printf "Expected failure 15"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=1/-2  100 100 > ${test_out} || \
-        echo -n "Expected failure 16"
+        printf "Expected failure 16"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # Denominator must be 65536 or less
 pbmnoise -ratio=1/65537 100 100 > ${test_out} || \
-  echo -n "Expected failure 17"
+  printf "Expected failure 17"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -ratio=1/131072 100 100 > ${test_out} || \
-  echo -n "Expected failure 17"
+  printf "Expected failure 17"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -endian=large 100 100 > ${test_out} || \
-  echo -n "Expected failure 18"
+  printf "Expected failure 18"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -randomseed 100 100 > ${test_out} || \
-  echo -n "Expected failure 19"
+  printf "Expected failure 19"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -randomseed=-1 100 100 > ${test_out} || \
-  echo -n "Expected failure 20"
+  printf "Expected failure 20"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise -randomseed=0.1 100 100 > ${test_out} || \
-  echo -n "Expected failure 21"
+  printf "Expected failure 21"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise > ${test_out} || \
-  echo -n "Expected failure 22"
+  printf "Expected failure 22"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise 100 > ${test_out} || \
-  echo -n "Expected failure 23"
+  printf "Expected failure 23"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmnoise 100 200 300 > ${test_out} || \
-  echo -n "Expected failure 24"
+  printf "Expected failure 24"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pbmpage.test b/test/pbmpage.test
index 2805ec97..4f15452d 100755
--- a/test/pbmpage.test
+++ b/test/pbmpage.test
@@ -20,16 +20,16 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pbmpage -a3 1 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmpage 0 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmpage 4 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pbmpscale.test b/test/pbmpscale.test
index 9c38bb2e..efa5bfb5 100755
--- a/test/pbmpscale.test
+++ b/test/pbmpscale.test
@@ -36,16 +36,16 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pbmpscale testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmpscale 0 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmpscale 2 3 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pbmtext-utf8.test b/test/pbmtext-utf8.test
index 0677d161..9203607f 100755
--- a/test/pbmtext-utf8.test
+++ b/test/pbmtext-utf8.test
@@ -135,20 +135,20 @@ echo "Test input text which exceeds length limit" 1>&2
 echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
-echo -n "z" >> ${long_txt}
+printf "z" >> ${long_txt}
 cat ${long_txt} | wc -c | tr -d ' '
 
 cat ${long_txt} | \
   LC_ALL=${locale_for_test} \
   pbmtext -nomargins -builtin fixed -wchar > ${test_out} || \
-  echo -n "Expected failure 1";
+  printf "Expected failure 1";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 cat ${long_txt} | \
   LC_ALL=${locale_for_test} \
   pbmtext -nomargins -builtin fixed -wchar > ${test_out} || \
-  echo -n "Expected failure 2";
+  printf "Expected failure 2";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -169,41 +169,41 @@ echo "Test 6 Invalid"
 awk 'BEGIN { printf("%c%c",128,129);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-1:" ${PIPESTATUS[@]} ":" $?
+  printf "6-1: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 awk 'BEGIN { printf("ABC%c%c",192, 193);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-2:" ${PIPESTATUS[@]} ":" $?
+  printf "6-2: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 awk 'BEGIN { printf("abcde%c%c", 254, 253);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-3:" ${PIPESTATUS[@]} ":" $?
+  printf "6-3: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 awk 'BEGIN { printf("abcdefg%c%c", 195, 15);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-4:" ${PIPESTATUS[@]} ":" $?
+  printf "6-4: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 awk 'BEGIN { printf("123456789%c%c%c", 224, 143 ,0);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-5:" ${PIPESTATUS[@]} ":" $?
+  printf "6-5: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 awk 'BEGIN { printf("abcdefg123ABCDEFG%c%c%c%c",247, 135, 135, 7);  print ""}' | \
         LC_ALL=${locale_for_test} \
         pbmtext -builtin bdf -wchar -text-dump > ${test_out}
-  echo -n "6-6:" ${PIPESTATUS[@]} ":" $?
+  printf "6-6: ${PIPESTATUS[*]} : $?"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pbmtext.test b/test/pbmtext.test
index b82b0771..247f76c5 100755
--- a/test/pbmtext.test
+++ b/test/pbmtext.test
@@ -10,13 +10,13 @@ echo "Test 1"
 # Should print 3898818212 967 twice
 
 pbmtext UNIX Philosophy: Do one thing and do it well. | cksum
-echo -n "UNIX Philosophy: Do one thing and do it well." | pbmtext | cksum
+printf "UNIX Philosophy: Do one thing and do it well." | pbmtext | cksum
 
 # Should print 2506052117 1354 twice
 
 pbmtext -builtin fixed \
     For truth is always strange. Stranger than fiction. Lord Byron | cksum
-echo -n "For truth is always strange. Stranger than fiction. Lord Byron" | \
+printf "For truth is always strange. Stranger than fiction. Lord Byron" | \
     pbmtext -builtin fixed | cksum
 
 
@@ -112,42 +112,42 @@ echo "Test 6 Invalid"
 test_out=${tmpdir}/test_out
 
 pbmtext -font=testgrid.pbm foo > ${test_out} || \
-  echo -n "Expected failure 1";
+  printf "Expected failure 1";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -font=testimg.ppm  foo > ${test_out} || \
-  echo -n "Expected failure 2";
+  printf "Expected failure 2";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -builtin=void      foo > ${test_out} || \
-  echo -n "Expected failure 3";
+  printf "Expected failure 3";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -font=${font_pbm} -builtin=fixed foo > ${test_out}  || \
-  echo -n "Expected failure 4";
+  printf "Expected failure 4";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -dry-run    -text-dump  foo > ${test_out} || \
-  echo -n "Expected failure 5";
+  printf "Expected failure 5";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -dump-sheet -text-dump  foo > ${test_out} || \
-  echo -n "Expected failure 6";
+  printf "Expected failure 6";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -dry-run    -dump-sheet foo > ${test_out} || \
-  echo -n "Expected failure 7";
+  printf "Expected failure 7";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtext -wchar foo > ${test_out} || \
-  echo -n "Expected failure 8";
+  printf "Expected failure 8";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -168,7 +168,7 @@ cat ${long_txt} | pbmtext -nomargins -builtin fixed  | cksum
 
 echo "Test 8 Invalid"
 
-echo -n "z" >> ${long_txt}
+printf "z" >> ${long_txt}
 awk '{print "text length:", length($0)}' ${long_txt}
 
 echo 1>&2
@@ -177,18 +177,18 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pbmtext -nomargins -builtin fixed `cat ${long_txt}` > ${test_out}  || \
-  echo -n "Expected failure 1";
+  printf "Expected failure 1";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 cat ${long_txt} | pbmtext -nomargins -builtin fixed > ${test_out}  || \
-  echo -n "Expected failure 2";
+  printf "Expected failure 2";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 cat ${long_txt} | \
   LC_ALL=C pbmtext -nomargins -builtin fixed -wchar > ${test_out}  || \
-  echo -n "Expected failure 3";
+  printf "Expected failure 3";
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pbmtextps-dump.ok b/test/pbmtextps-dump.ok
new file mode 100644
index 00000000..9fe3b2ea
--- /dev/null
+++ b/test/pbmtextps-dump.ok
@@ -0,0 +1,65 @@
+Test 1
+-font=ZQUEl8eS38RlsvEahHGNfnrbSswrcJKFwvlCdEttwcheuXvCN49MvWmndqj4
+< /FindFont {/Times-Roman findfont} def
+> /FindFont {/ZQUEl8eS38RlsvEahHGNfnrbSswrcJKFwvlCdEttwcheuXvCN49MvWmndqj4 findfont} def
+-fontsize 2000
+< /fontsize 24.000000 def
+> /fontsize 2000.000000 def
+< /descent 36.000000 def
+< /leftmargin 12.000000 def
+> /descent 3000.000000 def
+> /leftmargin 1000.000000 def
+-resolution 7200
+-leftmargin=15
+< /leftmargin 12.000000 def
+> /leftmargin 15.000000 def
+-rightmargin=20
+< /rightmargin 0.000000 def
+> /rightmargin 20.000000 def
+-topmargin=10
+< /topmargin 0.000000 def
+> /topmargin 10.000000 def
+-bottommargin=14
+< /descent 36.000000 def
+> /descent 0.000000 def
+< /bottommargin 0.000000 def
+> /bottommargin 14.000000 def
+-ascent=30
+< /ascent 0.000000 def
+> /ascent 30.000000 def
+-descent=20
+< /descent 36.000000 def
+> /descent 20.000000 def
+-pad
+< /descent 36.000000 def
+> /descent 0.000000 def
+< /pad false def
+> /pad true def
+-crop
+< /descent 36.000000 def
+< /leftmargin 12.000000 def
+> /descent 0.000000 def
+> /leftmargin 0.000000 def
+-stroke 1
+< /pensize -1.000000 def
+> /pensize 1.000000 def
+Test Invalid
+Expected failure 1 (-fontsize) 1
+Expected failure 2 (-fontsize 0) 1
+Expected failure 3 (-resolution) 1
+Expected failure 4 (-resolution=0) 1
+Expected failure 5 (-leftmargin) 1
+Expected failure 6 (-leftmargin -1) 1
+Expected failure 7 (-rightmargin) 1
+Expected failure 8 (-rightmargin -1) 1
+Expected failure 9 (-topmargin) 1
+Expected failure 10 (-topmargin -1) 1
+Expected failure 11 (-bottommargin) 1
+Expected failure 12 (-bottommargin -1) 1
+Expected failure 13 (-ascent) 1
+Expected failure 14 (-ascent -1) 1
+Expected failure 15 (-descent) 1
+Expected failure 16 (-descent -1) 1
+Expected failure 17 (-stroke=A) 1
+Expected failure 18 (-pad -crop) 1
+Expected failure 19 (-font="") 1
diff --git a/test/pbmtextps-dump.test b/test/pbmtextps-dump.test
new file mode 100755
index 00000000..3b3fbadd
--- /dev/null
+++ b/test/pbmtextps-dump.test
@@ -0,0 +1,84 @@
+#! /bin/sh
+# This script tests: pbmtextps
+# Also requires:
+
+# Dump several variants of the ps file and compare against default.
+# Ghostscript is not required.
+
+tmpdir=${tmpdir:-/tmp}
+text_pbm=${tmpdir}/text.pbm
+text_ps=${tmpdir}/text.ps
+
+text="UNIX Philosophy: Do one thing and do it well."
+
+# Test 1:
+echo "Test 1"
+
+pbmtextps -dump-ps ${text} > ${text_ps}
+
+# Font name is random sequence of alphanumerical characters.
+# Should not match any real name.
+
+for flag in \
+  "-font=ZQUEl8eS38RlsvEahHGNfnrbSswrcJKFwvlCdEttwcheuXvCN49MvWmndqj4" \
+  "-fontsize 2000" \
+  "-resolution 7200" \
+  "-leftmargin=15" \
+  "-rightmargin=20" \
+  "-topmargin=10" \
+  "-bottommargin=14" \
+  "-ascent=30" \
+  "-descent=20" \
+  "-pad" \
+  "-crop" \
+  "-stroke 1"
+  do
+  echo ${flag}
+  pbmtextps -dump-ps ${flag} ${text} | diff ${text_ps} - | grep "^[<>]"
+  done
+
+rm ${text_ps}
+
+
+echo "Test Invalid"
+
+echo 1>&2
+echo "Invalid command line arguments" 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+test_out=${tmpdir}/test.out
+
+n=1
+
+for error_flag in \
+  "-fontsize" \
+  "-fontsize 0" \
+  "-resolution" \
+  "-resolution=0" \
+  "-leftmargin" \
+  "-leftmargin -1" \
+  "-rightmargin" \
+  "-rightmargin -1" \
+  "-topmargin" \
+  "-topmargin -1" \
+  "-bottommargin" \
+  "-bottommargin -1" \
+  "-ascent" \
+  "-ascent -1" \
+  "-descent" \
+  "-descent -1" \
+  "-stroke=A" \
+  "-pad -crop" 
+  do
+    pbmtextps ${error_flag} -dump-ps ${text} >${test_out} || \
+    printf "Expected failure $n (${error_flag})";
+    test -s ${test_out}; echo " "$?
+    rm -f ${test_out}
+    n=$((n + 1))
+  done
+
+  pbmtextps -font="" -dump-ps ${text} >${test_out} || \
+  printf "Expected failure $n (-font=\"\")";
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
diff --git a/test/pbmtextps.ok b/test/pbmtextps.ok
new file mode 100644
index 00000000..d407cb0a
--- /dev/null
+++ b/test/pbmtextps.ok
@@ -0,0 +1,16 @@
+Test 1.  Should print 0 five times.
+0
+0
+0
+0
+0
+Test 2.  Should print P1 1 1 0 three times
+P1 1 1 0 
+P1 1 1 0 
+P1 1 1 0 
+Test 3.  Should print eulerchi: N
+ eulerchi:	1
+ eulerchi:	2
+ eulerchi:	3
+ eulerchi:	4
+ eulerchi:	5
diff --git a/test/pbmtextps.test b/test/pbmtextps.test
new file mode 100755
index 00000000..975f960c
--- /dev/null
+++ b/test/pbmtextps.test
@@ -0,0 +1,53 @@
+#! /bin/sh
+# This script tests: pbmtextps pbmminkowski
+# Also requires: gs pnmcrop
+
+tmpdir=${tmpdir:-/tmp}
+text_pbm=${tmpdir}/text.pbm
+
+text="Do one thing and do it well."
+
+echo "Test 1.  Should print 0 five times."
+# -ascent -descent values too small to have effect
+
+pbmtextps -descent=1 ${text} > ${text_pbm}
+  echo $?
+  test -s ${text_pbm}
+  echo $?
+pbmtextps -ascent=10 -descent=1 ${text} | cmp -s - ${text_pbm}
+  echo $?
+pbmtextps -ascent=1 -descent=1 ${text}  | cmp -s - ${text_pbm}
+  echo $?
+pbmtextps -descent=2 ${text} | cmp -s - ${text_pbm}
+  echo $?
+
+rm ${text_pbm}
+
+
+echo "Test 2.  Should print P1 1 1 0 three times"
+# blank images
+
+pbmtextps " " | pnmcrop -plain -blank-image=minimize |\
+  tr '\n' ' ' ; echo
+pbmtextps -fontsize=12   " " | pnmcrop -plain -blank-image=minimize |\
+  tr '\n' ' ' ; echo
+pbmtextps -resolution=50 " " | pnmcrop -plain -blank-image=minimize |\
+  tr '\n' ' ' ; echo 
+
+
+
+echo "Test 3.  Should print eulerchi: N"
+# Test with characters known to produce stable eulerchi values
+# accross various fonts
+
+pbmtextps " " | pbmminkowski | grep "eulerchi"
+
+pbmtextps "+" | pbmminkowski | grep "eulerchi"
+
+pbmtextps "+" | pnmcrop -left -right | pbmminkowski | grep "eulerchi"
+
+pbmtextps "+" | pnmcrop | pbmminkowski | grep "eulerchi"
+
+pbmtextps "o" | pnmcrop | pbmminkowski | grep "eulerchi"
+
+
diff --git a/test/pbmtopgm.test b/test/pbmtopgm.test
index 46c04705..02f2bb21 100755
--- a/test/pbmtopgm.test
+++ b/test/pbmtopgm.test
@@ -18,21 +18,21 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pbmtopgm 5 0 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtopgm 0 9 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtopgm 15 5 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmtopgm 5 17 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pbmupc.test b/test/pbmupc.test
index 8d726de5..f8343dd7 100755
--- a/test/pbmupc.test
+++ b/test/pbmupc.test
@@ -20,46 +20,46 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pbmupc -s3 0 72890 00011     > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1   72890 00011     > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0 72890           > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 10 72890 00011    > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0 172890 00011    > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0   2890 00011    > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0 72890 100011    > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0 72890   0011    > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pbmupc -s1 0 72890 100011 1  > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmhist.test b/test/pgmhist.test
index 83f3c52e..c141627d 100755
--- a/test/pgmhist.test
+++ b/test/pgmhist.test
@@ -35,21 +35,21 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pgmhist -median   -quartile testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmhist -median   -decile   testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmhist -quartile -decile   testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmhist testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmmake.test b/test/pgmmake.test
index 84c03778..3c96d99e 100755
--- a/test/pgmmake.test
+++ b/test/pgmmake.test
@@ -19,41 +19,41 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pgmmake 100  5 5 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake 1.01 5 5 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake .5   5   > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake .5       > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake -maxval=5        5 5 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake -maxval=0     .5 5 5 > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake -maxval=-1    .5 5 5 > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmmake -maxval=65536 .5 5 5 > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmnoise-parameters.test b/test/pgmnoise-parameters.test
index 2615c8e8..c5e6ada5 100755
--- a/test/pgmnoise-parameters.test
+++ b/test/pgmnoise-parameters.test
@@ -11,26 +11,26 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pgmnoise -maxval=255  -randomseed=1 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 100 -randomseed=1 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 100 0 -randomseed=1 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 0 100 -randomseed=1 > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 100 100 100 -randomseed=1 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmnoise.test b/test/pgmnoise.test
index 45374793..6141121a 100755
--- a/test/pgmnoise.test
+++ b/test/pgmnoise.test
@@ -29,7 +29,7 @@ echo "Test 4."
 for maxval in  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \
                30 31 32 254 255 256 65534 65535
   do
-  echo -n ${maxval} " "
+  printf "%d  " ${maxval}
   pgmnoise -maxval=${maxval} -randomseed=1 -plain ${maxval} 10 | \
     pamvalidate | pamfile
   done
@@ -78,51 +78,51 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pgmnoise 0 0  > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 0 1  > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 1 0  > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise      > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 1    > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise 100 -1 > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise -randomseed=-1 100 100  > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise -maxval=-1 100 100  > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise -maxval=0 100 100  > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmnoise -maxval=$((256 * 256 * 256 * 256)) 10 10 > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmramp.test b/test/pgmramp.test
index c3b0ad1c..b4dbb3cd 100755
--- a/test/pgmramp.test
+++ b/test/pgmramp.test
@@ -31,17 +31,17 @@ test_out=${tmpdir}/test_out
 
 for combination in "-lr -tb" "-lr -rectangle" "-rectangle -ellipse"
 do pgmramp $combination 10 10 > ${test_out} || \
-  echo -n "Expected failure: $combination"
+  printf "Expected failure: $combination"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 done
 
 pgmramp -lr     1 > ${test_out} || \
-  echo -n "Expected failure: insufficient parameters"; \
+  printf "Expected failure: insufficient parameters"; \
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmramp -tb 1 1 1 > ${test_out} || \
-  echo -n "Expected failure: excessive parameters"; \
+  printf "Expected failure: excessive parameters"; \
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pgmtoppm.test b/test/pgmtoppm.test
index d98418b8..36f63de7 100755
--- a/test/pgmtoppm.test
+++ b/test/pgmtoppm.test
@@ -66,73 +66,73 @@ echo "-----------------------------------------------------------" 1>&2
 
 pgmtoppm white testimg.ppm > \
   ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm -map=/dev/null testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm black white testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm hwite testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm off-color testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm rgb-255:7/7/7 testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm -black=black -white=white white testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm -black=rgb:0/0/0 -map=${palette_ppm} testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm -white=rgb:ff/ff/ff -map=${palette_ppm} testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm rgb:ff/ff/ff -map=${palette_ppm} testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pgmtoppm rgb:00/00/00-rgb:ff/ff/ff -map=${palette_ppm} testgrid.pbm > \
   ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 #pgmtoppm rgb-255:7/7/7 testgrid.pbm > \
 #  ${test_out} || \
-#  echo -n "Expected failure 12"
+#  printf "Expected failure 12"
 #  test -s ${test_out}; echo " "$?
 #  rm -f ${test_out}
 
diff --git a/test/pnmcat.ok b/test/pnmcat.ok
index 57769353..96e2b884 100644
--- a/test/pnmcat.ok
+++ b/test/pnmcat.ok
@@ -1,4 +1,8 @@
-Test 1.  Should print a simple PBM image four times
+Test 1.  Should print a simple PBM image five times
+P1
+2 2
+01
+10
 P1
 2 2
 01
diff --git a/test/pnmcat.test b/test/pnmcat.test
index 173aa608..bf40ada2 100755
--- a/test/pnmcat.test
+++ b/test/pnmcat.test
@@ -1,6 +1,12 @@
 #! /bin/sh
 # This script tests: pnmcat
-# Also requires: pbmmake pgmramp ppmtoppm
+# Also requires: pbmmake pgmramp ppmtoppm pamcat
+
+if [ ${CHECK_TYPE} = tree ]
+  then echo "Skipping." 1>&2
+       echo "Running \"make package; make check\" is recommended." 1>&2
+  exit 80;
+fi
 
 tmpdir=${tmpdir:-/tmp}
 check2x2_pbm=${tmpdir}/check2x2.pbm
@@ -11,9 +17,13 @@ diag_ppm=${tmpdir}/diag.ppm
 diag2lr_ppm=${tmpdir}/diag2lr.ppm
 diag2tb_ppm=${tmpdir}/diag2tb.ppm
 
-echo "Test 1.  Should print a simple PBM image four times"
+echo "Test 1.  Should print a simple PBM image five times"
+# Check input from stdin and from designated input file
+# Similar to tests in stdin-p?m.test
+
 pbmmake -g 2 2 | tee ${check2x2_pbm} | pnmcat -lr -plain
 pnmcat -tb -plain ${check2x2_pbm}
+pnmcat -tb -plain < ${check2x2_pbm}
 pnmcat -lr -black -plain ${check2x2_pbm}
 cat ${check2x2_pbm} | pnmcat -tb -white -plain
 
@@ -39,9 +49,13 @@ cat maze.pbm | pnmcat -tb - ${maze2tb_pbm} | cksum
 rm ${maze2lr_pbm} ${maze2tb_pbm}
 
 echo "Test 6.  Should print a simple PGM image three times"
-pgmramp -diag 4 4 -maxval=3 | tee ${diag_pgm} | pnmcat -lr -plain
-pnmcat -tb -plain ${diag_pgm}
-cat ${diag_pgm} | pnmcat -tb -plain
+# Use sed to scrape off space at line end for compatibility
+# with older versions of pnmcat
+
+pgmramp -diag 4 4 -maxval=3 | tee ${diag_pgm} | pnmcat -lr -plain |\
+  sed 's/ *$//'
+pnmcat -tb -plain ${diag_pgm} | sed 's/ *$//'
+cat ${diag_pgm} | pnmcat -tb -plain | sed 's/ *$//'
 
 rm ${diag_pgm}
 
@@ -95,76 +109,76 @@ echo "-----------------------------------------------------------" 1>&2
 
 # direction not specified
 pnmcat testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # both directions specified
 pnmcat -topbottom -leftright testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # both pad colors specified
 pnmcat -topbottom -white -black testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # justification parameters overspecified
 pnmcat -lr -jtop -jbottom testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -lr -jtop -jcenter testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -lr -jcenter -jbottom testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -tb -jleft -jright testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -tb -jleft -jcenter testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -tb -jcenter -jright testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # justification parameter in the wrong direction
 pnmcat -lr -jleft    testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -lr -jright   testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -tb -jtop     testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcat -tb -jbottom  testgrid.pbm testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # more than one input image from standard input
 cat testgrid.pbm | pnmcat -lr - - testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pnmcolormap.test b/test/pnmcolormap.test
index 089e0d1e..1a7ec266 100755
--- a/test/pnmcolormap.test
+++ b/test/pnmcolormap.test
@@ -15,37 +15,37 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pnmcolormap 0 testimg.ppm   > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap -1 testimg.ppm  > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap 0.1 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap -center -meancolor 16 testimg.ppm    > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap -center -meanpixel 16 testimg.ppm    > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap -meancolor -meanpixel 16 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmcolormap -spreadbrightness -spreadluminosity 16 \
   testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pnmcrop1.test b/test/pnmcrop1.test
index 00d5ee80..3c86efd7 100755
--- a/test/pnmcrop1.test
+++ b/test/pnmcrop1.test
@@ -78,7 +78,7 @@ for option in "-reportfull -reportsize" \
               "-bg-color=black -closeness=101"
     do
     pnmcrop -reportfull ${option} testgrid.pbm > ${test_out} || \
-        echo -n "Expected failure: " ${option}
+        printf "Expected failure:  %s" "${option}"
         test -s ${test_out}; echo " "$?
         rm ${test_out}
     done
diff --git a/test/pnmcrop3.test b/test/pnmcrop3.test
index 04cf6f52..1e8da345 100755
--- a/test/pnmcrop3.test
+++ b/test/pnmcrop3.test
@@ -68,12 +68,12 @@ echo "Error messages should appear below the line." 1>&2
 echo "--------------------------------------------" 1>&2
 
 pnmcrop -borderfile=${border_ppm} ${test_pbm} > ${test_out} || \
-        echo -n "Expected failure 1";
+        printf "Expected failure 1";
         test -s ${test_out}; echo " "$?
         rm ${test_out}
 
 pnmcrop -borderfile=${border_pbm} ${test_ppm} > ${test_out} || \
-        echo -n "Expected failure 2";
+        printf "Expected failure 2";
         test -s ${test_out}; echo " "$?
         rm ${test_out}
 
diff --git a/test/pnmpsnr.test b/test/pnmpsnr.test
index 3223f86d..ae6ba3cc 100755
--- a/test/pnmpsnr.test
+++ b/test/pnmpsnr.test
@@ -36,22 +36,22 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pnmpsnr ${b_pbm} ${w_pbm} ${b_pbm}     > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmpsnr ${b_pbm}                       > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmpsnr ${b_pbm} ${w_pbm} -target1=100 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmpsnr                   -machine     > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
diff --git a/test/pnmquant.test b/test/pnmquant.test
index 100fb52e..cbe69f5c 100755
--- a/test/pnmquant.test
+++ b/test/pnmquant.test
@@ -42,41 +42,41 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pnmquant 0 testimg.ppm   > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant testimg.ppm  > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant 10.5 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant -center -meancolor 16 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant -center -meanpixel 16 testimg.ppm    > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant -meancolor -meanpixel 16 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant -spreadbrightness -spreadluminosity 16 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmquant -randomseed 1 -norandom 10 testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pnmquantall.test b/test/pnmquantall.test
index f3594c0f..f047e856 100755
--- a/test/pnmquantall.test
+++ b/test/pnmquantall.test
@@ -1,6 +1,6 @@
 #! /bin/sh
 # This script tests: pnmquantall
-# Also requires: ppmtorgb3 pgmhist pnmcat
+# Also requires: ppmtorgb3 pgmhist pamcat
 
 tmpdir=${tmpdir:-/tmp}
 rose_ppm=${tmpdir}/rose.ppm
@@ -21,7 +21,7 @@ done
 
 # Should print 1
 
-pnmcat ${rose_red} ${rose_grn} ${rose_blu} -tb | \
+pamcat ${rose_red} ${rose_grn} ${rose_blu} -tb | \
     pgmhist -m | awk '$2>0 {s++}; END { print (s<=20) }'
 
 
@@ -44,4 +44,4 @@ rm ${rose_red}xx ${rose_grn}xx ${rose_blu}xx || \
 pnmquantall -ext xx 2 || \
    echo "Expected failure 3"
 
-rm ${rose_red} ${rose_grn} ${rose_blu} ${rose_ppm}
\ No newline at end of file
+rm ${rose_red} ${rose_grn} ${rose_blu} ${rose_ppm}
diff --git a/test/pnmremap1.test b/test/pnmremap1.test
index fcd382a7..8626c50f 100755
--- a/test/pnmremap1.test
+++ b/test/pnmremap1.test
@@ -33,32 +33,32 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pnmremap -mapfile=/dev/null testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmremap -mapfile=/dev/zero testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmremap testimg.ppm                    > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmremap -fs -nofs testimg.ppm          > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmremap -mapfile=testgrid.pbm -missingcolor=rgb:00/ff/00 testimg.ppm \
  > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmremap -mapfile=testgrid.pbm -firstisdefault testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/pnmtile.ok b/test/pnmtile.ok
index 279251b8..d28fafa3 100644
--- a/test/pnmtile.ok
+++ b/test/pnmtile.ok
@@ -1,5 +1,5 @@
 4228632379 259
-0 0 : 0
+0 0 0 : 0
 Expected failure 1 1
 Expected failure 2 1
 Expected failure 3 1
diff --git a/test/pnmtile.test b/test/pnmtile.test
index b9597467..b72ef946 100755
--- a/test/pnmtile.test
+++ b/test/pnmtile.test
@@ -1,6 +1,6 @@
 #! /bin/bash
 # This script tests: pnmtile
-# Also requires: pnmcat
+# Also requires: pamcat
 
 
 # Test 1.  Should print 4228632379 259
@@ -8,14 +8,14 @@ pnmtile 40 50 testgrid.pbm | cksum
 
 tmpdir=${tmpdir:-/tmp}
 
-# Test 2.  Compare 2x2 tile images produced by pnmtile and pnmcat
+# Test 2.  Compare 2x2 tile images produced by pnmtile and pamcat
 # Should print 0
 testimg2_ppm=${tmpdir}/testimg2.ppm
 testimg4_ppm=${tmpdir}/testimg4.ppm
 
 pnmtile 454 298 testimg.ppm > ${testimg4_ppm} &&
-pnmcat -lr testimg.ppm testimg.ppm > ${testimg2_ppm} &&
-pnmcat -tb ${testimg2_ppm} ${testimg2_ppm} | \
+pamcat -lr testimg.ppm testimg.ppm > ${testimg2_ppm} &&
+pamcat -tb ${testimg2_ppm} ${testimg2_ppm} | pamtopnm --assume |\
 cmp -s - ${testimg4_ppm}
 echo ${PIPESTATUS[@]} ":" $?
 
@@ -29,21 +29,21 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 pnmtile 100 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmtile 100 0 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmtile 0 100 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 pnmtile 100 100 100 testgrid.pbm > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmforge-parameters.test b/test/ppmforge-parameters.test
index 83758213..e5822673 100755
--- a/test/ppmforge-parameters.test
+++ b/test/ppmforge-parameters.test
@@ -30,36 +30,36 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 ppmforge -night  -dimension=0  > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge  -dimension=10  > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge  -dimension=-1  > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge -clouds -mesh=1.99    > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge -clouds -power=0      > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge         -ice=-1       > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmforge         -glaciers=-1  > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmforge.test b/test/ppmforge.test
index e83ad553..75de7cf7 100755
--- a/test/ppmforge.test
+++ b/test/ppmforge.test
@@ -47,6 +47,6 @@ ppmforge -seed 1 -stars 0 -ice 0.01 \
     -inclination 9  -hour 12 -power 200 > ${test_ppm} 
 ppmforge -seed 1 -stars 0 -ice 0.01 \
     -inclination 10 -hour 12 -power 200 | \
-  pnmpsnr -target1=53.89 -target2=49.38 -target3=65.15 - ${test_ppm}
+  pnmpsnr -target1=46.07 -target2=52.00 -target3=67.77 - ${test_ppm}
 
 rm ${test_ppm}
diff --git a/test/ppmhist.test b/test/ppmhist.test
index 34eb0ea0..8c2eedbc 100755
--- a/test/ppmhist.test
+++ b/test/ppmhist.test
@@ -36,16 +36,16 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 ppmhist -hexcolor -float testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmhist -hexcolor -map   testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmhist -float    -map   testimg.ppm > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmmake.test b/test/ppmmake.test
index 714573b2..30eb596a 100755
--- a/test/ppmmake.test
+++ b/test/ppmmake.test
@@ -19,51 +19,51 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 ppmmake rgb:gg/00/00  2 2  > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake rgb:ff/ff/00  2    > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake rgbi:1.1/0/0  2 2  > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake rgbi:1.0/.5   2 2  > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake rainbow       2 2  > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake               2 2  > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake blue -maxval=0 2 2  > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake blue -maxval=-1 2 2  > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmmake blue -maxval=65536 2 2  > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 RGBDEF=/dev/null ppmmake red 2 2 > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmpat.test b/test/ppmpat.test
index 8f00b9f5..ced2e64a 100755
--- a/test/ppmpat.test
+++ b/test/ppmpat.test
@@ -50,47 +50,47 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 ppmpat -g2 -g3 10 10 > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -madras -tartan 10 10 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -poles -squig 10 10 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -camo -anticamo 10 10 > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -argyle1 -argyle2 10 10 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat 10 10 > ${test_out} || \
-  echo -n "Expected failure 6"
+  printf "Expected failure 6"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -g2 10 > ${test_out} || \
-  echo -n "Expected failure 7"
+  printf "Expected failure 7"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -g2 10 10 10 > ${test_out} || \
-  echo -n "Expected failure 8"
+  printf "Expected failure 8"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -g2 10 > ${test_out} || \
-  echo -n "Expected failure 9"
+  printf "Expected failure 9"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
@@ -101,89 +101,89 @@ clist4="-color=rgb:00/00/00,rgb:00/00/ff,rgb:00/ff/ff,rgb:ff/ff/ff"
 
 # These patterns require exactly 2 colors
 ppmpat -gingham2 ${clist1} 10 10 > ${test_out} || \
-  echo -n "Expected failure 10"
+  printf "Expected failure 10"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -argyle1  ${clist1} 10 10 > ${test_out} || \
-  echo -n "Expected failure 11"
+  printf "Expected failure 11"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -gingham2 ${clist3} 10 10 > ${test_out} || \
-  echo -n "Expected failure 12"
+  printf "Expected failure 12"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -argyle1  ${clist3} 10 10 > ${test_out} || \
-  echo -n "Expected failure 13"
+  printf "Expected failure 13"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # These require exactly 3 colors
 ppmpat -gingham3 ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 14"
+  printf "Expected failure 14"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -argyle2  ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 15"
+  printf "Expected failure 15"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -madras   ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 16"
+  printf "Expected failure 16"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -tartan   ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 17"
+  printf "Expected failure 17"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -gingham3 ${clist4} 10 10 > ${test_out} || \
-  echo -n "Expected failure 18"
+  printf "Expected failure 18"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -argyle2  ${clist4} 10 10 > ${test_out} || \
-  echo -n "Expected failure 19"
+  printf "Expected failure 19"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -madras   ${clist4} 10 10 > ${test_out} || \
-  echo -n "Expected failure 20"
+  printf "Expected failure 20"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -tartan   ${clist4} 10 10 > ${test_out} || \
-  echo -n "Expected failure 21"
+  printf "Expected failure 21"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # These require at least 3 colors
 ppmpat -squig    ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 22"
+  printf "Expected failure 22"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -camo     ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 23"
+  printf "Expected failure 23"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -anticamo ${clist2} 10 10 > ${test_out} || \
-  echo -n "Expected failure 24"
+  printf "Expected failure 24"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 # The squig pattern has an aspect ratio restriction
 ppmpat -squig ${clist3} 10 250  > ${test_out} || \
-  echo -n "Expected failure 25"
+  printf "Expected failure 25"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmpat -squig ${clist3} 500 20  > ${test_out} || \
-  echo -n "Expected failure 26"
+  printf "Expected failure 26"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmtoapplevol.test b/test/ppmtoapplevol.test
index 6b109251..4964ede8 100755
--- a/test/ppmtoapplevol.test
+++ b/test/ppmtoapplevol.test
@@ -15,16 +15,16 @@ tmpdir=${tmpdir:-/tmp}
 test_out=${tmpdir}/test_out
 
 pbmmake 10 11 | ppmtoapplevol > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
  
 pbmmake 10 13 | ppmtoapplevol > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
  
 pbmmake 256 12 | ppmtoapplevol > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ppmwheel.test b/test/ppmwheel.test
index fc390f26..c6583f97 100755
--- a/test/ppmwheel.test
+++ b/test/ppmwheel.test
@@ -122,26 +122,26 @@ echo "Error messages should appear below the line." 1>&2
 echo "-----------------------------------------------------------" 1>&2
 
 ppmwheel 10 -huevalue -huesaturation > ${test_out} || \
-  echo -n "Expected failure 1"
+  printf "Expected failure 1"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmwheel 0 > ${test_out} || \
-  echo -n "Expected failure 2"
+  printf "Expected failure 2"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmwheel 3 > ${test_out} || \
-  echo -n "Expected failure 3"
+  printf "Expected failure 3"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmwheel > ${test_out} || \
-  echo -n "Expected failure 4"
+  printf "Expected failure 4"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
 
 ppmwheel 10 10 > ${test_out} || \
-  echo -n "Expected failure 5"
+  printf "Expected failure 5"
   test -s ${test_out}; echo " "$?
   rm -f ${test_out}
diff --git a/test/ps-roundtrip.test b/test/ps-roundtrip.test
index 8b107315..5d7b7d65 100755
--- a/test/ps-roundtrip.test
+++ b/test/ps-roundtrip.test
@@ -1,6 +1,6 @@
 #! /bin/sh
 # This script tests: pnmtops pstopnm
-# Also requires: pamtopnm gs pbmmake pnmshear pnmpad pnmcat
+# Also requires: pamtopnm gs pbmmake pnmshear pnmpad pamcat
 
 
 # Failure message
@@ -37,7 +37,7 @@ pbmmake -g 2 2 > ${g_pbm}
 pbmmake -g 8 4 | \
   pnmshear 45 -noantialias -background=black | \
   pnmpad -right 60 | \
-  pnmcat -tb -jright - ${g_pbm} > ${t_pbm} &&
+  pamcat -tb -jright - ${g_pbm} > ${t_pbm} &&
 for flag in "" "-rle" "-ps -rle -ascii" \
             "-bitspersample=2 -rle" "-ps -bitspersample=4 -rle" \
             "-bitspersample=8 -rle" "-ps -bitspersample=12 -rle -dict"
diff --git a/test/qoi-roundtrip.ok b/test/qoi-roundtrip.ok
new file mode 100644
index 00000000..faa7601f
--- /dev/null
+++ b/test/qoi-roundtrip.ok
@@ -0,0 +1,34 @@
+Test 1.  Should print 1.N 0 0
+1.0 0 0
+1.1 0 0
+1.2 0 0
+1.3 0 0
+1.4 0 0
+Test 2.  Should print 2.N 0 0
+2.0 0 0
+2.1 0 0
+2.2 0 0
+2.3 0 0
+2.4 0 0
+Test 3.  Should print 3.N 0 0
+3.0 0 0
+3.1 0 0
+3.2 0 0
+3.3 0 0
+3.4 0 0
+Test Invalid
+Should print: Expected failure N 1
+Expected failure 1 1
+Expected failure 2 1
+Expected failure 3 1
+Expected failure 4 1
+Expected failure 5 1
+Expected failure 6 1
+Expected failure 7 1
+Expected failure 8 1
+------------------------------
+Should print: Expected failure N 0
+Expected failure 9 0
+Expected failure 10 0
+Expected failure 11 0
+Expected failure 12 0
diff --git a/test/qoi-roundtrip.test b/test/qoi-roundtrip.test
new file mode 100755
index 00000000..f758abcc
--- /dev/null
+++ b/test/qoi-roundtrip.test
@@ -0,0 +1,254 @@
+#! /bin/bash
+# This script tests: qoitopam pamtoqoi
+# Also requires: pamdepth pamfile pamseq pamstack pamtopnm pbmmake
+# Also requires: pgmnoise ppmpat ppmtopgm
+
+# Ensure that sed operates in original narrow-character mode
+
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+
+tmpdir=${tmpdir:-/tmp}
+
+maze_qoi=${tmpdir}/maze.qoi
+maze_bw_alpha=${tmpdir}/maze_bw_alpha.pam
+maze_rgb=${tmpdir}/maze_rgb.pam
+maze_rgb_alpha=${tmpdir}/maze_rgb_alpha.pam
+
+echo "Test 1.  Should print 1.N 0 0"
+
+pbmmake -g $(pamfile -size maze.pbm) |\
+  pamstack -tupletype="BLACKANDWHITE_ALPHA" maze.pbm - > ${maze_bw_alpha}
+
+pbmmake -g $(pamfile -size maze.pbm) |\
+  pamstack -tupletype="RGB" maze.pbm maze.pbm maze.pbm |\
+  pamdepth 255 > ${maze_rgb}
+printf "1.0 $? "
+
+pbmmake -g $(pamfile -size maze.pbm) |\
+  pamstack -tupletype="RGB_ALPHA" maze.pbm maze.pbm maze.pbm - |\
+  pamdepth 255 > ${maze_rgb_alpha}
+echo $?
+
+# PBM, PBM + alpha
+
+pamtoqoi maze.pbm | tee ${maze_qoi} | qoitopam | cmp -s - ${maze_rgb}
+printf "1.1 $? "
+test -s ${maze_qoi}
+echo $?
+
+pamdepth 8 maze.pbm | pamtoqoi | qoitopam | cmp -s - ${maze_rgb}
+printf "1.2 $? "
+test -s ${maze_rgb}
+echo $?
+
+pamtoqoi ${maze_bw_alpha} | qoitopam | cmp -s - ${maze_rgb_alpha}
+printf "1.3 $? "
+test -s ${maze_bw_alpha}
+echo $?
+
+pamdepth 2 ${maze_bw_alpha} | pamstack -tupletype="GRAYSCALE_ALPHA" |\
+  pamtoqoi | qoitopam | cmp -s - ${maze_rgb_alpha}
+printf "1.4 $? "
+test -s ${maze_rgb_alpha}
+echo $?
+
+rm ${maze_rgb} ${maze_rgb_alpha} ${maze_bw_alpha}
+
+echo "Test 2.  Should print 2.N 0 0"
+
+# ---- PGM, PGM + alpha
+
+noise1_pgm=${tmpdir}/noise1.pgm
+noise2_pgm=${tmpdir}/noise2.pgm
+test_gray_alpha=${tmpdir}/test_gray_alpha.pam
+res_gray_alpha=${tmpdir}/res_gray_alpha.pam
+
+pgmnoise --randomseed=0 20 20 > ${noise1_pgm}
+pgmnoise --randomseed=1 20 20 | tee ${noise2_pgm} |\
+  pamstack -tupletype="GRAYSCALE_ALPHA" ${noise1_pgm} - \
+  > ${test_gray_alpha}
+printf "2.0 $? "
+
+pamstack -tupletype="RGB_ALPHA" \
+  ${noise1_pgm} ${noise1_pgm} ${noise1_pgm} ${noise2_pgm} > ${res_gray_alpha}
+echo $?
+
+rm ${noise2_pgm}
+
+pamtoqoi ${noise1_pgm} | qoitopam | pamtopnm | ppmtopgm |\
+  cmp -s - ${noise1_pgm}
+printf "2.1 $? "
+test -s ${noise1_pgm}
+echo $?
+
+
+pamdepth 65535 ${noise1_pgm} | pamtoqoi | qoitopam | pamtopnm | ppmtopgm |\
+  cmp -s - ${noise1_pgm}
+printf "2.2 $? "
+test -s ${noise1_pgm}
+echo $?
+
+pamtoqoi ${test_gray_alpha} | qoitopam | cmp -s - ${res_gray_alpha}
+printf "2.3 $? "
+test -s ${test_gray_alpha}
+echo $?
+
+pamdepth 4095 ${test_gray_alpha} | pamtoqoi | qoitopam |\
+  cmp -s - ${res_gray_alpha}
+printf "2.4 $? "
+test -s ${res_gray_alpha}
+echo $?
+
+rm ${test_gray_alpha} ${res_gray_alpha}
+
+echo "Test 3.  Should print 3.N 0 0"
+
+# rgb rgb-alpha
+
+tartan_ppm=${tmpdir}/tartan.ppm
+tartan_qoi=${tmpdir}/tartan.qoi
+test_rgb_alpha=${tmpdir}/test_rgb_alpha.pam
+
+ppmpat -tartan 20 20 | tee ${tartan_ppm} |\
+  pamstack -tupletype="RGB_ALPHA" - ${noise1_pgm} > ${test_rgb_alpha}
+printf "3.0 $? "
+test -s ${tartan_ppm}
+echo $?
+
+pamtoqoi ${tartan_ppm} | tee ${tartan_qoi} | qoitopam | pamtopnm |\
+  cmp -s - ${tartan_ppm}
+printf "3.1 $? "
+test -s ${tartan_qoi}
+echo $?
+rm ${tartan_qoi}
+
+pamdepth 4095 ${tartan_ppm} | pamtoqoi | tee ${tartan_qoi} |\
+  qoitopam | pamtopnm | cmp -s - ${tartan_ppm}
+printf "3.2 $? "
+test -s ${tartan_qoi}
+echo $?
+rm ${tartan_qoi}
+
+pamtoqoi ${test_rgb_alpha} | qoitopam | cmp -s - ${test_rgb_alpha}
+printf "3.3 $? "
+test -s ${test_rgb_alpha}
+echo $?
+
+pamdepth 511 ${test_rgb_alpha} | pamtoqoi | qoitopam |\
+  cmp -s - ${test_rgb_alpha}
+printf "3.4 $? "
+test -s ${test_rgb_alpha}
+echo $?
+
+rm ${noise1_pgm} ${tartan_ppm} ${test_rgb_alpha}
+
+
+echo "Test Invalid"
+
+test_out=${tmpdir}/test.out
+maze_qoi_size=$(cat ${maze_qoi} | wc -c)
+
+echo 1>&2
+echo "Invalid command-line argument combinations." 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
+echo "Should print: Expected failure N 1"
+
+pamseq -tupletype="void" 1 1 | pamtoqoi > \
+  ${test_out} || \
+  printf "Expected failure 1"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( echo "P1"; echo "400000001 1" ;
+  head -c 400000001 /dev/zero ) | pamtoqoi > \
+  ${test_out} || \
+  printf "Expected failure 2"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# Replace fields in the qoi file header with invalid values
+  
+( printf "qojf"
+  tail -c +5 ${maze_qoi} ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 3"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( head -c 4 ${maze_qoi}
+  head -c 4 /dev/zero
+  tail -c +9 ${maze_qoi} ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 4"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( head -c 8 ${maze_qoi}
+  head -c 4 /dev/zero
+  tail -c +13 ${maze_qoi} ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 5"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+# Following sed construct means: "replace first 8 bytes of input
+# stream."  echo -e -n and printf may be straightforward but
+# unfortunately they are not portable.  Is there any better solution?
+# Suggestions welcome.
+
+( head -c 4 ${maze_qoi}
+  tail -c +5 ${maze_qoi} | \
+  sed '1s/^......../\x00\x00\x4F\xFF\x00\x00\x4F\xFF/' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 6"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( head -c 12 ${maze_qoi}
+  tail -c +13 ${maze_qoi} | sed '1s/^./\x01/' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 7"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( head -c 13 ${maze_qoi}
+  tail -c +14 ${maze_qoi} | sed '1s/^./\x02/' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 8"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+echo "------------------------------"
+echo "Should print: Expected failure N 0"
+
+( head -c $(( ${maze_qoi_size} - 9 )) ${maze_qoi}
+  tail -c 9 ${maze_qoi} |   sed '1s/^./\xfd/' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 9"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+head -c $(( ${maze_qoi_size} - 1 )) ${maze_qoi} | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 10"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( head -c $(( ${maze_qoi_size} - 1 )) ${maze_qoi}
+  printf '1' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 11"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+( cat ${maze_qoi}; printf '1' ) | qoitopam > \
+  ${test_out} || \
+  printf "Expected failure 12"
+  test -s ${test_out}; echo " "$?
+  rm -f ${test_out}
+
+rm ${maze_qoi}
diff --git a/test/stdin-pam1.ok b/test/stdin-pam1.ok
index e66da036..10e48622 100644
--- a/test/stdin-pam1.ok
+++ b/test/stdin-pam1.ok
@@ -3,6 +3,7 @@ pamaltsat -strength=1: 0 0 0 0
 pambackground: 0 0 0 0
 pambayer -type=2: 0 0 0 0
 pambrighten: 0 0 0 0
+pamcat -topbottom: 0 0 0 0
 pamcut: 0 0 0 0
 pamdeinterlace: 0 0 0 0
 pamdepth 15: 0 0 0 0
diff --git a/test/stdin-pam1.test b/test/stdin-pam1.test
index 1331abd0..7cb15dfc 100755
--- a/test/stdin-pam1.test
+++ b/test/stdin-pam1.test
@@ -1,6 +1,6 @@
 #! /bin/bash
 # This script tests: pamaddnoise pamaltsat pambackground pambayer
-# This script tests: pambrighten pamcut pamdeinterlace pamdepth pamditherbw
+# This script tests: pambrighten pamcat pamcut pamdeinterlace pamdepth pamditherbw
 # This script tests: pamedge pamexec pamfile pamfind pamfix pamflip
 # This script tests: pamfunc pamhomography pamhue pamlevels
 # This script tests: pammixinterlace pammosaicknit pamoil
@@ -24,6 +24,7 @@ for testprog in \
     pambackground \
     "pambayer -type=2" \
     pambrighten \
+    "pamcat -topbottom" \
     pamcut \
     pamdeinterlace \
     "pamdepth 15" \
diff --git a/test/stdin-pam3.ok b/test/stdin-pam3.ok
index 0ec9cf5d..516e9554 100644
--- a/test/stdin-pam3.ok
+++ b/test/stdin-pam3.ok
@@ -14,6 +14,8 @@ pamtopfm: 0 0 0 0
 pfmtopam: 0 0 0 0
 pamtopng: 0 0 0 0
 pngtopam: 0 0 0 0
+pamtoqoi: 0 0 0 0
+qoitopam: 0 0 0 0
 pamtosrf: 0 0 0 0
 srftopam: 0 0 0 0
 pamtosvg: 0 0 0 0
diff --git a/test/stdin-pam3.test b/test/stdin-pam3.test
index e8c31781..02257942 100755
--- a/test/stdin-pam3.test
+++ b/test/stdin-pam3.test
@@ -5,6 +5,7 @@
 # This script tests: pamtopdbimg pdbimgtopam
 # This script tests: pamtopfm pfmtopam
 # This script tests: pamtopng pngtopam
+# This script tests: pamtoqoi qoitopam
 # This script tests: pamtosrf srftopam
 # This script tests: pamtosvg svgtopam
 # This script tests: pamtowinicon winicontopam
@@ -36,6 +37,7 @@ for fmt in \
     pdbimg \
     pfm \
     png \
+    qoi \
     srf \
     svg \
     winicon \
diff --git a/test/stdin-pnm1.ok b/test/stdin-pnm1.ok
index 0a63558c..9f427b23 100644
--- a/test/stdin-pnm1.ok
+++ b/test/stdin-pnm1.ok
@@ -1,5 +1,4 @@
 pnmalias: 0 0 0 0
-pnmcat -lr : 0 0 0 0
 pnmcolormap all : 0 0 0 0
 pnmconvol -matrix=-1,3,-1 : 0 0 0 0
 pnmcrop: 0 0 0 0
diff --git a/test/stdin-pnm1.test b/test/stdin-pnm1.test
index e692ceba..ae848752 100755
--- a/test/stdin-pnm1.test
+++ b/test/stdin-pnm1.test
@@ -1,5 +1,5 @@
 #! /bin/sh
-# This script tests: pnmalias pnmcat pnmcolormap pnmconvol pnmcrop pnmgamma
+# This script tests: pnmalias pnmcolormap pnmconvol pnmcrop pnmgamma
 # This script tests: pnmhisteq pnmhistmap pnminvert pnmmargin pnmmercator
 # This script tests: pnmmontage pnmnlfilt pnmnorm pnmpad pnmquant pnmrotate
 # This script tests: pnmscalefixed pnmshear pnmsmooth pnmtile pnmtoddif
@@ -15,7 +15,6 @@ ppmpat -g2 -color=rgb:00/00/ff,rgb:ff/00/00 -mesh 4 4 > ${small_ppm}
 
 for testprog in  \
         pnmalias \
-        "pnmcat -lr " \
         "pnmcolormap all " \
         "pnmconvol -matrix=-1,3,-1 " \
         pnmcrop \
@@ -57,4 +56,4 @@ rm ${small_ppm}
 # Pnmstitch: requires two input files
 
 # pnmmargin: uses pamflip
-# pnmsmooth: uses pnmconvol
\ No newline at end of file
+# pnmsmooth: uses pnmconvol
diff --git a/test/stdin-ppm2.ok b/test/stdin-ppm2.ok
index e3fb4219..e1335bfb 100644
--- a/test/stdin-ppm2.ok
+++ b/test/stdin-ppm2.ok
@@ -1,11 +1,11 @@
 ppmtoacad: 0 0 0 0
 ppmtoapplevol: 0 0 0 0
 ppmtoascii: 0 0 0 0
+ppmtoicr -windowname testimage: 0 0 0 0
 ppmtolj: 0 0 0 0
 ppmtomitsu: 0 0 0 0
 ppmtopgm: 0 0 0 0
 ppmtopuzz: 0 0 0 0
 ppmtosixel: 0 0 0 0
 ppmtoterm: 0 0 0 0
-ppmtoicr: 0 0 0 0
 ppmtoyuvsplit: 0 0 0 0
diff --git a/test/stdin-ppm2.test b/test/stdin-ppm2.test
index 81b3954e..e43b6d4c 100755
--- a/test/stdin-ppm2.test
+++ b/test/stdin-ppm2.test
@@ -17,6 +17,7 @@ for testprog in  \
         ppmtoacad \
         ppmtoapplevol \
         ppmtoascii \
+        "ppmtoicr -windowname testimage" \
         ppmtolj \
         ppmtomitsu \
         ppmtopgm \
@@ -34,17 +35,6 @@ for testprog in  \
 
 rm ${test_ppm}
 
-testprog=ppmtoicr
-
-# File name embedded in output; "untitled" if no name
-
-  ${testprog} testgrid.pbm > ${out1};     status1=$?
-  ${testprog} < testgrid.pbm > ${out2};   status2=$?
-  test -s ${out1};                        status3=$?
-  sed 's/untitled/testgrid.pbm/g' ${out2} | cmp -s ${out1} -
-  echo ${testprog}": "${status1} ${status2} ${status3} $?
-  rm ${out1} ${out2}
-
 testprog=ppmtoyuvsplit
 
 # Produces three output files
diff --git a/test/stdin-ppm3.ok b/test/stdin-ppm3.ok
index f7ba5623..42932e92 100644
--- a/test/stdin-ppm3.ok
+++ b/test/stdin-ppm3.ok
@@ -16,5 +16,5 @@ ppmtopj: 0 0 0 0
 pjtoppm: 0 0 0 0
 ppmtospu: 0 0 0 0
 sputoppm: 0 0 0 0
-ppmtoxpm: 0 0 0 0
+ppmtoxpm -name small: 0 0 0 0
 xpmtoppm: 0 0 0 0
diff --git a/test/stdin-ppm3.test b/test/stdin-ppm3.test
index 605b535b..064e3e22 100755
--- a/test/stdin-ppm3.test
+++ b/test/stdin-ppm3.test
@@ -1,4 +1,4 @@
-#! /bin/sh
+o#! /bin/sh
 # This script tests: ppmtobmp bmptopnm
 # This script tests: ppmtoilbm ilbmtoppm
 # This script tests: ppmtoleaf leaftoppm
@@ -33,26 +33,24 @@ for fmt in \
         pj \
         spu \
         xpm
-  do
-  testprog1="ppmto"${fmt}
+do
+  if [ ${fmt} = "xpm" ]
+    then testprog1="ppmto${fmt} -name small";
+  else 	 testprog1="ppmto${fmt}";
+  fi
   if [ ${fmt} = "bmp" ]
     then testprog2=${fmt}"topnm";
-  else testprog2=${fmt}"toppm";
+  else   testprog2=${fmt}"toppm";
   fi
 
   if [ ${fmt} = "spu" ]
     then test_ppm=${large_ppm};
-  else test_ppm=${small_ppm};
+  else   test_ppm=${small_ppm};
   fi
 
   ${testprog1} ${test_ppm} > ${out1};    status1=$?
   ${testprog1} < ${test_ppm} > ${out2};  status2=$?
   test -s ${out1};                       status3=$?
-  if [ ${fmt} = "xpm" ]
-    then
-    sed -i '/^static char/s/static char .* = {/static char file/' \
-	    ${out1} ${out2};
-  fi
   cmp -s ${out1} ${out2}
   echo ${testprog1}": "${status1} ${status2} ${status3} $?
   rm ${out2}
diff --git a/version.mk b/version.mk
index bb914d76..1f56bf78 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
-NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 99
-NETPBM_POINT_RELEASE = 3
+NETPBM_MAJOR_RELEASE = 11
+NETPBM_MINOR_RELEASE = 0
+NETPBM_POINT_RELEASE = 0