about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile55
-rw-r--r--lib/colorname.c36
-rw-r--r--lib/libpam.c553
-rw-r--r--lib/libpamcolor.c17
-rw-r--r--lib/libpamd.c1524
-rw-r--r--lib/libpammap.c60
-rw-r--r--lib/libpamn.c46
-rw-r--r--lib/libpamread.c79
-rw-r--r--lib/libpamwrite.c26
-rw-r--r--lib/libpbm1.c79
-rw-r--r--lib/libpbm2.c95
-rw-r--r--lib/libpbm3.c166
-rw-r--r--lib/libpbmfont.c476
-rw-r--r--lib/libpbmvms.c734
-rw-r--r--lib/libpgm1.c104
-rw-r--r--lib/libpgm2.c18
-rw-r--r--lib/libpm.c477
-rw-r--r--lib/libpnm1.c36
-rw-r--r--lib/libpnm2.c4
-rw-r--r--lib/libpnm3.c2
-rw-r--r--lib/libppm1.c193
-rw-r--r--lib/libppm2.c17
-rw-r--r--lib/libppmcmap.c95
-rw-r--r--lib/libppmcolor.c134
-rw-r--r--lib/libppmd.c228
-rw-r--r--lib/libppmfloyd.c2
-rw-r--r--lib/libppmfuzzy.c4
-rw-r--r--lib/libsystem.c64
-rw-r--r--lib/libsystem_dummy.c2
-rw-r--r--lib/lum.h2
-rw-r--r--lib/pam.h56
-rw-r--r--lib/pamdraw.h317
-rw-r--r--lib/pammap.h6
-rw-r--r--lib/path.c4
-rw-r--r--lib/pbm.h22
-rw-r--r--lib/pbmfont.h2
-rw-r--r--lib/pm.h47
-rw-r--r--lib/pmfileio.c239
-rw-r--r--lib/pnm.h5
-rw-r--r--lib/ppm.h9
-rw-r--r--lib/ppmdfont.c2
-rw-r--r--lib/ppmdraw.h2
-rw-r--r--lib/rgb.txt10
-rw-r--r--lib/standardppmdfont.c2
-rw-r--r--lib/util/Makefile26
-rw-r--r--lib/util/bitio.c (renamed from lib/bitio.c)0
-rw-r--r--lib/util/bitio.h (renamed from lib/bitio.h)0
-rw-r--r--lib/util/filename.c2
-rw-r--r--lib/util/floatcode.h4
-rw-r--r--lib/util/intcode.h2
-rw-r--r--lib/util/io.c92
-rw-r--r--lib/util/io.h11
-rw-r--r--lib/util/mallocvar.c185
-rw-r--r--lib/util/mallocvar.h23
-rw-r--r--lib/util/matrix.c223
-rw-r--r--lib/util/matrix.h11
-rw-r--r--lib/util/nsleep.c8
-rw-r--r--lib/util/nsleep.h2
-rw-r--r--lib/util/nstring.c358
-rw-r--r--lib/util/nstring.h62
-rw-r--r--lib/util/pm_c_util.h67
-rw-r--r--lib/util/runlength.c374
-rw-r--r--lib/util/runlength.h51
-rw-r--r--lib/util/shhopt.c135
-rw-r--r--lib/util/shhopt.h53
-rw-r--r--lib/util/token.c80
-rw-r--r--lib/util/token.h11
-rw-r--r--lib/util/vasprintf.c35
-rw-r--r--lib/util/wordaccess.h51
-rw-r--r--lib/util/wordaccess_be_aligned.h35
-rw-r--r--lib/util/wordaccess_be_unaligned.h (renamed from lib/util/wordaccess_gcc3_be.h)7
-rw-r--r--lib/util/wordaccess_generic.h6
72 files changed, 5715 insertions, 2250 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 0738e5cb..1e607ee5 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -13,7 +13,7 @@ else
 LIBNETPBM = $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).$(NETPBMLIBSUFFIX)
 endif
 
-ifeq ($(STATICLIB_TOO),y)
+ifeq ($(STATICLIB_TOO),Y)
 EXTRA_STATICLIB = libnetpbm.$(STATICLIBSUFFIX)
 else
 EXTRA_STATICLIB =
@@ -25,7 +25,8 @@ else
   LIBSYSTEM = libsystem.o
 endif
 
-LIBOBJECTS = libpm.o pmfileio.o fileio.o bitio.o colorname.o \
+LIBOBJECTS = libpm.o pmfileio.o fileio.o colorname.o \
+	libpamd.o \
 	libpbm1.o libpbm2.o libpbm3.o libpbmfont.o \
 	libpgm1.o libpgm2.o \
 	libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \
@@ -36,24 +37,28 @@ LIBOBJECTS = libpm.o pmfileio.o fileio.o bitio.o colorname.o \
 	libpamn.o libpammap.o libpamcolor.o \
 	$(LIBSYSTEM) \
 
-ifneq (${VMS}x,x)
-LIBOBJECTS += libpbmvms.o
-endif
 # Library objects to be linked but not built by common.mk:
 LIBOBJECTS_X = \
-  util/shhopt.o \
-  util/nstring.o \
-  util/vasprintf.o \
+  util/bitio.o \
   util/filename.o \
+  util/io.o \
+  util/mallocvar.o \
+  util/matrix.o \
   util/nsleep.o \
+  util/nstring.o \
+  util/runlength.o \
+  util/shhopt.o \
+  util/token.o \
+  util/vasprintf.o \
 
 MANUALS3 = libnetpbm
 MANUALS5 = pbm pgm ppm pnm pam
 
-INTERFACE_HEADERS =  pm.h pbm.h bitio.h pbmfont.h \
-	pgm.h ppm.h ppmcmap.h ppmfloyd.h colorname.h \
-	pnm.h pam.h pammap.h util/shhopt.h util/mallocvar.h \
-	pm_system.h pm_gamma.h ppmdraw.h ppmdfont.h \
+INTERFACE_HEADERS = colorname.h \
+	pam.h pamdraw.h pammap.h pbm.h pbmfont.h \
+	pgm.h pm.h pm_gamma.h pm_system.h pnm.h \
+	ppm.h ppmcmap.h ppmdfont.h ppmdraw.h ppmfloyd.h \
+	util/mallocvar.h util/runlength.h util/shhopt.h \
 
 DATAFILES = rgb.txt
 
@@ -65,10 +70,9 @@ SCRIPTS =
 BINARIES = 
 
 OMIT_LIBRARY_RULE = 1
+ALL_INTERNAL_HEADER_FILES_ARE_QUALIFIED = Y
 include $(SRCDIR)/common.mk
 
-INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
-
 # The following must go after common.mk because $(LIBNETPBM) may 
 # contain a reference to $(NETPBM_MAJOR_RELEASE).
 .PHONY: libnetpbm
@@ -83,12 +87,14 @@ extra_staticlib: $(EXTRA_STATICLIB)
 # type, but request a static library in addition.
 #----------------------------------------------------------------------------
 
+$(LIBOBJECTS): CFLAGS_TARGET=$(CFLAGS_SHLIB)
+
+libpbm3.o: CFLAGS_TARGET+=$(CFLAGS_SSE)
+
 $(LIBOBJECTS): %.o: %.c importinc
-# Note that the user may have configured -I options into CPPFLAGS/CFLAGS.
-	$(CC) -c $(INCLUDES) -DNDEBUG $(CPPFLAGS) $(CFLAGS) $(CFLAGS_SHLIB) \
-	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+	$(CC) -c $(INCLUDES) $(CFLAGS_ALL) -o $@ $<
 
-MAJ = $(NETPBM_MAJOR_RELEASE)
+MAJ = 11
 MIN = $(NETPBM_MINOR_RELEASE)
 
 SONAME = libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ)
@@ -127,7 +133,7 @@ libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN): $(LIBOBJECTS) $(LIBOBJECTS_X)
 endif
 
 ifeq ($(NETPBMLIBTYPE),dll)
-ifeq ($(STATICLIB_TOO),y)
+ifeq ($(STATICLIB_TOO),Y)
 $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X) libnetpbm.$(STATICLIBSUFFIX)
 else
 $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X)
@@ -167,22 +173,23 @@ endif
 # STATICLIB_SUFFIX may just be arbitrary.
 #-----------------------------------------------------------------------------
 ifeq ($(NETPBMLIBTYPE),unixstatic)
-  BUILD_STATICLIB = y
+  BUILD_STATICLIB = Y
 else
-  ifeq ($(STATICLIB_TOO),y)
-    BUILD_STATICLIB = y
+  ifeq ($(STATICLIB_TOO),Y)
+    BUILD_STATICLIB = Y
   else
-    BUILD_STATICLIB = n
+    BUILD_STATICLIB = N
   endif
 endif
 
-ifeq ($(BUILD_STATICLIB),y)
+ifeq ($(BUILD_STATICLIB),Y)
 libnetpbm.$(STATICLIBSUFFIX): $(LIBOBJECTS) $(LIBOBJECTS_X)
 	-rm -f $@
 	$(AR) rc $@ $(LIBOBJECTS) $(LIBOBJECTS_X)
 	-$(RANLIB) $@
 endif
 
+
 # To avoid major hassles with having ppmdcfont available here, we just ship a
 # pre-made standardppmfont.c, so this rule will not normally be used.  Though
 # standardppmdfont.c depends upon standard.ppmdfont, we don't declare that
diff --git a/lib/colorname.c b/lib/colorname.c
index ce53bdcd..83cf5d1a 100644
--- a/lib/colorname.c
+++ b/lib/colorname.c
@@ -15,28 +15,32 @@
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
 #include <ctype.h>
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
 
-#include "nstring.h"
+#include "netpbm/nstring.h"
+
 #include "colorname.h"
 
 static int lineNo;
 
+
+
 void 
-pm_canonstr(char * const str) {
-
-    char * p;
-    for (p = str; *p; ) {
-        if (ISSPACE(*p)) {
-            strcpy(p, &(p[1]));
-        } else {
-            if (ISUPPER(*p))
-                *p = tolower(*p);
-            ++p;
+pm_canonstr(char * const arg) {
+/*----------------------------------------------------------------------------
+   Modify string 'arg' to canonical form: lower case, no white space.
+-----------------------------------------------------------------------------*/
+    const char * srcCursor;
+    char * dstCursor;
+
+    for (srcCursor = arg, dstCursor = arg; *srcCursor; ++srcCursor) {
+        if (!ISSPACE(*srcCursor)) {
+            *dstCursor++ =
+                ISUPPER(*srcCursor) ? tolower(*srcCursor) : *srcCursor;
         }
     }
 }
@@ -65,7 +69,7 @@ openColornameFileSearch(const char * const searchPath,
         *filePP = NULL;  /* initial value */
         while (!eol && !*filePP) {
             const char * token;
-            token = strsepN(&cursor, ":");
+            token = pm_strsep(&cursor, ":");
             if (token) {
                 *filePP = fopen(token, "r");
             } else
@@ -84,8 +88,8 @@ pm_openColornameFile(const char * const fileName, const int must_open) {
    Open the colorname dictionary file.  Its file name is 'fileName', unless
    'fileName' is NULL.  In that case, its file name is the value of the
    environment variable whose name is RGB_ENV (e.g. "RGBDEF").  Except
-   if that environment variable is not set, it is RGB_DB1, RGB_DB2,
-   or RGB_DB3 (e.g. "/usr/lib/X11/rgb.txt"), whichever exists.
+   if that environment variable is not set, it is the first file found,
+   if any, in the search path RGB_DB_PATH.
    
    'must_open' is a logical: we must get the file open or die.  If
    'must_open' is true and we can't open the file (e.g. it doesn't
@@ -195,7 +199,7 @@ pm_parse_dictionary_name(char    const colorname[],
     pixval r,g,b;
 
     f = pm_openColornameFile(NULL, TRUE);  /* exits if error */
-    canoncolor = strdup(colorname);
+    canoncolor = pm_strdup(colorname);
 
     if (!canoncolor)
         pm_error("Failed to allocate memory for %u-byte color name",
diff --git a/lib/libpam.c b/lib/libpam.c
index ab75fab6..cc6368e1 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -1,9 +1,12 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
                                   libpam.c
-------------------------------------------------------------------------------
+===============================================================================
    These are the library functions, which belong in the libnetpbm library,
    that deal with the PAM (Portable Arbitrary Format) image format.
------------------------------------------------------------------------------*/
+
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 
 /* See pmfileio.c for the complicated explanation of this 32/64 bit file
    offset stuff.
@@ -20,9 +23,10 @@
 
 #include <math.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "ppm.h"
 #include "libpbm.h"
@@ -70,6 +74,7 @@ pamCommentP(const struct pam * const pamP) {
 }
 
 
+
 static void
 validateComputableSize(struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -205,21 +210,6 @@ pnm_createBlackTuple(const struct pam * const pamP,
 
 
 
-void
-createBlackTuple(const struct pam * const pamP, 
-                 tuple *            const blackTupleP) {
-
-/* This is poorly named, because it lacks the "pnm" prefix.  But for some
-   reason, this is how we originally named this.  So to maintain backward
-   compatibility with binaries that refer to "createBlackTuple", we define
-   this.  The preferred name, pnm_createBlackTuple() was new in Netpbm 10.20,
-   January 2004.  We should eventually retire createBlackTuple().
-*/
-    pnm_createBlackTuple(pamP, blackTupleP);
-}
-
-
-
 static tuple *
 allocPamRow(const struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -227,12 +217,11 @@ allocPamRow(const struct pam * const pamP) {
    overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
    ensures this assumption is valid.
 -----------------------------------------------------------------------------*/
-    /* The tuple row data structure starts with 'width' pointers to
-       the tuples, immediately followed by the 'width' tuples
-       themselves.  Each tuple consists of 'depth' samples.  
+    /* The tuple row data structure starts with pointers to the tuples,
+       immediately followed by the tuples themselves.
     */
 
-    int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
+    unsigned int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
     tuple * tuplerow;
 
     tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple));
@@ -262,9 +251,9 @@ pnm_allocpamrow(const struct pam * const pamP) {
     tuple * const tuplerow = allocPamRow(pamP);
 
     if (tuplerow == NULL)
-        pm_error("Out of memory allocating space for a tuple row of\n"
-                 "%d tuples by %d samples per tuple by %d bytes per sample.",
-                 pamP->width, allocationDepth(pamP), sizeof(sample));
+        pm_error("Out of memory allocating space for a tuple row of "
+                 "%d tuples by %d samples per tuple by %u bytes per sample.",
+                 pamP->width, allocationDepth(pamP), (unsigned)sizeof(sample));
 
     return tuplerow;
 }
@@ -401,7 +390,7 @@ pnm_setminallocationdepth(struct pam * const pamP,
         pm_error("Can't set minimum allocation depth in pam structure, "
                  "because the structure is only %u bytes long, and to "
                  "have an allocation_depth field, it must bea at least %u",
-                 pamP->len, PAM_STRUCT_SIZE(allocation_depth));
+                 pamP->len, (unsigned)PAM_STRUCT_SIZE(allocation_depth));
 
     pamP->allocation_depth = MAX(allocationDepth, pamP->depth);
         
@@ -430,8 +419,9 @@ pnm_setpamrow(const struct pam * const pamP,
 #define MAX_VALUE_LENGTH 255
 
 static void
-parse_header_line(const char buffer[], char label[MAX_LABEL_LENGTH+1], 
-                  char value[MAX_VALUE_LENGTH+1]) {
+parseHeaderLine(const char buffer[],
+                char label[MAX_LABEL_LENGTH+1], 
+                char value[MAX_VALUE_LENGTH+1]) {
     
     int buffer_curs;
 
@@ -483,9 +473,69 @@ struct headerSeen {
 
 
 static void
-process_header_line(char                const buffer[],
-                    struct pam *        const pamP,
-                    struct headerSeen * const headerSeenP) {
+parseHeaderUint(const char *   const valueString,
+                unsigned int * const valueNumP,
+                const char *   const name) {
+/*----------------------------------------------------------------------------
+   Interpret 'valueString' as the number in a header such as
+   "WIDTH 200".
+
+   'name' is the header name ("WIDTH" in the example).
+-----------------------------------------------------------------------------*/
+
+    if (strlen(valueString) == 0)
+        pm_error("Missing value for %s in PAM file header.", name);
+    else {
+        char * endptr;
+        long int valueNum;
+        errno = 0;  /* Clear errno so we can detect strtol() failure */
+        valueNum = strtol(valueString, &endptr, 10);
+        if (errno != 0)
+            pm_error("Too-large value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if (*endptr != '\0') 
+            pm_error("Non-numeric value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if (valueNum < 0) 
+            pm_error("Negative value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if ((unsigned int)valueNum != valueNum)
+            pm_error("Ridiculously large value for %s in "
+                     "PAM file header: %lu", name, valueNum);
+        else
+            *valueNumP = (unsigned int)valueNum;
+    }
+}
+
+
+
+static void
+parseHeaderInt(const char * const valueString,
+               int *        const valueNumP,
+               const char * const name) {
+/*----------------------------------------------------------------------------
+  This is not what it seems.  It is the same thing as
+  parseHeaderUint, except that the type of the value it returns is
+  "int" instead of "unsigned int".  But that doesn't mean the value can
+  be negative.  We throw an error is it is not positive.
+-----------------------------------------------------------------------------*/
+    unsigned int valueNum;
+
+    parseHeaderUint(valueString, &valueNum, name);
+
+    if ((int)valueNum != valueNum)
+        pm_error("Ridiculously large value for %s in "
+                 "PAM file header: %u", name, valueNum);
+    else
+        *valueNumP = (int)valueNum;
+}
+
+
+
+static void
+processHeaderLine(char                const buffer[],
+                  struct pam *        const pamP,
+                  struct headerSeen * const headerSeenP) {
 /*----------------------------------------------------------------------------
    Process a line from the PAM header.  The line is buffer[], and it is not
    a comment or blank.
@@ -497,67 +547,44 @@ process_header_line(char                const buffer[],
     char label[MAX_LABEL_LENGTH+1];
     char value[MAX_VALUE_LENGTH+1];
 
-    parse_header_line(buffer, label, value);
+    parseHeaderLine(buffer, label, value);
 
-    if (strcmp(label, "ENDHDR") == 0)
+    if (streq(label, "ENDHDR"))
         headerSeenP->endhdr = TRUE;
-    else {
-        if (strcmp(label, "WIDTH") == 0 ||
-            strcmp(label, "HEIGHT") == 0 ||
-            strcmp(label, "DEPTH") == 0 ||
-            strcmp(label, "MAXVAL") == 0) {
-
-            if (strlen(value) == 0)
-                pm_error("Missing value for %s in PAM file header.",
-                         label);
+    else if (streq(label, "WIDTH")) {
+        parseHeaderInt(value, &pamP->width, label);
+        headerSeenP->width = TRUE;
+    } else if (streq(label, "HEIGHT")) {
+        parseHeaderInt(value, &pamP->height, label);
+        headerSeenP->height = TRUE;
+    } else if (streq(label, "DEPTH")) {
+        parseHeaderUint(value, &pamP->depth, label);
+        headerSeenP->depth = TRUE;
+    } else if (streq(label, "MAXVAL")) {
+        unsigned int maxval;
+        parseHeaderUint(value, &maxval, label);
+        if (maxval >= (1<<16))
+            pm_error("Maxval too large: %u.  Max is 65535", maxval);
+        pamP->maxval = maxval;
+        headerSeenP->maxval = TRUE;
+    } else if (streq(label, "TUPLTYPE")) {
+        if (strlen(value) == 0)
+            pm_error("TUPLTYPE header does not have any tuple type text");
+        else {
+            size_t const oldLen = strlen(pamP->tuple_type);
+            if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
+                pm_error("TUPLTYPE value too long in PAM header");
+            if (oldLen == 0)
+                strcpy(pamP->tuple_type, value);
             else {
-                char *endptr;
-                long int numeric_value;
-                errno = 0;  /* Clear errno so we can detect strtol() failure */
-                numeric_value = strtol(value, &endptr, 10);
-                if (errno != 0)
-                    pm_error("Too-large value for %s in "
-                             "PAM file header: '%s'", label, value);
-                if (*endptr != '\0') 
-                    pm_error("Non-numeric value for %s in "
-                             "PAM file header: '%s'", label, value);
-                else if (numeric_value < 0) 
-                    pm_error("Negative value for %s in "
-                             "PAM file header: '%s'", label, value);
+                strcat(pamP->tuple_type, " ");
+                strcat(pamP->tuple_type, value);
             }
+            pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
         }
-    
-        if (strcmp(label, "WIDTH") == 0) {
-            pamP->width = atoi(value);
-            headerSeenP->width = TRUE;
-        } else if (strcmp(label, "HEIGHT") == 0) {
-            pamP->height = atoi(value);
-            headerSeenP->height = TRUE;
-        } else if (strcmp(label, "DEPTH") == 0) {
-            pamP->depth = atoi(value);
-            headerSeenP->depth = TRUE;
-        } else if (strcmp(label, "MAXVAL") == 0) {
-            pamP->maxval = atoi(value);
-            headerSeenP->maxval = TRUE;
-        } else if (strcmp(label, "TUPLTYPE") == 0) {
-            if (strlen(value) == 0)
-                pm_error("TUPLTYPE header does not have any tuple type text");
-            else {
-                size_t const oldLen = strlen(pamP->tuple_type);
-                if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
-                    pm_error("TUPLTYPE value too long in PAM header");
-                if (oldLen == 0)
-                    strcpy(pamP->tuple_type, value);
-                else {
-                    strcat(pamP->tuple_type, " ");
-                    strcat(pamP->tuple_type, value);
-                }
-                pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
-            }
-        } else 
-            pm_error("Unrecognized header line: '%s'.  "
-                     "Possible missing ENDHDR line?", label);
-    }
+    } else 
+        pm_error("Unrecognized header line type: '%s'.  "
+                 "Possible missing ENDHDR line?", label);
 }
 
 
@@ -576,7 +603,7 @@ appendComment(char **      const commentsP,
 
     if (*commentsP == NULL)
         pm_error("Couldn't get storage for %u characters of comments from "
-                 "the PAM header", commentLen);
+                 "the PAM header", (unsigned)commentLen);
 
     strcat(*commentsP, commentLine);
 }
@@ -592,7 +619,7 @@ disposeOfComments(const struct pam * const pamP,
     if (retP)
         *retP = comments;
     else
-        strfree(comments);
+        pm_strfree(comments);
 }
 
 
@@ -635,10 +662,10 @@ readpaminitrest(struct pam * const pamP) {
             buffer[256-1-1] = '\n';  /* In case fgets() truncated */
             if (buffer[0] == '#')
                 appendComment(&comments, buffer);
-            else if (stripeq(buffer, ""));
+            else if (pm_stripeq(buffer, ""));
                 /* Ignore it; it's a blank line */
             else 
-                process_header_line(buffer, pamP, &headerSeen);
+                processHeaderLine(buffer, pamP, &headerSeen);
         }
     }
 
@@ -718,13 +745,127 @@ pnm_readpaminitrestaspnm(FILE * const fileP,
                 
 unsigned int
 pnm_bytespersample(sample const maxval) {
+/*----------------------------------------------------------------------------
+   Return the number of bytes per sample in the PAM raster of a PAM image
+   with maxval 'maxval'.  It's defined to be the minimum number of bytes
+   needed for that maxval, i.e. 1 for maxval < 256, 2 otherwise.
+-----------------------------------------------------------------------------*/
+
+    /* The PAM format requires maxval to be greater than zero and less than
+       1<<16, but since that is a largely arbitrary restriction, we don't want
+       to rely on it.
+    */
 
-    assert(sizeof(maxval) * 8 <= 32);
+    unsigned int i;
+    sample a;
+
+    for (i = 0, a = maxval; i <= sizeof(maxval); ++i) {
+        if (a == 0)
+            return i;
+        a >>= 8;
+    }
+    return 0;  /* silence compiler warning */
+}
+
+
+
+static void
+validateMinDepth(const struct pam * const pamP,
+                 unsigned int       const minDepth) {
 
-    if      (maxval >>  8 == 0) return 1;
-    else if (maxval >> 16 == 0) return 2;
-    else if (maxval >> 24 == 0) return 3;
-    else                        return 4;
+    if (pamP->depth < minDepth)
+        pm_error("Depth %u is insufficient for tuple type '%s'.  "
+                 "Minimum depth is %u",
+                 pamP->depth, pamP->tuple_type, minDepth);
+}
+
+
+
+static void
+interpretTupleType(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Fill in redundant convenience fields in *pamP with information the
+   pamP->tuple_type value implies:
+
+     visual
+     colorDepth
+     haveOpacity
+     opacityPlane
+
+   Validate the tuple type against the depth and maxval as well.
+-----------------------------------------------------------------------------*/
+    const char * const tupleType =
+        pamP->len >= PAM_STRUCT_SIZE(tuple_type) ? pamP->tuple_type : "";
+
+    bool         visual;
+    unsigned int colorDepth;
+    bool         haveOpacity;
+    unsigned int opacityPlane;
+
+    assert(pamP->depth > 0);
+
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: {
+        if (streq(tupleType, "BLACKANDWHITE")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = false;
+            if (pamP->maxval != 1)
+                pm_error("maxval %u is not consistent with tuple type "
+                         "BLACKANDWHITE (should be 1)",
+                         (unsigned)pamP->maxval);
+        } else if (streq(tupleType, "GRAYSCALE")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = false;
+        } else if (streq(tupleType, "GRAYSCALE_ALPHA")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = true;
+            opacityPlane = PAM_GRAY_TRN_PLANE;
+            validateMinDepth(pamP, 2);
+        } else if (streq(tupleType, "RGB")) {
+            visual = true;
+            colorDepth = 3;
+            haveOpacity = false;
+            validateMinDepth(pamP, 3);
+        } else if (streq(tupleType, "RGB_ALPHA")) {
+            visual = true;
+            colorDepth = 3;
+            haveOpacity = true;
+            opacityPlane = PAM_TRN_PLANE;
+            validateMinDepth(pamP, 4);
+        } else {
+            visual = false;
+        }
+    } break;
+    case PPM_TYPE:
+        visual = true;
+        colorDepth = 3;
+        haveOpacity = false;
+        assert(pamP->depth == 3);
+        break;
+    case PGM_TYPE:
+        visual = true;
+        colorDepth = 1;
+        haveOpacity = false;
+        break;
+    case PBM_TYPE:
+        visual = true;
+        colorDepth = 1;
+        haveOpacity = false;
+        break;
+    default:
+        assert(false);
+    }
+    if (pamP->size >= PAM_STRUCT_SIZE(visual))
+        pamP->visual = visual;
+    if (pamP->size >= PAM_STRUCT_SIZE(color_depth))
+        pamP->color_depth = colorDepth;
+    if (pamP->size >= PAM_STRUCT_SIZE(have_opacity))
+        pamP->have_opacity = haveOpacity;
+    if (pamP->size >= PAM_STRUCT_SIZE(opacity_plane))
+        pamP->opacity_plane = opacityPlane;
 }
 
 
@@ -736,9 +877,9 @@ pnm_readpaminit(FILE *       const file,
 
     if (size < PAM_STRUCT_SIZE(tuple_type)) 
         pm_error("pam object passed to pnm_readpaminit() is too small.  "
-                 "It must be large\n"
+                 "It must be large "
                  "enough to hold at least up to the "
-                 "'tuple_type' member, but according\n"
+                 "'tuple_type' member, but according "
                  "to the 'size' argument, it is only %d bytes long.", 
                  size);
 
@@ -794,6 +935,8 @@ pnm_readpaminit(FILE *       const file,
     pamP->plainformat = FALSE;
         /* See below for complex explanation of why this is FALSE. */
 
+    interpretTupleType(pamP);
+
     validateComputableSize(pamP);
 }
 
@@ -867,9 +1010,9 @@ pnm_writepaminit(struct pam * const pamP) {
 
     if (pamP->size < PAM_STRUCT_SIZE(bytes_per_sample))
         pm_error("pam object passed to pnm_writepaminit() is too small.  "
-                 "It must be large\n"
+                 "It must be large "
                  "enough to hold at least up through the "
-                 "'bytes_per_sample' member, but according\n"
+                 "'bytes_per_sample' member, but according "
                  "to its 'size' member, it is only %u bytes long.", 
                  pamP->size);
     if (pamP->len < PAM_STRUCT_SIZE(maxval))
@@ -881,27 +1024,37 @@ pnm_writepaminit(struct pam * const pamP) {
         pm_error("maxval (%lu) passed to pnm_writepaminit() "
                  "is greater than %u", pamP->maxval, PAM_OVERALL_MAXVAL);
 
-    if (pamP->len < PAM_STRUCT_SIZE(tuple_type))
+    if (pamP->len < PAM_STRUCT_SIZE(tuple_type)) {
         tupleType = "";
-    else
+        if (pamP->size >= PAM_STRUCT_SIZE(tuple_type))
+            pamP->tuple_type[0] = '\0';
+    } else
         tupleType = pamP->tuple_type;
 
-    if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample))
-        pamP->len = PAM_STRUCT_SIZE(bytes_per_sample);
     pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+
+    if (pamP->size >= PAM_STRUCT_SIZE(comment_p) &&
+        pamP->len < PAM_STRUCT_SIZE(comment_p))
+        pamP->comment_p = NULL;
+
+    if (pamP->size >= PAM_STRUCT_SIZE(allocation_depth) &&
+        pamP->len < PAM_STRUCT_SIZE(allocation_depth))
+        pamP->allocation_depth = 0;
+
+    interpretTupleType(pamP);
+
+    pamP->len = MIN(pamP->size, PAM_STRUCT_SIZE(opacity_plane));
     
     switch (PAM_FORMAT_TYPE(pamP->format)) {
     case PAM_TYPE:
-        if (pm_plain_output)
-            pm_error("There is no plain version of PAM.  -plain option "
-                     "is not allowed");
+        /* See explanation below of why we ignore 'pm_plain_output' here. */
         fprintf(pamP->file, "P7\n");
         writeComments(pamP);
         fprintf(pamP->file, "WIDTH %u\n",   (unsigned)pamP->width);
         fprintf(pamP->file, "HEIGHT %u\n",  (unsigned)pamP->height);
         fprintf(pamP->file, "DEPTH %u\n",   pamP->depth);
         fprintf(pamP->file, "MAXVAL %lu\n", pamP->maxval);
-        if (!stripeq(tupleType, ""))
+        if (!pm_stripeq(tupleType, ""))
             fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type);
         fprintf(pamP->file, "ENDHDR\n");
         break;
@@ -954,6 +1107,23 @@ pnm_writepaminit(struct pam * const pamP) {
 
 
 
+/* EFFECT OF -plain WHEN WRITING PAM FORMAT:
+
+   Before Netpbm 10.63 (June 2013), pnm_writepaminit() did a pm_error() here
+   if 'pm_plain_output' was set (i.e. the user said -plain).  But this isn't
+   really logical, because -plain is a global option for the program and here
+   we are just writing one image.  As a global option, -plain must be defined
+   to have effect where it makes sense and have no effect where it doesn't.
+   Note that a program that generates GIF just ignores -plain.  Note also that
+   a program could conceivably generate both a PPM image and a PAM image.
+
+   Note also how we handle the other a user can request plain format: the
+   'plainformat' member of the PAM struct.  In the case of PAM, we ignore that
+   member.
+*/
+
+
+
 void
 pnm_checkpam(const struct pam *   const pamP, 
              enum pm_check_type   const checkType, 
@@ -1046,19 +1216,107 @@ pnm_makearrayrgb(const struct pam * const pamP,
 
 
 
+void 
+pnm_makerowrgba(const struct pam * const pamP,
+                tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Make the tuples 'tuplerow' the RGBA equivalent of what they are now,
+   which is described by *pamP.
+
+   This means afterward, *pamP no longer correctly describes these tuples;
+   Caller must be sure to update *pamP it or not use it anymore.
+
+   We fail if Caller did not supply enough allocated space in 'tuplerow' for
+   the extra planes (tuple allocation depth).
+-----------------------------------------------------------------------------*/
+    if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) {
+        pm_message("struct pam length %u is too small for pnm_makerowrgba().  "
+                   "This function requires struct pam fields through "
+                   "'opacity_plane'", pamP->len);
+        abort();
+    } else {
+        if (!pamP->visual)
+            pm_error("Non-visual tuples given to pnm_addopacityrow()");
+        
+        if (pamP->color_depth >= 3 && pamP->have_opacity) {
+            /* It's already in RGBA format.  Leave it alone. */
+        } else {
+            unsigned int col;
+
+            if (allocationDepth(pamP) < 4)
+                pm_error("allocation depth %u passed to pnm_makerowrgba().  "
+                         "Must be at least 4.", allocationDepth(pamP));
+        
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[PAM_TRN_PLANE] = 
+                    pamP->have_opacity ? thisTuple[pamP->opacity_plane] :
+                    pamP->maxval;
+
+                assert(PAM_RED_PLANE == 0);
+                thisTuple[PAM_BLU_PLANE] = thisTuple[0];
+                thisTuple[PAM_GRN_PLANE] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void 
+pnm_addopacityrow(const struct pam * const pamP,
+                  tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Add an opacity plane to the tuples in 'tuplerow', if one isn't already
+   there.
+
+   This means afterward, *pamP no longer correctly describes these tuples;
+   Caller must be sure to update *pamP it or not use it anymore.
+
+   We fail if Caller did not supply enough allocated space in 'tuplerow' for
+   the extra plane (tuple allocation depth).
+-----------------------------------------------------------------------------*/
+    if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) {
+        pm_message("struct pam length %u is too small for pnm_makerowrgba().  "
+                   "This function requires struct pam fields through "
+                   "'opacity_plane'", pamP->len);
+        abort();
+    } else {
+        if (!pamP->visual)
+            pm_error("Non-visual tuples given to pnm_addopacityrow()");
+        
+        if (pamP->have_opacity) {
+            /* It already has opacity.  Leave it alone. */
+        } else {
+            unsigned int const opacityPlane = pamP->color_depth;
+
+            unsigned int col;
+
+            if (allocationDepth(pamP) < opacityPlane + 1)
+                pm_error("allocation depth %u passed to pnm_addopacityrow().  "
+                         "Must be at least %u.",
+                         allocationDepth(pamP), opacityPlane + 1);
+        
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][opacityPlane] = pamP->maxval;
+        }
+    }
+}
+
+
+
 void
 pnm_getopacity(const struct pam * const pamP,
-               bool *             const haveOpacityP,
+               int *              const haveOpacityP,
                unsigned int *     const opacityPlaneP) {
 
-    /* Design note; If use of this information proliferates, we should
-       probably add it to struct pam as convenience values analogous to
-       bytes_per_sample.
+    /* Usage note: this is obsolete since we added 'have_opacity', etc.
+       to struct pam.
     */
-    if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) {
+    if (streq(pamP->tuple_type, "RGB_ALPHA")) {
         *haveOpacityP = TRUE;
         *opacityPlaneP = PAM_TRN_PLANE;
-    } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) {
+    } else if (streq(pamP->tuple_type, "GRAYSCALE_ALPHA")) {
         *haveOpacityP = TRUE;
         *opacityPlaneP = PAM_GRAY_TRN_PLANE;
     } else
@@ -1067,6 +1325,67 @@ pnm_getopacity(const struct pam * const pamP,
 
 
 
+tuple
+pnm_backgroundtuple(struct pam *  const pamP,
+                    tuple      ** const tuples) {
+/*--------------------------------------------------------------------
+  This function was copied from libpnm3.c's pnm_backgroundxel() and
+  modified to use tuples instead of xels.
+----------------------------------------------------------------------*/
+    tuple tuplePtr, bgtuple, ul, ur, ll, lr;
+
+    /* Guess a good background value. */
+    ul = tuples[0][0];
+    ur = tuples[0][pamP->width-1];
+    ll = tuples[pamP->height-1][0];
+    lr = tuples[pamP->height-1][pamP->width-1];
+    bgtuple = NULL;
+
+    /* We first recognize three corners equal.  If not, we look for any
+       two.  If not, we just average all four.
+    */
+    if (pnm_tupleequal(pamP, ul, ur) && pnm_tupleequal(pamP, ur, ll))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ur) &&
+             pnm_tupleequal(pamP, ur, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ll) &&
+             pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ur, ll) &&
+             pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ul, ur))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ll))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ur, ll))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ur, lr))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ll;
+    else {
+        /* Reimplement libpnm3.c's mean4() but for tuples. */
+        unsigned int plane;
+        bgtuple = pnm_allocpamtuple(pamP);
+        for (plane = 0; plane < pamP->depth; ++plane)
+          bgtuple[plane] = (ul[plane] + ur[plane] + ll[plane] + lr[plane]) / 4;
+    }
+    if (!bgtuple) {
+        unsigned int plane;
+        bgtuple = pnm_allocpamtuple(pamP);
+        for (plane = 0; plane < pamP->depth; ++plane)
+          bgtuple[plane] = tuplePtr[plane];
+    }
+
+    return bgtuple;
+}
+
+
+
 /*=============================================================================
    pm_system() Standard Input feeder and Standard Output accepter functions.   
 =============================================================================*/
diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c
index b64f8963..f3ca9a86 100644
--- a/lib/libpamcolor.c
+++ b/lib/libpamcolor.c
@@ -1,9 +1,12 @@
-/*----------------------------------------------------------------------------
+/*============================================================================
                                   libpamcolor.c
-------------------------------------------------------------------------------
-   These are the library functions, which belong in the libnetpbm library,
-   that deal with colors in the PAM image format.
------------------------------------------------------------------------------*/
+==============================================================================
+  These are the library functions, which belong in the libnetpbm library,
+  that deal with colors in the PAM image format.
+
+  This file was originally written by Bryan Henderson and is contributed
+  to the public domain by him and subsequent authors.
+=============================================================================*/
 
 /* See pmfileio.c for the complicated explanation of this 32/64 bit file
    offset stuff.
@@ -17,11 +20,13 @@
 #include <string.h>
 #include <limits.h>
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pam.h"
 #include "ppm.h"
 
 
+
 tuple
 pnm_parsecolor(const char * const colorname,
                sample       const maxval) {
diff --git a/lib/libpamd.c b/lib/libpamd.c
new file mode 100644
index 00000000..952150b4
--- /dev/null
+++ b/lib/libpamd.c
@@ -0,0 +1,1524 @@
+/* 
+**
+** This library module contains the pamdraw routines.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Modified from ppm to pam by Willem van Schaik, Feb 2011
+**
+** 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.
+** 
+** The character drawing routines are by John Walker
+** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+
+#include "pam.h"
+#include "ppmdfont.h"
+
+#include "pamdraw.h"
+
+
+struct penpos {
+    int x;
+    int y;
+};
+
+struct rectangle {
+    /* ((0,0),(0,0)) means empty. */
+    /* 'lr' is guaranteed not to be left of or above 'ul' */
+    struct penpos ul;
+    struct penpos lr;
+};
+
+static struct rectangle const emptyRectangle = {
+    {0, 0},
+    {0, 0},
+};
+
+
+
+static pamd_point
+makePoint(int const x,
+          int const y) {
+
+    return pamd_makePoint(x, y);
+}
+
+
+
+static pamd_point
+middlePoint(pamd_point const a,
+            pamd_point const b) {
+
+    pamd_point retval;
+
+    retval.x = (a.x + b.x) / 2;
+    retval.y = (a.y + b.y) / 2;
+
+    return retval;
+}
+
+
+
+static bool
+pointsEqual(pamd_point const a,
+            pamd_point const b) {
+
+    return a.x == b.x && a.y == b.y;
+}
+
+
+
+static bool
+pointIsWithinBounds(pamd_point   const p,
+                    unsigned int const cols,
+                    unsigned int const rows) {
+
+    return (p.x >= 0 && p.x < cols && p.y >= 0 && p.y < rows);
+}
+
+        
+
+static pamd_point
+vectorSum(pamd_point const a,
+          pamd_point const b) {
+
+    return makePoint(a.x + b.x, a.y + b.y);
+}
+
+
+
+static long int const DDA_SCALE = 8192;
+
+#define PAMD_MAXCOORD 32767
+/*
+  Several factors govern the limit of x, y coordination values.
+
+  The limit must be representable as (signed) int for coordinates to 
+  be carried in struct penpos (immediately above).
+
+  The following calculation, done with long ints, must not overflow:
+  cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0);
+
+  The following must not overflow when DDA_SCALE is set to 8092:
+  dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0);
+
+  Overflow conditions for pamd_text are rather complicated, for commands
+  come from an external PPMD font file.  See comments below.  
+*/
+
+
+
+void
+pamd_validateCoord(int const c) {
+
+    if (c < -PAMD_MAXCOORD || c > PAMD_MAXCOORD)
+        pm_error("Coordinate out of bounds: %d", c);
+}
+
+
+
+void
+pamd_validatePoint(pamd_point const p) {
+
+    if (p.x < -PAMD_MAXCOORD || p.x > PAMD_MAXCOORD)
+        pm_error("x coordinate of (%d, %d) out of bounds", p.x, p.y);
+
+    if (p.y < -PAMD_MAXCOORD || p.y > PAMD_MAXCOORD)
+        pm_error("y coordinate of (%d, %d) out of bounds", p.x, p.y);
+}
+
+
+
+static void
+drawPoint(pamd_drawproc       drawproc,
+          const void *  const clientdata,
+          tuple **      const tuples, 
+          int           const cols, 
+          int           const rows, 
+          int           const depth, 
+          sample        const maxval, 
+          pamd_point    const p) {
+/*----------------------------------------------------------------------------
+   Draw a single point, assuming that it is within the bounds of the
+   image.
+-----------------------------------------------------------------------------*/
+    int i;
+
+    if (drawproc == PAMD_NULLDRAWPROC) {
+        assert(p.x >= 0); assert(p.x < cols);
+        assert(p.y >= 0); assert(p.y < rows);
+
+        for (i = 0; i < depth; i++)
+            tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i);
+    } else {
+        drawproc(tuples, cols, rows, depth, maxval, p, clientdata);
+    }
+}
+
+
+
+void
+pamd_point_drawproc(tuple **     const tuples, 
+                    unsigned int const cols, 
+                    unsigned int const rows, 
+                    unsigned int const depth, 
+                    sample       const maxval, 
+                    pamd_point   const p,
+                    const void * const clientdata) {
+
+    unsigned int i;
+    
+    if ((p.x >= 0) && (p.x < cols) && (p.y >= 0) && (p.y < rows))
+        for (i = 0; i < depth; ++i)
+            tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i);
+}
+
+
+
+static void
+findRectangleIntersection(struct rectangle   const rect1,
+                          struct rectangle   const rect2,
+                          struct rectangle * const intersectionP) {
+/*----------------------------------------------------------------------------
+   Find the intersection between rectangles 'rect1' and 'rect2'.
+   Return it as *intersectionP.
+-----------------------------------------------------------------------------*/
+    struct penpos tentativeUl, tentativeLr;
+
+    tentativeUl.x = MAX(rect1.ul.x, rect2.ul.x);
+    tentativeUl.y = MAX(rect1.ul.y, rect2.ul.y);
+    tentativeLr.x = MIN(rect1.lr.x, rect2.lr.x);
+    tentativeLr.y = MIN(rect1.lr.y, rect2.lr.y);
+
+    if (tentativeLr.x <= tentativeUl.x ||
+        tentativeLr.y <= tentativeUl.y) {
+        /* No intersection */
+        *intersectionP = emptyRectangle;
+    } else {
+        intersectionP->ul = tentativeUl;
+        intersectionP->lr = tentativeLr;
+    }
+}
+
+
+
+void
+pamd_filledrectangle(tuple **      const tuples, 
+                     int           const cols, 
+                     int           const rows, 
+                     int           const depth, 
+                     sample        const maxval, 
+                     int           const left, 
+                     int           const top, 
+                     int           const width, 
+                     int           const height, 
+                     pamd_drawproc       drawProc,
+                     const void *  const clientdata) {
+
+    struct rectangle image, request, intersection;
+    unsigned int row;
+
+    if (width < 0)
+        pm_error("negative width %d passed to pamd_filledrectanglep", width);
+    if (height < 0)
+        pm_error("negative height %d passed to pamd_filledrectanglep", height);
+    if (cols < 0)
+        pm_error("negative image width %d passed to pamd_filledrectanglep",
+                 cols);
+    if (rows < 0)
+        pm_error("negative image height %d passed to pamd_filledrectanglep",
+                 rows);
+
+    request.ul.x = left;
+    request.ul.y = top;
+    request.lr.x = left + width;
+    request.lr.y = top + height;
+
+    image.ul.x = 0;
+    image.ul.y = 0;
+    image.lr.x = cols;
+    image.lr.y = rows;
+
+    findRectangleIntersection(image, request, &intersection);
+
+    /* Draw. */
+    for (row = intersection.ul.y; row < intersection.lr.y; ++row) {
+        unsigned int col;
+        for (col = intersection.ul.x; col < intersection.lr.x; ++col)
+            drawPoint(drawProc, clientdata,
+                      tuples, cols, rows, depth, maxval, makePoint(col, row));
+    }
+}
+
+
+
+/* Outline drawing stuff. */
+
+static int linetype = PAMD_LINETYPE_NORMAL;
+
+
+
+int
+pamd_setlinetype(int const type) {
+
+    int old;
+
+    old = linetype;
+    linetype = type;
+    return old;
+}
+
+
+static bool lineclip = TRUE;
+
+
+
+int
+pamd_setlineclip(int const newSetting) {
+
+    bool previousSetting;
+
+    previousSetting = lineclip;
+
+    lineclip = newSetting;
+
+    return previousSetting;
+}
+
+
+
+static void
+clipEnd0(pamd_point   const p0,
+         pamd_point   const p1,
+         int          const cols,
+         int          const rows,
+         pamd_point * const c0P,
+         bool *       const noLineP) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from p0 to p1, where any of these coordinates may be
+   anywhere in space -- not just in the frame, clip the p0 end to bring it
+   into the frame.  Return the clipped-to location as *c0P.
+
+   Iff this is not possible because the entire line described is
+   outside the frame, return *nolineP == true.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+    pamd_point c0;
+    bool noLine;
+
+    c0 = p0;         /* initial value */
+    noLine = FALSE;  /* initial value */
+
+    /* Clip End 0 of the line horizontally */
+    if (c0.x < 0) {
+        if (p1.x < 0)
+            noLine = TRUE;
+        else {
+            c0.y = c0.y + (p1.y - c0.y) * (-c0.x) / (p1.x - c0.x);
+            c0.x = 0;
+        }
+    } else if (c0.x >= cols) {
+        if (p1.x >= cols)
+            noLine = TRUE;
+        else {
+            c0.y = c0.y + (p1.y - c0.y) * (cols - 1 - c0.x) / (p1.x - c0.x);
+            c0.x = cols - 1;
+        }
+    }
+
+    /* Clip End 0 of the line vertically */
+    if (c0.y < 0) {
+        if (p1.y < 0)
+            noLine = TRUE;
+        else {
+            c0.x = c0.x + (p1.x - c0.x) * (-c0.y) / (p1.y - c0.y);
+            c0.y = 0;
+        }
+    } else if (c0.y >= rows) {
+        if (p1.y >= rows)
+            noLine = TRUE;
+        else {
+            c0.x = c0.x + (p1.x - c0.x) * (rows - 1 - c0.y) / (p1.y - c0.y);
+            c0.y = rows - 1;
+        }
+    }
+
+    /* Clipping vertically may have moved the endpoint out of frame
+       horizontally.  If so, we know the other endpoint is also out of
+       frame horizontally and the line misses the frame entirely.
+    */
+    if (c0.x < 0 || c0.x >= cols) {
+        assert(p1.x < 0 || p1.x >= cols);
+        noLine = TRUE;
+    }
+    *c0P = c0;
+    *noLineP = noLine;
+}
+
+
+
+static void
+clipEnd1(pamd_point   const p0,
+         pamd_point   const p1,
+         int          const cols,
+         int          const rows,
+         pamd_point * const c1P) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from p0 to p1, where p0 is within the frame, but p1
+   can be anywhere in space, clip the p1 end to bring it into the frame.
+   Return the clipped-to location as *c1P.
+
+   This is guaranteed to be possible, since we already know at least one point
+   (i.e. p0) is in the frame.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+    pamd_point c1;
+        /* The current clipped location of p1; we clip it multile times
+           to get the final location.
+        */
+    /* p0 is in the frame: */
+    assert(p0.x >= 0 && p0.x < cols);
+    assert(p0.y >= 0 && p0.y < rows);
+    
+    /* Clip End 1 of the line horizontally */
+    c1 = p1;  /* initial value */
+    
+    if (c1.x < 0) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is left of frame.
+        */
+        c1.y = c1.y + (p0.y - c1.y) * (-c1.x) / (p0.x - c1.x);
+        c1.x = 0;
+    } else if (c1.x >= cols) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is right of frame.
+        */
+        c1.y = c1.y + (p0.y - c1.y) * (cols - 1 - c1.x) / (p0.x - c1.x);
+        c1.x = cols - 1;
+    }
+    
+    /* Clip End 1 of the line vertically */
+    if (c1.y < 0) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is above frame.
+        */
+        c1.x = c1.x + (p0.x - c1.x) * (-c1.y) / (p0.y - c1.y);
+        c1.y = 0;
+    } else if (c1.y >= rows) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is below frame.
+        */
+        c1.x = c1.x + (p0.x - c1.x) * (rows - 1 - c1.y) / (p0.y - c1.y);
+        c1.y = rows - 1;
+    }
+
+    *c1P = c1;
+}
+
+
+
+static void
+clipLine(pamd_point   const p0,
+         pamd_point   const p1,
+         int          const cols,
+         int          const rows,
+         pamd_point * const c0P,
+         pamd_point * const c1P,
+         bool *       const noLineP) {
+/*----------------------------------------------------------------------------
+   Clip the line that goes from p0 to p1 so that none of it is outside the
+   boundaries of the raster with width 'cols' and height 'rows'
+
+   The clipped line goes from *c0P to *c1P.
+
+   But if the entire line is outside the boundaries (i.e. we clip the
+   entire line), return *noLineP true and the other values undefined.
+-----------------------------------------------------------------------------*/
+    pamd_point c0, c1;
+        /* The line we successively modify.  Starts out as the input
+           line and ends up as the output line.
+        */
+    bool noLine;
+
+    clipEnd0(p0, p1, cols, rows, &c0, &noLine);
+
+    if (!noLine) {
+        /* p0 is in the frame: */
+        assert(c0.x >= 0 && c0.x < cols);
+        assert(c0.y >= 0 && c0.y < rows);
+
+        clipEnd1(c0, p1, cols, rows, &c1);
+    }
+
+    *c0P = c0;
+    *c1P = c1;
+    *noLineP = noLine;
+}
+
+
+
+static void
+drawShallowLine(pamd_drawproc       drawProc,
+                const void *  const clientdata,
+                tuple **      const tuples, 
+                int           const cols, 
+                int           const rows, 
+                int           const depth, 
+                sample        const maxval, 
+                pamd_point    const p0,
+                pamd_point    const p1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more horizontal than vertical.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+    /* Loop over X domain. */
+    long dy, srow;
+    int dx, col, row, prevrow;
+
+    if (p1.x > p0.x)
+        dx = 1;
+    else
+        dx = -1;
+    dy = (p1.y - p0.y) * DDA_SCALE / abs(p1.x - p0.x);
+    prevrow = row = p0.y;
+    srow = row * DDA_SCALE + DDA_SCALE / 2;
+    col = p0.x;
+    for ( ; ; ) {
+        if (linetype == PAMD_LINETYPE_NODIAGS && row != prevrow) {
+            drawPoint(drawProc, clientdata,
+                      tuples, cols, rows, depth, maxval,
+                      makePoint(col, prevrow));
+            prevrow = row;
+        }
+        drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval,
+                  makePoint(col, row));
+        if (col == p1.x)
+            break;
+        srow += dy;
+        row = srow / DDA_SCALE;
+        col += dx;
+    }
+}
+
+
+
+static void
+drawSteepLine(pamd_drawproc       drawProc,
+              const void *  const clientdata,
+              tuple **      const tuples, 
+              int           const cols, 
+              int           const rows, 
+              int           const depth,
+              sample        const maxval, 
+              pamd_point    const p0,
+              pamd_point    const p1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more vertical than horizontal.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+    /* Loop over Y domain. */
+
+    long dx, scol;
+    int dy, col, row, prevcol;
+
+    if (p1.y > p0.y)
+        dy = 1;
+    else
+        dy = -1;
+    dx = (p1.x - p0.x) * DDA_SCALE / abs(p1.y - p0.y);
+    row = p0.y;
+    prevcol = col = p0.x;
+    scol = col * DDA_SCALE + DDA_SCALE / 2;
+    for ( ; ; ) {
+        if (linetype == PAMD_LINETYPE_NODIAGS && col != prevcol) {
+            drawPoint(drawProc, clientdata,
+                      tuples, cols, rows, depth, maxval,
+                      makePoint(prevcol, row));
+            prevcol = col;
+        }
+        drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval,
+                  makePoint(col, row));
+        if (row == p1.y)
+            break;
+        row += dy;
+        scol += dx;
+        col = scol / DDA_SCALE;
+    }
+}
+
+
+
+void
+pamd_line(tuple **      const tuples, 
+          int           const cols, 
+          int           const rows, 
+          int           const depth, 
+          sample        const maxval, 
+          pamd_point    const p0,
+          pamd_point    const p1,
+          pamd_drawproc       drawProc,
+          const void *  const clientdata) {
+
+    pamd_point c0, c1;
+    bool noLine;  /* There's no line left after clipping */
+
+    pamd_validateCoord(cols);
+    pamd_validateCoord(rows);
+    pamd_validatePoint(p0);
+    pamd_validatePoint(p1);
+
+    if (lineclip) {
+        clipLine(p0, p1, cols, rows, &c0, &c1, &noLine);
+    } else {
+        c0 = p0;
+        c1 = p1;
+        noLine = FALSE;
+    }
+
+    if (noLine) {
+        /* Nothing to draw */
+    } else if (pointsEqual(c0, c1)) {
+        /* This line is just a point.  Because there aren't two
+           distinct endpoints, we have a special case.
+        */
+        drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval, c0);
+    } else {
+        /* Draw, using a simple DDA. */
+        if (abs(c1.x - c0.x) > abs(c1.y - c0.y))
+            drawShallowLine(drawProc, clientdata, tuples, cols, rows,
+                            depth, maxval, c0, c1);
+        else
+            drawSteepLine(drawProc, clientdata, tuples, cols, rows,
+                          depth, maxval, c0, c1);
+    }
+}
+
+
+
+static unsigned int
+distanceFromLine(pamd_point const p,
+                 pamd_point const l0,
+                 pamd_point const l1) {
+/*----------------------------------------------------------------------------
+  Compute, sort of, the distance between point 'p' and the line through
+  'l0' and 'l1'.
+
+  I don't really know the signficance of this measurement.
+-----------------------------------------------------------------------------*/
+
+    pamd_point const middle = middlePoint(l0, l1);
+
+    return (abs(p.x - middle.x) + abs(p.y - middle.y));
+}
+
+
+
+void
+pamd_spline3(tuple **      const tuples, 
+             int           const cols, 
+             int           const rows, 
+             int           const depth, 
+             sample        const maxval, 
+             pamd_point    const p0,
+             pamd_point    const ctl,
+             pamd_point    const p1,
+             pamd_drawproc       drawProc,
+             const void *  const clientdata) {
+
+    static unsigned int const splineThresh = 3;
+        /* The limit of recursion */
+
+    if (distanceFromLine(ctl, p0, p1) <= splineThresh) {
+        /* The control point is pretty close to the straight line that
+           joins the endpoints, so we'll just draw a straight line.
+        */
+        pamd_line(
+            tuples, cols, rows, depth, maxval, p0, p1, drawProc, clientdata);
+    } else {
+        /* We want some curvature, so pick a point (b) sort of between the
+           two endpoints and the control point and then draw a spline
+           between each of the endpoints and (b):
+        */
+        pamd_point const a = middlePoint(p0, ctl);
+        pamd_point const c = middlePoint(ctl, p1);
+        pamd_point const b = middlePoint(a, c);
+
+        pamd_spline3(
+            tuples, cols, rows, depth, maxval, p0, a, b, drawProc, clientdata);
+
+        pamd_spline3(
+            tuples, cols, rows, depth, maxval, b, c, p1, drawProc, clientdata);
+    }
+}
+
+
+
+void
+pamd_polyspline(tuple **      const tuples, 
+                unsigned int  const cols, 
+                unsigned int  const rows, 
+                unsigned int  const depth, 
+                sample        const maxval, 
+                pamd_point    const p0,
+                unsigned int  const nc,
+                pamd_point *  const c,
+                pamd_point    const p1,
+                pamd_drawproc       drawProc,
+                const void *  const clientdata) {
+
+    pamd_point p;
+    
+    unsigned int i;
+
+    assert(nc > 0);
+
+    p = p0;
+    for (i = 0; i < nc - 1; ++i) {
+        pamd_point const n = middlePoint(c[i], c[i+1]);
+        pamd_spline3(
+            tuples, cols, rows, depth, maxval, p, c[i], n,
+            drawProc, clientdata);
+        p = n;
+    }
+    pamd_spline3(
+        tuples, cols, rows, depth, maxval, p, c[nc - 1], p1,
+        drawProc, clientdata);
+}
+
+
+
+void
+pamd_spline4(tuple **      const tuples, 
+             unsigned int  const cols, 
+             unsigned int  const rows, 
+             unsigned int  const depth, 
+             sample        const maxval, 
+             pamd_point    const endPt0,
+             pamd_point    const endPt1,
+             pamd_point    const ctlPt0,
+             pamd_point    const ctlPt1,
+             pamd_drawproc       drawproc,
+             const void *  const clientdata) {
+/*----------------------------------------------------------------------------
+   Draw a cubic spline from 'endPt0' to 'endPt1', using 'ctlPt0' and
+   'ctlPt1' as control points in the classic way: a line through
+   'endPt0' and 'ctlPt0' is tangent to the curve at 'entPt0' and the
+   length of that line controls "enthusiasm," whatever that is.
+   Same for 'endPt1' and 'ctlPt1'.
+-----------------------------------------------------------------------------*/
+
+    pm_error("pamd_spline4() has not been written yet!");
+
+}
+
+
+
+void
+pamd_circle(tuple **      const tuples, 
+            unsigned int  const cols, 
+            unsigned int  const rows, 
+            unsigned int  const depth, 
+            sample        const maxval, 
+            pamd_point    const center,
+            unsigned int  const radius, 
+            pamd_drawproc       drawProc,
+            const void *  const clientData) {
+/*----------------------------------------------------------------------------
+  If lineclip mode is on, draw only points within the image.
+  If lineclip is off, "draw" all points (by designated drawproc).  Note
+  that the drawproc can't actually draw a point outside the image, but
+  it might maintain state that is affected by imaginary points outside
+  the image.
+
+  Initial point is 3 o'clock. 
+-----------------------------------------------------------------------------*/
+    if (radius >= DDA_SCALE)
+        pm_error("Error drawing circle.  Radius %d is too large.", radius);
+
+    pamd_validateCoord(center.x + radius);
+    pamd_validateCoord(center.y + radius);
+    pamd_validateCoord(center.x - radius);
+    pamd_validateCoord(center.y - radius);
+
+    if (radius > 0) {
+        long const e = DDA_SCALE / radius;
+
+        pamd_point const p0 = makePoint(radius, 0);  /* 3 o'clock */
+            /* The starting point around the circle, assuming (0, 0) center */
+        pamd_point p;
+            /* Current drawing position in the circle, assuming (0,0) center */
+        bool onFirstPoint;
+        bool prevPointExists;
+        pamd_point prevPoint;
+            /* Previous drawing position, assuming (0, 0) center*/
+        long sx, sy;  /* 'p', scaled by DDA_SCALE */
+
+        p = p0;
+
+        sx = p.x * DDA_SCALE + DDA_SCALE / 2;
+        sy = p.y * DDA_SCALE + DDA_SCALE / 2;
+
+        onFirstPoint = TRUE;
+        prevPointExists = FALSE;
+
+        while (onFirstPoint || !pointsEqual(p, p0)) {
+            if (prevPointExists && pointsEqual(p, prevPoint)) {
+                /* We're on the same point we were on last time (we moved less
+                   than a point's worth).  Just keep moving.
+                */
+            } else {
+                pamd_point const imagePoint = vectorSum(center,p);
+                if (!lineclip || pointIsWithinBounds(imagePoint, cols, rows))
+                    drawPoint(drawProc, clientData,
+                              tuples, cols, rows, depth, maxval, imagePoint);
+
+                prevPoint = p;
+                prevPointExists = TRUE;
+            }
+
+            if (!pointsEqual(p, p0))
+                onFirstPoint = FALSE;
+
+            sx += e * sy / DDA_SCALE;
+            sy -= e * sx / DDA_SCALE;
+            p = makePoint(sx / DDA_SCALE, sy / DDA_SCALE);
+        }
+    }
+}
+
+
+
+/* Arbitrary fill stuff. */
+
+typedef struct {
+    pamd_point point;
+    int edge;
+} coord;
+
+typedef struct fillState {
+    int n;
+        /* Number of elements in 'coords' */
+    int size;
+    int curedge;
+    int segstart;
+    int ydir;
+    int startydir;
+    coord * coords;
+} fillState;
+
+typedef struct fillobj {
+
+    /* The only reason we have a struct fillState separate from
+       struct fillobj is that the drawproc interface is defined to
+       have drawing not modify the fillobj, i.e. it passed
+       const fillobj * to the drawing program.
+    */
+    struct fillState * stateP;
+} fillobj;
+
+#define SOME 1000
+
+static int oldclip;
+
+
+
+struct fillobj *
+pamd_fill_create(void) {
+
+    fillobj * fillObjP;
+    struct fillState * stateP;
+
+    MALLOCVAR(fillObjP);
+    if (fillObjP == NULL)
+        pm_error("out of memory allocating a fillhandle");
+
+    MALLOCVAR(stateP);
+    if (stateP == NULL)
+        pm_error("out of memory allocating a fillhandle");
+
+    stateP->n = 0;
+    stateP->size = SOME;
+    MALLOCARRAY(stateP->coords, stateP->size);
+    if (stateP->coords == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    stateP->curedge = 0;
+
+    fillObjP->stateP = stateP;
+    
+    /* Turn off line clipping. */
+    /* UGGH! We must eliminate this global variable */
+    oldclip = pamd_setlineclip(0);
+    
+    return fillObjP;
+}
+
+
+
+void
+pamd_fill_destroy(struct fillobj * const fillObjP) {
+
+    free(fillObjP->stateP->coords);
+    free(fillObjP->stateP);
+    free(fillObjP);
+}
+
+
+
+static void
+addCoord(struct fillState *  const stateP,
+         pamd_point const point) {
+
+    stateP->coords[stateP->n].point = point;
+    stateP->coords[stateP->n].edge = stateP->curedge;
+
+    ++stateP->n;
+}
+
+
+
+static void
+startNewSegment(struct fillState * const stateP) {
+/*----------------------------------------------------------------------------
+   Close off the segment we're currently building and start a new one.
+-----------------------------------------------------------------------------*/
+    if (stateP->startydir != 0 && stateP->ydir != 0) {
+        /* There's stuff in the current segment.  */
+        if (stateP->startydir == stateP->ydir) {
+            /* Oops, first edge and last edge of current segment are the same.
+               Change all points in the first edge to be in the last.
+            */
+            int const firstEdge = stateP->coords[stateP->segstart].edge;
+            int const lastEdge  = stateP->coords[stateP->n - 1].edge;
+            coord * const segStartCoordP = &stateP->coords[stateP->segstart];
+            coord * const segEndCoordP   = &stateP->coords[stateP->n];
+
+            coord * fcP;
+
+            for (fcP = segStartCoordP;
+                 fcP < segEndCoordP && fcP->edge == firstEdge;
+                 ++fcP)
+                fcP->edge = lastEdge;
+        }
+    }
+    /* And start new segment. */
+    ++stateP->curedge;
+    stateP->segstart  = stateP->n;
+    stateP->ydir      = 0;
+    stateP->startydir = 0;
+}
+
+
+
+static void
+continueSegment(struct fillState * const stateP,
+                int                const dy) {
+/*----------------------------------------------------------------------------
+   'dy' is how much the current point is above the previous one.
+-----------------------------------------------------------------------------*/
+    if (dy != 0) {
+        if (stateP->ydir != 0 && stateP->ydir != dy) {
+            /* Direction changed.  Insert a fake coord, old
+               position but new edge number.
+            */
+            ++stateP->curedge;
+            addCoord(stateP, stateP->coords[stateP->n - 1].point);
+        }
+        stateP->ydir = dy;
+        if (stateP->startydir == 0)
+            stateP->startydir = dy;
+    }
+}
+
+
+
+/* pamd_fill_drawproc() is a drawproc that turns an outline drawing function
+   into a filled shape function.  This is a somewhat off-label application of
+   a drawproc:  A drawproc is intended just to draw a point.  So e.g. you
+   might draw a circle with a fat brush by calling pamd_circle with a drawproc
+   that draws a point as a 10-tuple disk.
+
+   But pamd_fill_drawproc() just draws a point the trivial way: as one tuple.
+   However, it tracks every point that is drawn in a form that a subsequent
+   pamd_fill() call can use to to fill in the shape drawn, assuming it turns
+   out to be a closed shape.
+*/
+
+void
+pamd_fill_drawproc(tuple **     const tuples, 
+                   unsigned int const cols, 
+                   unsigned int const rows, 
+                   unsigned int const depth, 
+                   sample       const maxval, 
+                   pamd_point   const p,
+                   const void * const clientdata) {
+
+    const fillobj *    const fillObjP = clientdata;
+    struct fillState * const stateP   = fillObjP->stateP;
+
+    /* Make room for two more coords, the max we might add. */
+    if (stateP->n + 2 > stateP->size) {
+        stateP->size += SOME;
+        REALLOCARRAY(stateP->coords, stateP->size);
+        if (stateP->coords == NULL)
+            pm_error("out of memory enlarging a fillhandle");
+    }
+
+    if (stateP->n == 0) {
+        /* Start first segment. */
+        stateP->segstart = stateP->n;
+        stateP->ydir = 0;
+        stateP->startydir = 0;
+        addCoord(stateP, p);
+    } else {
+        pamd_point const prevPoint = stateP->coords[stateP->n - 1].point;
+        int const dx = p.x - prevPoint.x;
+        int const dy = p.y - prevPoint.y;
+
+        if (dx == 0 && dy == 0) {
+            /* These are the same coords we had last time; don't bother */
+        } else {
+            if (abs(dx) > 1 || abs(dy) > 1)
+                startNewSegment(stateP);
+            else
+                continueSegment(stateP, dy);
+
+            addCoord(stateP, p);
+        }
+    }
+}
+
+
+
+#ifndef LITERAL_FN_DEF_MATCH
+static qsort_comparison_fn yxCompare;
+#endif
+
+static int
+yxCompare(const void * const c1Arg,
+          const void * const c2Arg) {
+
+    const coord * const c1P = c1Arg;
+    const coord * const c2P = c2Arg;
+
+    pamd_point const p1 = c1P->point;
+    pamd_point const p2 = c2P->point;
+
+    int retval;
+    
+    if (p1.y > p2.y)
+        retval = 1;
+    else if (p1.y < p2.y)
+        retval = -1;
+    else if (p1.x > p2.x)
+        retval = 1;
+    else if (p1.x < p2.x)
+        retval = -1;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+void
+pamd_fill(tuple **         const tuples, 
+          int              const cols, 
+          int              const rows, 
+          int              const depth, 
+          sample           const maxval, 
+          struct fillobj * const fillObjP,
+          pamd_drawproc          drawProc,
+          const void *     const clientdata) {
+    
+    struct fillState * const fh = fillObjP->stateP;
+
+    int pedge;
+    int i, edge, lx, rx, py;
+    coord * cp;
+    bool eq;
+    bool leftside;
+
+    /* Close off final segment. */
+    if (fh->n > 0 && fh->startydir != 0 && fh->ydir != 0) {
+        if (fh->startydir == fh->ydir) {
+            /* Oops, first edge and last edge are the same. */
+            coord * fcp;
+            const coord * const fcpLast = & (fh->coords[fh->n - 1]);
+            int lastedge, oldedge;
+
+            lastedge = fh->coords[fh->n - 1].edge;
+            fcp = &(fh->coords[fh->segstart]);
+            oldedge = fcp->edge;
+            for ( ; fcp<=fcpLast && fcp->edge == oldedge; ++fcp )
+                fcp->edge = lastedge;
+        }
+    }
+    /* Restore clipping now. */
+    pamd_setlineclip(oldclip);
+
+    /* Sort the coords by Y, secondarily by X. */
+    qsort((char*) fh->coords, fh->n, sizeof(coord), yxCompare);
+
+    /* Find equal coords with different edge numbers, and swap if necessary. */
+    edge = -1;
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i > 1 && eq && cp->edge != edge && cp->edge == pedge) {
+            /* Swap .-1 and .-2. */
+            coord t;
+
+            t = fh->coords[i-1];
+            fh->coords[i-1] = fh->coords[i-2];
+            fh->coords[i-2] = t;
+        }
+        if (i > 0) {
+            if (cp->point.x == lx && cp->point.y == py) {
+                eq = TRUE;
+                if (cp->edge != edge && cp->edge == pedge) {
+                    /* Swap . and .-1. */
+                    coord t;
+
+                    t = *cp;
+                    *cp = fh->coords[i-1];
+                    fh->coords[i-1] = t;
+                }
+            } else
+                eq = FALSE;
+        }
+        lx    = cp->point.x;
+        py    = cp->point.y;
+        pedge = edge;
+        edge  = cp->edge;
+    }
+
+    /* Ok, now run through the coords filling spans. */
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i == 0) {
+            lx       = rx = cp->point.x;
+            py       = cp->point.y;
+            edge     = cp->edge;
+            leftside = TRUE;
+        } else {
+            if (cp->point.y != py) {
+                /* Row changed.  Emit old span and start a new one. */
+                pamd_filledrectangle(
+                    tuples, cols, rows, depth, maxval, lx, py, rx - lx + 1, 1,
+                    drawProc, clientdata);
+                lx       = rx = cp->point.x;
+                py       = cp->point.y;
+                edge     = cp->edge;
+                leftside = TRUE;
+            } else {
+                if (cp->edge == edge) {
+                    /* Continuation of side. */
+                    rx = cp->point.x;
+                } else {
+                    /* Edge changed.  Is it a span? */
+                    if (leftside) {
+                        rx       = cp->point.x;
+                        leftside = FALSE;
+                    } else {
+                        /* Got a span to fill. */
+                        pamd_filledrectangle(
+                            tuples, cols, rows, depth, maxval,
+                            lx, py, rx - lx + 1, 1, drawProc, clientdata);
+                        lx       = rx = cp->point.x;
+                        leftside = TRUE;
+                    }
+                    edge = cp->edge;
+                }
+            }
+        }
+    }
+}
+
+
+
+/* Table used to look up sine of angles from 0 through 90 degrees.
+   The value returned is the sine * 65536.  Symmetry is used to
+   obtain sine and cosine for arbitrary angles using this table. */
+
+static long sintab[] = {
+    0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380,
+    12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336,
+    22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767,
+    31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440,
+    40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142,
+    47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683,
+    54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903,
+    59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672,
+    62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898,
+    65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536
+};
+
+static int extleft, exttop, extright, extbottom;  /* To accumulate extents */
+
+
+
+/* LINTLIBRARY */
+
+static long
+isin(int const argDeg) {
+/*----------------------------------------------------------------------------
+    Return sine of an angle in integral degrees.  The value returned is 65536
+    times the sine.
+-----------------------------------------------------------------------------*/
+
+    int deg360;
+        /* The argument reduced to the range 0-360 degrees */
+
+    if (argDeg < 0) {
+        deg360 = (360 - ((- argDeg) % 360)) % 360;
+    } else if (argDeg >= 360) {
+        deg360 = argDeg % 360;
+    } else
+        deg360 = argDeg;
+
+    /* Now look up from table according to quadrant. */
+
+    if (deg360 <= 90) {
+        return sintab[deg360];
+    } else if (deg360 <= 180) {
+        return sintab[180 - deg360];
+    } else if (deg360 <= 270) {
+        return -sintab[deg360 - 180];
+    }
+    return -sintab[360 - deg360];
+}
+
+
+
+static long
+icos(int const deg) {
+/*----------------------------------------------------------------------------
+  Return cosine of an angle in integral degrees.  The value returned is 65536
+  times the cosine
+-----------------------------------------------------------------------------*/
+    return isin(deg + 90);
+}  
+
+
+
+static int
+twosCompByteValue(unsigned char const c) {
+/*----------------------------------------------------------------------------
+   E.g. if 'c' is 0x5, return 5.  If 'c' is 0xF0, return -16.
+-----------------------------------------------------------------------------*/
+    return (char)c;
+}
+
+
+
+static int
+glyphSkipBefore(const struct ppmd_glyph * const glyphP) {
+
+    return twosCompByteValue(glyphP->header.skipBefore);
+}
+
+
+
+static int
+glyphWidth(const struct ppmd_glyph * const glyphP) {
+
+    return twosCompByteValue(glyphP->header.skipAfter) -
+        twosCompByteValue(glyphP->header.skipBefore);
+}
+
+
+static pamd_point
+commandPoint(const struct ppmd_glyphCommand * const commandP) {
+/*----------------------------------------------------------------------------
+   Return the point which is the argument of glyph drawing command
+   *commandP.  The origin of the coordinate system for this point
+   is the center of the glyph cell and the scale is the scale of the
+   font, so (-10, -10) means the upper left corner of the glyph cell.
+-----------------------------------------------------------------------------*/
+    return makePoint(twosCompByteValue(commandP->x),
+                     twosCompByteValue(commandP->y));
+}
+
+#define Scalef 21       /* Font design size */
+#define Descend 9       /* Descender offset */
+
+
+static pamd_point
+textPosFromFontPos(pamd_point   const fontPos,
+                   pamd_point   const textBoxOrigin,
+                   pamd_point   const center,
+                   pamd_point   const glyphOrigin,
+                   unsigned int const height,
+                   long         const rotcos,
+                   long         const rotsin) {
+/*----------------------------------------------------------------------------
+   'fontPos' is a position within a glyph as told by the font definition.
+   It is relative to the center of the glyph, in units of font tuples
+   (1/21 of a glyph cell).
+
+   We return the position on the canvas of that point.
+
+   That takes into account where in the text box we are, where the text box
+   is on the canvas, the size of the characters, and the rotation of the
+   text box.
+-----------------------------------------------------------------------------*/
+    pamd_point const ptl = vectorSum(center, fontPos);
+        /* Position relative to the top left of the standard glyph cell */
+
+    pamd_point const pl = vectorSum(glyphOrigin, ptl);
+        /* Position relative to the top left of the whole text box,
+           assuming the text box is horizontal and has font scale.
+        */
+    
+    pamd_point const ps = makePoint((pl.x * (int)height) / Scalef,
+                                    (pl.y * (int)height) / Scalef);
+         /* Same as above, but with the text box its actual size */
+
+    pamd_point const retval =
+        makePoint(textBoxOrigin.x +
+                  (ps.x * rotcos - (ps.y-(int)height) * rotsin) / 65536,
+                  textBoxOrigin.y +
+                  (ps.x * rotsin + (ps.y-(int)height) * rotcos) / 65536);
+
+    pamd_validatePoint(retval);
+
+    return retval;
+}
+
+
+
+static void
+drawGlyph(const struct ppmd_glyph * const glyphP,
+          pamd_point                const glyphOrigin,
+          tuple **                  const tuples,
+          unsigned int              const cols,
+          unsigned int              const rows,
+          unsigned int              const depth,
+          sample                    const maxval,
+          int                       const height,
+          pamd_point                const textBoxOrigin,
+          long                      const rotcos,
+          long                      const rotsin,
+          pamd_drawproc                   drawProc,
+          const void *              const clientdata,
+          unsigned int *            const cursorAdvanceP
+    ) {
+/*----------------------------------------------------------------------------
+  'glyphOrigin' is the position relative to the upper left corner of the text
+  box of the upper left corner of this glyph cell.  It is in units of font
+  tuples (so you have to scale it by the font size to actual distance on
+  the canvas).
+
+  We return as *cursorAdvanceP the amount to the right of this glyph cell
+  the next glyph cell on the line (if any) should be.
+
+  The actual glyph cell may be a little to the left of the nominal position
+  because of kerning.  The font says how much to shift the cell left.
+
+  'textBoxOrigin' is the left end of the baseline of the top line in the
+  text box, in the coordinate system of the canvas.  'rotcos' and 'rotsin'
+  tell how that text box is rotated with respect to the horizontal on the
+  canvas.
+  
+  'height' is the height in canvas tuples of a glyph.  This is a scale factor
+  to convert font coordinates to canvas coordinates.
+-----------------------------------------------------------------------------*/
+    pamd_point const center = makePoint(-glyphSkipBefore(glyphP), Scalef/2);
+        /* This is what you have to add to the coordinates in a glyph
+           command, which are relative to the center of the glyph, to get
+           coordinates relative to the upper left corner of the glyph
+        */
+    pamd_point p;
+        /* Current drawing position within the glyph.  Origin is the top
+           left of the glyph cell.  Units are font tuples.
+        */
+    unsigned int commandNum;
+
+    p = textPosFromFontPos(makePoint(0, 0),
+                           textBoxOrigin,
+                           center,
+                           glyphOrigin,
+                           height,
+                           rotcos, rotsin);   /* initial value */
+
+    for (commandNum = 0;
+         commandNum < glyphP->header.commandCount;
+         ++commandNum) {
+
+        const struct ppmd_glyphCommand * const commandP =
+            &glyphP->commandList[commandNum];
+
+        switch (commandP->verb) {
+        case CMD_NOOP:
+            break;
+        case CMD_DRAWLINE:
+        {
+            pamd_point const n = textPosFromFontPos(commandPoint(commandP),
+                                                    textBoxOrigin,
+                                                    center,
+                                                    glyphOrigin,
+                                                    height,
+                                                    rotcos, rotsin);
+                                                    
+            pamd_line(tuples, cols, rows, depth, maxval, p, n,
+                      drawProc, clientdata);
+
+            p = n;
+        }
+        break;
+        case CMD_MOVEPEN:
+            p = textPosFromFontPos(commandPoint(commandP),
+                                   textBoxOrigin,
+                                   center,
+                                   glyphOrigin,
+                                   height,
+                                   rotcos, rotsin);
+            break;
+        }
+    }
+    *cursorAdvanceP = glyphWidth(glyphP);
+}
+
+
+
+void
+pamd_text(tuple**       const tuples, 
+          int           const cols, 
+          int           const rows, 
+          int           const depth, 
+          sample        const maxval, 
+          pamd_point    const pos,
+          int           const height, 
+          int           const angle, 
+          const char *  const sArg, 
+          pamd_drawproc       drawProc,
+          const void *  const clientdata) {
+/*----------------------------------------------------------------------------
+   Draw the zero-terminated string 'sArg', with its baseline starting at point
+   'pos', inclined by 'angle' degrees to the X axis, with letters 'height'
+   tuples high (descenders will extend below the baseline).  We pass the
+   supplied drawproc and clientdata to pamd_linep, which performs the actual
+   drawing.
+
+   There may be multiple lines of text.  The baseline of the topmost line
+   starts at 'pos'.
+-----------------------------------------------------------------------------*/
+    const struct ppmd_font * const fontP = ppmd_get_font();
+
+    long rotsin, rotcos;
+    pamd_point p;
+    const char * s;
+
+    pamd_validatePoint(pos);
+
+    p = makePoint(0, 0);
+    rotsin = isin(-angle);
+    rotcos = icos(-angle);
+
+    for (s = &sArg[0]; *s; ) {
+        unsigned char const ch = *s++;
+
+        if (ch >= fontP->header.firstCodePoint &&
+            ch < fontP->header.firstCodePoint + fontP->header.characterCount) {
+
+            const struct ppmd_glyph * const glyphP =
+                &fontP->glyphTable[ch - fontP->header.firstCodePoint];
+
+            unsigned int cursorAdvance;
+
+            pamd_validatePoint(p); 
+
+            drawGlyph(glyphP, p, tuples, cols, rows, depth, maxval,
+                      height, pos, rotcos, rotsin,
+                      drawProc, clientdata, &cursorAdvance);
+            p.x += cursorAdvance;
+        } else if (ch == '\n') {
+            /* Move to the left edge of the next line down */
+            p.y += Scalef + Descend;
+            p.x = 0;
+        }
+    }
+}
+
+
+
+#ifndef LITERAL_FN_DEF_MATCH
+static pamd_drawproc extetnsDrawproc;
+#endif
+
+static void 
+extentsDrawproc(tuple**      const tuples, 
+                unsigned int const cols, 
+                unsigned int const rows,
+                unsigned int const depth,
+                sample       const maxval, 
+                pamd_point   const p,
+                const void * const clientdata) {
+/*----------------------------------------------------------------------------
+    Drawproc which just accumulates the extents rectangle bounding the
+    text.
+-----------------------------------------------------------------------------*/
+    extleft   = MIN(extleft,   p.x);
+    exttop    = MIN(exttop,    p.y);
+    extright  = MAX(extright,  p.x);
+    extbottom = MAX(extbottom, p.y);
+}
+
+
+
+void
+pamd_text_box(int          const height, 
+              int          const angle, 
+              const char * const s, 
+              int *        const leftP, 
+              int *        const topP, 
+              int *        const rightP, 
+              int *        const bottomP) {
+/*----------------------------------------------------------------------------
+  Calculate extents rectangle for a given piece of text.  For most
+  applications where extents are needed, angle should be zero to obtain the
+  unrotated extents.  If you need the extents box for post-rotation text,
+  however, you can set angle nonzero and it will be calculated correctly.
+-----------------------------------------------------------------------------*/
+    extleft   =  32767;
+    exttop    =  32767;
+    extright  = -32767;
+    extbottom = -32767;
+
+    pamd_text(NULL, 32767, 32767, 255, 255, makePoint(1000, 1000),
+              height, angle, s, 
+              extentsDrawproc, NULL);
+
+    *leftP   = extleft   - 1000; 
+    *topP    = exttop    - 1000;
+    *rightP  = extright  - 1000;
+    *bottomP = extbottom - 1000;
+}
+
+
+
diff --git a/lib/libpammap.c b/lib/libpammap.c
index 6fea0eb9..22224913 100644
--- a/lib/libpammap.c
+++ b/lib/libpammap.c
@@ -1,6 +1,6 @@
-/******************************************************************************
+/*=============================================================================
                                   libpammap.c
-*******************************************************************************
+===============================================================================
 
   These are functions that deal with tuple hashes and tuple tables.
 
@@ -11,13 +11,16 @@
   A tuple table lets you scan all the values, being a table of elements
   that consist of an ordered pair of a tuple value and integer.
 
-******************************************************************************/
+  This file was originally written by Bryan Henderson and is contributed
+  to the public domain by him and subsequent authors.
+=============================================================================*/
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "pammap.h"
 
@@ -31,13 +34,14 @@ pnm_hashtuple(struct pam * const pamP,
    Return the hash value of the tuple 'tuple' -- i.e. an index into a hash
    table.
 -----------------------------------------------------------------------------*/
+    unsigned int const hash_factor[] = {1, 33, 33*33};
+
     unsigned int i;
     unsigned int hash;
-    const unsigned int hash_factor[] = {33023, 30013, 27011};
 
     hash = 0;  /* initial value */
     for (i = 0; i < MIN(pamP->depth, 3); ++i) {
-        hash += tuple[i] * hash_factor[i];  /* May overflow */
+        hash += tuple[i] * hash_factor[i];
     }
     hash %= HASH_SIZE;
     return hash;
@@ -71,7 +75,7 @@ pnm_createtuplehash(void) {
 void
 pnm_destroytuplehash(tuplehash const tuplehash) {
 
-    int i;
+    unsigned int i;
 
     /* Free the chains */
 
@@ -152,7 +156,13 @@ pnm_lookuptuple(struct pam *    const pamP,
                 const tuple           searchval, 
                 int *           const foundP, 
                 int *           const retvalP) {
-    
+/*----------------------------------------------------------------------------
+   Return as *revtvalP the index of the tuple value 'searchval' in the
+   tuple hash 'tuplehash'.
+
+   But iff the tuple value isn't in the hash, return *foundP == false
+   and nothing as *retvalP.
+-----------------------------------------------------------------------------*/
     unsigned int const hashvalue = pnm_hashtuple(pamP, searchval);
     struct tupleint_list_item * p;
     struct tupleint_list_item * found;
@@ -189,7 +199,7 @@ addColorOccurrenceToHash(tuple          const color,
          p = p->next);
 
     if (p) {
-        /* It's in the hash; just tally one more occurence */
+        /* It's in the hash; just tally one more occurrence */
         ++p->tupleint.value;
         *fullP = FALSE;
     } else {
@@ -217,7 +227,16 @@ pnm_addtuplefreqoccurrence(struct pam *   const pamP,
                            tuple          const value,
                            tuplehash      const tuplefreqhash,
                            int *          const firstOccurrenceP) {
+/*----------------------------------------------------------------------------
+  Tally one more occurence of the tuple value 'value' to the tuple frequencey
+  hash 'tuplefreqhash', adding the tuple to the hash if it isn't there
+  already.
+
+  Allocate new space for the tuple value and the hash chain element.
 
+  If we can't allocate space for the new hash chain element, abort the
+  program.
+-----------------------------------------------------------------------------*/
     unsigned int const hashvalue = pnm_hashtuple(pamP, value);
             
     struct tupleint_list_item * p;
@@ -227,7 +246,7 @@ pnm_addtuplefreqoccurrence(struct pam *   const pamP,
          p = p->next);
 
     if (p) {
-        /* It's in the hash; just tally one more occurence */
+        /* It's in the hash; just tally one more occurrence */
         ++p->tupleint.value;
         *firstOccurrenceP = FALSE;
     } else {
@@ -407,7 +426,7 @@ alloctupletable(const struct pam * const pamP,
                 const char **      const errorP) {
     
     if (UINT_MAX / sizeof(struct tupleint) < size)
-        asprintfN(errorP, "size %u is too big for arithmetic", size);
+        pm_asprintf(errorP, "size %u is too big for arithmetic", size);
     else {
         unsigned int const mainTableSize = size * sizeof(struct tupleint *);
         unsigned int const tupleIntSize = 
@@ -419,7 +438,7 @@ alloctupletable(const struct pam * const pamP,
            as a single malloc block and suballocate internally.
         */
         if ((UINT_MAX - mainTableSize) / tupleIntSize < size)
-            asprintfN(errorP, "size %u is too big for arithmetic", size);
+            pm_asprintf(errorP, "size %u is too big for arithmetic", size);
         else {
             unsigned int const allocSize = mainTableSize + size * tupleIntSize;
             void * pool;
@@ -427,8 +446,9 @@ alloctupletable(const struct pam * const pamP,
             pool = malloc(allocSize);
 
             if (!pool)
-                asprintfN(errorP, "Unable to allocate %u bytes for a %u-entry "
-                          "tuple table", allocSize, size);
+                pm_asprintf(errorP,
+                            "Unable to allocate %u bytes for a %u-entry "
+                            "tuple table", allocSize, size);
             else {
                 tupletable const tbl = (tupletable) pool;
 
@@ -459,7 +479,7 @@ pnm_alloctupletable(const struct pam * const pamP,
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
     return retval;
@@ -514,7 +534,7 @@ tuplehashtotable(const struct pam * const pamP,
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     } else {
         unsigned int i, j;
@@ -570,7 +590,7 @@ pnm_computetupletablehash(struct pam * const pamP,
 -----------------------------------------------------------------------------*/
     tuplehash tupletablehash;
     unsigned int i;
-    bool fits;
+    int fits;
     
     tupletablehash = pnm_createtuplehash();
 
@@ -730,3 +750,5 @@ pam_colorname(struct pam *         const pamP,
     sprintf(colorname, "#%02x%02x%02x", r, g, b);
     return colorname;
 }
+
+
diff --git a/lib/libpamn.c b/lib/libpamn.c
index fe004e91..26dbbfe3 100644
--- a/lib/libpamn.c
+++ b/lib/libpamn.c
@@ -1,16 +1,20 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
                                   libpamn.c
-------------------------------------------------------------------------------
+===============================================================================
    These are the library functions, which belong in the libnetpbm library,
    that deal with the PAM image format via maxval-normalized, floating point
    sample values.
------------------------------------------------------------------------------*/
+
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "fileio.h"
 #include "pm_gamma.h"
@@ -40,9 +44,10 @@ allocpamrown(const struct pam * const pamP,
 
     tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
     if (tuplerown == NULL)
-        asprintfN(&error, "Out of memory allocating space for a tuple row of"
-                  "%u tuples by %u samples per tuple by %u bytes per sample.",
-                  pamP->width, pamP->depth, sizeof(samplen));
+        pm_asprintf(&error, "Out of memory allocating space for a tuple row of"
+                    "%u tuples by %u samples per tuple "
+                    "by %u bytes per sample.",
+                    pamP->width, pamP->depth, (unsigned)sizeof(samplen));
     else {
         /* Now we initialize the pointers to the individual tuples to make this
            a regulation C two dimensional array.
@@ -78,7 +83,7 @@ pnm_allocpamrown(const struct pam * const pamP) {
 
     if (error) {
         pm_errormsg("pnm_allocpamrown() failed.  %s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 
@@ -279,9 +284,9 @@ pnm_allocpamarrayn(const struct pam * const pamP) {
     
     MALLOCARRAY(tuplenarray, pamP->height);
     if (tuplenarray == NULL) 
-        asprintfN(&error,
-                  "Out of memory allocating the row pointer section of "
-                  "a %u row array", pamP->height);
+        pm_asprintf(&error,
+                    "Out of memory allocating the row pointer section of "
+                    "a %u row array", pamP->height);
     else {
         unsigned int rowsDone;
 
@@ -302,7 +307,7 @@ pnm_allocpamarrayn(const struct pam * const pamP) {
     }
     if (error) {
         pm_errormsg("pnm_allocpamarrayn() failed.  %s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 
@@ -494,7 +499,7 @@ gammaCommon(struct pam *  const pamP,
 
     unsigned int plane;
     unsigned int opacityPlane;
-    bool haveOpacity;
+    int haveOpacity;
     
     pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
 
@@ -537,9 +542,14 @@ static void
 applyopacityCommon(enum applyUnapply const applyUnapply,
                    struct pam *      const pamP,
                    tuplen *          const tuplenrow) {
-
+/*----------------------------------------------------------------------------
+   Either apply or unapply opacity to the row tuplenrow[], per
+   'applyUnapply'.  Apply means to multiply each foreground sample by
+   the opacity value for that pixel; Unapply means to do the inverse, as
+   if the foreground values had already been so multiplied.
+-----------------------------------------------------------------------------*/
     unsigned int opacityPlane;
-    bool haveOpacity;
+    int haveOpacity;
     
     pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
 
@@ -636,7 +646,7 @@ createUngammaMapOffset(const struct pam * const pamP,
         MALLOCARRAY(ungammaTransformMap, pamP->maxval+1);
 
         if (ungammaTransformMap != NULL) {
-            bool haveOpacity;
+            int haveOpacity;
             unsigned int opacityPlane;
             unsigned int plane;
 
diff --git a/lib/libpamread.c b/lib/libpamread.c
index 0506d020..74b1ab83 100644
--- a/lib/libpamread.c
+++ b/lib/libpamread.c
@@ -1,10 +1,13 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
                                   libpamread.c
-------------------------------------------------------------------------------
+===============================================================================
    These are the library functions, which belong in the libnetpbm library,
    that deal with reading the PAM (Portable Arbitrary Format) image format
    raster (not the header).
------------------------------------------------------------------------------*/
+
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 
 /* See pmfileio.c for the complicated explanation of this 32/64 bit file
    offset stuff.
@@ -16,8 +19,10 @@
 #include <limits.h>
 #include <assert.h>
 
+#include "netpbm/pm_config.h"
+#include "netpbm/nstring.h"
+
 #include "fileio.h"
-#include "nstring.h"
 #include "pam.h"
 
 
@@ -198,6 +203,51 @@ parse4BpsRow(const struct pam *    const pamP,
 
 
 static void
+validatePamRow(const struct pam * const pamP,
+               tuple *            const tuplerow,
+               const char **      const errorP) {
+/*----------------------------------------------------------------------------
+  Check for sample values above maxval in input.  
+
+  Note: a program that wants to deal with invalid sample values itself can
+  simply make sure it sets pamP->maxval sufficiently high, so this validation
+  never fails.
+-----------------------------------------------------------------------------*/
+    /* To save time, skip the test for if the maxval is a saturated value
+       (255, 65535) or format is PBM.
+
+       This is an expensive test, but is skipped in most cases: in practice
+       maxvals other than 255 or 65535 are uncommon.  Thus we do this in a
+       separate pass through the row rather than while reading in the row.
+    */
+
+    if (pamP->maxval == (((sample) 0x1) << pamP->bytes_per_sample*8) - 1 ||
+        PAM_FORMAT_TYPE(pamP->format) == PBM_FORMAT) {
+        /* There's no way a sample can be invalid, so we don't need to
+           look at the samples individually.
+        */
+        *errorP = NULL;
+    } else {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane) {
+                if (tuplerow[col][plane] > pamP->maxval) {
+                    pm_asprintf(errorP,
+                                "Plane %u sample value %lu exceeds the "
+                                "image maxval of %lu",
+                                plane, tuplerow[col][plane], pamP->maxval);
+                    return;
+                }
+            }
+        }
+        *errorP = NULL;
+    }
+}
+
+
+
+static void
 readRawNonPbmRow(const struct pam * const pamP,
                  tuple *            const tuplerow) {
 
@@ -214,13 +264,12 @@ readRawNonPbmRow(const struct pam * const pamP,
 
     if (bytesRead != rowImageSize) {
         if (feof(pamP->file))
-            asprintfN(&error,
-                      "End of file encountered when trying to read a row from "
-                      "input file.");
+            pm_asprintf(&error, "End of file encountered "
+                        "when trying to read a row from input file.");
         else 
-            asprintfN(&error, "Error reading a row from input file.  "
-                      "fread() fails with errno=%d (%s)",
-                      errno, strerror(errno));
+            pm_asprintf(&error, "Error reading a row from input file.  "
+                        "fread() fails with errno=%d (%s)",
+                        errno, strerror(errno));
     } else {
         error = NULL;  /* initial assumption */
         if (tuplerow) {
@@ -230,16 +279,18 @@ readRawNonPbmRow(const struct pam * const pamP,
             case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
             case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
             default:
-                asprintfN(&error, "invalid bytes per sample passed to "
-                          "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+                pm_asprintf(&error, "invalid bytes per sample passed to "
+                            "pnm_formatpamrow(): %u", pamP->bytes_per_sample);
             }
+            if (error == NULL)
+                validatePamRow(pamP, tuplerow, &error);
         }
     }
     pnm_freerowimage(inbuf);
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 }
@@ -261,7 +312,7 @@ pnm_readpamrow(const struct pam * const pamP,
     /* For speed, we don't check any of the inputs for consistency 
        here (unless it's necessary to avoid crashing).  Any consistency
        checking should have been done by a prior call to 
-       pnm_writepaminit().
+       pnm_readpaminit().
     */  
 
     /* Need a special case for raw PBM because it has multiple tuples (8)
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
index dd319d4a..29ddeaa2 100644
--- a/lib/libpamwrite.c
+++ b/lib/libpamwrite.c
@@ -1,10 +1,13 @@
-/*----------------------------------------------------------------------------
+/*============================================================================
                                   libpamwrite.c
-------------------------------------------------------------------------------
+==============================================================================
    These are the library functions, which belong in the libnetpbm library,
    that deal with writing the PAM (Portable Arbitrary Format) image format
    raster (not the header).
------------------------------------------------------------------------------*/
+
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 
 /* See pmfileio.c for the complicated explanation of this 32/64 bit file
    offset stuff.
@@ -16,9 +19,11 @@
 #include <stdio.h>
 #include <limits.h>
 #include <assert.h>
-
 #include <math.h>
 
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pam.h"
 
 
@@ -351,7 +356,9 @@ pnm_writepamrow(const struct pam * const pamP,
        pnm_writepaminit().
     */
     
-    if (pm_plain_output || pamP->plainformat) {
+    if (pamP->format == PAM_FORMAT || !(pm_plain_output || pamP->plainformat))
+        writePamRawRow(pamP, tuplerow, 1);
+    else {
         switch (PAM_FORMAT_TYPE(pamP->format)) {
         case PBM_TYPE:
             writePamPlainPbmRow(pamP, tuplerow);
@@ -361,18 +368,13 @@ pnm_writepamrow(const struct pam * const pamP,
             writePamPlainRow(pamP, tuplerow);
             break;
         case PAM_TYPE:
-            /* pm_plain_output is impossible here due to assumption stated
-               above about pnm_writepaminit() having checked it.  The
-               pamP->plainformat is meaningless for PAM.
-            */
-            writePamRawRow(pamP, tuplerow, 1);
+            assert(false);
             break;
         default:
             pm_error("Invalid 'format' value %u in pam structure", 
                      pamP->format);
         }
-    } else
-        writePamRawRow(pamP, tuplerow, 1);
+    }
 }
 
 
diff --git a/lib/libpbm1.c b/lib/libpbm1.c
index fc20071c..49ab7fdf 100644
--- a/lib/libpbm1.c
+++ b/lib/libpbm1.c
@@ -18,9 +18,10 @@
 
 #include <stdio.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "shhopt.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/shhopt.h"
+
 #include "pbm.h"
 
 
@@ -57,27 +58,28 @@ pbm_nextimage(FILE *file, int * const eofP) {
 
 
 void
-pbm_check(FILE * file, const enum pm_check_type check_type, 
-          const int format, const int cols, const int rows,
-          enum pm_check_code * const retval_p) {
+pbm_check(FILE *               const fileP,
+          enum pm_check_type   const checkType, 
+          int                  const format,
+          int                  const cols,
+          int                  const rows,
+          enum pm_check_code * const retvalP) {
 
     if (rows < 0)
         pm_error("Invalid number of rows passed to pbm_check(): %d", rows);
     if (cols < 0)
         pm_error("Invalid number of columns passed to pbm_check(): %d", cols);
     
-    if (check_type != PM_CHECK_BASIC) {
-        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP)
+            *retvalP = PM_CHECK_UNKNOWN_TYPE;
     } else if (format != RPBM_FORMAT) {
-        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        if (retvalP)
+            *retvalP = PM_CHECK_UNCHECKABLE;
     } else {        
-        pm_filepos const bytes_per_row = (cols+7)/8;
-        pm_filepos const need_raster_size = rows * bytes_per_row;
-#ifdef LARGEFILEDEBUG
-        pm_message("pm_filepos passed to pm_check() is %u bytes",
-                   sizeof(pm_filepos));
-#endif
-        pm_check(file, check_type, need_raster_size, retval_p);
+        pm_filepos const bytesPerRow    = (cols+7)/8;
+        pm_filepos const needRasterSize = rows * bytesPerRow;
+        pm_check(fileP, checkType, needRasterSize, retvalP);
     }
 }
 
@@ -113,23 +115,36 @@ static unsigned int const p[256] = {
 
 static int
 bitpop(const unsigned char * const packedRow,
-       unsigned int          const cols) {
+       unsigned int          const cols,
+       unsigned int          const offset) {
 /*----------------------------------------------------------------------------
-  Return the number of 1 bits in 'packedRow'.
+  Return the number of 1 bits in 'packedRow', ignoring 0 to 7 bits
+  at the row start (= on the left edge), indicated by offset.
 -----------------------------------------------------------------------------*/
-    unsigned int const colByteCnt  = pbm_packed_bytes(cols);
-    unsigned int const fullByteCnt = cols/8;
+    unsigned int const fullLength = cols + offset;
 
-    unsigned int i;
     unsigned int sum;
 
-    sum = 0;  /* initial value */
+    if (fullLength <= 8) {
+        /* All bits are in a single byte */
+        sum = bitpop8((packedRow[0] << offset ) & (0xff << (8 - cols)));
+    } else {
+        unsigned int const colByteCnt  = pbm_packed_bytes(fullLength);
+        unsigned int const fullByteCnt = fullLength/8;
+
+        unsigned int i;
+
+        /* First byte, whether it is full or not */
+        sum = bitpop8(packedRow[0] << offset );
 
-    for (i = 0; i < fullByteCnt; ++i)
-        sum += bitpop8(packedRow[i]);
+        /* Second byte to last full byte */
+        for (i = 1; i < fullByteCnt; ++i)
+            sum += bitpop8(packedRow[i]);
 
-    if (colByteCnt > fullByteCnt)
-        sum += bitpop8(packedRow[i] >> (8-cols%8));
+        /* Partial byte at the right end */
+        if (colByteCnt > fullByteCnt)
+            sum += bitpop8(packedRow[i] >> (8 - fullLength%8));
+    }
 
     return sum;
 }
@@ -151,19 +166,13 @@ pbm_backgroundbitrow(unsigned const char * const packedBits,
 
     unsigned int retval;
 
-    unsigned int firstbit, lastbit;
-    unsigned int totalBitpop, headBitpop;
-
-    firstbit = (row[0] >> (7-rs)) & 0x01;
-    lastbit  = (row[last] >> (7- (cols+rs-1)%8)) & 0x01;
+    bool const firstbit = (row[0] >> (7-rs)) & 0x01;
+    bool const lastbit  = (row[last] >> (7- (cols+rs-1)%8)) & 0x01;
 
     if (firstbit == lastbit)
         retval = firstbit;
     else {
-        totalBitpop = bitpop(row, cols + rs);
-        headBitpop  = (rs == 0) ? 0 : bitpop(row, rs);
-
-        if (totalBitpop - headBitpop >= cols/2)
+        if (bitpop(row, cols, rs) >= cols/2)
             retval = PBM_BLACK;
         else
             retval = PBM_WHITE;
diff --git a/lib/libpbm2.c b/lib/libpbm2.c
index a8e4b0f6..f199c51a 100644
--- a/lib/libpbm2.c
+++ b/lib/libpbm2.c
@@ -10,6 +10,7 @@
 ** implied warranty.
 */
 
+#include <assert.h>
 #include <limits.h>
 
 #include "pbm.h"
@@ -34,11 +35,9 @@ getbit (FILE * const file) {
 
 
 void
-pbm_readpbminitrest( file, colsP, rowsP )
-    FILE* file;
-    int* colsP;
-    int* rowsP;
-    {
+pbm_readpbminitrest( FILE * const file,
+                     int  * const colsP,
+                     int  * const rowsP ) {
     /* Read size. */
     *colsP = (int)pm_getuint( file );
     *rowsP = (int)pm_getuint( file );
@@ -55,7 +54,7 @@ pbm_readpbminitrest( file, colsP, rowsP )
         pm_error("Number of columns in header is too large.");
     if (*rowsP < 0)
         pm_error("Number of columns in header is too large.");
-    }
+}
 
 
 
@@ -87,10 +86,13 @@ pbm_readpbminit(FILE * const ifP,
                 int *  const rowsP,
                 int *  const formatP) {
 
-    *formatP = pm_readmagicnumber(ifP);
+    int realFormat;
 
-    switch (PAM_FORMAT_TYPE(*formatP)) {
+    realFormat = pm_readmagicnumber(ifP);
+
+    switch (PAM_FORMAT_TYPE(realFormat)) {
     case PBM_TYPE:
+        *formatP = realFormat;
         pbm_readpbminitrest(ifP, colsP, rowsP);
         break;
 
@@ -110,7 +112,8 @@ pbm_readpbminit(FILE * const ifP,
                  "to PBM with 'pamtopnm'");
         break;
     default:
-        pm_error("bad magic number - not a Netpbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
@@ -118,32 +121,29 @@ pbm_readpbminit(FILE * const ifP,
 
 
 void
-pbm_readpbmrow( file, bitrow, cols, format )
-    FILE* file;
-    bit* bitrow;
-    int cols, format;
-    {
-    register int col, bitshift;
-    register bit* bP;
+pbm_readpbmrow( FILE * const file,
+                bit * const bitrow,
+                int const cols,
+                int const format) {
+
+    int col, bitshift;
 
     switch ( format )
     {
     case PBM_FORMAT:
-    for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
-        *bP = getbit( file );
+    for ( col = 0; col < cols; ++col )
+        bitrow[col] = getbit( file );
     break;
 
     case RPBM_FORMAT: {
-        register unsigned char item;
+        unsigned char item;
         bitshift = -1;  item = 0;  /* item's value is meaningless here */
-        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
-          {
-              if ( bitshift == -1 )
-                {
+        for ( col = 0; col < cols; ++col ) {
+              if ( bitshift == -1 ) {
                     item = pm_getrawbyte( file );
                     bitshift = 7;
                 }
-              *bP = ( item >> bitshift ) & 1;
+              bitrow[col] = ( item >> bitshift ) & 1;
               --bitshift;
           }
     }
@@ -152,7 +152,7 @@ pbm_readpbmrow( file, bitrow, cols, format )
     default:
     pm_error( "can't happen" );
     }
-    }
+}
 
 
 
@@ -180,12 +180,12 @@ pbm_readpbmrow_packed(FILE *          const fileP,
     break;
 
     case RPBM_FORMAT: {
-        int bytes_read;
-        bytes_read = fread(packedBits, 1, pbm_packed_bytes(cols), fileP);
+        unsigned int bytesReadCt;
+        bytesReadCt = fread(packedBits, 1, pbm_packed_bytes(cols), fileP);
              
-        if (bytes_read < pbm_packed_bytes(cols)) {
+        if (bytesReadCt < pbm_packed_bytes(cols)) {
             if (feof(fileP)) 
-                if (bytes_read == 0) 
+                if (bytesReadCt == 0) 
                     pm_error("Attempt to read a raw PBM image row, but "
                              "no more rows left in file.");
                 else
@@ -197,7 +197,7 @@ pbm_readpbmrow_packed(FILE *          const fileP,
     break;
     
     default:
-        pm_error( "Internal error in pbm_readpbmrow_packed." );
+        pm_error("Internal error in pbm_readpbmrow_packed.");
     }
 }
 
@@ -258,17 +258,36 @@ pbm_readpbmrow_bitoffset(FILE *          const ifP,
 
         window[last] =  leftBits | rightBits;
     }
-} 
+}
+
+
+
+void
+pbm_cleanrowend_packed(unsigned char * const packedBits,
+                       unsigned int    const cols) {
+/*----------------------------------------------------------------------------
+  Set fractional "don't care" bits at end of row to zero.
+----------------------------------------------------------------------------*/
+    unsigned int const bitsPerChar = 8;
+
+    if (cols % bitsPerChar > 0) {
+        unsigned int const last = pbm_packed_bytes(cols) - 1;
+
+        assert(pbm_packed_bytes(cols) > 0);
+
+        packedBits[last] >>= bitsPerChar - cols % bitsPerChar;
+        packedBits[last] <<= bitsPerChar - cols % bitsPerChar;
+    }
+}
 
 
 
 bit**
-pbm_readpbm( file, colsP, rowsP )
-    FILE* file;
-    int* colsP;
-    int* rowsP;
-    {
-    register bit** bits;
+pbm_readpbm( FILE * const file,
+             int  * const colsP,
+             int  * const rowsP) {
+
+    bit ** bits;
     int format, row;
 
     pbm_readpbminit( file, colsP, rowsP, &format );
@@ -279,4 +298,4 @@ pbm_readpbm( file, colsP, rowsP )
         pbm_readpbmrow( file, bits[row], *colsP, format );
 
     return bits;
-    }
+}
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
index 9200d30e..c8389824 100644
--- a/lib/libpbm3.c
+++ b/lib/libpbm3.c
@@ -12,21 +12,33 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pbm.h"
 
-#if HAVE_GCC_MMXSSE
-#include "bitreverse.h"
+#ifndef PACKBITS_SSE
+#if WANT_SSE && defined(__SSE2__) && HAVE_GCC_BSWAP
+  #define PACKBITS_SSE 2
+#else
+  #define PACKBITS_SSE 0
+#endif
 #endif
 
-/* HAVE_GCC_MMXSSE means we have the means to use MMX and SSE CPU facilities
-   to make PBM raster processing faster.  GCC only.
+/* WANT_SSE means we want to use SSE CPU facilities to make PBM raster
+   processing faster.  This implies it's actually possible - i.e. the
+   build environment has <emmintrin.h>.
 
-   The GNU Compiler -msse option makes SSE available.
-   For x86-32 with MMX/SSE, "-msse" must be explicitly given.
-   For x86-64 and AMD64, "-msse" is on by default.
+   The GNU Compiler -msse2 option makes SSE/SSE2 available, and is
+   evidenced by __SSE2__.
+   For x86-32 with SSE, "-msse2" must be explicitly given.
+   For x86-64 and AMD64, "-msse2" is the default (from Gcc v.4.)
 */
 
+#if PACKBITS_SSE == 2
+  #include <emmintrin.h>
+#endif
+
+
 void
 pbm_writepbminit(FILE * const fileP, 
                  int    const cols, 
@@ -35,9 +47,6 @@ pbm_writepbminit(FILE * const fileP,
 
     if (!forceplain && !pm_plain_output) {
         fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, RPBM_MAGIC2, cols, rows);
-#ifdef VMS
-        set_outfile_binary();
-#endif
     } else
         fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, PBM_MAGIC2, cols, rows);
 }
@@ -56,80 +65,107 @@ writePackedRawRow(FILE *                const fileP,
 } 
 
 
-#if HAVE_GCC_MMXSSE
+
+#if PACKBITS_SSE == 2
 static void
-packBitsWithMmxSse(FILE *          const fileP,
+packBitsWithSse2(  FILE *          const fileP,
                    const bit *     const bitrow,
                    unsigned char * const packedBits,
-                   unsigned int    const cols,
-                   unsigned int *  const nextColP) {
+                   unsigned int    const cols) {
 /*----------------------------------------------------------------------------
-   Pack the bits of bitrow[] into bytes at 'packedBits'.  Going left to right,
-   stop when there aren't enough bits left to fill a whole byte.  Return
-   as *nextColP the number of the next column after the rightmost one we
-   packed.
+    Pack the bits of bitrow[] into bytes at 'packedBits'.
+
+    Use the SSE2 facilities to pack the bits quickly, but
+    perform the exact same function as the simpler
+    packBitsGeneric() + packPartialBytes()
 
-   Use the Pentium MMX and SSE facilities to pack the bits quickly, but
-   perform the exact same function as the simpler packBitsGeneric().
+    Unlike packBitsGeneric(), the whole row is converted.
 -----------------------------------------------------------------------------*/
     /*
-      We use MMX/SSE facilities that operate on 8 bytes at once to pack
-      the bits quickly.
-    
-      We use 2 MMX registers (no SSE registers).
+      We use 2 SSE registers.
     
       The key machine instructions are:
-    
-    
-      PCMPGTB  Packed CoMPare Greater Than Byte
-    
-        Compares 8 bytes in parallel
-        Result is x00 if greater than, xFF if not for each byte       
-    
-      PMOVMSKB Packed MOVe MaSK Byte 
-    
-        Result is a byte of the MSBs of 8 bytes
-        x00 xFF x00 xFF xFF xFF x00 x00 --> 01011100B = 0x5C
         
-        The result is actually a 32 bit int, but the higher bits are
-        always 0.  (0x0000005C in the above case)
+      PCMPGTB128  Packed CoMPare Greater Than Byte
     
-      EMMS     Empty MMx State
+        Compares 16 bytes in parallel
+        Result is x00 if greater than, xFF if not for each byte
+
     
-        Free MMX registers  
+      PMOVMSKB128 Packed MOVe MaSK Byte 
     
-    */
-
+        Result is 16 bits, the MSBs of 16 bytes
+        x00 xFF x00 xFF xFF xFF x00 x00 xFF xFF xFF xFF x00 x00 x00 x00 
+        --> 0101110011110000B = 0x5CF0
+        
+        The result is actually a 64 bit int, but the higher bits are
+        always 0.
 
-    typedef char v8qi __attribute__ ((vector_size(8)));
-    typedef int di __attribute__ ((mode(DI)));
+      We use SSE instructions in "_mm_" form in favor of "__builtin_".
+      In GCC the "__builtin_" form is documented but "_mm_" is not.
+      Former versions of this source file used "__builtin_".  This was
+      changed to make possible compilation with clang, which does not
+      implement some "__builtin_" forms.
 
-    unsigned int col;
-    v8qi const zero64 =(v8qi)((di)0);  /* clear to zero */
+      __builtin_ia32_pcmpgtb128 :  _mm_cmpgt_epi8
+      __builtin_ia32_pmovmskb128 : _mm_movemask_epi8
 
-    for (col = 0; col + 7 < cols; col += 8) {
+      The conversion requires <emmintrin.h> .
+    */
 
-        v8qi const compare =
-            __builtin_ia32_pcmpgtb(*(v8qi*) (&bitrow[col]), (v8qi) zero64);
-        uint32_t const backwardBlackMask =  __builtin_ia32_pmovmskb(compare);
-        unsigned char const blackMask = bitreverse[backwardBlackMask];
+    typedef char v16qi __attribute__ ((vector_size(16)));
 
-        packedBits[col/8] = blackMask;
+    unsigned int col;
+    union {
+        v16qi    v16;
+        uint64_t i64[2];
+        unsigned char byte[16];
+    } bit128;
+
+    v16qi zero128;
+    zero128 = zero128 ^ zero128;   /* clear to zero */
+
+    for (col = 0; col + 15 < cols; col += 16) {
+        bit128.i64[0]=__builtin_bswap64( *(uint64_t*) &bitrow[col]);
+        bit128.i64[1]=__builtin_bswap64( *(uint64_t*) &bitrow[col+8]);
+
+        {
+            v16qi const compare = (v16qi)
+                _mm_cmpgt_epi8((__m128i)bit128.v16, (__m128i) zero128);
+            uint16_t const blackMask = _mm_movemask_epi8 ((__m128i)compare);
+            
+            *(uint16_t *) & packedBits[col/8] = blackMask;
+        }
     }
-    *nextColP = col;
 
-    __builtin_ia32_emms();
+    if (cols % 16 > 0) {
+        unsigned int i, j;
 
+        bit128.v16 = bit128.v16 ^ bit128.v16;
+    
+        for (i = 0, j = col ; j < cols; ++i, ++j) 
+            bit128.byte[ (i&8) + 7-(i&7) ] = bitrow[j];
+      
+        {
+            v16qi const compare = (v16qi)
+                _mm_cmpgt_epi8((__m128i)bit128.v16, (__m128i) zero128);
+            uint16_t const blackMask = _mm_movemask_epi8 ((__m128i)compare);
+
+            if ( cols%16 >8 )  /* Two partial bytes */
+                *(uint16_t *) & packedBits[col/8] = blackMask;
+            else              /* One partial byte */
+                packedBits[col/8] = (unsigned char) blackMask ;
+        }
+    }
 }
 #else
 /* Avoid undefined function warning; never actually called */
 
-#define packBitsWithMmxSse(a,b,c,d,e) packBitsGeneric(a,b,c,d,e)
+#define packBitsWithSse2(a,b,c,d) packBitsGeneric((a),(b),(c),(d),NULL)
 #endif
 
 
 
-
 static unsigned int
 bitValue(unsigned char const byteValue) {
 
@@ -212,18 +248,20 @@ writePbmRowRaw(FILE *      const fileP,
         pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
     } else {
-        unsigned int nextCol;
 
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-        if (HAVE_GCC_MMXSSE)
-            packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
-        else 
+        switch (PACKBITS_SSE) {
+        case 2: 
+            packBitsWithSse2(fileP, bitrow, packedBits, cols);
+            break;
+        default: {
+            unsigned int nextCol;
             packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
-
-        if (cols % 8 > 0)
-            packPartialBytes(bitrow, cols, nextCol, packedBits);
-        
+            if (cols % 8 > 0)
+                packPartialBytes(bitrow, cols, nextCol, packedBits);
+        }
+        }
         writePackedRawRow(fileP, packedBits, cols);
 
         pm_setjmpbuf(origJmpbufP);
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
index 4ba812cd..d3551e78 100644
--- a/lib/libpbmfont.c
+++ b/lib/libpbmfont.c
@@ -16,12 +16,14 @@
 
 #include <assert.h>
 #include <string.h>
+#include <ctype.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 
-#include "pm_c_util.h"
-#include "nstring.h"
-#include "pbm.h"
 #include "pbmfont.h"
-#include "mallocvar.h"
+#include "pbm.h"
 
 static unsigned int const firstCodePoint = 32;
     /* This is the code point of the first character in a pbmfont font.
@@ -1072,12 +1074,48 @@ pbm_dumpfont( fn )
 /* Routines for loading a BDF font file */
 
 
-static unsigned int
-mk_argvn(char *        const s,
-         const char ** const vec,
-         unsigned int  const mk_max) {
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is an object for reading lines of a font file.  It reads and tokenizes
+   them into words.
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
 
-    int n;
+    char line[1024];
+        /* This is the storage space for the words of the line.  The
+           words go in here, one after another, separated by NULs.
+
+           It also functions as a work area for readline_read().
+        */
+    const char * arg[32];
+        /* These are the words; each entry is a pointer into line[] (above) */
+} readline;
+
+
+
+static void
+readline_init(readline * const readlineP,
+              FILE *     const ifP) {
+
+    readlineP->ifP = ifP;
+
+    readlineP->arg[0] = NULL;
+}
+
+
+
+static void
+tokenize(char *         const s,
+         const char **  const words,
+         unsigned int   const maxWordCt) {
+/*----------------------------------------------------------------------------
+   Chop up 's' into words by changing space characters to NUL.  Return
+   as 'words' pointer to the beginning of those words in 's'.
+
+   If there are more than 'maxWordCt' words in 's', ignore the excess on
+   the right.
+-----------------------------------------------------------------------------*/
+    unsigned int n;
     char * p;
 
     p = &s[0];
@@ -1087,30 +1125,27 @@ mk_argvn(char *        const s,
         if (ISSPACE(*p))
             *p++ = '\0';
         else {
-            vec[n++] = p;
-            if (n >= mk_max-1)
+            words[n++] = p;
+            if (n >= maxWordCt-1)
                 break;
             while (*p && !ISSPACE(*p))
                 ++p;
         }
     }
-    vec[n] = NULL;
-
-    return n;
+    words[n] = NULL;
 }
 
 
 
-static int
-readline(FILE *        const ifP,
-         char *        const line,
-         const char ** const wordList) {
+static void
+readline_read(readline * const readlineP,
+              bool *     const eofP) {
 /*----------------------------------------------------------------------------
-   Read a nonblank line from file *ifP.  Return the value of the whole line
-   in *buf (must be at least 1024 bytes long), and parse it into words
-   in *wordList (must have at least 32 entries).
+   Read a nonblank line from the file.  Make its contents available
+   as readlineP->arg[].
 
-   If there is no nonblank line before EOF, return rc == -1.
+   Return *eofP == true iff there is no nonblank line before EOF or we
+   are unable to read the file.
 -----------------------------------------------------------------------------*/
     bool gotLine;
     bool error;
@@ -1118,22 +1153,85 @@ readline(FILE *        const ifP,
     for (gotLine = false, error = false; !gotLine && !error; ) {
         char * rc;
 
-        rc = fgets(line, 1024, ifP);
+        rc = fgets(readlineP->line, 1024, readlineP->ifP);
         if (rc == NULL)
             error = true;
         else {
-            mk_argvn(line, wordList, 32);
-            if (wordList[0] != NULL)
+            tokenize(readlineP->line,
+                     readlineP->arg, ARRAY_SIZE(readlineP->arg));
+            if (readlineP->arg[0] != NULL)
                 gotLine = true;
         }
     }
-    return error ? -1 : 0;
+    *eofP = error;
+}
+
+
+
+static void
+parseBitmapRow(const char *    const hex,
+               unsigned int    const glyphWidth,
+               unsigned char * const bmap,
+               unsigned int    const origBmapIndex,
+               unsigned int *  const newBmapIndexP,
+               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Parse one row of the bitmap for a glyph, from the hexadecimal string
+   for that row in the font file, 'hex'.  The glyph is 'glyphWidth'
+   pixels wide.
+
+   We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP.
+-----------------------------------------------------------------------------*/
+    unsigned int bmapIndex;
+    int i;  /* dot counter */
+    const char * p;
+
+    bmapIndex = origBmapIndex;
+
+    for (i = glyphWidth, p = &hex[0], *errorP = NULL;
+         i > 0 && !*errorP;
+         i -= 4) {
+
+        if (*p == '\0')
+            pm_asprintf(errorP, "Not enough hexadecimal digits for glyph "
+                        "of width %u in '%s'",
+                        glyphWidth, hex);
+        else {
+            char const hdig = *p++;
+            unsigned int hdigValue;
+
+            if (hdig >= '0' && hdig <= '9')
+                hdigValue = hdig - '0';
+            else if (hdig >= 'a' && hdig <= 'f')
+                hdigValue = 10 + (hdig - 'a');
+            else if (hdig >= 'A' && hdig <= 'F')
+                hdigValue = 10 + (hdig - 'A');
+            else 
+                pm_asprintf(errorP,
+                            "Invalid hex digit x%02x (%c) in bitmap data '%s'",
+                            (unsigned int)(unsigned char)hdig, 
+                            isprint(hdig) ? hdig : '.',
+                            hex);
+
+            if (!*errorP) {
+                if (i > 0)
+                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
+                if (i > 1)
+                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
+                if (i > 2)
+                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
+                if (i > 3)
+                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
+            }
+        }
+    }
+    *newBmapIndexP = bmapIndex;
 }
 
 
 
 static void
-readBitmap(FILE *          const fp,
+readBitmap(readline *      const readlineP,
            unsigned int    const glyphWidth,
            unsigned int    const glyphHeight,
            const char *    const charName,
@@ -1145,48 +1243,26 @@ readBitmap(FILE *          const fp,
     bmapIndex = 0;
            
     for (n = glyphHeight; n > 0; --n) {
-        int i;  /* dot counter */
-        int rc;
-        char * hex;
-        char line[1024];
-        const char * arg[32];
+        bool eof;
+        const char * error;
 
-        rc = readline(fp, line, arg);
+        readline_read(readlineP, &eof);
 
-        if (rc < 0)
+        if (eof)
             pm_error("End of file in bitmap for character '%s' in BDF "
                      "font file.", charName);
 
-        hex = line;
-        for (i = glyphWidth; i > 0; i -= 4) {
-            if (*hex == '\0')
-                pm_error("Premature end of line in line '%s' of "
-                         "bitmap for character '%s' "
-                         "in BDF font file", line, charName);
-            else {
-                char const hdig = *hex++;
-                unsigned int hdigValue;
-
-                if (hdig >= '0' && hdig <= '9')
-                    hdigValue = hdig - '0';
-                else if (hdig >= 'a' && hdig <= 'f')
-                    hdigValue = 10 + (hdig - 'a');
-                else if (hdig >= 'A' && hdig <= 'F')
-                    hdigValue = 10 + (hdig - 'A');
-                else 
-                    pm_error("Invalid hex digit '%c' in line '%s' of "
-                             "bitmap for character '%s' "
-                             "in BDF font file", hdig, line, charName);
-                        
-                if (i > 0)
-                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
-                if (i > 1)
-                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
-                if (i > 2)
-                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
-                if (i > 3)
-                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
-            }
+        if (!readlineP->arg[0])
+            pm_error("A line that is supposed to contain bitmap data, "
+                     "in hexadecimal, for character '%s' is empty", charName);
+
+        parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex,
+                       &bmapIndex, &error);
+
+        if (error) {
+            pm_error("Error in line %d of bitmap for character '%s': %s",
+                     n, charName, error);
+            pm_strfree(error);
         }
     }
 }
@@ -1196,15 +1272,13 @@ readBitmap(FILE *          const fp,
 static void
 createBmap(unsigned int  const glyphWidth,
            unsigned int  const glyphHeight,
-           FILE *        const fp,
+           readline *    const readlineP,
            const char *  const charName,
            const char ** const bmapP) {
 
-    char line[1024];
-    const char * arg[32];
     unsigned char * bmap;
-    int rc;
-
+    bool eof;
+    
     if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight)
         pm_error("Ridiculously large glyph");
 
@@ -1213,24 +1287,26 @@ createBmap(unsigned int  const glyphWidth,
     if (!bmap)
         pm_error("no memory for font glyph byte map");
 
-    rc = readline(fp, line, arg);
-    if (rc < 0)
+    readline_read(readlineP, &eof);
+    if (eof)
         pm_error("End of file encountered reading font glyph byte map from "
                  "BDF font file.");
-
-    if (streq(arg[0], "ATTRIBUTES")) {
-        rc = readline(fp, line, arg);
-        if (rc < 0)
+    
+    if (streq(readlineP->arg[0], "ATTRIBUTES")) {
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
             pm_error("End of file encountered after ATTRIBUTES in BDF "
                      "font file.");
     }                
-    if (!streq(arg[0], "BITMAP"))
+    if (!streq(readlineP->arg[0], "BITMAP"))
         pm_error("'%s' found where BITMAP expected in definition of "
-                 "character '%s' in BDF font file.", line, charName);
+                 "character '%s' in BDF font file.",
+                 readlineP->arg[0], charName);
 
-    assert(streq(arg[0], "BITMAP"));
+    assert(streq(readlineP->arg[0], "BITMAP"));
 
-    readBitmap(fp, glyphWidth, glyphHeight, charName, bmap);
+    readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap);
 
     *bmapP = (char *)bmap;
 }
@@ -1238,52 +1314,53 @@ createBmap(unsigned int  const glyphWidth,
 
 
 static void
-expect(FILE *        const fp,
-       const char *  const expected,
-       const char ** const arg,
-       char *        const line) {
-
-    int rc;
+readExpectedStatement(readline *    const readlineP,
+                      const char *  const expected) {
+/*----------------------------------------------------------------------------
+  Have the readline object *readlineP read the next line from the file, but
+  expect it to be a line of type 'expected' (i.e. the verb token at the
+  beginning of the line is that, e.g. "STARTFONT").  If it isn't, fail the
+  program.
+-----------------------------------------------------------------------------*/
+    bool eof;
 
-    rc = readline(fp, line, arg);
+    readline_read(readlineP, &eof);
 
-    if (rc < 0)
+    if (eof)
         pm_error("EOF in BDF font file where '%s' expected", expected);
-    else if (!streq(arg[0], expected))
-        pm_error("'%s' where '%s' expected in BDF font file",
-                 line, expected);
+    else if (!streq(readlineP->arg[0], expected))
+        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
+                 readlineP->arg[0], expected);
 }
 
 
 
 static void
-skipCharacter(FILE * const fp) {
+skipCharacter(readline * const readlineP) {
 /*----------------------------------------------------------------------------
-  In BDF font file 'fp', skip through the end of the character we are
-  presently in.
+  In the BDF font file being read by readline object *readlineP, skip through
+  the end of the character we are presently in.
 -----------------------------------------------------------------------------*/
     bool endChar;
                         
     endChar = FALSE;
                         
     while (!endChar) {
-        char line[1024];
-        const char * arg[32];
-        int rc;
-        rc = readline(fp, line, arg);
-        if (rc < 0)
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
             pm_error("End of file in the middle of a character (before "
                      "ENDCHAR) in BDF font file.");
-        endChar = streq(arg[0], "ENDCHAR");
+        endChar = streq(readlineP->arg[0], "ENDCHAR");
     }                        
 }
 
 
 
 static void
-validateEncoding(const char **  const arg,
-                 unsigned int * const codepointP,
-                 bool *         const badCodepointP) {
+interpEncoding(const char **  const arg,
+               unsigned int * const codepointP,
+               bool *         const badCodepointP) {
 /*----------------------------------------------------------------------------
    With arg[] being the ENCODING statement from the font, return as
    *codepointP the codepoint that it indicates (code point is the character
@@ -1323,44 +1400,57 @@ validateEncoding(const char **  const arg,
 
 
 
-
 static void
-processCharsLine(FILE *        const fp,
-                 const char ** const arg,
-                 struct font * const fontP) {
+readEncoding(readline *     const readlineP,
+             unsigned int * const codepointP,
+             bool *         const badCodepointP) {
+
+    readExpectedStatement(readlineP, "ENCODING");
+    
+    interpEncoding(readlineP->arg, codepointP, badCodepointP);
+}
+
 
+
+static void
+processChars(readline *    const readlineP,
+             struct font * const fontP) {
+/*----------------------------------------------------------------------------
+   Process the CHARS block in a BDF font file, assuming the file is positioned
+   just after the CHARS line.  Read the rest of the block and apply its
+   contents to *fontP.
+-----------------------------------------------------------------------------*/
     unsigned int nCharacters;
     unsigned int nCharsDone;
 
-    if (!arg[1])
+    if (!readlineP->arg[1])
         pm_error("Invalid CHARS line - no arguments");
 
-    nCharacters = atoi(arg[1]);
+    nCharacters = atoi(readlineP->arg[1]);
 
     nCharsDone = 0;
 
     while (nCharsDone < nCharacters) {
-        char line[1024];
-        const char * arg[32];
-        int rc;
+        bool eof;
 
-        rc = readline(fp, line, arg);
-        if (rc < 0)
+        readline_read(readlineP, &eof);
+        if (eof)
             pm_error("End of file after CHARS reading BDF font file");
 
-        if (streq(arg[0], "COMMENT")) {
+        if (streq(readlineP->arg[0], "COMMENT")) {
             /* ignore */
-        } else if (!streq(arg[0], "STARTCHAR"))
+        } else if (!streq(readlineP->arg[0], "STARTCHAR"))
             pm_error("no STARTCHAR after CHARS in BDF font file");
-        else if (!arg[1])
+        else if (!readlineP->arg[1])
             pm_error("Invalid STARTCHAR - no arguments");
         else {
-            const char * const charName = arg[1];
+            const char * const charName = readlineP->arg[1];
+
             struct glyph * glyphP;
             unsigned int codepoint;
             bool badCodepoint;
 
-            assert(streq(arg[0], "STARTCHAR"));
+            assert(streq(readlineP->arg[0], "STARTCHAR"));
 
             MALLOCVAR(glyphP);
 
@@ -1368,52 +1458,39 @@ processCharsLine(FILE *        const fp,
                 pm_error("no memory for font glyph for '%s' character",
                          charName);
 
-            {
-                const char * arg[32];
-                expect(fp, "ENCODING", arg, line);
+            readEncoding(readlineP, &codepoint, &badCodepoint);
 
-                validateEncoding(arg, &codepoint, &badCodepoint);
-            }
             if (badCodepoint)
-                skipCharacter(fp);
+                skipCharacter(readlineP);
             else {
-                {
-                    const char * arg[32];
-                    expect(fp, "SWIDTH", arg, line);
-                }
-                {
-                    const char * arg[32];
-                    
-                    expect(fp, "DWIDTH", arg, line);
-                    if (!arg[1])
-                        pm_error("Invalid DWIDTH statement - no arguments");
-                    glyphP->xadd = atoi(arg[1]);
-                }
-                {
-                    const char * arg[32];
+                readExpectedStatement(readlineP, "SWIDTH");
                     
-                    expect(fp, "BBX", arg, line);
-                    if (!arg[1])
-                        pm_error("Invalid BBX statement - no arguments");
-                    glyphP->width  = atoi(arg[1]);
-                    if (!arg[2])
-                        pm_error("Invalid BBX statement - only 1 argument");
-                    glyphP->height = atoi(arg[2]);
-                    if (!arg[3])
-                        pm_error("Invalid BBX statement - only 2 arguments");
-                    glyphP->x      = atoi(arg[3]);
-                    if (!arg[4])
-                        pm_error("Invalid BBX statement - only 3 arguments");
-                    glyphP->y      = atoi(arg[4]);
-                }
-                createBmap(glyphP->width, glyphP->height, fp, charName,
+                readExpectedStatement(readlineP, "DWIDTH");
+                if (!readlineP->arg[1])
+                    pm_error("Invalid DWIDTH statement - no arguments");
+                glyphP->xadd = atoi(readlineP->arg[1]);
+
+                readExpectedStatement(readlineP, "BBX");
+                if (!readlineP->arg[1])
+                    pm_error("Invalid BBX statement - no arguments");
+                glyphP->width  = atoi(readlineP->arg[1]);
+                if (!readlineP->arg[2])
+                    pm_error("Invalid BBX statement - only 1 argument");
+                glyphP->height = atoi(readlineP->arg[2]);
+                if (!readlineP->arg[3])
+                    pm_error("Invalid BBX statement - only 2 arguments");
+                glyphP->x      = atoi(readlineP->arg[3]);
+                if (!readlineP->arg[4])
+                    pm_error("Invalid BBX statement - only 3 arguments");
+                glyphP->y      = atoi(readlineP->arg[4]);
+
+                createBmap(glyphP->width, glyphP->height, readlineP, charName,
                            &glyphP->bmap);
                 
-                {
-                    const char * arg[32];
-                    expect(fp, "ENDCHAR", arg, line);
-                }
-                assert(codepoint < 256); /* Ensured by validateEncoding() */
+
+                readExpectedStatement(readlineP, "ENDCHAR");
+
+                assert(codepoint < 256); /* Ensured by readEncoding() */
 
                 fontP->glyph[codepoint] = glyphP;
             }
@@ -1425,51 +1502,57 @@ processCharsLine(FILE *        const fp,
 
 
 static void
-processFontLine(FILE *        const fp,
-                const char *  const line,
-                const char ** const arg,
-                struct font * const fontP,
-                bool *        const endOfFontP) {
+processBdfFontLine(readline *    const readlineP,
+                   struct font * const fontP,
+                   bool *        const endOfFontP) {
+/*----------------------------------------------------------------------------
+   Process a nonblank line just read from a BDF font file.
 
+   This processing may involve reading more lines.
+-----------------------------------------------------------------------------*/
     *endOfFontP = FALSE;  /* initial assumption */
 
-    if (streq(arg[0], "COMMENT")) {
+    assert(readlineP->arg[0] != NULL);  /* Entry condition */
+
+    if (streq(readlineP->arg[0], "COMMENT")) {
         /* ignore */
-    } else if (streq(arg[0], "SIZE")) {
+    } else if (streq(readlineP->arg[0], "SIZE")) {
         /* ignore */
-    } else if (streq(arg[0], "STARTPROPERTIES")) {
+    } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) {
+        /* Read off the properties and ignore them all */
         unsigned int propCount;
         unsigned int i;
 
-        if (!arg[1])
+        if (!readlineP->arg[1])
             pm_error("Invalid STARTPROPERTIES statement - no arguments");
-        propCount = atoi(arg[1]);
+        propCount = atoi(readlineP->arg[1]);
 
         for (i = 0; i < propCount; ++i) {
-            char line[1024];
-            const char * arg[32];
-            int rc;
-            rc = readline(fp, line, arg);
-            if (rc < 0)
+            bool eof;
+            readline_read(readlineP, &eof);
+            if (eof)
                 pm_error("End of file after STARTPROPERTIES in BDF font file");
         }
-    } else if (streq(arg[0], "FONTBOUNDINGBOX")) {
-        if (!arg[1])
+    } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) {
+        if (!readlineP->arg[1])
             pm_error("Invalid FONTBOUNDINGBOX  statement - no arguments");
-        fontP->maxwidth  = atoi(arg[1]);
-        if (!arg[2])
+        fontP->maxwidth  = atoi(readlineP->arg[1]);
+        if (!readlineP->arg[2])
             pm_error("Invalid FONTBOUNDINGBOX  statement - only 1 argument");
-        fontP->maxheight = atoi(arg[2]);
-        if (!arg[3])
+        fontP->maxheight = atoi(readlineP->arg[2]);
+        if (!readlineP->arg[3])
             pm_error("Invalid FONTBOUNDINGBOX  statement - only 2 arguments");
-        fontP->x         = atoi(arg[3]);
-        if (!arg[4])
+        fontP->x         = atoi(readlineP->arg[3]);
+        if (!readlineP->arg[4])
             pm_error("Invalid FONTBOUNDINGBOX  statement - only 3 arguments");
-        fontP->y         = atoi(arg[4]);
-    } else if (streq(arg[0], "ENDFONT")) {
+        fontP->y         = atoi(readlineP->arg[4]);
+    } else if (streq(readlineP->arg[0], "ENDFONT")) {
         *endOfFontP = true;
-    } else if (!strcmp(arg[0], "CHARS"))
-        processCharsLine(fp, arg, fontP);
+    } else if (streq(readlineP->arg[0], "CHARS")) {
+        processChars(readlineP, fontP);
+    } else {
+        /* ignore */
+    }
 }
 
 
@@ -1477,18 +1560,17 @@ processFontLine(FILE *        const fp,
 struct font *
 pbm_loadbdffont(const char * const name) {
 
-    FILE * fp;
-    char line[1024];
-    const char * arg[32];
+    FILE * ifP;
+    readline readline;
     struct font * fontP;
     bool endOfFont;
 
-    fp = fopen(name, "rb");
-    if (!fp)
+    ifP = fopen(name, "rb");
+    if (!ifP)
         pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
                  name, errno, strerror(errno));
 
-    expect(fp, "STARTFONT", arg, line);
+    readline_init(&readline, ifP);
 
     MALLOCVAR(fontP);
     if (fontP == NULL)
@@ -1500,20 +1582,22 @@ pbm_loadbdffont(const char * const name) {
            find in the bdf file later.
         */
         unsigned int i;
-        for (i = 0; i < 256; i++) 
+        for (i = 0; i < 256; ++i) 
             fontP->glyph[i] = NULL;
     }
     fontP->x = fontP->y = 0;
 
+    readExpectedStatement(&readline, "STARTFONT");
+
     endOfFont = FALSE;
 
     while (!endOfFont) {
-        int rc;
-        rc = readline(fp, line, arg);
-        if (rc < 0)
+        bool eof;
+        readline_read(&readline, &eof);
+        if (eof)
             pm_error("End of file before ENDFONT statement in BDF font file");
 
-        processFontLine(fp, line, arg, fontP, &endOfFont);
+        processBdfFontLine(&readline, fontP, &endOfFont);
     }
     return fontP;
 }
diff --git a/lib/libpbmvms.c b/lib/libpbmvms.c
deleted file mode 100644
index 8a2a54f8..00000000
--- a/lib/libpbmvms.c
+++ /dev/null
@@ -1,734 +0,0 @@
-/***************************************************************************
-  This file contains library routines needed to build Netpbm for VMS.
-  However, as of 2000.05.26, when these were split out of libpbm1.c
-  (where they were all switched by #ifdef VMS), there is no evidence
-  that anyone is building Netpbm for VMS.
-****************************************************************************/
-
-/*
- * @(#)argproc.c 1.0 89/02/01		Mark Pizzolato (mark@infopiz.uucp)	
- */
-
-#ifndef lint
-char argproc_version[] = "@(#)argproc.c VMS uucp Version infopiz-1.0";
-#endif
-
-#include "includes.h"		/* System include files, system dependent */
-
-
-/*
- * getredirection() is intended to aid in porting C programs
- * to VMS (Vax-11 C) which does not have '>' and '<'
- * I/O redirection, along with a command line pipe mechanism
- * using the '|' AND background command execution '&'.
- * The piping mechanism will probably work with almost any 'filter' type
- * of program.  With suitable modification, it may useful for other
- * portability problems as well.
- *
- * Author:  Mark Pizzolato	mark@infopiz.UUCP
- */
-struct list_item
-    {
-    struct list_item *next;
-    char *value;
-    };
-
-int
-getredirection(ac, av)
-int		*ac;
-char		***av;
-/*
- * Process vms redirection arg's.  Exit if any error is seen.
- * If getredirection() processes an argument, it is erased
- * from the vector.  getredirection() returns a new argc and argv value.
- * In the event that a background command is requested (by a trailing "&"),
- * this routine creates a background subprocess, and simply exits the program.
- *
- * Warning: do not try to simplify the code for vms.  The code
- * presupposes that getredirection() is called before any data is
- * read from stdin or written to stdout.
- *
- * Normal usage is as follows:
- *
- *	main(argc, argv)
- *	int		argc;
- *    	char		*argv[];
- *	{
- *		getredirection(&argc, &argv);
- *	}
- */
-{
-    int			argc = *ac;	/* Argument Count	  */
-    char		**argv = *av;	/* Argument Vector	  */
-    char		*ap;   		/* Argument pointer	  */
-    int	       		j;		/* argv[] index		  */
-    extern int		errno;		/* Last vms i/o error 	  */
-    int			item_count = 0;	/* Count of Items in List */
-    int			new_file;	/* flag, true if '>' used */
-    struct list_item 	*list_head = 0;	/* First Item in List	    */
-    struct list_item	*list_tail;	/* Last Item in List	    */
-    char 		*in = NULL;	/* Input File Name	    */
-    char 		*out = NULL;	/* Output File Name	    */
-    char 		*outmode = "w";	/* Mode to Open Output File */
-    int			cmargc = 0;    	/* Piped Command Arg Count  */
-    char		**cmargv = NULL;/* Piped Command Arg Vector */
-    stat_t		statbuf;	/* fstat buffer		    */
-
-    /*
-     * First handle the case where the last thing on the line ends with
-     * a '&'.  This indicates the desire for the command to be run in a
-     * subprocess, so we satisfy that desire.
-     */
-    ap = argv[argc-1];
-    if (0 == strcmp("&", ap))
-	exit(background_process(--argc, argv));
-    if ('&' == ap[strlen(ap)-1])
-	{
-	ap[strlen(ap)-1] = '\0';
-	exit(background_process(argc, argv));
-	}
-    /*
-     * Now we handle the general redirection cases that involve '>', '>>',
-     * '<', and pipes '|'.
-     */
-    for (new_file = 0, j = 0; j < argc; ++j)
-	{
-	if (0 == strcmp("<", argv[j]))
-	    {
-	    if (j+1 >= argc)
-		{
-		errno = EINVAL;
-		perror("No input file");
-		exit(EXIT_ERR);
-		}
-	    in = argv[++j];
-	    continue;
-	    }
-	if ('<' == *(ap = argv[j]))
-	    {
-	    in = 1 + ap;
-	    continue;
-	    }
-	if (0 == strcmp(">", ap))
-	    {
-	    if (j+1 >= argc)
-		{
-		errno = EINVAL;
-		perror("No output file");
-		exit(EXIT_ERR);
-		}
-	    out = argv[++j];
-	    new_file = 1;
-	    continue;
-	    }
-	if ('>' == *ap)
-	    {
-	    if ('>' == ap[1])
-		{
-		outmode = "a";
-		if ('\0' == ap[2])
-		    out = argv[++j];
-		else
-		    out = 2 + ap;
-		}
-	    else
-		{ out = 1 + ap;  new_file = 1; }
-	    continue;
-	    }
-	if (0 == strcmp("|", argv[j]))
-	    {
-	    if (j+1 >= argc)
-		{
-		errno = EPIPE;
-		perror("No command to Pipe to");
-		exit(EXIT_ERR);
-		}
-	    cmargc = argc-(j+1);
-	    cmargv = &argv[j+1];
-	    argc = j;
-	    outmode = "wb";	/* pipes are binary mode devices */
-	    continue;
-	    }
-	if ('|' == *(ap = argv[j]))
-	    {
-	    ++argv[j];
-	    cmargc = argc-j;
-	    cmargv = &argv[j];
-	    argc = j;
-	    outmode = "wb";	/* pipes are binary mode devices */
-	    continue;
-	    }
-	expand_wild_cards(ap, &list_head, &list_tail, &item_count);
-	}
-    /*
-     * Allocate and fill in the new argument vector, Some Unix's terminate
-     * the list with an extra null pointer.
-     */
-    argv = *av = calloc(item_count+1, sizeof(char *));
-    for (j = 0; j < item_count; ++j, list_head = list_head->next)
-	argv[j] = list_head->value;
-    *ac = item_count;
-    if (cmargv != NULL)
-	{
-	char subcmd[1024];
-	static char *pipe_and_fork();
-
-	if (out != NULL)
-	    {
-	    errno = EINVAL;
-	    perror("Invalid '|' and '>' specified");
-	    exit(EXIT_ERR);
-	    }
-	strcpy(subcmd, cmargv[0]);
-	for (j = 1; j < cmargc; ++j)
-	    {
-	    strcat(subcmd, " \"");
-	    strcat(subcmd, cmargv[j]);
-	    strcat(subcmd, "\"");
-	    }
-	out = pipe_and_fork(subcmd);
-	outmode = "wb";
-	}
-	
-    /* Check for input from a pipe (mailbox) */
-
-    if(fstat(0, &statbuf) == 0){
-	if(strncmp(statbuf.st_dev, "MB", 2) == 0 || 
-	    strncmp(statbuf.st_dev, "_MB", 3) == 0){
-
-	    /* Input from a pipe, reopen it in binary mode to disable	*/
-	    /* carriage control processing.				*/
-
-	    if (in != NULL){
-		errno = EINVAL;
-		perror("Invalid '|' and '<' specified");
-		exit(EXIT_ERR);
-		}
-	    freopen(statbuf.st_dev, "rb", stdin);
-	    }
-	}
-    else {
-	perror("fstat failed");
-	exit(EXIT_ERR);
-	}
-
-#ifdef __ALPHA
-        /*, "mbc=32", "mbf=2"))) blows up on the ALPHA cbm 11/08/92 */
-    if ((in != NULL) && (NULL == freopen(in, "r", stdin)))
-        {
-#else
-    if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2")))
-	{
-#endif
-	perror(in);    	       	/* Can't find file		*/
-	exit(EXIT_ERR);		/* Is a fatal error		*/
-	}
-#ifdef __ALPHA
-    if ((out != NULL) && (NULL == freopen(out, outmode, stdout)))
-        {
-#else
-    if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2")))
-	{	
-#endif
-	perror(ap);		/* Error, can't write or append	*/
-	exit(EXIT_ERR);		/* Is a fatal error		*/
-	}
-
-     if ( new_file ) {
-	/*
-	 * We are making an explicit output file, fstat the file and
-         * declare exit handler to be able change the file to fixed length
-	 * records if necessary. 
-	 */
-	char fname[256];
-	static int outfile_rundown(), check_outfile_filetype();
-	static stat_t ofile_stat;
-	static struct exit_control_block {
-    	    struct exit_control_block *flink;
-    	    int	(*exit_routine)();
-	    int arg_count;
-	    int *status_address;	/* arg 1 */
-	    stat_t *stat;		/* arg 2 */
-	    int exit_status;
-	    int skew[128];
-	} exit_block = 
-	    { 0, outfile_rundown, 2, &exit_block.exit_status, &ofile_stat, 0 };
-
-	if ( fstat ( fileno(stdout), &ofile_stat ) == 0 )
-	     sys$dclexh ( &exit_block );
-	else fprintf(stderr,"Error fstating stdout - %s\n",
-		strerror(errno,vaxc$errno) );
-
-	if ( fgetname ( stdout, fname, 0 ) ) check_outfile_filetype ( fname );
-     }
-#ifdef DEBUG
-    fprintf(stderr, "Arglist:\n");
-    for (j = 0; j < *ac;  ++j)
-	fprintf(stderr, "argv[%d] = '%s'\n", j, argv[j]);
-#endif
-}
-
-static int binary_outfile = 0;
-void set_outfile_binary() { binary_outfile = 1; return; }
-
-/*
- * Check if output file should be set to binary (fixed 512) on exit based
- * upon the filetype.
- */
-static int check_outfile_filetype ( name )
-    char *name;
-{
-    char *binary_filetypes, *ext, *p, *t;
-    binary_filetypes = (char *) getenv ( "PBM_BINARY_FILETYPES" );
-    if ( binary_filetypes == NULL ) return 0;
-
-    ext = strchr ( name, '.' );  if ( ext == NULL ) return 0;
-    ext++;
-    t = strrchr ( name, '.' );   if ( t != NULL ) *t = '\0';
-
-    for ( p = binary_filetypes; *p != '\0'; p++ ) {
-	for ( t = p;
-	      (*p != '\0' ) && (strchr ( ", 	", *p ) == NULL); 
-	     p++ ) *p = toupper(*p);
-	*p = '\0';
-
-	if ( strcmp ( t, ext ) == 0 ) {
-	    binary_outfile = 1;
-	    break;
-	}
-    }
-    return binary_outfile;
-}
-
-
-/*
- * Exit handler to set output file to binary on image exit.
- */
-static int outfile_rundown ( reason, statbuf )
-    int *reason;
-    stat_t *statbuf;
-{
-    int code, channel, status, LIB$GETDVI(), sys$assign(), sys$qiow();
-    long int fib_desc[2], devchar;
-    short int iosb[4];
-    $DESCRIPTOR(device, statbuf->st_dev);
-    struct fibdef fib;		/* File information block (XQP) */
-    struct atrdef atr[2];
-    struct fat {
-      unsigned char rtype, ratattrib;
-      unsigned short int rsize, hiblk[2], efblk[2], ffbyte, maxrec, defext, gbc;
-      unsigned short int reserved[4], versions;
-    } rat;
-
-    if ( !binary_outfile ) return 1;	/* leave file alone */
-    /*
-     * Assign channel to device listed in stat block and test if it is
-     * a directory structured device, returning if not.
-     */
-    device.dsc$w_length = strlen ( statbuf->st_dev );
-    status = sys$assign ( &device, &channel, 0, 0 );
-    if ((status & 1) == 0) { fprintf(stderr, 
-	"assign error to %s (%d)\n", device.dsc$a_pointer, status);
-		return status; }
-    code = DVI$_DEVCHAR;
-    status = LIB$GETDVI ( &code, &channel, 0, &devchar );
-    if ((status & 1) == 0) { fprintf(stderr, "getdvi error: %d\n", status);
-		return status; }
-    if ( (devchar & DEV$M_DIR) == 0 ) return 1;
-    /*
-     * Build File Information Block and Atrribute block.
-     */
-#ifdef __ALPHA
-    fib.fib$w_fid[0] = statbuf->st_ino[0];
-    fib.fib$w_fid[1] = statbuf->st_ino[1];
-    fib.fib$w_fid[2] = statbuf->st_ino[2];
-#else
-    fib.fib$r_fid_overlay.fib$w_fid[0] = statbuf->st_ino[0];
-    fib.fib$r_fid_overlay.fib$w_fid[1] = statbuf->st_ino[1];
-    fib.fib$r_fid_overlay.fib$w_fid[2] = statbuf->st_ino[2];
-#endif
-
-    atr[0].atr$w_size = sizeof(rat);
-    atr[0].atr$w_type = ATR$C_RECATTR;
-    atr[0].atr$l_addr = &rat;
-    atr[1].atr$w_size = 0; atr[1].atr$w_type = 0;
-    /*
-     * Perform access function with read_attributes sub-function.
-     */
-    freopen ( "SYS$OUTPUT:", "a", stdout );
-    fib_desc[0] = 10; fib_desc[1] = (long int) &fib;
-#ifdef __ALPHA
-    fib.fib$l_acctl = FIB$M_WRITE;
-#else
-    fib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_WRITE;
-#endif
-    status = sys$qiow ( 0, channel, IO$_ACCESS|IO$M_ACCESS,
-		 &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
-    /*
-     * If status successful, update record byte and perform a MODIFY.
-     */
-    if ( (status&1) == 1 ) status = iosb[0];
-    if ( (status&1) == 1 ) {
-      rat.rtype = 1;		/* fixed length records */
-      rat.rsize = 512;  	/* 512 byte block recrods */
-      rat.ratattrib = 0;        /* Record attributes: none */
-
-     status = sys$qiow
-	( 0, channel, IO$_MODIFY, &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
-       if ( (status&1) == 1 ) status = iosb[0];
-   }
-   sys$dassgn ( channel );
-   if ( (status & 1) == 0 ) fprintf ( stderr,
-	"Failed to convert output file to binary format, status: %d\n", status);
-   return status;
-}
-
-
-static add_item(head, tail, value, count)
-struct list_item **head;
-struct list_item **tail;
-char *value;
-int *count;
-{
-    if (*head == 0)
-	{
-	if (NULL == (*head = calloc(1, sizeof(**head))))
-	    {
-	    errno = ENOMEM;
-	    perror("");
-	    exit(EXIT_ERR);
-	    }
-	*tail = *head;
-	}
-    else
-	if (NULL == ((*tail)->next = calloc(1, sizeof(**head))))
-	    {
-	    errno = ENOMEM;
-	    perror("");
-	    exit(EXIT_ERR);
-	    }
-	else
-	    *tail = (*tail)->next;
-    (*tail)->value = value;
-    ++(*count);
-}
-
-static expand_wild_cards(item, head, tail, count)
-char *item;
-struct ltem_list **head;
-struct ltem_list **tail;
-int *count;
-{
-int expcount = 0;
-int context = 0;
-int status;
-int status_value;
-int had_version;
-$DESCRIPTOR(filespec, item);
-$DESCRIPTOR(defaultspec, "SYS$DISK:[]*.*;");
-$DESCRIPTOR(resultspec, "");
-
-    if (strcspn(item, "*%") == strlen(item))
-	{
-	add_item(head, tail, item, count);
-	return;
-	}
-    resultspec.dsc$b_dtype = DSC$K_DTYPE_T;
-    resultspec.dsc$b_class = DSC$K_CLASS_D;
-    resultspec.dsc$a_pointer = NULL;
-    filespec.dsc$w_length = strlen(item);
-    /*
-     * Only return version specs, if the caller specified a version
-     */
-    had_version = strchr(item, ';');
-    while (1 == (1&lib$find_file(&filespec, &resultspec, &context,
-    				 &defaultspec, 0, &status_value, &0)))
-	{
-	char *string;
-	char *c;
-
-	if (NULL == (string = calloc(1, resultspec.dsc$w_length+1)))
-	    {
-	    errno = ENOMEM;
-	    perror("");
-	    exit(EXIT_ERR);
-	    }
-	strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length);
-	string[resultspec.dsc$w_length] = '\0';
-	if (NULL == had_version)
-	    *((char *)strrchr(string, ';')) = '\0';
-	/*
-	 * Be consistent with what the C RTL has already done to the rest of
-	 * the argv items and lowercase all of these names.
-	 */
-	for (c = string; *c; ++c)
-	    if (isupper(*c))
-		*c = tolower(*c);
-	add_item(head, tail, string, count);
-	++expcount;
-	}
-    if (expcount == 0)
-	add_item(head, tail, item, count);
-    lib$sfree1_dd(&resultspec);
-    lib$find_file_end(&context);
-}
-
-static int child_st[2];	/* Event Flag set when child process completes	*/
-
-static short child_chan;/* I/O Channel for Pipe Mailbox			*/
-
-static exit_handler(status)
-int *status;
-{
-short iosb[4];
-
-    if (0 == child_st[0])
-	{
-#ifdef DEBUG
-	fprintf(stderr, "Waiting for Child Process to Finnish . . .\n");
-#endif
-	fflush(stdout);	    /* Have to flush pipe for binary data to	*/
-			    /* terminate properly -- <tp@mccall.com>	*/
-#ifdef DEBUG
-	fprintf(stderr, "    stdout flushed. . .\n");
-#endif
-	sys$qio(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0);
-#ifdef DEBUG
-	fprintf(stderr, "    Pipe terminated. . .\n");
-#endif
-	fclose(stdout);
-#ifdef DEBUG
-	fprintf(stderr, "    stdout closed. . .\n");
-#endif
-	sys$synch(0, child_st);
-	sys$dassgn(child_chan);
-	}
-#ifdef DEBUG
-	fprintf(stderr, "    sync done. . .\n");
-#endif
-}
-
-#include <syidef>		/* System Information Definitions	*/
-
-static sig_child(chan)
-int chan;
-{
-#ifdef DEBUG
-    fprintf(stderr, "Child Completion AST, st: %x\n", child_st[0] );
-#endif
-    if (child_st[0] == 0)
-	{ child_st[0] = 1; }
-    sys$setef ( 0 );
-}
-
-static struct exit_control_block
-    {
-    struct exit_control_block *flink;
-    int	(*exit_routine)();
-    int arg_count;
-    int *status_address;
-    int exit_status;
-    } exit_block =
-    {
-    0,
-    exit_handler,
-    1,
-    &exit_block.exit_status,
-    0
-    };
-
-static char *pipe_and_fork(cmd)
-char *cmd;
-{
-    $DESCRIPTOR(cmddsc, cmd);
-    static char mbxname[64], ef = 0;
-    $DESCRIPTOR(mbxdsc, mbxname);
-    short iosb[4];
-    int status;
-    int pid;
-    struct
-	{
-	short dna_buflen;
-	short dna_itmcod;
-	char *dna_buffer;
-	short *dna_retlen;
-	int listend;
-	} itmlst =
-	{
-	sizeof(mbxname),
-	DVI$_DEVNAM,
-	mbxname,
-	&mbxdsc.dsc$w_length,
-	0
-	};
-    int mbxsize;
-    struct
-	{
-	short mbf_buflen;
-	short mbf_itmcod;
-	int *mbf_maxbuf;
-	short *mbf_retlen;
-	int listend;
-	} syiitmlst =
-	{
-	sizeof(mbxsize),
-	SYI$_MAXBUF,
-	&mbxsize,
-	0,
-	0
-	};
-
-    cmddsc.dsc$w_length = strlen(cmd);
-    /*
-     * Get the SYSGEN parameter MAXBUF, and the smaller of it and 2048 as
-     * the size of the 'pipe' mailbox.
-     */
-    if (1 == (1&(vaxc$errno = sys$getsyiw(0, 0, 0, &syiitmlst, iosb, 0, 0, 0))))
-	vaxc$errno = iosb[0];
-    if (0 == (1&vaxc$errno))
-	{
- 	errno = EVMSERR;
-	perror("Can't get SYSGEN parameter value for MAXBUF");
-	exit(EXIT_ERR);
-	}
-    if (mbxsize > 2048)
-	mbxsize = 2048;
-    if (0 == (1&(vaxc$errno = sys$crembx(0, &child_chan, mbxsize, mbxsize, 0, 0, 0))))
-	{
-	errno = EVMSERR;
-	perror("Can't create pipe mailbox");
-	exit(EXIT_ERR);
-	}
-    if (1 == (1&(vaxc$errno = sys$getdviw(0, child_chan, 0, &itmlst, iosb,
-    					  0, 0, 0))))
-	vaxc$errno = iosb[0];
-    if (0 == (1&vaxc$errno))
-	{
- 	errno = EVMSERR;
-	perror("Can't get pipe mailbox device name");
-	exit(EXIT_ERR);
-	}
-    mbxname[mbxdsc.dsc$w_length] = '\0';
-#ifdef DEBUG
-    fprintf(stderr, "Pipe Mailbox Name = '%s'\n", mbxname);
-#endif
-    if (0 == (1&(vaxc$errno = lib$spawn(&cmddsc, &mbxdsc, 0, &1,
-    					0, &pid, child_st, &ef, sig_child,
-    					&child_chan))))
-	{
-	errno = EVMSERR;
-	perror("Can't spawn subprocess");
-	exit(EXIT_ERR);
-	}
-#ifdef DEBUG
-    fprintf(stderr, "Subprocess's Pid = %08X\n", pid);
-#endif
-    sys$dclexh(&exit_block);
-    return(mbxname);
-}
-
-background_process(argc, argv)
-int argc;
-char **argv;
-{
-char command[2048] = "$";
-$DESCRIPTOR(value, command);
-$DESCRIPTOR(cmd, "BACKGROUND$COMMAND");
-$DESCRIPTOR(null, "NLA0:");
-int pid;
-
-    strcat(command, argv[0]);
-    while (--argc)
-	{
-	strcat(command, " \"");
-	strcat(command, *(++argv));
-	strcat(command, "\"");
-	}
-    value.dsc$w_length = strlen(command);
-    if (0 == (1&(vaxc$errno = lib$set_symbol(&cmd, &value))))
-	{
-	errno = EVMSERR;
-	perror("Can't create symbol for subprocess command");
-	exit(EXIT_ERR);
-	}
-    if (0 == (1&(vaxc$errno = lib$spawn(&cmd, &null, 0, &17, 0, &pid))))
-	{
-	errno = EVMSERR;
-	perror("Can't spawn subprocess");
-	exit(EXIT_ERR);
-	}
-#ifdef DEBUG
-    fprintf(stderr, "%s\n", command);
-#endif
-    fprintf(stderr, "%08X\n", pid);
-    return(EXIT_OK);
-}
-
-/* got this off net.sources */
-
-#ifdef	VMS
-#define	index	strchr
-#endif	/*VMS*/
-
-/*
- * get option letter from argument vector
- */
-int	opterr = 1,		/* useless, never set or used */
-	optind = 1,		/* index into parent argv vector */
-	optopt;			/* character checked for validity */
-char	*optarg;		/* argument associated with option */
-
-#define BADCH	(int)'?'
-#define EMSG	""
-#define tell(s)	fputs(progname,stderr);fputs(s,stderr); \
-		fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);
-
-getopt(nargc,nargv,ostr)
-int	nargc;
-char	**nargv,
-	*ostr;
-{
-	static char	*place = EMSG;	/* option letter processing */
-	register char	*oli;		/* option letter list index */
-	char	*index();
-	char *progname;
-
-	if(!*place) {			/* update scanning pointer */
-		if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF);
-		if (*place == '-') {	/* found "--" */
-			++optind;
-			return(EOF);
-		}
-	}				/* option letter okay? */
-	if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
-		if(!*place) ++optind;
-#ifdef VMS
-		progname = strrchr(nargv[0],']');
-#else
-		progname = rindex(nargv[0],'/');
-#endif
-		if (!progname) progname = nargv[0]; else progname++;
-		tell(": illegal option -- ");
-	}
-	if (*++oli != ':') {		/* don't need argument */
-		optarg = NULL;
-		if (!*place) ++optind;
-	}
-	else {				/* need an argument */
-		if (*place) optarg = place;	/* no white space */
-		else if (nargc <= ++optind) {	/* no arg */
-			place = EMSG;
-#ifdef VMS
-			progname = strrchr(nargv[0],']');
-#else
-			progname = rindex(nargv[0],'/');
-#endif
-			if (!progname) progname = nargv[0]; else progname++;
-			tell(": option requires an argument -- ");
-		}
-	 	else optarg = nargv[optind];	/* white space */
-		place = EMSG;
-		++optind;
-	}
-	return(optopt);			/* dump back option letter */
-}
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
index 4d93e4be..9ef5d2c1 100644
--- a/lib/libpgm1.c
+++ b/lib/libpgm1.c
@@ -20,6 +20,9 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pgm.h"
 #include "libpgm.h"
 #include "pbm.h"
@@ -27,8 +30,6 @@
 #include "pam.h"
 #include "libpam.h"
 #include "fileio.h"
-#include "mallocvar.h"
-#include "nstring.h"
 
 
 gray *
@@ -100,10 +101,13 @@ validateComputableSize(unsigned int const cols,
    you expect.  That failed expectation can be disastrous if you use
    it to allocate memory.
 
+   It is very normal to allocate space for a pixel row, so we make sure
+   the size of a pixel row, in bytes, can be represented by an 'int'.
+
    A common operation is adding 1 or 2 to the highest row or
    column number in the image, so we make sure that's possible.
 -----------------------------------------------------------------------------*/
-    if (cols > INT_MAX - 2)
+    if (cols > INT_MAX / (sizeof(gray)) || cols > INT_MAX - 2)
         pm_error("image width (%u) too large to be processed", cols);
     if (rows > INT_MAX - 2)
         pm_error("image height (%u) too large to be processed", rows);
@@ -164,7 +168,8 @@ pgm_readpgminit(FILE * const fileP,
         break;
 
     default:
-        pm_error("bad magic number - not a Netpbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
@@ -172,11 +177,44 @@ pgm_readpgminit(FILE * const fileP,
 
 
 static void
+validateRpgmRow(gray *         const grayrow, 
+                unsigned int   const cols,
+                gray           const maxval,
+                const char **  const errorP) {
+/*----------------------------------------------------------------------------
+  Check for sample values above maxval in input.  
+
+  Note: a program that wants to deal with invalid sample values itself can
+  simply make sure it uses a sufficiently high maxval on the read function
+  call, so this validation never fails.
+-----------------------------------------------------------------------------*/
+    if (maxval == 255 || maxval == 65535) {
+        /* There's no way a sample can be invalid, so we don't need to look at
+           the samples individually.
+        */
+        *errorP = NULL;
+    } else {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            if (grayrow[col] > maxval) {
+                pm_asprintf(errorP,
+                            "gray value %u is greater than maxval (%u)",
+                            grayrow[col], maxval);
+                return;
+            }
+        }
+        *errorP = NULL;
+    }
+}  
+
+
+
+static void
 readRpgmRow(FILE * const fileP,
-               gray * const grayrow, 
-               int    const cols,
-               gray   const maxval,
-               int    const format) {
+            gray * const grayrow, 
+            int    const cols,
+            gray   const maxval,
+            int    const format) {
 
     unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
     int          const bytesPerRow    = cols * bytesPerSample;
@@ -186,19 +224,18 @@ readRpgmRow(FILE * const fileP,
     
     MALLOCARRAY(rowBuffer, bytesPerRow);
     if (rowBuffer == NULL)
-        asprintfN(&error, "Unable to allocate memory for row buffer "
-                  "for %u columns", cols);
+        pm_asprintf(&error, "Unable to allocate memory for row buffer "
+                    "for %u columns", cols);
     else {
-        ssize_t rc;
+        size_t rc;
         rc = fread(rowBuffer, 1, bytesPerRow, fileP);
         if (rc == 0)
-            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
-                      errno, strerror(errno));
+            pm_asprintf(&error, "Error reading row.  fread() errno=%d (%s)",
+                        errno, strerror(errno));
         else if (rc != bytesPerRow)
-            asprintfN(&error, "Error reading row.  Short read of %u bytes "
-                      "instead of %u", rc, bytesPerRow);
+            pm_asprintf(&error, "Error reading row.  Short read of %u bytes "
+                        "instead of %u", (unsigned)rc, bytesPerRow);
         else {
-            error = NULL;
             if (maxval < 256) {
                 unsigned int col;
                 for (col = 0; col < cols; ++col)
@@ -218,12 +255,13 @@ readRpgmRow(FILE * const fileP,
                     grayrow[col] = g;
                 }
             }
+            validateRpgmRow(grayrow, cols, maxval, &error); 
         }
         free(rowBuffer);
     }
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 } 
@@ -241,7 +279,7 @@ readPbmRow(FILE * const fileP,
     jmp_buf * origJmpbufP;
     bit * bitrow;
     
-    bitrow = pbm_allocrow(cols);
+    bitrow = pbm_allocrow_packed(cols);
     if (setjmp(jmpbuf) != 0) {
         pbm_freerow(bitrow);
         pm_setjmpbuf(origJmpbufP);
@@ -251,10 +289,14 @@ readPbmRow(FILE * const fileP,
 
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-        pbm_readpbmrow(fileP, bitrow, cols, format);
-        for (col = 0; col < cols; ++col)
-            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
+        pbm_readpbmrow_packed(fileP, bitrow, cols, format);
 
+        for (col = 0; col < cols; ++col) {
+            grayrow[col] =
+                ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ?
+                maxval : 0
+                ;
+        }
         pm_setjmpbuf(origJmpbufP);
     }
     pbm_freerow(bitrow);
@@ -338,28 +380,30 @@ pgm_readpgm(FILE * const fileP,
 
 void
 pgm_check(FILE *               const file, 
-          enum pm_check_type   const check_type, 
+          enum pm_check_type   const checkType, 
           int                  const format, 
           int                  const cols, 
           int                  const rows, 
           gray                 const maxval,
-          enum pm_check_code * const retval_p) {
+          enum pm_check_code * const retvalP) {
 
     if (rows < 0)
         pm_error("Invalid number of rows passed to pgm_check(): %d", rows);
     if (cols < 0)
         pm_error("Invalid number of columns passed to pgm_check(): %d", cols);
     
-    if (check_type != PM_CHECK_BASIC) {
-        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP)
+            *retvalP = PM_CHECK_UNKNOWN_TYPE;
     } else if (PGM_FORMAT_TYPE(format) == PBM_TYPE) {
-        pbm_check(file, check_type, format, cols, rows, retval_p);
+        pbm_check(file, checkType, format, cols, rows, retvalP);
     } else if (format != RPGM_FORMAT) {
-        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        if (retvalP)
+            *retvalP = PM_CHECK_UNCHECKABLE;
     } else {        
-        pm_filepos const bytes_per_row = cols * (maxval > 255 ? 2 : 1);
-        pm_filepos const need_raster_size = rows * bytes_per_row;
+        pm_filepos const bytesPerRow    = cols * (maxval > 255 ? 2 : 1);
+        pm_filepos const needRasterSize = rows * bytesPerRow;
         
-        pm_check(file, check_type, need_raster_size, retval_p);
+        pm_check(file, checkType, needRasterSize, retvalP);
     }
 }
diff --git a/lib/libpgm2.c b/lib/libpgm2.c
index 650d2cb5..80b5cf42 100644
--- a/lib/libpgm2.c
+++ b/lib/libpgm2.c
@@ -13,8 +13,8 @@
 #include <string.h>
 #include <errno.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "pgm.h"
 
 
@@ -37,10 +37,6 @@ pgm_writepgminit(FILE * const fileP,
             PGM_MAGIC1, 
             plainFormat || maxval >= 1<<16 ? PGM_MAGIC2 : RPGM_MAGIC2, 
             cols, rows, maxval );
-#ifdef VMS
-    if (!plainFormat)
-        set_outfile_binary();
-#endif
 }
 
 
@@ -125,10 +121,12 @@ writepgmrowraw(FILE *       const fileP,
     if (rc < 0)
         pm_error("Error writing row.  fwrite() errno=%d (%s)",
                  errno, strerror(errno));
-    else if (rc != bytesPerRow)
-        pm_error("Error writing row.  Short write of %u bytes "
-                 "instead of %u", rc, bytesPerRow);
-
+    else {
+        size_t const bytesWritten = rc;
+        if (bytesWritten != bytesPerRow)
+            pm_error("Error writing row.  Short write of %u bytes "
+                     "instead of %u", (unsigned)bytesWritten, bytesPerRow);
+    }
     free(rowBuffer);
 }
 
diff --git a/lib/libpm.c b/lib/libpm.c
index 910d5666..4374bbe3 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -8,8 +8,12 @@
   Netpbm library subroutines.
 **************************************************************************/
 
+#define _BSD_SOURCE          /* Make sure strdup is in string.h */
 #define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */
 
+#include "netpbm/pm_config.h"
+
+#include <assert.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdarg.h>
@@ -18,13 +22,17 @@
 #include <setjmp.h>
 #include <time.h>
 #include <limits.h>
+#if HAVE_FORK
+#include <sys/wait.h>
+#endif
+#include <sys/types.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "version.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/version.h"
+#include "netpbm/nstring.h"
+#include "netpbm/shhopt.h"
 #include "compile.h"
-#include "nstring.h"
-#include "shhopt.h"
 
 #include "pm.h"
 
@@ -95,6 +103,86 @@ pm_longjmp(void) {
 
 
 void
+pm_fork(int *         const iAmParentP,
+        pid_t *       const childPidP,
+        const char ** const errorP) {
+/*----------------------------------------------------------------------------
+   Same as POSIX fork, except with a nicer interface and works
+   (fails cleanly) on systems that don't have POSIX fork().
+-----------------------------------------------------------------------------*/
+#if HAVE_FORK
+    int rc;
+
+    rc = fork();
+
+    if (rc < 0) {
+        pm_asprintf(errorP, "Failed to fork a process.  errno=%d (%s)",
+                    errno, strerror(errno));
+    } else {
+        *errorP = NULL;
+
+        if (rc == 0) {
+            *iAmParentP = FALSE;
+        } else {
+            *iAmParentP = TRUE;
+            *childPidP = rc;
+        }
+    }
+#else
+    pm_asprintf(errorP, "Cannot fork a process, because this system does "
+                "not have POSIX fork()");
+#endif
+}
+
+
+
+void
+pm_waitpid(pid_t         const pid,
+           int *         const statusP,
+           int           const options,
+           pid_t *       const exitedPidP,
+           const char ** const errorP) {
+
+#if HAVE_FORK
+    pid_t rc;
+    rc = waitpid(pid, statusP, options);
+    if (rc == (pid_t)-1) {
+        pm_asprintf(errorP, "Failed to wait for process exit.  "
+                    "waitpid() errno = %d (%s)",
+                    errno, strerror(errno));
+    } else {
+        *exitedPidP = rc;
+        *errorP = NULL;
+    }
+#else
+    pm_error("INTERNAL ERROR: Attempt to wait for a process we created on "
+             "a system on which we can't create processes");
+#endif
+}
+
+
+
+void
+pm_waitpidSimple(pid_t const pid) {
+
+    int status;
+    pid_t exitedPid;
+    const char * error;
+
+    pm_waitpid(pid, &status, 0, &exitedPid, &error);
+
+    if (error) {
+        pm_errormsg("%s", error);
+        pm_strfree(error);
+        pm_longjmp();
+    } else {
+        assert(exitedPid != 0);
+    }
+}
+
+
+
+void
 pm_setusererrormsgfn(pm_usererrormsgfn * fn) {
 
     userErrorMsgFn = fn;
@@ -126,14 +214,14 @@ pm_message(const char format[], ...) {
 
     if (pm_showmessages) {
         const char * msg;
-        vasprintfN(&msg, format, args);
+        pm_vasprintf(&msg, format, args);
 
         if (userMessageFn)
             userMessageFn(msg);
         else
             fprintf(stderr, "%s: %s\n", pm_progname, msg);
 
-        strfree(msg);
+        pm_strfree(msg);
     }
     va_end(args);
 }
@@ -159,11 +247,11 @@ pm_errormsg(const char format[], ...) {
 
     va_start(args, format);
 
-    vasprintfN(&msg, format, args);
+    pm_vasprintf(&msg, format, args);
     
     errormsg(msg);
 
-    strfree(msg);
+    pm_strfree(msg);
 
     va_end(args);
 }
@@ -177,11 +265,11 @@ pm_error(const char format[], ...) {
 
     va_start(args, format);
 
-    vasprintfN(&msg, format, args);
+    pm_vasprintf(&msg, format, args);
     
     errormsg(msg);
 
-    strfree(msg);
+    pm_strfree(msg);
 
     va_end(args);
 
@@ -190,6 +278,20 @@ pm_error(const char format[], ...) {
 
 
 
+int
+pm_have_float_format(void) {
+/*----------------------------------------------------------------------------
+  Return 1 if %f, %e, and %g work in format strings for pm_message, etc.;
+  0 otherwise.
+
+  Where they don't "work," that means the specifier just appears itself in
+  the formatted strings, e.g. "the number is g".
+-----------------------------------------------------------------------------*/
+    return pm_vasprintf_knows_float();
+}
+
+
+
 static void *
 mallocz(size_t const size) {
 
@@ -224,122 +326,30 @@ pm_freerow(void * const itrow) {
 
 
 
-static void
-allocarrayNoHeap(unsigned char ** const rowIndex,
-                 unsigned int     const cols,
-                 unsigned int     const rows,
-                 unsigned int     const size,
-                 const char **    const errorP) {
-
-    if (cols != 0 && UINT_MAX / cols < size)
-        asprintfN(errorP,
-                  "Arithmetic overflow multiplying %u by %u to get the "
-                  "size of a row to allocate.", cols, size);
-    else {
-        unsigned int rowsDone;
-
-        rowsDone = 0;
-        *errorP = NULL;
-
-        while (rowsDone < rows && !*errorP) {
-            unsigned char * const rowSpace = mallocz(cols * size);
-            if (rowSpace == NULL)
-                asprintfN(errorP,
-                          "Unable to allocate a %u-column by %u byte row",
-                          cols, size);
-            else
-                rowIndex[rowsDone++] = rowSpace;
-        }
-        if (*errorP) {
-            unsigned int row;
-            for (row = 0; row < rowsDone; ++row)
-                free(rowIndex[row]);
-        }
-    }
-}
-
-
-
-static unsigned char *
-allocRowHeap(unsigned int const cols,
-             unsigned int const rows,
-             unsigned int const size) {
-
-    unsigned char * retval;
-
-    if (cols != 0 && rows != 0 && UINT_MAX / cols / rows < size)
-        /* Too big even to request the memory ! */
-        retval = NULL;
-    else
-        retval = mallocz(rows * cols * size);
-
-    return retval;
-}
-
-
-
 char **
 pm_allocarray(int const cols,
               int const rows,
-              int const size )  {
+              int const elementSize ) {
 /*----------------------------------------------------------------------------
-   Allocate an array of 'rows' rows of 'cols' columns each, with each
-   element 'size' bytes.
-
-   We use a special format where we tack on an extra element to the row
-   index to indicate the format of the array.
-
-   We have two ways of allocating the space: fragmented and
-   unfragmented.  In both, the row index (plus the extra element) is
-   in one block of memory.  In the fragmented format, each row is
-   also in an independent memory block, and the extra row pointer is
-   NULL.  In the unfragmented format, all the rows are in a single
-   block of memory called the row heap and the extra row pointer is
-   the address of that block.
-
-   We use unfragmented format if possible, but if the allocation of the
-   row heap fails, we fall back to fragmented.
------------------------------------------------------------------------------*/
-    unsigned char ** rowIndex;
-    const char * error;
+   This is for backward compatibility.  MALLOCARRAY2 is usually better.
 
-    MALLOCARRAY(rowIndex, rows + 1);
-    if (rowIndex == NULL)
-        asprintfN(&error,
-                  "out of memory allocating row index (%u rows) for an array",
-                  rows);
-    else {
-        unsigned char * rowheap;
+   A problem with pm_allocarray() is that its return type is char **
+   even though 'elementSize' can be other than 1.  So users have
+   traditionally type cast the result.  In the old days, that was just
+   messy; modern compilers can produce the wrong code if you do that.
+-----------------------------------------------------------------------------*/
+    char ** retval;
+    void * result;
 
-        rowheap = allocRowHeap(cols, rows, size);
+    pm_mallocarray2(&result, rows, cols, elementSize);
 
-        if (rowheap) {
-            /* It's unfragmented format */
+    if (result == NULL)
+        pm_error("Failed to allocate a raster array of %u columns x %u rows",
+                 cols, rows);
 
-            rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+    retval = result;
 
-            if (rowheap) {
-                unsigned int row;
-                
-                for (row = 0; row < rows; ++row)
-                    rowIndex[row] = &(rowheap[row * cols * size]);
-            }
-            error = NULL;
-        } else {
-            /* We couldn't get the whole heap in one block, so try fragmented
-               format.
-            */
-            rowIndex[rows] = NULL;   /* Declare it fragmented format */
-            
-            allocarrayNoHeap(rowIndex, cols, rows, size, &error);
-        }
-    }
-    if (error) {
-        pm_errormsg("Couldn't allocate %u-row array.  %s", rows, error);
-        strfree(error);
-        pm_longjmp();
-    }
-    return (char **)rowIndex;
+    return retval;
 }
 
 
@@ -348,16 +358,9 @@ void
 pm_freearray(char ** const rowIndex, 
              int     const rows) {
 
-    void * const rowheap = rowIndex[rows];
+    void * const rowIndexVoid = rowIndex;
 
-    if (rowheap != NULL)
-        free(rowheap);
-    else {
-        unsigned int row;
-        for (row = 0; row < rows; ++row)
-            pm_freerow(rowIndex[row]);
-    }
-    free(rowIndex);
+    pm_freearray2(rowIndexVoid);
 }
 
 
@@ -475,37 +478,6 @@ pm_lcm(unsigned int const x,
 }
 
 
-/* Initialization. */
-
-
-#ifdef VMS
-static const char *
-vmsProgname(int * const argcP, char * argv[]) {   
-    char **temp_argv = argv;
-    int old_argc = *argcP;
-    int i;
-    const char * retval;
-    
-    getredirection( argcP, &temp_argv );
-    if (*argcP > old_argc) {
-        /* Number of command line arguments has increased */
-        fprintf( stderr, "Sorry!! getredirection() for VMS has "
-                 "changed the argument list!!!\n");
-        fprintf( stderr, "This is intolerable at the present time, "
-                 "so we must stop!!!\n");
-        exit(1);
-    }
-    for (i=0; i<*argcP; i++)
-        argv[i] = temp_argv[i];
-    retval = strrchr( argv[0], '/');
-    if ( retval == NULL ) retval = rindex( argv[0], ']');
-    if ( retval == NULL ) retval = rindex( argv[0], '>');
-
-    return retval;
-}
-#endif
-
-
 
 void
 pm_init(const char * const progname,
@@ -555,11 +527,7 @@ showVersion(void) {
     pm_message( "BSD defined" );
 #endif /*BSD*/
 #ifdef SYSV
-#ifdef VMS
-    pm_message( "VMS & SYSV defined" );
-#else
     pm_message( "SYSV defined" );
-#endif
 #endif /*SYSV*/
 #ifdef MSDOS
     pm_message( "MSDOS defined" );
@@ -626,6 +594,8 @@ showNetpbmHelp(const char progname[]) {
         if (docurl == NULL)
             pm_message("No 'docurl=' line in Netpbm configuration file '%s'.",
                        netpbmConfigFileName);
+
+        fclose(netpbmConfigFile);
     }
     if (docurl == NULL)
         pm_message("We have no reliable indication of where the Netpbm "
@@ -639,71 +609,85 @@ showNetpbmHelp(const char progname[]) {
 
 
 
+static void
+parseCommonOptions(int *         const argcP,
+                   const char ** const argv,
+                   bool *        const showMessagesP,
+                   bool *        const showVersionP,
+                   bool *        const showHelpP,
+                   bool *        const plainOutputP) {
+
+    unsigned int inCursor;
+    unsigned int outCursor;
+
+    *showMessagesP = true;   /* initial assumption */
+    *showVersionP  = false;  /* initial assumption */
+    *showHelpP     = false;  /* initial assumption */
+    *plainOutputP  = false;  /* initial assumption */
+
+    for (inCursor = 1, outCursor = 1; inCursor < *argcP; ++inCursor) {
+            if (strcaseeq(argv[inCursor], "-quiet") ||
+                strcaseeq(argv[inCursor], "--quiet")) 
+                *showMessagesP = false;
+            else if (strcaseeq(argv[inCursor], "-version") ||
+                     strcaseeq(argv[inCursor], "--version")) 
+                *showVersionP = true;
+            else if (strcaseeq(argv[inCursor], "-help") ||
+                     strcaseeq(argv[inCursor], "--help") ||
+                     strcaseeq(argv[inCursor], "-?")) 
+                *showHelpP = true;
+            else if (strcaseeq(argv[inCursor], "-plain") ||
+                     strcaseeq(argv[inCursor], "--plain"))
+                *plainOutputP = true;
+            else
+                argv[outCursor++] = argv[inCursor];
+        }
+    *argcP = outCursor;
+}
+
+
+
 void
-pm_proginit(int * const argcP, const char * argv[]) {
+pm_proginit(int *         const argcP,
+            const char ** const argv) {
 /*----------------------------------------------------------------------------
    Do various initialization things that all programs in the Netpbm package,
    and programs that emulate such programs, should do.
 
-   This includes processing global options.
+   This includes processing global options.  We scan argv[], which has *argcP
+   elements, for common options and execute the functions for the ones we
+   find.  We remove the common options from argv[], updating *argcP
+   accordingly.
 
    This includes calling pm_init() to initialize the Netpbm libraries.
 -----------------------------------------------------------------------------*/
-    int argn, i;
-    const char * progname;
-    bool showmessages;
-    bool show_version;
+    const char * const progname = pm_arg0toprogname(argv[0]);
+        /* points to static memory in this library */
+    bool showMessages;
+    bool plain;
+    bool justShowVersion;
         /* We're supposed to just show the version information, then exit the
            program.
         */
-    bool show_help;
+    bool justShowHelp;
         /* We're supposed to just tell user where to get help, then exit the
            program.
         */
-    
-    /* Extract program name. */
-#ifdef VMS
-    progname = vmsProgname(argcP, argv);
-#else
-    progname = strrchr( argv[0], '/');
-#endif
-    if (progname == NULL)
-        progname = argv[0];
-    else
-        ++progname;
 
     pm_init(progname, 0);
 
-    /* Check for any global args. */
-    showmessages = TRUE;
-    show_version = FALSE;
-    show_help = FALSE;
-    pm_plain_output = FALSE;
-    for (argn = i = 1; argn < *argcP; ++argn) {
-        if (pm_keymatch(argv[argn], "-quiet", 6) ||
-            pm_keymatch(argv[argn], "--quiet", 7)) 
-            showmessages = FALSE;
-        else if (pm_keymatch(argv[argn], "-version", 8) ||
-                   pm_keymatch(argv[argn], "--version", 9)) 
-            show_version = TRUE;
-        else if (pm_keymatch(argv[argn], "-help", 5) ||
-                 pm_keymatch(argv[argn], "--help", 6) ||
-                 pm_keymatch(argv[argn], "-?", 2)) 
-            show_help = TRUE;
-        else if (pm_keymatch(argv[argn], "-plain", 6) ||
-                 pm_keymatch(argv[argn], "--plain", 7))
-            pm_plain_output = TRUE;
-        else
-            argv[i++] = argv[argn];
-    }
-    *argcP=i;
+    parseCommonOptions(argcP, argv,
+                       &showMessages, &justShowVersion, &justShowHelp,
+                       &plain);
 
-    pm_setMessage((unsigned int) showmessages, NULL);
+    pm_plain_output = plain ? 1 : 0;
 
-    if (show_version) {
+    pm_setMessage(showMessages ? 1 : 0, NULL);
+
+    if (justShowVersion) {
         showVersion();
-        exit( 0 );
-    } else if (show_help) {
+        exit(0);
+    } else if (justShowHelp) {
         pm_error("Use 'man %s' for help.", progname);
         /* If we can figure out a way to distinguish Netpbm programs from 
            other programs using the Netpbm libraries, we can do better here.
@@ -715,8 +699,10 @@ pm_proginit(int * const argcP, const char * argv[]) {
 }
 
 
+
 void
-pm_setMessage(int const newState, int * const oldStateP) {
+pm_setMessage(int   const newState,
+              int * const oldStateP) {
     
     if (oldStateP)
         *oldStateP = pm_showmessages;
@@ -726,6 +712,39 @@ pm_setMessage(int const newState, int * const oldStateP) {
 
 
 
+int
+pm_getMessage(void) {
+
+    return pm_showmessages;
+}
+
+
+
+static void
+extractAfterLastSlash(const char * const fullPath,
+                      char *       const retval,
+                      size_t       const retvalSize) {
+    
+    char * slashPos;
+
+    /* Chop any directories off the left end */
+    slashPos = strrchr(fullPath, '/');
+
+    if (slashPos == NULL) {
+        strncpy(retval, fullPath, retvalSize);
+        retval[retvalSize-1] = '\0';
+    } else {
+        strncpy(retval, slashPos +1, retvalSize);
+        retval[retvalSize-1] = '\0';
+    }
+
+    /* Chop any .exe off the right end */
+    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
+        retval[strlen(retval)-4] = 0;
+}
+
+
+
 char *
 pm_arg0toprogname(const char arg0[]) {
 /*----------------------------------------------------------------------------
@@ -742,25 +761,23 @@ pm_arg0toprogname(const char arg0[]) {
    The return value is in static storage within.  It is NUL-terminated,
    but truncated at 64 characters.
 -----------------------------------------------------------------------------*/
-    static char retval[64+1];
-    char *slash_pos;
-
-    /* Chop any directories off the left end */
-    slash_pos = strrchr(arg0, '/');
-
-    if (slash_pos == NULL) {
-        strncpy(retval, arg0, sizeof(retval));
-        retval[sizeof(retval)-1] = '\0';
-    } else {
-        strncpy(retval, slash_pos +1, sizeof(retval));
-        retval[sizeof(retval)-1] = '\0';
-    }
-
-    /* Chop any .exe off the right end */
-    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
-        retval[strlen(retval)-4] = 0;
+#define MAX_RETVAL_SIZE 64
+#if MSVCRT
+    /* Note that there exists _splitpath_s, which takes a size argument,
+       but it is only in "secure" extensions of MSVCRT that come only with
+       MSVC; but _splitpath() comes with Windows.  MinGW has a header file
+       for it.
+    */
+    static char retval[_MAX_FNAME];
+    _splitpath(arg0, 0, 0,  retval, 0);
+    if (MAX_RETVAL_SIZE < _MAX_FNAME)
+        retval[MAX_RETVAL_SIZE] = '\0';
+#else
+    static char retval[MAX_RETVAL_SIZE+1];
+    extractAfterLastSlash(arg0, retval, sizeof(retval));
+#endif
 
-    return(retval);
+    return retval;
 }
 
 
@@ -784,11 +801,11 @@ pm_parse_width(const char * const arg) {
     unsigned int width;
     const char * error;
 
-    interpret_uint(arg, &width, &error);
+    pm_interpret_uint(arg, &width, &error);
 
     if (error) {
         pm_error("'%s' is invalid as an image width.  %s", arg, error);
-        strfree(error);
+        pm_strfree(error);
     } else {
         if (width > INT_MAX-10)
             pm_error("Width %u is too large for computations.", width);
@@ -809,11 +826,11 @@ pm_parse_height(const char * const arg) {
     unsigned int height;
     const char * error;
 
-    interpret_uint(arg, &height, &error);
+    pm_interpret_uint(arg, &height, &error);
 
     if (error) {
         pm_error("'%s' is invalid as an image height.  %s", arg, error);
-        strfree(error);
+        pm_strfree(error);
     } else {
         if (height > INT_MAX-10)
             pm_error("Height %u is too large for computations.", height);
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
index 536e5dc4..db21b078 100644
--- a/lib/libpnm1.c
+++ b/lib/libpnm1.c
@@ -13,6 +13,8 @@
 #include <string.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+
 #include "pnm.h"
 
 #include "ppm.h"
@@ -27,8 +29,6 @@
 #include "pam.h"
 #include "libpam.h"
 
-#include "mallocvar.h"
-
 
 
 xel *
@@ -47,15 +47,12 @@ pnm_allocrow(unsigned int const cols) {
 
 
 void
-pnm_init( argcP, argv )
-    int* argcP;
-    char* argv[];
-    {
+pnm_init(int * const argcP, char ** const argv) {
     ppm_init( argcP, argv );
-    }
+}
 
 void
-pnm_nextimage(FILE *file, int * const eofP) {
+pnm_nextimage(FILE * const file, int * const eofP) {
     pm_nextimage(file, eofP);
 }
 
@@ -72,10 +69,13 @@ validateComputableSize(unsigned int const cols,
    you expect.  That failed expectation can be disastrous if you use
    it to allocate memory.
 
+   It is very normal to allocate space for a pixel row, so we make sure
+   the size of a pixel row, in bytes, can be represented by an 'int'.
+
    A common operation is adding 1 or 2 to the highest row or
    column number in the image, so we make sure that's possible.
 -----------------------------------------------------------------------------*/
-    if (cols > INT_MAX - 2)
+    if (cols > INT_MAX/(sizeof(pixval) * 3) || cols > INT_MAX - 2)
         pm_error("image width (%u) too large to be processed", cols);
     if (rows > INT_MAX - 2)
         pm_error("image height (%u) too large to be processed", rows);
@@ -126,7 +126,8 @@ pnm_readpnminit(FILE *   const fileP,
     break;
 
     default:
-        pm_error("bad magic number - not a ppm, pgm, or pbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
@@ -178,10 +179,10 @@ readpbmrow(FILE * const fileP,
     jmp_buf * origJmpbufP;
     bit * bitrow;
 
-    bitrow = pbm_allocrow(cols);
+    bitrow = pbm_allocrow_packed(cols);
 
     if (setjmp(jmpbuf) != 0) {
-        pbm_freerow(bitrow);
+        pbm_freerow_packed(bitrow);
         pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
     } else {
@@ -189,11 +190,14 @@ readpbmrow(FILE * const fileP,
 
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-        pbm_readpbmrow(fileP, bitrow, cols, format);
-
-        for (col = 0; col < cols; ++col)
-            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0 : maxval);
+        pbm_readpbmrow_packed(fileP, bitrow, cols, format);
 
+        for (col = 0; col < cols; ++col) {
+            pixval const g =
+                ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ?
+                maxval : 0;
+            PNM_ASSIGN1(xelrow[col], g);
+        }
         pm_setjmpbuf(origJmpbufP);
     }
     pbm_freerow(bitrow);
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
index 01ffa389..fa4bb8be 100644
--- a/lib/libpnm2.c
+++ b/lib/libpnm2.c
@@ -10,7 +10,7 @@
 ** implied warranty.
 */
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
 
 #include "pnm.h"
 
@@ -20,6 +20,8 @@
 
 #include "pbm.h"
 
+
+
 void
 pnm_writepnminit(FILE * const fileP, 
                  int    const cols, 
diff --git a/lib/libpnm3.c b/lib/libpnm3.c
index c41a2108..0426ebcb 100644
--- a/lib/libpnm3.c
+++ b/lib/libpnm3.c
@@ -45,7 +45,7 @@ mean4(int const format,
         break;
 
     default:
-        pm_error( "Invalid format passed to pnm_backgroundxel()");
+        pm_error("Invalid format passed to pnm_backgroundxel()");
     }
     return retval;
 }
diff --git a/lib/libppm1.c b/lib/libppm1.c
index 97110730..c1eb152c 100644
--- a/lib/libppm1.c
+++ b/lib/libppm1.c
@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 #include "libppm.h"
 #include "pgm.h"
@@ -29,8 +31,6 @@
 #include "pam.h"
 #include "libpam.h"
 #include "fileio.h"
-#include "mallocvar.h"
-#include "nstring.h"
 
 
 pixel *
@@ -49,12 +49,11 @@ ppm_allocrow(unsigned int const cols) {
 
 
 void
-ppm_init( argcP, argv )
-    int* argcP;
-    char* argv[];
-    {
+ppm_init(int * const argcP, char ** const argv) {
     pgm_init( argcP, argv );
-    }
+}
+
+
 
 void
 ppm_nextimage(FILE * const fileP, 
@@ -79,7 +78,7 @@ ppm_readppminitrest(FILE *   const fileP,
     maxval = pm_getuint(fileP);
     if (maxval > PPM_OVERALLMAXVAL)
         pm_error("maxval of input image (%u) is too large.  "
-                 "The maximum allowed by the PPM is %u.",
+                 "The maximum allowed by the PPM format is %u.",
                  maxval, PPM_OVERALLMAXVAL); 
     if (maxval == 0)
         pm_error("maxval of input image is zero.");
@@ -100,10 +99,13 @@ validateComputableSize(unsigned int const cols,
    you expect.  That failed expectation can be disastrous if you use
    it to allocate memory.
 
+   It is very normal to allocate space for a pixel row, so we make sure
+   the size of a pixel row, in bytes, can be represented by an 'int'.
+
    A common operation is adding 1 or 2 to the highest row or
    column number in the image, so we make sure that's possible.
 -----------------------------------------------------------------------------*/
-    if (cols > INT_MAX - 2)
+    if (cols > INT_MAX/(sizeof(pixval) * 3) || cols > INT_MAX - 2)
         pm_error("image width (%u) too large to be processed", cols);
     if (rows > INT_MAX - 2)
         pm_error("image height (%u) too large to be processed", rows);
@@ -184,6 +186,92 @@ readPpmRow(FILE *       const fileP,
 
 
 static void
+interpRasterRowRaw(const unsigned char * const rowBuffer,
+                   pixel *               const pixelrow,
+                   unsigned int          const cols,
+                   unsigned int          const bytesPerSample) {
+
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* start at beginning of rowBuffer[] */
+        
+    if (bytesPerSample == 1) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            pixval const r = rowBuffer[bufferCursor++];
+            pixval const g = rowBuffer[bufferCursor++];
+            pixval const b = rowBuffer[bufferCursor++];
+            PPM_ASSIGN(pixelrow[col], r, g, b);
+        }
+    } else  {
+        /* two byte samples */
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            pixval r, g, b;
+                    
+            r = rowBuffer[bufferCursor++] << 8;
+            r |= rowBuffer[bufferCursor++];
+                    
+            g = rowBuffer[bufferCursor++] << 8;
+            g |= rowBuffer[bufferCursor++];
+                    
+            b = rowBuffer[bufferCursor++] << 8;
+            b |= rowBuffer[bufferCursor++];
+                    
+            PPM_ASSIGN(pixelrow[col], r, g, b);
+        }
+    }
+}
+
+
+
+static void
+validateRppmRow(pixel *       const pixelrow,
+                unsigned int  const cols,
+                pixval        const maxval,
+                const char ** const errorP) {
+/*----------------------------------------------------------------------------
+  Check for sample values above maxval in input.  
+
+  Note: a program that wants to deal with invalid sample values itself can
+  simply make sure it uses a sufficiently high maxval on the read function
+  call, so this validation never fails.
+-----------------------------------------------------------------------------*/
+    if (maxval == 255 || maxval == 65535) {
+        /* There's no way a sample can be invalid, so we don't need to look at
+           the samples individually.
+        */
+        *errorP = NULL;
+    } else {
+        unsigned int col;
+
+        for (col = 0, *errorP = NULL; col < cols && !*errorP; ++col) {
+            pixval const r = PPM_GETR(pixelrow[col]);
+            pixval const g = PPM_GETG(pixelrow[col]);
+            pixval const b = PPM_GETB(pixelrow[col]);
+
+            if (r > maxval)
+                pm_asprintf(
+                    errorP,
+                    "Red sample value %u is greater than maxval (%u)",
+                    r, maxval);
+            else if (g > maxval)
+                pm_asprintf(
+                    errorP,
+                    "Green sample value %u is greater than maxval (%u)",
+                    g, maxval);
+            else if (b > maxval)
+                pm_asprintf(
+                    errorP,
+                    "Blue sample value %u is greater than maxval (%u)",
+                    b, maxval);
+        }
+    }
+}
+
+
+
+static void
 readRppmRow(FILE *       const fileP, 
             pixel *      const pixelrow, 
             unsigned int const cols, 
@@ -199,60 +287,35 @@ readRppmRow(FILE *       const fileP,
     MALLOCARRAY(rowBuffer, bytesPerRow);
         
     if (rowBuffer == NULL)
-        asprintfN(&error, "Unable to allocate memory for row buffer "
-                  "for %u columns", cols);
+        pm_asprintf(&error, "Unable to allocate memory for row buffer "
+                    "for %u columns", cols);
     else {
         ssize_t rc;
 
         rc = fread(rowBuffer, 1, bytesPerRow, fileP);
     
         if (feof(fileP))
-            asprintfN(&error, "Unexpected EOF reading row of PPM image.");
+            pm_asprintf(&error, "Unexpected EOF reading row of PPM image.");
         else if (ferror(fileP))
-            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
-                      errno, strerror(errno));
-        else if (rc != bytesPerRow)
-            asprintfN(&error, "Error reading row.  Short read of %u bytes "
-                      "instead of %u", rc, bytesPerRow);
+            pm_asprintf(&error, "Error reading row.  fread() errno=%d (%s)",
+                        errno, strerror(errno));
         else {
-            unsigned int bufferCursor;
-
-            error = NULL;
-
-            bufferCursor = 0;  /* start at beginning of rowBuffer[] */
-        
-            if (bytesPerSample == 1) {
-                unsigned int col;
-                for (col = 0; col < cols; ++col) {
-                    pixval const r = rowBuffer[bufferCursor++];
-                    pixval const g = rowBuffer[bufferCursor++];
-                    pixval const b = rowBuffer[bufferCursor++];
-                    PPM_ASSIGN(pixelrow[col], r, g, b);
-                }
-            } else  {
-                /* two byte samples */
-                unsigned int col;
-                for (col = 0; col < cols; ++col) {
-                    pixval r, g, b;
-                    
-                    r = rowBuffer[bufferCursor++] << 8;
-                    r |= rowBuffer[bufferCursor++];
-                    
-                    g = rowBuffer[bufferCursor++] << 8;
-                    g |= rowBuffer[bufferCursor++];
-                    
-                    b = rowBuffer[bufferCursor++] << 8;
-                    b |= rowBuffer[bufferCursor++];
-                    
-                    PPM_ASSIGN(pixelrow[col], r, g, b);
-                }
+            size_t const bytesRead = rc;
+            if (bytesRead != bytesPerRow)
+                pm_asprintf(&error,
+                            "Error reading row.  Short read of %u bytes "
+                            "instead of %u", (unsigned)bytesRead, bytesPerRow);
+            else {
+                interpRasterRowRaw(rowBuffer, pixelrow, cols, bytesPerSample);
+
+                validateRppmRow(pixelrow, cols, maxval, &error);
             }
         }
         free(rowBuffer);
     }
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 }
@@ -305,10 +368,10 @@ readPbmRow(FILE *       const fileP,
     jmp_buf * origJmpbufP;
     bit * bitrow;
 
-    bitrow = pbm_allocrow(cols);
+    bitrow = pbm_allocrow_packed(cols);
 
     if (setjmp(jmpbuf) != 0) {
-        pbm_freerow(bitrow);
+        pbm_freerow_packed(bitrow);
         pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
     } else {
@@ -316,10 +379,12 @@ readPbmRow(FILE *       const fileP,
 
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-        pbm_readpbmrow(fileP, bitrow, cols, format);
+        pbm_readpbmrow_packed(fileP, bitrow, cols, format);
 
         for (col = 0; col < cols; ++col) {
-            pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0;
+            pixval const g =
+                ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ?
+                maxval : 0;
             PPM_ASSIGN(pixelrow[col], g, g, g);
         }
         pm_setjmpbuf(origJmpbufP);
@@ -408,30 +473,32 @@ ppm_readppm(FILE *   const fileP,
 
 void
 ppm_check(FILE *               const fileP, 
-          enum pm_check_type   const check_type, 
+          enum pm_check_type   const checkType, 
           int                  const format, 
           int                  const cols, 
           int                  const rows, 
           pixval               const maxval,
-          enum pm_check_code * const retval_p) {
+          enum pm_check_code * const retvalP) {
 
     if (rows < 0)
         pm_error("Invalid number of rows passed to ppm_check(): %d", rows);
     if (cols < 0)
         pm_error("Invalid number of columns passed to ppm_check(): %d", cols);
     
-    if (check_type != PM_CHECK_BASIC) {
-        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP)
+            *retvalP = PM_CHECK_UNKNOWN_TYPE;
     } else if (PPM_FORMAT_TYPE(format) == PBM_TYPE) {
-        pbm_check(fileP, check_type, format, cols, rows, retval_p);
+        pbm_check(fileP, checkType, format, cols, rows, retvalP);
     } else if (PPM_FORMAT_TYPE(format) == PGM_TYPE) {
-        pgm_check(fileP, check_type, format, cols, rows, maxval, retval_p);
+        pgm_check(fileP, checkType, format, cols, rows, maxval, retvalP);
     } else if (format != RPPM_FORMAT) {
-        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        if (retvalP)
+            *retvalP = PM_CHECK_UNCHECKABLE;
     } else {        
-        pm_filepos const bytes_per_row = cols * 3 * (maxval > 255 ? 2 : 1);
-        pm_filepos const need_raster_size = rows * bytes_per_row;
+        pm_filepos const bytesPerRow    = cols * 3 * (maxval > 255 ? 2 : 1);
+        pm_filepos const needRasterSize = rows * bytesPerRow;
         
-        pm_check(fileP, check_type, need_raster_size, retval_p);
+        pm_check(fileP, checkType, needRasterSize, retvalP);
     }
 }
diff --git a/lib/libppm2.c b/lib/libppm2.c
index 3bf74bd4..694ecdd7 100644
--- a/lib/libppm2.c
+++ b/lib/libppm2.c
@@ -14,8 +14,8 @@
 #include <errno.h>
 
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 
 void
@@ -36,10 +36,6 @@ ppm_writeppminit(FILE*  const fileP,
             PPM_MAGIC1, 
             plainFormat || maxval >= 1<<16 ? PPM_MAGIC2 : RPPM_MAGIC2, 
             cols, rows, maxval );
-#ifdef VMS
-    if (!plainFormat)
-        set_outfile_binary();
-#endif
 }
 
 
@@ -133,10 +129,13 @@ ppm_writeppmrowraw(FILE *        const fileP,
     if (rc < 0)
         pm_error("Error writing row.  fwrite() errno=%d (%s)",
                  errno, strerror(errno));
-    else if (rc != bytesPerRow)
-        pm_error("Error writing row.  Short write of %u bytes "
-                 "instead of %u", rc, bytesPerRow);
+    else {
+        size_t const bytesWritten = rc;
 
+        if (bytesWritten != bytesPerRow)
+            pm_error("Error writing row.  Short write of %u bytes "
+                     "instead of %u", (unsigned)bytesWritten, bytesPerRow);
+    }
     free(rowBuffer);
 }
 
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
index 055bfc8b..f78d0516 100644
--- a/lib/libppmcmap.c
+++ b/lib/libppmcmap.c
@@ -12,18 +12,26 @@
 ** implied warranty.
 */
 
-#include "pm_c_util.h"
-#include "nstring.h"
-#include "mallocvar.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/nstring.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmcmap.h"
 
 #define HASH_SIZE 20023
 
-#define ppm_hashpixel(p) ( ( ( (long) PPM_GETR(p) * 33023 + \
-                               (long) PPM_GETG(p) * 30013 + \
-                               (long) PPM_GETB(p) * 27011 ) \
-                             & 0x7fffffff ) % HASH_SIZE )
+
+
+static __inline__ unsigned int
+ppm_hashpixel(pixel const p) {
+
+    return (unsigned int) (PPM_GETR(p) * 33 * 33
+                           + PPM_GETG(p) * 33
+                           + PPM_GETB(p)) % HASH_SIZE;
+}
+
+
 
 colorhist_vector
 ppm_computecolorhist( pixel ** const pixels, 
@@ -153,7 +161,7 @@ readppmrow(FILE *        const fileP,
     
     if (setjmp(jmpbuf) != 0) {
         pm_setjmpbuf(origJmpbufP);
-        asprintfN(errorP, "Failed to read row of image.");
+        pm_asprintf(errorP, "Failed to read row of image.");
     } else {
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
@@ -227,8 +235,8 @@ buildHashTable(FILE *          const ifP,
                 else {
                     MALLOCVAR(chl);
                     if (chl == NULL)
-                        asprintfN(errorP,
-                                  "out of memory computing hash table");
+                        pm_asprintf(errorP,
+                                    "out of memory computing hash table");
                     chl->ch.color = apixel;
                     chl->ch.value = 1;
                     chl->next = cht[hash];
@@ -282,7 +290,7 @@ computecolorhash(pixel **          const pixels,
     MALLOCARRAY(rowbuffer, cols);
         
     if (rowbuffer == NULL)
-        asprintfN(errorP, "Unable to allocate %u-column row buffer.", cols);
+        pm_asprintf(errorP, "Unable to allocate %u-column row buffer.", cols);
     else {
         colorhash_table cht;
         bool tooManyColors;
@@ -290,7 +298,7 @@ computecolorhash(pixel **          const pixels,
         cht = alloccolorhash();
 
         if (cht == NULL)
-            asprintfN(errorP, "Unable to allocate color hash.");
+            pm_asprintf(errorP, "Unable to allocate color hash.");
         else {
             buildHashTable(ifP, pixels, cols, rows, maxval, format, maxcolors,
                            cht, rowbuffer,
@@ -326,7 +334,7 @@ ppm_computecolorhash(pixel ** const pixels,
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
     return cht;
@@ -351,7 +359,7 @@ ppm_computecolorhash2(FILE * const ifP,
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
     return cht;
@@ -484,7 +492,7 @@ ppm_colorhisttocolorhash(colorhist_vector const chv,
 
     cht = alloccolorhash( );  /* Initializes to NULLs */
     if (cht == NULL)
-        asprintfN(&error, "Unable to allocate color hash");
+        pm_asprintf(&error, "Unable to allocate color hash");
     else {
         unsigned int i;
 
@@ -496,13 +504,13 @@ ppm_colorhisttocolorhash(colorhist_vector const chv,
 
             for (chl = cht[hash]; chl && !error; chl = chl->next)
                 if (PPM_EQUAL(chl->ch.color, color))
-                    asprintfN(&error, "same color found twice: (%u %u %u)",
-                              PPM_GETR(color),
-                              PPM_GETG(color),
-                              PPM_GETB(color));
+                    pm_asprintf(&error, "same color found twice: (%u %u %u)",
+                                PPM_GETR(color),
+                                PPM_GETG(color),
+                                PPM_GETB(color));
             MALLOCVAR(chl);
             if (chl == NULL)
-                asprintfN(&error, "out of memory");
+                pm_asprintf(&error, "out of memory");
             else {
                 chl->ch.color = color;
                 chl->ch.value = i;
@@ -515,7 +523,7 @@ ppm_colorhisttocolorhash(colorhist_vector const chv,
     }
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     } else
         retval = cht;
@@ -681,9 +689,33 @@ fail:
 }
 
 
+
+static int (*customCmp)(pixel *, pixel *);
+
+#ifndef LITERAL_FN_DEF_MATCH
+static qsort_comparison_fn customStub;
+#endif
+
 static int
-pixel_cmp(const void * const a, const void * const b) {
-    const pixel *p1 = (const pixel *)a, *p2 = (const pixel *)b;
+customStub(const void * const a,
+           const void * const b) {
+
+    return (*customCmp)((pixel *)a, (pixel *)b);
+}
+
+
+
+#ifndef LITERAL_FN_DEF_MATCH
+static qsort_comparison_fn pixelCmp;
+#endif
+
+static int
+pixelCmp(const void * const a,
+         const void * const b) {
+
+    const pixel * const p1 = (const pixel *)a;
+    const pixel * const p2 = (const pixel *)b;
+
     int diff;
 
     diff = PPM_GETR(*p1) - PPM_GETR(*p2);
@@ -695,23 +727,18 @@ pixel_cmp(const void * const a, const void * const b) {
     return diff;
 }
 
-static int (*custom_cmp)(pixel *, pixel *);
-
-static int
-custom_stub(const void * const a, const void * const b) {
-    return (*custom_cmp)((pixel *)a, (pixel *)b);
-}
 
 
 void
-ppm_sortcolorrow(pixel * const colorrow, const int ncolors, 
+ppm_sortcolorrow(pixel * const colorrow,
+                 int     const ncolors, 
                  int (*cmpfunc)(pixel *, pixel *)) {
 
-    if( cmpfunc ) {
-        custom_cmp = cmpfunc;
-        qsort((void *)colorrow, ncolors, sizeof(pixel), custom_stub);
+    if (cmpfunc) {
+        customCmp = cmpfunc;
+        qsort((void *)colorrow, ncolors, sizeof(pixel), customStub);
     } else
-        qsort((void *)colorrow, ncolors, sizeof(pixel), pixel_cmp);
+        qsort((void *)colorrow, ncolors, sizeof(pixel), pixelCmp);
 }
 
 
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
index 58663c94..aee8fd83 100644
--- a/lib/libppmcolor.c
+++ b/lib/libppmcolor.c
@@ -17,9 +17,9 @@
 #include <string.h>
 #include <math.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 #include "colorname.h"
 
@@ -264,7 +264,7 @@ parseOldX11(char       const colorname[],
     
     computeHexTable(hexit);
 
-    if (!strishex(&colorname[1]))
+    if (!pm_strishex(&colorname[1]))
         pm_error("Non-hexadecimal characters in #-type color specification");
 
     switch (strlen(colorname) - 1 /* (Number of hex digits) */) {
@@ -451,6 +451,43 @@ ppm_colorname(const pixel * const colorP,
 
 #define MAXCOLORNAMES 1000u
 
+static const char **
+allocColorNames() {
+
+    const char ** colornames;
+
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
+
+    if (colornames) {
+        unsigned int i;
+        for (i = 0; i < MAXCOLORNAMES; ++i)
+            colornames[i] = NULL;
+    }
+    return colornames;
+}
+
+
+
+static colorhash_table
+allocColorHash(void) {
+
+    colorhash_table cht;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    if (setjmp(jmpbuf) != 0)
+        cht = NULL;
+    else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        cht = ppm_alloccolorhash();
+    }
+    pm_setjmpbuf(origJmpbufP);
+
+    return cht;
+}
+
+
+
 static void
 processColorfileEntry(struct colorfile_entry const ce,
                       colorhash_table        const cht,
@@ -460,8 +497,8 @@ processColorfileEntry(struct colorfile_entry const ce,
                       const char **          const errorP) {
 
     if (*colornameIndexP >= MAXCOLORNAMES)
-        asprintfN(errorP, "Too many colors in colorname dictionary.  "
-                  "Max allowed is %u", MAXCOLORNAMES);
+        pm_asprintf(errorP, "Too many colors in colorname dictionary.  "
+                    "Max allowed is %u", MAXCOLORNAMES);
     else {
         pixel color;
 
@@ -479,7 +516,7 @@ processColorfileEntry(struct colorfile_entry const ce,
             colornames[*colornameIndexP] = strdup(ce.colorname);
             colors[*colornameIndexP] = color;
             if (colornames[*colornameIndexP] == NULL)
-                asprintfN(errorP, "Unable to allocate space for color name");
+                pm_asprintf(errorP, "Unable to allocate space for color name");
             else {
                 *errorP = NULL;
                 ++(*colornameIndexP);
@@ -500,7 +537,7 @@ openColornameFile(const char *  const fileName,
     jmp_buf * origJmpbufP;
 
     if (setjmp(jmpbuf) != 0) {
-        asprintfN(errorP, "Failed to open color name file");
+        pm_asprintf(errorP, "Failed to open color name file");
         pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
     } else {
@@ -525,6 +562,9 @@ readOpenColorFile(FILE *          const colorFileP,
    Read the color dictionary file *colorFileP and add the colors in it
    to colornames[], colors[], and 'cht'.
 
+   colornames[] and colors[] must be allocated with MAXCOLORNAMES entries
+   at entry.
+
    We may add colors to 'cht' even if we fail.
 -----------------------------------------------------------------------------*/
     unsigned int nColorsDone;
@@ -543,43 +583,18 @@ readOpenColorFile(FILE *          const colorFileP,
             processColorfileEntry(ce, cht, colornames, colors,
                                   &nColorsDone, errorP);
     }
-    if (!*errorP) {
-        *nColorsP = nColorsDone;
-        
-        while (nColorsDone < MAXCOLORNAMES)
-            colornames[nColorsDone++] = NULL;
-    }
+    *nColorsP = nColorsDone;
     
     if (*errorP) {
         unsigned int colorIndex;
 
         for (colorIndex = 0; colorIndex < nColorsDone; ++colorIndex)
-            strfree(colornames[colorIndex]);
+            pm_strfree(colornames[colorIndex]);
     }
 }
 
 
 
-static colorhash_table
-allocColorHash(void) {
-
-    colorhash_table cht;
-    jmp_buf jmpbuf;
-    jmp_buf * origJmpbufP;
-
-    if (setjmp(jmpbuf) != 0)
-        cht = NULL;
-    else {
-        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
-        cht = ppm_alloccolorhash();
-    }
-    pm_setjmpbuf(origJmpbufP);
-
-    return cht;
-}
-
-
-
 static void
 readColorFile(const char *    const fileName,
               bool            const mustOpen,
@@ -588,7 +603,20 @@ readColorFile(const char *    const fileName,
               pixel *         const colors,
               colorhash_table const cht,
               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Read the color dictionary file named 'fileName' and add the colors in it
+   to colornames[], colors[], and 'cht'.  Return as *nColorsP the number
+   of colors in it.
 
+   If the file is not openable (e.g. not file by that name exists), abort the
+   program if 'mustOpen' is true; otherwise, return values indicating a
+   dictionary with no colors.
+
+   colornames[] and colors[] must be allocated with MAXCOLORNAMES entries
+   at entry.
+
+   We may add colors to 'cht' even if we fail.
+-----------------------------------------------------------------------------*/
     FILE * colorFileP;
 
     openColornameFile(fileName, mustOpen, &colorFileP, errorP);
@@ -598,11 +626,6 @@ readColorFile(const char *    const fileName,
                empty file
             */
             *nColorsP = 0;
-            {
-                unsigned int i;
-                for (i = 0; i < MAXCOLORNAMES; ++i)
-                    colornames[i] = NULL;
-            }
             *errorP = NULL;
         } else {
             readOpenColorFile(colorFileP, nColorsP, colornames, colors, cht,
@@ -626,24 +649,24 @@ readcolordict(const char *      const fileName,
 
     const char ** colornames;
 
-    MALLOCARRAY(colornames, MAXCOLORNAMES);
+    colornames = allocColorNames();
 
     if (colornames == NULL)
-        asprintfN(errorP, "Unable to allocate space for colorname table.");
+        pm_asprintf(errorP, "Unable to allocate space for colorname table.");
     else {
         pixel * colors;
 
         MALLOCARRAY(colors, MAXCOLORNAMES);
         
         if (colors == NULL)
-            asprintfN(errorP, "Unable to allocate space for color table.");
+            pm_asprintf(errorP, "Unable to allocate space for color table.");
         else {
             colorhash_table cht;
 
             cht = allocColorHash();
             
             if (cht == NULL)
-                asprintfN(errorP, "Unable to allocate space for color hash");
+                pm_asprintf(errorP, "Unable to allocate space for color hash");
             else {
                 readColorFile(fileName, mustOpen,
                               nColorsP, colornames, colors, cht,
@@ -675,7 +698,28 @@ ppm_readcolordict(const char *      const fileName,
                   const char ***    const colornamesP,
                   pixel **          const colorsP,
                   colorhash_table * const chtP) {
+/*----------------------------------------------------------------------------
+   Read the color dictionary from the file named 'fileName'.  If we can't open
+   the file (e.g. because it does not exist), and 'mustOpen' is false, return
+   an empty dictionary (it contains no colors).  But if 'mustOpen' is true,
+   abort the program instead of returning an empty dictionary.
+
+   Return as *nColorsP the number of colors in the dictionary.
 
+   Return as *colornamesP the names of those colors.  *colornamesP is a
+   malloced array that Caller must free with ppm_freecolornames().
+   The first *nColorsP entries are valid; *chtP contains indices into this
+   array.
+
+   Return as *colorsP the colors.  *colorsP is a malloced array of size
+   MAXCOLORS with the first elements filled in and the rest undefined.
+
+   Return as *chtP a color hash table mapping each color in the dictionary
+   to the index into *colornamesP for the name of the color.
+
+   Each of 'nColorsP, 'colornamesP', and 'colorsP' may be null, in which case
+   we do not return the corresponding information (or allocate memory for it).
+-----------------------------------------------------------------------------*/
     colorhash_table cht;
     const char ** colornames;
     pixel * colors;
@@ -687,7 +731,7 @@ ppm_readcolordict(const char *      const fileName,
 
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         ppm_freecolorhash(cht);
     } else {
         if (chtP)
diff --git a/lib/libppmd.c b/lib/libppmd.c
index 2325dd68..262679ec 100644
--- a/lib/libppmd.c
+++ b/lib/libppmd.c
@@ -18,9 +18,9 @@
 #include <assert.h>
 #include <stdlib.h>
 
-#include "pm_config.h"
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmdfont.h"
 #include "ppmdraw.h"
@@ -966,14 +966,25 @@ typedef struct
     int edge;
 } coord;
 
-typedef struct fillobj {
+typedef struct fillState {
     int n;
+        /* Number of elements in 'coords' */
     int size;
     int curedge;
     int segstart;
     int ydir;
     int startydir;
     coord * coords;
+} fillState;
+
+typedef struct fillobj {
+
+    /* The only reason we have a struct fillState separate from
+       struct fillobj is that the drawproc interface is defined to
+       have drawing not modify the fillobj, i.e. it passed
+       const fillobj * to the drawing program.
+    */
+    struct fillState * stateP;
 } fillobj;
 
 #define SOME 1000
@@ -984,16 +995,24 @@ struct fillobj *
 ppmd_fill_create(void) {
 
     fillobj * fillObjP;
+    struct fillState * stateP;
 
     MALLOCVAR(fillObjP);
     if (fillObjP == NULL)
         pm_error("out of memory allocating a fillhandle");
-    fillObjP->n = 0;
-    fillObjP->size = SOME;
-    MALLOCARRAY(fillObjP->coords, fillObjP->size);
-    if (fillObjP->coords == NULL)
+
+    MALLOCVAR(stateP);
+    if (stateP == NULL)
         pm_error("out of memory allocating a fillhandle");
-    fillObjP->curedge = 0;
+
+    stateP->n = 0;
+    stateP->size = SOME;
+    MALLOCARRAY(stateP->coords, stateP->size);
+    if (stateP->coords == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    stateP->curedge = 0;
+
+    fillObjP->stateP = stateP;
     
     /* Turn off line clipping. */
     /* UGGH! We must eliminate this global variable */
@@ -1021,14 +1040,95 @@ ppmd_fill_init(void) {
 
 
 void
-ppmd_fill_destroy(struct fillobj * fillObjP) {
+ppmd_fill_destroy(struct fillobj * const fillObjP) {
 
-    free(fillObjP->coords);
+    free(fillObjP->stateP->coords);
+    free(fillObjP->stateP);
     free(fillObjP);
 }
 
 
 
+static void
+addCoord(struct fillState *  const stateP,
+         ppmd_point const point) {
+
+    stateP->coords[stateP->n].point = point;
+    stateP->coords[stateP->n].edge = stateP->curedge;
+
+    ++stateP->n;
+}
+
+
+
+static void
+startNewSegment(struct fillState * const stateP) {
+/*----------------------------------------------------------------------------
+   Close off the segment we're currently building and start a new one.
+-----------------------------------------------------------------------------*/
+    if (stateP->startydir != 0 && stateP->ydir != 0) {
+        /* There's stuff in the current segment.  */
+        if (stateP->startydir == stateP->ydir) {
+            /* Oops, first edge and last edge of current segment are the same.
+               Change all points in the first edge to be in the last.
+            */
+            int const firstEdge = stateP->coords[stateP->segstart].edge;
+            int const lastEdge  = stateP->coords[stateP->n - 1].edge;
+            coord * const segStartCoordP = &stateP->coords[stateP->segstart];
+            coord * const segEndCoordP   = &stateP->coords[stateP->n];
+
+            coord * fcP;
+
+            for (fcP = segStartCoordP;
+                 fcP < segEndCoordP && fcP->edge == firstEdge;
+                 ++fcP)
+                fcP->edge = lastEdge;
+        }
+    }
+    /* And start new segment. */
+    ++stateP->curedge;
+    stateP->segstart  = stateP->n;
+    stateP->ydir      = 0;
+    stateP->startydir = 0;
+}
+
+
+
+static void
+continueSegment(struct fillState * const stateP,
+                int                const dy) {
+/*----------------------------------------------------------------------------
+   'dy' is how much the current point is above the previous one.
+-----------------------------------------------------------------------------*/
+    if (dy != 0) {
+        if (stateP->ydir != 0 && stateP->ydir != dy) {
+            /* Direction changed.  Insert a fake coord, old
+               position but new edge number.
+            */
+            ++stateP->curedge;
+            addCoord(stateP, stateP->coords[stateP->n - 1].point);
+        }
+        stateP->ydir = dy;
+        if (stateP->startydir == 0)
+            stateP->startydir = dy;
+    }
+}
+
+
+
+
+/* ppmd_fill_drawprocp() is a drawproc that turns an outline drawing function
+   into a filled shape function.  This is a somewhat off-label application of
+   a drawproc:  A drawproc is intended just to draw a point.  So e.g. you
+   might draw a circle with a fat brush by calling ppmd_circle with a drawproc
+   that draws a point as a 10-pixel disk.
+
+   But ppmd_fill_drawproc() just draws a point the trivial way: as one pixel.
+   However, it tracks every point that is drawn in a form that a subsequent
+   ppmd_fill() call can use to to fill in the shape drawn, assuming it turns
+   out to be a closed shape.
+*/
+
 void
 ppmd_fill_drawprocp(pixel **     const pixels, 
                     unsigned int const cols, 
@@ -1037,85 +1137,39 @@ ppmd_fill_drawprocp(pixel **     const pixels,
                     ppmd_point   const p,
                     const void * const clientdata) {
 
-    fillobj * fh;
-    coord * cp;
-
-    fh = (fillobj*) clientdata;
+    const fillobj *    const fillObjP = clientdata;
+    struct fillState * const stateP   = fillObjP->stateP;
 
-    /* If these are the same coords we saved last time, don't bother. */
-    if (fh->n > 0) {
-        ppmd_point const lastPoint = fh->coords[fh->n - 1].point;
-        if (pointsEqual(p, lastPoint))
-            return;
-    }
-
-    /* Ok, these are new; make room for two more coords. */
-    if (fh->n + 1 >= fh->size) {
-        fh->size += SOME;
-        REALLOCARRAY(fh->coords, fh->size);
-        if (fh->coords == NULL)
+    /* Make room for two more coords, the max we might add. */
+    if (stateP->n + 2 > stateP->size) {
+        stateP->size += SOME;
+        REALLOCARRAY(stateP->coords, stateP->size);
+        if (stateP->coords == NULL)
             pm_error("out of memory enlarging a fillhandle");
     }
 
-    /* Check for extremum and set the edge number. */
-    if (fh->n == 0) {
+    if (stateP->n == 0) {
         /* Start first segment. */
-        fh->segstart = fh->n;
-        fh->ydir = 0;
-        fh->startydir = 0;
+        stateP->segstart = stateP->n;
+        stateP->ydir = 0;
+        stateP->startydir = 0;
+        addCoord(stateP, p);
     } else {
-        coord * const ocp = &(fh->coords[fh->n - 1]);
-        int dx, dy;
-
-        dx = p.x - ocp->point.x;
-        dy = p.y - ocp->point.y;
-        if (dx < -1 || dx > 1 || dy < -1 || dy > 1) {
-            /* Segment break.  Close off old one. */
-            if (fh->startydir != 0 && fh->ydir != 0)
-                if (fh->startydir == fh->ydir) {
-                    /* Oops, first edge and last edge are the same.
-                       Renumber the first edge in the old segment.
-                    */
-                    const coord * const fcpLast= &(fh->coords[fh->n -1]); 
-                    coord * fcp;
-
-                    int oldedge;
-
-                    fcp = &(fh->coords[fh->segstart]);
-                    oldedge = fcp->edge;
-                    for (; fcp <= fcpLast && fcp->edge == oldedge ; ++fcp)
-                        fcp->edge = ocp->edge;
-                }
-            /* And start new segment. */
-            ++fh->curedge;
-            fh->segstart = fh->n;
-            fh->ydir = 0;
-            fh->startydir = 0;
+        ppmd_point const prevPoint = stateP->coords[stateP->n - 1].point;
+        int const dx = p.x - prevPoint.x;
+        int const dy = p.y - prevPoint.y;
+
+        if (dx == 0 && dy == 0) {
+            /* These are the same coords we had last time; don't bother */
         } else {
-            /* Segment continues. */
-            if (dy != 0) {
-                if (fh->ydir != 0 && fh->ydir != dy) {
-                    /* Direction changed.  Insert a fake coord, old
-                       position but new edge number.
-                    */
-                    ++fh->curedge;
-                    cp = &fh->coords[fh->n];
-                    cp->point = ocp->point;
-                    cp->edge = fh->curedge;
-                    ++fh->n;
-                }
-                fh->ydir = dy;
-                if (fh->startydir == 0)
-                    fh->startydir = dy;
-            }
+            if (abs(dx) > 1 || abs(dy) > 1)
+                startNewSegment(stateP);
+            else
+                continueSegment(stateP, dy);
+
+            addCoord(stateP, p);
         }
     }
-
-    /* Save this coord. */
-    cp = &fh->coords[fh->n];
-    cp->point = p;
-    cp->edge = fh->curedge;
-    ++fh->n;
 }
 
 
@@ -1137,12 +1191,12 @@ ppmd_fill_drawproc(pixel**      const pixels,
 
 
 #ifndef LITERAL_FN_DEF_MATCH
-static qsort_comparison_fn yx_compare;
+static qsort_comparison_fn yxCompare;
 #endif
 
 static int
-yx_compare(const void * const c1Arg,
-           const void * const c2Arg) {
+yxCompare(const void * const c1Arg,
+          const void * const c2Arg) {
 
     const coord * const c1P = c1Arg;
     const coord * const c2P = c2Arg;
@@ -1173,10 +1227,12 @@ ppmd_fill(pixel **         const pixels,
           int              const cols, 
           int              const rows, 
           pixval           const maxval, 
-          struct fillobj * const fh,
+          struct fillobj * const fillObjP,
           ppmd_drawproc          drawProc,
           const void *     const clientdata) {
 
+    struct fillState * const fh = fillObjP->stateP;
+
     int pedge;
     int i, edge, lx, rx, py;
     coord * cp;
@@ -1202,7 +1258,7 @@ ppmd_fill(pixel **         const pixels,
     ppmd_setlineclip(oldclip);
 
     /* Sort the coords by Y, secondarily by X. */
-    qsort((char*) fh->coords, fh->n, sizeof(coord), yx_compare);
+    qsort((char*) fh->coords, fh->n, sizeof(coord), yxCompare);
 
     /* Find equal coords with different edge numbers, and swap if necessary. */
     edge = -1;
diff --git a/lib/libppmfloyd.c b/lib/libppmfloyd.c
index ec6256ff..d2b9c7b1 100644
--- a/lib/libppmfloyd.c
+++ b/lib/libppmfloyd.c
@@ -22,9 +22,9 @@ do Floyd-Steinberg.
 ** implied warranty.
 */
 
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmfloyd.h"
-#include "mallocvar.h"
 
 
 
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
index 2a54e896..f1573c68 100644
--- a/lib/libppmfuzzy.c
+++ b/lib/libppmfuzzy.c
@@ -5,8 +5,8 @@
   <kenan@unix.ba> in 2006.
 =============================================================================*/
 
-#include "pm_c_util.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 
 typedef double fzLog;
diff --git a/lib/libsystem.c b/lib/libsystem.c
index 1c407ce4..fd3c52ec 100644
--- a/lib/libsystem.c
+++ b/lib/libsystem.c
@@ -13,6 +13,7 @@
    Contributed to the public domain.
 =============================================================================*/
 #define _XOPEN_SOURCE
+#define _BSD_SOURCE  /* Make SIGWINCH defined on OpenBSD */
 
 #include <stdarg.h>
 #include <unistd.h>
@@ -23,8 +24,8 @@
 #include <signal.h>
 #include <sys/wait.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "pm.h"
 #include "pm_system.h"
 
@@ -133,7 +134,7 @@ createPipeFeeder(void          pipeFeederRtn(int, void *),
     int pipeToFeed[2];
     pid_t rc;
 
-    pipe(pipeToFeed);
+    pm_pipe(pipeToFeed);
     rc = fork();
     if (rc < 0) {
         pm_error("fork() of stdin feeder failed.  errno=%d (%s)", 
@@ -179,7 +180,7 @@ spawnProcessor(const char *  const progName,
     pid_t rc;
 
     if (pipeStdout)
-        pipe(stdoutpipe);
+        pm_pipe(stdoutpipe);
 
     rc = fork();
     if (rc < 0) {
@@ -220,6 +221,20 @@ spawnProcessor(const char *  const progName,
 static const char *
 signalName(unsigned int const signalClass) {
 
+/* There are various signal classes that are not universally defined,
+   so we make a half-hearted attempt to determine whether they are and
+   not try to recognize the ones that aren't.  We do this by testing
+   whether a macro is defind with the signal class name.  That could give
+   a false negative, because the signal class name isn't necessarily
+   defined as a macro, but it's a really, really small problem to miss
+   one of these signal classes here, so we don't bother with all the work
+   it would take to do it right.
+
+   OpenBSD does not have SIGWINCH and SIGIO in 2013.  Everyone else seems
+   to have them.  OpenBSD does have them if the code is not declared as
+   X/open code (i.e. OpenBSD seems to interpret _XOPEN_SOURCE backward -
+   it removes features rather than adds them).
+*/
     switch (signalClass) {
     case SIGHUP: /* POSIX.1 */
         return "SIGHUP";
@@ -263,6 +278,11 @@ signalName(unsigned int const signalClass) {
         return "SIGTTIN";
     case SIGTTOU: /* POSIX.1 */
         return "SIGTTOU";
+#ifdef SIGURG
+/* SCO Openserver 5.0.7/3.2 does not have SIGURG */
+    case SIGURG:
+        return "SIGURG";
+#endif
     case SIGXCPU:
         return "SIGXCPU";
     case SIGXFSZ:
@@ -271,17 +291,23 @@ signalName(unsigned int const signalClass) {
         return "SIGVTALRM";
     case SIGPROF:
         return "SIGPROF";
+#ifdef SIGWINCH
+    case SIGWINCH:
+        return "SIGWINCH";
+#endif
+#ifdef SIGIO
+/* SCO Openserver 5.0.7/3.2 does not have SIGIO */
+    case SIGIO:
+        return "SIGIO";
+#endif
+#ifdef SIGPWR
+    case SIGPWR:
+        return "SIGPWR";
+#endif
     case SIGSYS:
         return "SIGSYS";
     default:
         return "???";
-
-        /* There are various other signal classes on some systems, but
-           not defined by POSIX and not on at least one system we
-           know of for which someone wanted to compile Netpbm.  The
-           list includes: SIGPWR, SIGLOST, SIGINFO, SIGRTxx,
-           SIGURG, SIGWINCH, SIGIO.
-        */
     }
 }
 
@@ -508,20 +534,20 @@ pm_feed_from_memory(int    const pipeToFeedFd,
 
     struct bufferDesc * const inputBufferP = feederParm;
     
-    FILE * const outfile = fdopen(pipeToFeedFd, "w");
+    FILE * const outFileP = fdopen(pipeToFeedFd, "w");
     
-    int bytesTransferred;
+    size_t bytesTransferred;
 
     /* The following signals (and normally kills) the process with
        SIGPIPE if the pipe does not take all 'size' bytes.
     */
     bytesTransferred = 
-        fwrite(inputBufferP->buffer, 1, inputBufferP->size, outfile);
+        fwrite(inputBufferP->buffer, 1, inputBufferP->size, outFileP);
 
     if (inputBufferP->bytesTransferredP)
         *(inputBufferP->bytesTransferredP) = bytesTransferred;
 
-    fclose(outfile);
+    fclose(outFileP);
 }
 
 
@@ -532,14 +558,14 @@ pm_accept_to_memory(int             const pipetosuckFd,
 
     struct bufferDesc * const outputBufferP = accepterParm;
     
-    FILE * const infile = fdopen(pipetosuckFd, "r");
+    FILE * const inFileP = fdopen(pipetosuckFd, "r");
 
-    int bytesTransferred;
+    size_t bytesTransferred;
 
     bytesTransferred =
-        fread(outputBufferP->buffer, 1, outputBufferP->size, infile);
+        fread(outputBufferP->buffer, 1, outputBufferP->size, inFileP);
 
-    fclose(infile);
+    fclose(inFileP);
 
     if (outputBufferP->bytesTransferredP)
         *(outputBufferP->bytesTransferredP) = bytesTransferred;
diff --git a/lib/libsystem_dummy.c b/lib/libsystem_dummy.c
index db26ba7a..97dd8984 100644
--- a/lib/libsystem_dummy.c
+++ b/lib/libsystem_dummy.c
@@ -4,7 +4,7 @@
   This is a dummy version of libsystem.c, for use on systems that don't
   have the kind of process control that libsystem.c needs.
 
-  With this module, program will build cleanly, but if a program actually
+  With this module, programs will build cleanly, but if a program actually
   calls pm_system(), it will die with an error message saying that
   the facility is not available.
 =============================================================================*/
diff --git a/lib/lum.h b/lib/lum.h
index 34d3866e..3d20032b 100644
--- a/lib/lum.h
+++ b/lib/lum.h
@@ -1,3 +1,5 @@
+#include <netpbm/pm_config.h>
+
     /* Lookup tables for fast RGB -> luminance calculation. */
     static int times77[256] = {
 	    0,    77,   154,   231,   308,   385,   462,   539,
diff --git a/lib/pam.h b/lib/pam.h
index c28c5c2c..c58e36a2 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -1,11 +1,14 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
    These are declarations for use with the Portable Arbitrary Map (PAM)
    format and the Netpbm library functions specific to them.
------------------------------------------------------------------------------*/
 
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 #ifndef PAM_H
 #define PAM_H
 
+#include <netpbm/pm_config.h>
 #include <netpbm/pm.h>
 #include <netpbm/pnm.h>
 
@@ -49,9 +52,13 @@ struct pam {
         */
     FILE * file;
     int format;
-        /* The format code of the raw image.  This is PAM_FORMAT
+        /* The format code of the image.  This is PAM_FORMAT
            unless the PAM image is really a view of a PBM, PGM, or PPM
-           image.  Then it's PBM_FORMAT, RPBM_FORMAT, etc.
+           image.  Then it's PBM_FORMAT, RPBM_FORMAT, etc.  For output,
+           only the format _type_ is significant, e.g. PBM_FORMAT
+           and RPBM_FORMAT have identical effect.  This is because on
+           output, 'plainformat' determines whether the output is the
+           raw or plain format of the type given by 'format'.
            */
     unsigned int plainformat;
         /* Logical: On output, use plain version of the format type
@@ -64,6 +71,9 @@ struct pam {
            Before Netpbm 10.32, this was rather different.  It simply
            described for convenience the plainness of the format indicated
            by 'format'.
+
+           This is meaningless when 'format' is PAM_FORMAT, as PAM does not
+           have plain and raw variations.
         */
     int height;  /* Height of image in rows */
     int width;   
@@ -109,6 +119,23 @@ struct pam {
            libnetpbm does not return comments and does not allocate any
            storage.
         */
+    int visual;  /* boolean */
+        /* tuple_type is one of the PAM-defined tuple types for visual
+           images ("GRAYSCALE", "RGB_ALPHA", etc.).
+        */
+    unsigned int color_depth;
+        /* Number of color planes (i.e. 'depth', but without transparency).
+           The color planes are the lowest numbered ones.  Meaningless if
+           'visual' is false.
+        */
+    int have_opacity;   /* boolean */
+        /* The tuples have an opacity (transparency, alpha) plane.
+           Meaningless if 'visual' is false.
+        */
+    unsigned int opacity_plane;
+        /* The plane number of the opacity plane;  meaningless if
+           'haveOpacity' is false or 'visual' is false.
+        */
 };
 
 #define PAM_HAVE_ALLOCATION_DEPTH 1
@@ -136,6 +163,9 @@ struct pam {
 #define PAM_PBM_TUPLETYPE "BLACKANDWHITE"
 #define PAM_PGM_TUPLETYPE "GRAYSCALE"
 #define PAM_PPM_TUPLETYPE "RGB"
+#define PAM_PBM_ALPHA_TUPLETYPE "BLACKANDWHITE_ALPHA"
+#define PAM_PGM_ALPHA_TUPLETYPE "GRAYSCALE_ALPHA"
+#define PAM_PPM_ALPHA_TUPLETYPE "RGB_ALPHA"
 
 #define PAM_PBM_BLACK PAM_BLACK
 #define PAM_PBM_WHITE PAM_BW_WHITE
@@ -275,6 +305,14 @@ pnm_makearrayrgb(const struct pam * const pamP,
                  tuple **           const tuples);
 
 void
+pnm_makerowrgba(const struct pam * const pamP,
+                tuple *            const tuplerow);
+
+void
+pnm_addopacityrow(const struct pam * const pamP,
+                  tuple *            const tuplerow);
+
+void
 pnm_getopacity(const struct pam * const pamP,
                int *              const haveOpacityP,
                unsigned int *     const opacityPlaneP);
@@ -282,10 +320,6 @@ pnm_getopacity(const struct pam * const pamP,
 void
 pnm_createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
 
-void
-createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
-
-
 tuple
 pnm_allocpamtuple(const struct pam * const pamP);
 
@@ -483,9 +517,15 @@ pnm_YCbCr_to_rgbtuple(const struct pam * const pamP,
     ((tuple)[PAM_RED_PLANE] == (tuple)[PAM_GRN_PLANE] && \
      (tuple)[PAM_RED_PLANE] == (tuple)[PAM_BLU_PLANE])
 
+tuple
+pnm_backgroundtuple(struct pam *  const pamP,
+                    tuple      ** const tuples);
+
 /*----------------------------------------------------------------------------
    These are meant for passing to pm_system() as Standard Input feeder
    and Standard Output accepter.
+
+   The 'feederParm' or 'accepterParm' is a pointer to a struct pamtuples.
 -----------------------------------------------------------------------------*/
 
 void
diff --git a/lib/pamdraw.h b/lib/pamdraw.h
new file mode 100644
index 00000000..aaa6f20e
--- /dev/null
+++ b/lib/pamdraw.h
@@ -0,0 +1,317 @@
+/* pamdraw.h - header file for simple drawing routines in libnetpbm.
+**
+** Simple, yes, and also fairly slow if the truth be told; but also very
+** flexible and powerful.
+**
+** The two basic concepts are the drawproc and clientdata.  All the drawing
+** routines take a drawproc that does the actual drawing.  A drawproc draws
+** a single point, and it looks like this:
+*/
+
+#include <netpbm/pm_config.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+typedef struct {
+    int x;
+    int y;
+} pamd_point;
+
+static __inline__ pamd_point
+pamd_makePoint(int const x,
+               int const y) {
+
+    pamd_point p;
+
+    p.x = x;
+    p.y = y;
+
+    return p;
+}
+
+void
+pamd_validateCoord(int const c);
+
+void
+pamd_validatePoint(pamd_point const p);
+
+typedef enum {
+    PAMD_PATHLEG_LINE
+} pamd_pathlegtype;
+
+struct pamd_linelegparms {
+    pamd_point end;
+};
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A leg of a pamd_path.
+-----------------------------------------------------------------------------*/
+    pamd_pathlegtype type;
+    union {
+        struct pamd_linelegparms linelegparms;
+    } u;
+} pamd_pathleg;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A closed path
+-----------------------------------------------------------------------------*/
+    unsigned int version;
+        /* Must be zero.  For future expansion. */
+    pamd_point   begPoint;  
+    unsigned int legCount;
+        /* Number of legs in the path; i.e. size of 'legs' array */
+    size_t       legSize;
+        /* Size of storage occupied by one pamd_pathleg.  I.e.
+           sizeof(pamd_pathleg).  Used for
+           binary backward compatibility between callers and libpamd
+           as the definition of pamd_pathleg changes.
+        */
+    pamd_pathleg * legs;
+} pamd_path;
+
+
+
+typedef void pamd_drawproc(tuple **, unsigned int, unsigned int, unsigned int,
+                           sample, pamd_point, const void *);
+
+pamd_drawproc pamd_point_drawproc;
+
+/*
+** So, you call a drawing routine, e.g. pamd_line(), and pass it a drawproc;
+** it calls the drawproc for each point it wants to draw.  Why so complicated?
+** Because you can define your own drawprocs to do more interesting things than
+** simply draw the point.  For example, you could make one that calls back into
+** another drawing routine, say pamd_circle() to draw a circle at each point
+** of a line.
+**
+** Slow?  Well sure, we're talking results here, not realtime.  You can do
+** tricks with this arrangement that you couldn't even think of before.
+** Still, to speed things up for the 90% case you can use this:
+*/
+#define PAMD_NULLDRAWPROC NULL
+/*
+** Just like pamd_point_drawproc() it simply draws the point, but it's done
+** inline, and clipping is assumed to be handled at a higher level.
+**
+** Now, what about clientdata.  Well, it's an arbitrary pointer, and can
+** mean something different to each different drawproc.  For the above two
+** drawprocs, clientdata should be a pointer to a tuple holding the color
+** to be drawn.  Other drawprocs can use it to point to something else,
+** e.g. some structure to be modified, or they can ignore it.
+*/
+
+
+/* Outline drawing routines.  Lines, splines, circles, etc. */
+
+int 
+pamd_setlinetype(int const type);
+
+#define PAMD_LINETYPE_NORMAL 0
+#define PAMD_LINETYPE_NODIAGS 1
+/* If you set NODIAGS, all tuples drawn by pamd_line() will be 4-connected
+** instead of 8-connected; in other words, no diagonals.  This is useful
+** for some applications, for example when you draw many parallel lines
+** and you want them to fit together without gaps.
+*/
+
+int
+pamd_setlineclip(int const clip);
+
+#define pamd_setlineclipping(x)     pamd_setlineclip(x)
+/* Normally, pamd_line() clips to the edges of the pixmap.  You can use this
+** routine to disable the clipping, for example if you are using a drawproc
+** that wants to do its own clipping.
+*/
+
+void
+pamd_line(tuple **      const tuples, 
+          int           const cols, 
+          int           const rows, 
+          int           const depth, 
+          sample        const maxval, 
+          pamd_point    const p0,
+          pamd_point    const p1,
+          pamd_drawproc       drawProc,
+          const void *  const clientdata);
+
+    /* Draws a line from p0 to p1.  */
+
+void
+pamd_spline3(tuple **      const tuples, 
+             int           const cols, 
+             int           const rows, 
+             int           const depth, 
+             sample        const maxval, 
+             pamd_point    const p0,
+             pamd_point    const p1,
+             pamd_point    const p2,
+             pamd_drawproc       drawProc,
+             const void *  const clientdata);
+
+    /* Draws a three-point spline from p0 to p2, with p1
+       as the control point.  All drawing is done via pamd_line(),
+       so the routines that control it control pamd_spline3p() as well. 
+    */
+
+void
+pamd_polyspline(tuple **      const tuples, 
+                unsigned int  const cols, 
+                unsigned int  const rows, 
+                unsigned int  const depth, 
+                sample        const maxval, 
+                pamd_point    const p0,
+                unsigned int  const nc,
+                pamd_point *  const c,
+                pamd_point    const p1,
+                pamd_drawproc       drawProc,
+                const void *  const clientdata);
+
+    /* Draws a bunch of splines end to end.  p0 and p1 are the initial and
+       final points, and the c[] are the intermediate control points.  nc is
+       the number of these control points.
+    */
+
+void
+pamd_spline4(tuple **      const tuples, 
+             unsigned int  const cols, 
+             unsigned int  const rows, 
+             unsigned int  const depth, 
+             sample        const maxval, 
+             pamd_point    const endPt0,
+             pamd_point    const endPt1,
+             pamd_point    const ctlPt0,
+             pamd_point    const ctlPt1,
+             pamd_drawproc       drawProc,
+             const void *  const clientdata);
+
+void
+pamd_circle(tuple **      const tuples, 
+            unsigned int  const cols, 
+            unsigned int  const rows, 
+            unsigned int  const depth, 
+            sample        const maxval, 
+            pamd_point    const center,
+            unsigned int  const radius, 
+            pamd_drawproc       drawProc,
+            const void *  const clientData);
+    /* Draws a circle centered at 'center' with radius 'radius' */
+
+/* Simple filling routines.  */
+
+void
+pamd_filledrectangle(tuple **      const tuples, 
+                     int           const cols, 
+                     int           const rows, 
+                     int           const depth, 
+                     sample        const maxval, 
+                     int           const left, 
+                     int           const top, 
+                     int           const width, 
+                     int           const height, 
+                     pamd_drawproc       drawProc,
+                     const void *  const clientdata);
+    /* Fills in the rectangle [left, top, width, height]. */
+
+
+void
+pamd_fill_path(tuple **      const tuples, 
+               int           const cols, 
+               int           const rows, 
+               int           const depth, 
+               sample        const maxval,
+               pamd_path *   const pathP,
+               tuple         const color);
+    /* Fills in a closed path.  Not much different from pamd_fill(),
+       but with a different interface.
+    */
+
+
+/* Arbitrary filling routines.  With these you can fill any outline that
+** you can draw with the outline routines.
+*/
+
+struct fillobj;
+
+struct fillobj *
+pamd_fill_create(void);
+    /* Returns a blank fillhandle. */
+
+void
+pamd_fill_destroy(struct fillobj * fillObjP);
+
+void
+pamd_fill_drawproc(tuple **     const tuples, 
+                   unsigned int const cols, 
+                   unsigned int const rows, 
+                   unsigned int const depth, 
+                   sample       const maxval, 
+                   pamd_point   const p,
+                   const void * const clientdata);
+    /* Use this drawproc to trace the outline you want filled.  Use
+       the fillhandle as the clientdata.
+    */
+
+void
+pamd_fill(tuple **         const tuples, 
+          int              const cols, 
+          int              const rows, 
+          int              const depth, 
+          sample           const maxval, 
+          struct fillobj * const fillObjP,
+          pamd_drawproc          drawProc,
+          const void *     const clientdata);
+
+    /* Once you've traced the outline, give the fillhandle to this routine to
+       do the actual drawing.  As usual, it takes a drawproc and clientdata;
+       you could define drawprocs to do stipple fills and such.
+    */
+
+/* Text drawing routines. */
+
+void
+pamd_text(tuple**       const tuples, 
+          int           const cols, 
+          int           const rows, 
+          int           const depth, 
+          sample        const maxval, 
+          pamd_point    const pos,
+          int           const height, 
+          int           const angle, 
+          const char *  const sArg, 
+          pamd_drawproc       drawProc,
+          const void *  const clientdata);
+
+    /* Draws the null-terminated string 's' left justified at the point ('x',
+       'y').  The text will be 'height' tuples high and will be aligned on a
+       baseline inclined 'angle' degrees with the X axis.  The supplied
+       drawproc and clientdata are passed to pamd_line() which performs the
+       actual drawing.
+    */
+
+void
+pamd_text_box(int          const height, 
+              int          const angle, 
+              const char * const s, 
+              int *        const leftP, 
+              int *        const topP, 
+              int *        const rightP, 
+              int *        const bottomP);
+    /* Calculates the extents box for text drawn by pamd_text with the given
+       string, size, and orientation.  Most extent box calculations should use
+       an angle specification of zero to calculate the unrotated box enclosing
+       the text.  If you need the extents of rotated text, however, you can
+       call pamd_text_box with a nonzero angle.
+    */
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/lib/pammap.h b/lib/pammap.h
index c9c1deed..da2e2470 100644
--- a/lib/pammap.h
+++ b/lib/pammap.h
@@ -1,11 +1,11 @@
-/******************************************************************************
+/*=============================================================================
                                 pammap.h
-*******************************************************************************
+===============================================================================
 
   Interface header file for hash table and lookup table pam functions
   in libpnm.
 
-******************************************************************************/
+=============================================================================*/
 
 #ifndef PAMMAP_H
 #define PAMMAP_H
diff --git a/lib/path.c b/lib/path.c
index 5a1d4988..10ae92d2 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -10,8 +10,8 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmdfont.h"
 #include "ppmdraw.h"
diff --git a/lib/pbm.h b/lib/pbm.h
index 24574d07..a29adb48 100644
--- a/lib/pbm.h
+++ b/lib/pbm.h
@@ -58,9 +58,21 @@ pbm_allocrow(unsigned int const cols);
 #define pbm_freearray_packed(packed_bits, rows) \
     pm_freearray((char **) packed_bits, rows)
 
-bit** pbm_readpbm(FILE* file, int* colsP, int* rowsP);
-void pbm_readpbminit(FILE* file, int* colsP, int* rowsP, int* formatP);
-void pbm_readpbmrow(FILE* file, bit* bitrow, int cols, int format);
+bit**
+pbm_readpbm(FILE * const file,
+            int  * const colsP,
+            int  * const rowsP);
+
+void
+pbm_readpbminit(FILE * const file,
+                int  * const colsP,
+                int  * const rowsP, int * const formatP);
+
+void
+pbm_readpbmrow(FILE * const file,
+               bit  * const bitrow,
+               int    const cols,
+               int    const format);
 
 void
 pbm_readpbmrow_packed(FILE *          const file, 
@@ -76,6 +88,10 @@ pbm_readpbmrow_bitoffset(FILE *          const fileP,
                          unsigned int    const offset);
 
 void
+pbm_cleanrowend_packed(unsigned char * const packedBits,
+                       unsigned int    const cols);
+
+void
 pbm_writepbminit(FILE * const fileP, 
                  int    const cols, 
                  int    const rows, 
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
index a977a11f..bedf57b9 100644
--- a/lib/pbmfont.h
+++ b/lib/pbmfont.h
@@ -1,6 +1,8 @@
 /* pbmfont.h - header file for font routines in libpbm
 */
 
+#include "pbm.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/lib/pm.h b/lib/pm.h
index a0c2005e..47cbfe83 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -23,10 +23,6 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 
-#ifdef VMS
-#include <perror.h>
-#endif
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -90,7 +86,7 @@ extern "C" {
    We're ignoring S_IREAD now to see if anyone misses it.  If there are still
    users that need it, we can handle it here.
 */
-#ifdef WIN32
+#if MSVCRT
   #define PM_S_IWUSR _S_IWRITE
   #define PM_S_IRUSR _S_IREAD
 #else
@@ -123,6 +119,9 @@ pm_proginit(int * const argcP, const char * argv[]);
 void
 pm_setMessage(int const newState, int * const oldStateP);
 
+int
+pm_getMessage(void);
+
 FILE * 
 pm_tmpfile(void);
 
@@ -185,6 +184,21 @@ pm_setjmpbufsave(jmp_buf *  const jmpbufP,
 void
 pm_longjmp(void);
 
+void
+pm_fork(int *         const iAmParentP,
+        pid_t *       const childPidP,
+        const char ** const errorP);
+
+void
+pm_waitpid(pid_t         const pid,
+           int *         const statusP,
+           int           const options,
+           pid_t *       const exitedPidP,
+           const char ** const errorP);
+
+
+void
+pm_waitpidSimple(pid_t const pid);
 
 typedef void pm_usermessagefn(const char * msg);
 
@@ -205,6 +219,9 @@ pm_errormsg(const char format[], ...);
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_error (const char reason[], ...);       
 
+int
+pm_have_float_format(void);
+
 /* Obsolete - use shhopt and user's manual instead */
 void 
 pm_usage (const char usage[]);             
@@ -280,6 +297,16 @@ pm_readbiglongu(FILE *          const ifP,
 }
 
 int
+pm_readbiglong2(FILE * const ifP, 
+                int32_t * const lP);
+
+static __inline__ int
+pm_readbiglongu2(FILE *     const ifP,
+                 uint32_t * const lP) {
+    return pm_readbiglong2(ifP, (int32_t *) lP);
+}
+
+int
 pm_writebiglong(FILE * const ofP,
                 long   const l);
 
@@ -320,6 +347,16 @@ pm_readlittlelongu(FILE *          const ifP,
 }
 
 int
+pm_readlittlelong2(FILE *    const ifP,
+                   int32_t * const lP);
+
+static __inline__ int
+pm_readlittlelong2u(FILE *     const ifP,
+                    uint32_t * const lP) {
+    return pm_readlittlelong2(ifP, (int32_t *) lP);
+}
+
+int
 pm_writelittlelong(FILE * const ofP,
                    long   const l);
 
diff --git a/lib/pmfileio.c b/lib/pmfileio.c
index 84ab53ab..8176ae6a 100644
--- a/lib/pmfileio.c
+++ b/lib/pmfileio.c
@@ -10,7 +10,8 @@
        does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
        in some environments.
     */
-#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */
+#define _BSD_SOURCE    /* Make sure strdup is defined */
+#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko, strdup are defined */
 #define _LARGEFILE_SOURCE 1  /* Make sure ftello, fseeko are defined */
 #define _LARGEFILE64_SOURCE 1 
 #define _FILE_OFFSET_BITS 64
@@ -28,19 +29,21 @@
 #define _LARGE_FILE_API
     /* This makes the the x64() functions available on AIX */
 
+#include "netpbm/pm_config.h"
 #include <unistd.h>
+#include <assert.h>
 #include <stdio.h>
 #include <fcntl.h>
 #include <stdarg.h>
 #include <string.h>
 #include <errno.h>
-#ifdef __DJGPP__
-  #include <io.h>
+#if HAVE_IO_H
+  #include <io.h>  /* For mktemp */
 #endif
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 
 #include "pm.h"
 
@@ -48,18 +51,14 @@
 
 /* File open/close that handles "-" as stdin/stdout and checks errors. */
 
-FILE*
+FILE *
 pm_openr(const char * const name) {
-    FILE* f;
+    FILE * f;
 
-    if (strcmp(name, "-") == 0)
+    if (streq(name, "-"))
         f = stdin;
     else {
-#ifndef VMS
         f = fopen(name, "rb");
-#else
-        f = fopen(name, "r", "ctx=stm");
-#endif
         if (f == NULL) 
             pm_error("Unable to open file '%s' for reading.  "
                      "fopen() returns errno %d (%s)", 
@@ -70,18 +69,14 @@ pm_openr(const char * const name) {
 
 
 
-FILE*
+FILE *
 pm_openw(const char * const name) {
-    FILE* f;
+    FILE * f;
 
-    if (strcmp(name, "-") == 0)
+    if (streq(name, "-"))
         f = stdout;
     else {
-#ifndef VMS
         f = fopen(name, "wb");
-#else
-        f = fopen(name, "w", "mbc=32", "mbf=2");  /* set buffer factors */
-#endif
         if (f == NULL) 
             pm_error("Unable to open file '%s' for writing.  "
                      "fopen() returns errno %d (%s)", 
@@ -129,10 +124,10 @@ tempFileOpenFlags(void) {
     retval = 0
         | O_CREAT
         | O_RDWR
-#ifndef WIN32
+#if !MSVCRT
         | O_EXCL
 #endif
-#ifdef WIN32
+#if MSVCRT
         | O_BINARY
 #endif
         ;
@@ -226,25 +221,25 @@ makeTmpfileWithTemplate(const char *  const filenameTemplate,
     filenameBuffer = strdup(filenameTemplate);
 
     if (filenameBuffer == NULL)
-        asprintfN(errorP, "Unable to allocate storage for temporary "
-                  "file name");
+        pm_asprintf(errorP, "Unable to allocate storage for temporary "
+                    "file name");
     else {
         int rc;
         
         rc = mkstemp2(filenameBuffer);
         
         if (rc < 0)
-            asprintfN(errorP,
-                      "Unable to create temporary file according to name "
-                      "pattern '%s'.  mkstemp() failed with errno %d (%s)",
-                      filenameTemplate, errno, strerror(errno));
+            pm_asprintf(errorP,
+                        "Unable to create temporary file according to name "
+                        "pattern '%s'.  mkstemp() failed with errno %d (%s)",
+                        filenameTemplate, errno, strerror(errno));
         else {
             *fdP = rc;
             *filenameP = filenameBuffer;
             *errorP = NULL;
         }
         if (*errorP)
-            strfree(filenameBuffer);
+            pm_strfree(filenameBuffer);
     }
 }
 
@@ -255,13 +250,10 @@ pm_make_tmpfile_fd(int *         const fdP,
                    const char ** const filenameP) {
 
     const char * filenameTemplate;
-    unsigned int fnamelen;
     const char * tmpdir;
     const char * dirseparator;
     const char * error;
 
-    fnamelen = strlen(pm_progname) + 10; /* "/" + "_XXXXXX\0" */
-
     tmpdir = tmpDir();
 
     if (tmpdir[strlen(tmpdir) - 1] == '/')
@@ -269,20 +261,20 @@ pm_make_tmpfile_fd(int *         const fdP,
     else
         dirseparator = "/";
     
-    asprintfN(&filenameTemplate, "%s%s%s%s", 
-              tmpdir, dirseparator, pm_progname, "_XXXXXX");
+    pm_asprintf(&filenameTemplate, "%s%s%s%s", 
+                tmpdir, dirseparator, pm_progname, "_XXXXXX");
 
-    if (filenameTemplate == strsol)
-        asprintfN(&error,
-                  "Unable to allocate storage for temporary file name");
+    if (filenameTemplate == pm_strsol)
+        pm_asprintf(&error,
+                    "Unable to allocate storage for temporary file name");
     else {
         makeTmpfileWithTemplate(filenameTemplate, fdP, filenameP, &error);
 
-        strfree(filenameTemplate);
+        pm_strfree(filenameTemplate);
     }
     if (error) {
         pm_errormsg("%s", error);
-        strfree(error);
+        pm_strfree(error);
         pm_longjmp();
     }
 }
@@ -302,7 +294,7 @@ pm_make_tmpfile(FILE **       const filePP,
     if (*filePP == NULL) {
         close(fd);
         unlink(*filenameP);
-        strfree(*filenameP);
+        pm_strfree(*filenameP);
 
         pm_error("Unable to create temporary file.  "
                  "fdopen() failed with errno %d (%s)",
@@ -312,17 +304,130 @@ pm_make_tmpfile(FILE **       const filePP,
 
 
 
+bool const canUnlinkOpen = 
+#if CAN_UNLINK_OPEN
+    1
+#else
+    0
+#endif
+    ;
+
+
+
+typedef struct UnlinkListEntry {
+/*----------------------------------------------------------------------------
+   This is an entry in the linked list of files to close and unlink as the
+   program exits.
+-----------------------------------------------------------------------------*/
+    struct UnlinkListEntry * next;
+    int                      fd;
+    char                     fileName[1];  /* Actually variable length */
+} UnlinkListEntry;
+
+static UnlinkListEntry * unlinkListP;
+
+
+
+static void
+unlinkTempFiles(void) {
+/*----------------------------------------------------------------------------
+  Close and unlink (so presumably delete) the files in the list
+  *unlinkListP.
+
+  This is an atexit function.
+-----------------------------------------------------------------------------*/
+    while (unlinkListP) {
+        UnlinkListEntry * const firstEntryP = unlinkListP;
+
+        unlinkListP = unlinkListP->next;
+
+        close(firstEntryP->fd);
+        unlink(firstEntryP->fileName);
+
+        free(firstEntryP);
+    }
+}
+
+
+
+static UnlinkListEntry *
+newUnlinkListEntry(const char * const fileName,
+                   int          const fd) {
+
+    UnlinkListEntry * const unlinkListEntryP =
+        malloc(sizeof(*unlinkListEntryP) + strlen(fileName) + 1);
+
+    if (unlinkListEntryP) {
+        strcpy(unlinkListEntryP->fileName, fileName);
+        unlinkListEntryP->fd   = fd;
+        unlinkListEntryP->next = NULL;
+    }
+    return unlinkListEntryP;
+}
+
+
+
+static void
+addUnlinkListEntry(const char * const fileName,
+                   int          const fd) {
+
+    UnlinkListEntry * const unlinkListEntryP =
+        newUnlinkListEntry(fileName, fd);
+
+    if (unlinkListEntryP) {
+        unlinkListEntryP->next = unlinkListP;
+        unlinkListP = unlinkListEntryP;
+    }
+}
+
+
+
+static void
+scheduleUnlinkAtExit(const char * const fileName,
+                     int          const fd) {
+/*----------------------------------------------------------------------------
+   Set things up to have the file unlinked as the program exits.
+
+   This is messy and probably doesn't work in all situations; it is a hack
+   to get Unix code essentially working on Windows, without messing up the
+   code too badly for Unix.
+-----------------------------------------------------------------------------*/
+    static bool unlinkListEstablished = false;
+    
+    if (!unlinkListEstablished) {
+        atexit(unlinkTempFiles);
+        unlinkListP = NULL;
+        unlinkListEstablished = true;
+    }
+
+    addUnlinkListEntry(fileName, fd);
+}
+
+
+
+static void
+arrangeUnlink(const char * const fileName,
+              int          const fd) {
+
+    if (canUnlinkOpen)
+        unlink(fileName);
+    else
+        scheduleUnlinkAtExit(fileName, fd);
+}
+
+
+
 FILE * 
 pm_tmpfile(void) {
 
     FILE * fileP;
-    const char * tmpfile;
+    const char * tmpfileNm;
 
-    pm_make_tmpfile(&fileP, &tmpfile);
+    pm_make_tmpfile(&fileP, &tmpfileNm);
 
-    unlink(tmpfile);
+    arrangeUnlink(tmpfileNm, fileno(fileP));
 
-    strfree(tmpfile);
+    pm_strfree(tmpfileNm);
 
     return fileP;
 }
@@ -333,13 +438,13 @@ int
 pm_tmpfile_fd(void) {
 
     int fd;
-    const char * tmpfile;
+    const char * tmpfileNm;
 
-    pm_make_tmpfile_fd(&fd, &tmpfile);
+    pm_make_tmpfile_fd(&fd, &tmpfileNm);
 
-    unlink(tmpfile);
+    arrangeUnlink(tmpfileNm, fd);
 
-    strfree(tmpfile);
+    pm_strfree(tmpfileNm);
 
     return fd;
 }
@@ -557,6 +662,23 @@ pm_readbiglong(FILE * const ifP,
 
 
 int
+pm_readbiglong2(FILE *    const ifP,
+                int32_t * const lP) {
+    int rc;
+    long l;
+
+    rc = pm_readbiglong(ifP, &l);
+
+    assert((int32_t)l == l);
+
+    *lP = (int32_t)l;
+
+    return rc;
+}
+
+
+
+int
 pm_writebiglong(FILE * const ofP, 
                 long   const l) {
 
@@ -615,6 +737,23 @@ pm_readlittlelong(FILE * const ifP,
 
 
 int
+pm_readlittlelong2(FILE *    const ifP,
+                   int32_t * const lP) {
+    int rc;
+    long l;
+
+    rc = pm_readlittlelong(ifP, &l);
+
+    assert((int32_t)l == l);
+
+    *lP = (int32_t)l;
+
+    return rc;
+}
+
+
+
+int
 pm_writelittlelong(FILE * const ofP, 
                    long   const l) {
 
@@ -892,10 +1031,6 @@ pm_check(FILE *               const file,
     pm_filepos curpos;  /* Current position of file; -1 if none */
     int rc;
 
-#ifdef LARGEFILEDEBUG
-    pm_message("pm_filepos received by pm_check() is %u bytes.",
-               sizeof(pm_filepos));
-#endif
     /* Note: FTELLO() is either ftello() or ftell(), depending on the
        capabilities of the underlying C library.  It is defined in
        pm_config.h.  ftello(), in turn, may be either ftell() or
diff --git a/lib/pnm.h b/lib/pnm.h
index e4bd34a8..3b490552 100644
--- a/lib/pnm.h
+++ b/lib/pnm.h
@@ -20,6 +20,9 @@ typedef pixval xelval;
 #define PNM_OVERALLMAXVAL PPM_OVERALLMAXVAL
 #define PNM_MAXMAXVAL PPM_MAXMAXVAL
 #define PNM_GET1(x) PPM_GETB(x)
+#define PNM_GETR(x) PPM_GETR(x)
+#define PNM_GETG(x) PPM_GETG(x)
+#define PNM_GETB(x) PPM_GETB(x)
 #define PNM_ASSIGN1(x,v) PPM_ASSIGN(x,0,0,v)
 #define PNM_ASSIGN(x,r,g,b) PPM_ASSIGN(x,r,g,b)
 #define PNM_EQUAL(x,y) PPM_EQUAL(x,y)
@@ -34,7 +37,7 @@ pnm_init(int *   const argcP,
          char ** const argv);
 
 void
-pnm_nextimage(FILE *file, int * const eofP);
+pnm_nextimage(FILE * const file, int * const eofP);
 
 xel *
 pnm_allocrow(unsigned int const cols);
diff --git a/lib/ppm.h b/lib/ppm.h
index 719189bf..82241b70 100644
--- a/lib/ppm.h
+++ b/lib/ppm.h
@@ -3,6 +3,7 @@
 #ifndef _PPM_H_
 #define _PPM_H_
 
+#include <netpbm/pm_config.h>
 #include <netpbm/pm.h>
 #include <netpbm/pgm.h>
 
@@ -76,7 +77,7 @@ ppm_blackpixel(void) {
     return retval;
 }
 
-void ppm_init(int * argcP, char* argv[]);
+void ppm_init(int * const argcP, char ** const argv);
 
 #define ppm_allocarray(cols, rows) \
   ((pixel**) pm_allocarray(cols, rows, sizeof(pixel)))
@@ -250,6 +251,12 @@ ppm_hsv_from_color(pixel  const color,
                    pixval const maxval);
 
 static __inline__ pixval
+ppm_luminosity(pixel const p) {
+
+    return (pixval)(PPM_LUMIN(p) + 0.5);
+}
+
+static __inline__ pixval
 ppm_colorvalue(pixel const p) {
 /*----------------------------------------------------------------------------
   The color value (V is HSV) as a pixval
diff --git a/lib/ppmdfont.c b/lib/ppmdfont.c
index c0db3f51..f64cd10f 100644
--- a/lib/ppmdfont.c
+++ b/lib/ppmdfont.c
@@ -3,8 +3,8 @@
 #include <errno.h>
 #include <string.h>
 
+#include "netpbm/mallocvar.h"
 #include "pm.h"
-#include "mallocvar.h"
 #include "ppmdfont.h"
 
 
diff --git a/lib/ppmdraw.h b/lib/ppmdraw.h
index d7a02e79..df22b44d 100644
--- a/lib/ppmdraw.h
+++ b/lib/ppmdraw.h
@@ -8,6 +8,8 @@
 ** a single point, and it looks like this:
 */
 
+#include <netpbm/pm_config.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/lib/rgb.txt b/lib/rgb.txt
index ad4d9d87..28231080 100644
--- a/lib/rgb.txt
+++ b/lib/rgb.txt
@@ -876,6 +876,16 @@
 139   0   0 DarkRed
 144 238 144 LightGreen
 
+# From Wikipedia article on Ochre, 10.04.21
+
+204 119  34 Ochre
+
+# From Wikipedia article on Azure, 15.11.30
+# Note that this is much different from the older colors of that name in this
+# file.
+
+  0 127 255 Azure5
+
 
 # These were more or less invented for use with Netpbm:
 255 255 255  D65
diff --git a/lib/standardppmdfont.c b/lib/standardppmdfont.c
index aa4707e2..bc0c8ff8 100644
--- a/lib/standardppmdfont.c
+++ b/lib/standardppmdfont.c
@@ -1,4 +1,4 @@
-/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmfont file. */
+/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmdfont file. */
 
 #include "ppmdfont.h"
 
diff --git a/lib/util/Makefile b/lib/util/Makefile
index 8b3fa1c1..02119edf 100644
--- a/lib/util/Makefile
+++ b/lib/util/Makefile
@@ -5,23 +5,35 @@ endif
 SUBDIR = lib/util
 VPATH=.:$(SRCDIR)/$(SUBDIR)
 
+default:all
+
 include $(BUILDDIR)/config.mk
 
 # nstring is required for asprintf(), etc.  Also some systems don't have
 # snprintf(), e.g. Solaris 2.5.1.  2002.03.29.
-UTILOBJECTS = shhopt.o nstring.o vasprintf.o filename.o nsleep.o
+UTILOBJECTS = \
+  bitio.o \
+  filename.o \
+  io.o \
+  mallocvar.o \
+  matrix.o \
+  nsleep.o \
+  nstring.o \
+  runlength.o \
+  shhopt.o \
+  token.o \
+  vasprintf.o \
 
 MERGE_OBJECTS =
 
-all: $(UTILOBJECTS)
-
 include $(SRCDIR)/common.mk
 
-INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
+all: $(UTILOBJECTS)
+
+$(UTILOBJECTS): CFLAGS_TARGET=$(CFLAGS_SHLIB)
 
 $(UTILOBJECTS):%.o:%.c importinc
-	$(CC) -c $(INCLUDES) -DNDEBUG $(CPPFLAGS) $(CFLAGS) $(CFLAGS_SHLIB) \
-	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+	$(CC) -c $(INCLUDES) $(CFLAGS_ALL) -o $@ $<
 
 testnstring: test.c nstring.h nstring.o
-	$(CC) $(CFLAGS) $(CADD) -o $@ nstring.o $<
+	$(CC) $(CFLAGS_ALL) -o $@ nstring.o $<
diff --git a/lib/bitio.c b/lib/util/bitio.c
index ca1b55f9..ca1b55f9 100644
--- a/lib/bitio.c
+++ b/lib/util/bitio.c
diff --git a/lib/bitio.h b/lib/util/bitio.h
index dfc5a153..dfc5a153 100644
--- a/lib/bitio.h
+++ b/lib/util/bitio.h
diff --git a/lib/util/filename.c b/lib/util/filename.c
index e3a9a89f..18c12e3c 100644
--- a/lib/util/filename.c
+++ b/lib/util/filename.c
@@ -20,7 +20,7 @@ pm_basename(const char * const fileName) {
         if (fileName[i] == '/')
             basenameStart = i+1;
     }
-    asprintfN(&retval, "%s", &fileName[basenameStart]);
+    pm_asprintf(&retval, "%s", &fileName[basenameStart]);
 
     return retval;
 }
diff --git a/lib/util/floatcode.h b/lib/util/floatcode.h
index 23c57e9b..dc31d038 100644
--- a/lib/util/floatcode.h
+++ b/lib/util/floatcode.h
@@ -124,7 +124,7 @@ pm_doubleFromBigendDouble(pm_bigendDouble const arg) {
     }; break;
     case LITTLE_ENDIAN: {
         union {
-            unsigned char bytes[4];
+            unsigned char bytes[8];
             double native;
         } converter;
 
@@ -163,7 +163,7 @@ pm_bigendDoubleFromDouble(double const arg) {
     } break;
     case LITTLE_ENDIAN: {
         union {
-            unsigned char bytes[4];
+            unsigned char bytes[8];
             double native;
         } converter;
 
diff --git a/lib/util/intcode.h b/lib/util/intcode.h
index dd42f669..1066ee9b 100644
--- a/lib/util/intcode.h
+++ b/lib/util/intcode.h
@@ -188,8 +188,6 @@ typedef struct {
    This is an important data type because decent file formats use
    big-endian -- they don't care if some CPU happens to use some other
    code for its own work.
-
-   uint64_t is supported only when available.
 -----------------------------------------------------------------------------*/
     unsigned char bytes[8];
 } bigend64;
diff --git a/lib/util/io.c b/lib/util/io.c
new file mode 100644
index 00000000..54ecb6a8
--- /dev/null
+++ b/lib/util/io.c
@@ -0,0 +1,92 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+
+#include "io.h"
+
+
+void
+pm_freadline(FILE *        const fileP,
+             const char ** const lineP,
+             const char ** const errorP) {
+/*----------------------------------------------------------------------------
+   Read a line (assuming the file is text with lines delimited by newlines)
+   from file *fileP.  Return that line in newly malloced storage as *lineP.
+
+   The newline delimiter is not part of the line.
+
+   As a special case, if the file doesn't end in a newline, the characters
+   after the last newline are a line.
+
+   If there are no more lines in the file, return *lineP == NULL.
+-----------------------------------------------------------------------------*/
+    char * buffer;
+    size_t bufferSize;
+    size_t cursor;
+    bool gotLine;
+    bool eof;
+
+    bufferSize = 1024;  /* initial value */
+    *errorP = NULL; /* initial value */
+    
+    MALLOCARRAY(buffer, bufferSize);
+
+    for (cursor = 0, gotLine = false, eof = false;
+         !gotLine && !eof && !*errorP; ) {
+        if (cursor + 1 >= bufferSize) {
+            if (bufferSize > INT_MAX/2) {
+                free(buffer);
+                buffer = NULL;
+            } else {
+                bufferSize *= 2;
+                REALLOCARRAY(buffer, bufferSize);
+            }
+        }
+
+        if (!buffer)
+            pm_asprintf(errorP,
+                        "Couldn't get memory for a %u-byte file read buffer.",
+                        (unsigned int)bufferSize);
+        else {
+            int const rc = getc(fileP);
+        
+            if (rc < 0) {
+                if (feof(fileP))
+                    eof = true;
+                else
+                    pm_asprintf(errorP,
+                                "Failed to read a character from file.  "
+                                "Errno = %d (%s)",
+                                errno, strerror(errno));
+            } else {
+                char const c = (char)rc;
+
+                if (c == '\n')
+                    gotLine = true;
+                else {
+                    buffer[cursor++] = c;
+                }
+            }
+        }
+    }
+    if (*errorP) {
+        if (buffer)
+            free(buffer);
+    } else {
+        if (eof && cursor == 0) {
+            *lineP = NULL;
+            free(buffer);
+        } else {
+            assert(cursor < bufferSize);
+            buffer[cursor++] = '\0';
+
+            *lineP = buffer;
+        }
+    }
+}
+
+
diff --git a/lib/util/io.h b/lib/util/io.h
new file mode 100644
index 00000000..39ccd14b
--- /dev/null
+++ b/lib/util/io.h
@@ -0,0 +1,11 @@
+#ifndef IO_H_INCLUDED
+#define IO_H_INCLUDED
+
+#include <stdio.h>
+
+void
+pm_freadline(FILE *        const fileP,
+             const char ** const lineP,
+             const char ** const errorP);
+
+#endif
diff --git a/lib/util/mallocvar.c b/lib/util/mallocvar.c
new file mode 100644
index 00000000..339d5d61
--- /dev/null
+++ b/lib/util/mallocvar.c
@@ -0,0 +1,185 @@
+#include <limits.h>
+#include <stdlib.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+
+#include "mallocvar.h"
+
+
+static void *
+mallocz(size_t const size) {
+/*----------------------------------------------------------------------------
+   Same as malloc(), except it is legal to allocate zero bytes.
+-----------------------------------------------------------------------------*/
+    return malloc(MAX(1, size));
+}
+
+
+
+static void
+allocarrayNoHeap(void **      const rowIndex,
+                 unsigned int const rows,
+                 unsigned int const cols,
+                 unsigned int const elementSize,
+                 bool *       const failedP) {
+
+    unsigned int rowsDone;
+
+    for (rowsDone = 0, *failedP = false; rowsDone < rows && !*failedP; ) {
+        void * rowSpace;
+
+        mallocProduct(&rowSpace, cols, elementSize);
+        
+        if (rowSpace == NULL) {
+            unsigned int row;
+
+            *failedP = true;
+
+            /* unwind partially completed job */
+            for (row = 0; row < rowsDone; ++row)
+                free(rowIndex[row]);
+        } else
+            rowIndex[rowsDone++] = rowSpace;
+    }
+}
+
+
+
+static unsigned char *
+allocRowHeap(unsigned int const rows,
+             unsigned int const cols,
+             unsigned int const size) {
+/*----------------------------------------------------------------------------
+   Allocate a row heap.  That's a chunk of memory for use in a
+   pm_mallocarray2 two-dimensional array to contain the rows.
+
+   The heap must fit 'rows' rows of 'cols' columns each of elements
+   'size' bytes in size.
+
+   Return NULL if we can't get the memory.
+-----------------------------------------------------------------------------*/
+    unsigned char * retval;
+
+    if (cols != 0 && rows != 0 && UINT_MAX / cols / rows < size)
+        /* Too big even to request the memory ! */
+        retval = NULL;
+    else
+        retval = mallocz(rows * cols * size);
+
+    return retval;
+}
+
+
+
+void
+pm_mallocarray2(void **      const resultP,
+                unsigned int const rows,
+                unsigned int const cols,
+                unsigned int const elementSize)  {
+/*----------------------------------------------------------------------------
+   Allocate an array of 'rows' rows of 'cols' columns each, with each
+   element 'size' bytes.
+
+   We use the C multidimensional array paradigm:  The array is a row
+   index (array of pointers to rows) plus an array of elements for each
+   of those rows.  So a[row][col] gives you the element of the two
+   dimensional array at Row 'row', Column 'col'.
+
+   Each array element is ideally aligned to an 'elementSize' boundary.  But we
+   guarantee this only for 1, 2, 4, 8, and 16, because of limitations of libc
+   malloc() (which we use to allocate all the memory).
+
+   We tack on two extra elements to the end of the row index, transparent to
+   the user, for use in memory management (in particular, in destroying the
+   array).  The first is a NULL pointer, so you can tell the vertical
+   dimension of the array.  The second points to the row heap (see below).
+
+   We have two ways of allocating the space: fragmented and unfragmented.  In
+   both, the row index (plus the extra elements) is in one block of memory.
+   In the fragmented format, each row is also in an independent memory block,
+   and the row heap pointer (see above) is NULL.  In the unfragmented format,
+   all the rows are in a single block of memory called the row heap and the
+   row heap pointer points to that.
+
+   We use unfragmented format if possible, but if the allocation of the
+   row heap fails, we fall back to fragmented.
+-----------------------------------------------------------------------------*/
+    void ** rowIndex;
+    bool failed;
+
+    MALLOCARRAY(rowIndex, rows + 1 + 1);
+    if (rowIndex == NULL)
+        failed = true;
+    else {
+        unsigned char * rowheap;
+
+        rowheap = allocRowHeap(cols, rows, elementSize);
+
+        if (rowheap) {
+            /* It's unfragmented format */
+
+            rowIndex[rows+1] = rowheap;  /* Declare it unfragmented format */
+
+            if (rowheap) {
+                unsigned int row;
+                
+                for (row = 0; row < rows; ++row)
+                    rowIndex[row] = &(rowheap[row * cols * elementSize]);
+            }
+            failed = false;
+        } else {
+            /* We couldn't get the whole heap in one block, so try fragmented
+               format.
+            */
+            rowIndex[rows+1] = NULL;   /* Declare it fragmented format */
+            
+            allocarrayNoHeap(rowIndex, rows, cols, elementSize, &failed);
+        }
+        rowIndex[rows+0] = NULL;   /* mark end of rows */
+    }
+    if (failed)
+        *resultP = NULL;
+    else
+        *resultP = rowIndex;
+}
+
+
+
+static unsigned int
+array2RowCount(void ** const rowIndex) {
+/*----------------------------------------------------------------------------
+   Return the number of rows in the 2-dimensional array.
+-----------------------------------------------------------------------------*/
+    /* The end of the rows is marked by a null pointer where a row 
+       pointer otherwise would be.
+    */
+
+    unsigned int row;
+
+    for (row = 0; rowIndex[row]; ++row);
+
+    return row;
+}
+
+
+
+void
+pm_freearray2(void ** const rowIndex) {
+
+    unsigned int const rows = array2RowCount(rowIndex);
+
+    void * const rowheap = rowIndex[rows+1];
+
+    if (rowheap != NULL)
+        free(rowheap);
+    else {
+        /* Free each individually malloced row */
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pm_freerow(rowIndex[row]);
+    }
+    free(rowIndex);
+}
+
+
diff --git a/lib/util/mallocvar.h b/lib/util/mallocvar.h
index 1f2be127..e92e3fe4 100644
--- a/lib/util/mallocvar.h
+++ b/lib/util/mallocvar.h
@@ -87,7 +87,7 @@ reallocProduct(void **      const blockP,
     void * array; \
     array = arrayName; \
     reallocProduct(&array, nElements, sizeof(arrayName[0])); \
-    if (!array) \
+    if (!array && arrayName) \
         free(arrayName); \
     arrayName = array; \
 } while (0)
@@ -107,6 +107,21 @@ do { \
         abort(); \
 } while(0)
 
+#define MALLOCARRAY2(arrayName, nRows, nCols) do { \
+    void * array; \
+    pm_mallocarray2(&array, nRows, nCols, sizeof(arrayName[0][0]));  \
+    arrayName = array; \
+} while (0)
+
+#define MALLOCARRAY2_NOFAIL(arrayName, nRows, nCols) do { \
+    MALLOCARRAY2(arrayName, nRows, nCols);       \
+    if ((arrayName) == NULL) \
+        abort(); \
+} while (0)
+
+void
+pm_freearray2(void ** const rowIndex);
+
 
 #define MALLOCVAR(varName) \
     varName = malloc(sizeof(*varName))
@@ -114,6 +129,12 @@ do { \
 #define MALLOCVAR_NOFAIL(varName) \
     do {if ((varName = malloc(sizeof(*varName))) == NULL) abort();} while(0)
 
+void
+pm_mallocarray2(void **      const resultP,
+                unsigned int const cols,
+                unsigned int const rows,
+                unsigned int const elementSize);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/util/matrix.c b/lib/util/matrix.c
new file mode 100644
index 00000000..e9456e93
--- /dev/null
+++ b/lib/util/matrix.c
@@ -0,0 +1,223 @@
+/*=============================================================================
+                                matrix
+===============================================================================
+
+  Matrix math.
+
+=============================================================================*/
+
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#include "matrix.h"
+
+
+static double const epsilon = 1e-10;
+
+
+
+static void
+swap(double * const aP,
+     double * const bP) {
+
+    double const oldA = *aP;
+
+    *aP = *bP;
+    *bP = oldA;
+}
+
+
+
+static void
+initializeWorkMatrices(unsigned int   const n,
+                       double **      const aInit,
+                       const double * const cInit,
+                       double ***     const aP,
+                       double **      const cP,
+                       const char **  const errorP) {
+/*----------------------------------------------------------------------------
+   Allocate memory for an n x n matrix, initialize it to the value of
+   aInit[], and return it as *aP.
+
+   Allocate memory for an n x 1 matrix, initialize it to the value of
+   cInit[], and return it as *cP.
+-----------------------------------------------------------------------------*/
+    double ** a;
+    double * c;
+
+    MALLOCARRAY2(a, n, n);
+    if (a == NULL)
+        pm_asprintf(errorP, "Could not get memory for a %u x %u matrix", n, n);
+    else {
+        unsigned int i;
+        for (i = 0; i < n; ++i) {
+            unsigned int j;
+            for (j = 0; j < n; ++j)
+                a[i][j] = aInit[i][j];
+        }
+        MALLOCARRAY(c, n);
+        if (c == NULL)
+            pm_asprintf(errorP, "Could not get memory for a %u x 1 matrix", n);
+        else {
+            unsigned int i;
+            for (i = 0; i < n; ++i)
+                c[i] = cInit[i];
+
+            *errorP = NULL;
+        }
+        if (*errorP)
+            free(a);
+    }
+    *aP = a;
+    *cP = c;
+}
+
+
+
+static void
+findLargestIthCoeff(unsigned int   const n,
+                    double **      const a,
+                    unsigned int   const i,
+                    unsigned int * const istarP,
+                    const char **  const errorP) {
+/*----------------------------------------------------------------------------
+  Among the 'i'th and following rows in 'a' (which has 'n' total rows),
+  find the one with the largest 'i'th column.
+
+  And it had better be greater than zero; if not, we fail (return *errorP
+  non-null).
+
+  Return its index as *istarP.
+-----------------------------------------------------------------------------*/
+    double maxSoFar;
+    unsigned int maxIdx;
+    unsigned int ii;
+
+    for (ii = i, maxSoFar = 0.0; ii < n; ++ii) {
+        double const thisA = fabs(a[ii][i]);
+        if (thisA >= maxSoFar) {
+            maxIdx = ii;
+            maxSoFar = thisA;
+        }
+    }
+    if (maxSoFar < epsilon) {
+        const char * const baseMsg = "Matrix equation has no unique solution";
+        if (pm_have_float_format())
+            pm_asprintf(errorP, "%s.  (debug: coeff %u %e < %e)",
+                        baseMsg, i, maxSoFar, epsilon);
+        else
+            pm_asprintf(errorP, "%s", baseMsg);
+    } else {
+        *istarP = maxIdx;
+        *errorP = NULL;
+    }
+}
+
+
+
+static void
+eliminateOneUnknown(unsigned int  const i,
+                    unsigned int  const n,
+                    double **     const a,
+                    double *      const c,
+                    const char ** const errorP) {
+
+    unsigned int maxRow;
+
+    findLargestIthCoeff(n, a, i, &maxRow, errorP);
+
+    if (!*errorP) {
+        /* swap rows 'i' and 'maxRow' in 'a' and 'c', so that the ith
+           row has the largest ith coefficient.
+        */
+        unsigned int j;
+        for (j = 0; j < n; j++)
+            swap(&a[maxRow][j], &a[i][j]);
+
+        swap(&c[maxRow], &c[i]);
+
+        /* Combine rows so that the ith coefficient in every row below
+           the ith is zero.
+        */
+        {
+            unsigned int ii;
+            for (ii = i+1; ii < n; ++ii) {
+                double const multiplier = a[ii][i] / a[i][i];
+                    /* This is what we multiply the whole ith row by to make
+                       its ith coefficient equal to that in the iith row.
+                    */
+                unsigned int j;
+
+                /* Combine ith row into iith row so that the ith coefficient
+                   in the iith is zero.
+                */
+                c[ii] = c[ii] - multiplier * c[i];
+
+                for (j = 0; j < n; ++j)
+                    a[ii][j] = a[ii][j] - multiplier * a[i][j];
+
+                assert(a[ii][i] < epsilon);
+            }
+        }
+        *errorP = NULL;
+    }
+}
+
+
+
+void
+pm_solvelineareq(double **     const aArg,
+                 double *      const x,
+                 double *      const cArg,
+                 unsigned int  const n,
+                 const char ** const errorP) {
+/*----------------------------------------------------------------------------
+   Solve the matrix equation 'a' * 'x' = 'c' for 'x'.
+
+   'n' is the dimension of the matrices.  'a' is 'n' x 'n',
+   while 'x' and 'c' are 'n' x 1.
+-----------------------------------------------------------------------------*/
+    /* We use Gaussian reduction. */
+
+    double ** a;
+    double * c;
+
+    initializeWorkMatrices(n, aArg, cArg, &a, &c, errorP);
+
+    if (!*errorP) {
+        unsigned int i;
+
+        for (i = 0, *errorP = NULL; i < n && !*errorP; ++i)
+            eliminateOneUnknown(i, n, a, c, errorP);
+
+        if (!*errorP) {
+            /* a[] now has all zeros in the lower left triangle. */
+            /* Work from the bottom up to solve for the unknowns x[], from
+               the a and c rows in question and all the x[] below it
+            */
+            
+            unsigned int k;
+            for (k = 0; k < n; ++k) {
+                unsigned int const m = n - k - 1;
+                unsigned int j;
+                double xwork;
+
+                for (j = m+1, xwork = c[m]; j < n; ++j)
+                    xwork -= a[m][j] * x[j];
+                    
+                x[m] = xwork / a[m][m];
+            }
+        }
+    }
+    pm_freearray2((void**)a);
+    free(c);
+}
+
+
+
diff --git a/lib/util/matrix.h b/lib/util/matrix.h
new file mode 100644
index 00000000..13ae0373
--- /dev/null
+++ b/lib/util/matrix.h
@@ -0,0 +1,11 @@
+#ifndef MATRIX_H_INCLUDED
+#define MATRIX_H_INCLUDED
+
+void
+pm_solvelineareq(double **     const aArg,
+                 double *      const x,
+                 double *      const cArg,
+                 unsigned int  const n,
+                 const char ** const errorP);
+
+#endif
diff --git a/lib/util/nsleep.c b/lib/util/nsleep.c
index 943b8c77..24d48207 100644
--- a/lib/util/nsleep.c
+++ b/lib/util/nsleep.c
@@ -1,4 +1,6 @@
-#ifdef WIN32
+#include "pm_config.h"
+
+#if MSVCRT
   #include <windows.h>
   #include <process.h>
 #else
@@ -10,9 +12,9 @@
 
 
 void
-sleepN(unsigned int const milliseconds) {
+pm_sleep(unsigned int const milliseconds) {
 
-#ifdef WIN32
+#if MSVCRT
     SleepEx(milliseconds, TRUE);
 #else
 
diff --git a/lib/util/nsleep.h b/lib/util/nsleep.h
index 372b8008..fdf45ab7 100644
--- a/lib/util/nsleep.h
+++ b/lib/util/nsleep.h
@@ -2,6 +2,6 @@
 #define NSLEEP_H_INCLUDED
 
 void
-sleepN(unsigned int const milliseconds);
+pm_sleep(unsigned int const milliseconds);
 
 #endif
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 8842aa05..711cfca9 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -39,7 +39,7 @@
  * IMPLEMENTED CONVERSION SPECIFIERS AND DATA TYPES
  *
  * This snprintf implements only the following conversion specifiers:
- * s, c, d, u, o, x, X, p  (and synonyms: i, D, U, O - see below)
+ * 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.
  *
@@ -66,7 +66,7 @@
  *
  * The following is specifically NOT implemented:
  *   - flag ' (thousands' grouping character) is recognized but ignored
- *   - numeric conversion specifiers: f, e, E, g, G and synonym F,
+ *   - 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
@@ -113,6 +113,12 @@
  */
 
 
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+#define _BSD_SOURCE  /* Make sure strdup() is in string.h */
+#define _GNU_SOURCE
+   /* 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
 
@@ -190,12 +196,12 @@ static char credits[] = "\n\
 
 
 void
-vsnprintfN(char *       const str,
-           size_t       const str_m,
-           const char * const fmt,
-           va_list            ap,
-           size_t *     const sizeP) {
-
+pm_vsnprintf(char *       const str,
+             size_t       const str_m,
+             const char * const fmt,
+             va_list            ap,
+             size_t *     const sizeP) {
+    
     size_t str_l = 0;
     const char *p = fmt;
 
@@ -215,16 +221,19 @@ vsnprintfN(char *       const str,
             const char *q = strchr(p + 1,'%');
             size_t n = !q ? strlen(p) : (q - p);
             if (str_l < str_m) {
-                size_t avail = str_m - str_l;
-                fast_memcpy(str + str_l, p, (n > avail ? avail : n));
+                size_t const avail = str_m - str_l;
+                fast_memcpy(str + str_l, p, (MIN(n, avail)));
             }
             p += n; str_l += n;
         } else {
             const char *starting_p;
-            size_t min_field_width = 0, precision = 0;
-            int zero_padding = 0, precision_specified = 0, justify_left = 0;
-            int alternate_form = 0, force_sign = 0;
-            int space_for_positive = 1;
+            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.
                 */
@@ -242,16 +251,18 @@ vsnprintfN(char *       const str,
                    argument for the c conversion is unsigned.
                 */
 
-            size_t number_of_zeros_to_pad = 0;
+            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 = 0;
+            size_t zero_padding_insertion_ind;
                 /* index into tmp where zero padding is to be inserted */
 
-            char fmt_spec = '\0';
+            char fmt_spec;
                 /* current conversion specifier character */
 
             str_arg = credits;
@@ -261,17 +272,26 @@ vsnprintfN(char *       const str,
             ++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 = 1; break;
-                case '-': justify_left = 1; break;
-                case '+': force_sign = 1; space_for_positive = 0; break;
-                case ' ': force_sign = 1; break;
+                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 = 1; break;
+                case '#': alternate_form = true; break;
                 case '\'': break;
                 }
                 ++p;
@@ -283,9 +303,10 @@ vsnprintfN(char *       const str,
             /* parse field width */
             if (*p == '*') {
                 int j;
-                p++; j = va_arg(ap, int);
-                if (j >= 0) min_field_width = j;
-                else { min_field_width = -j; justify_left = 1; }
+                ++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
@@ -294,16 +315,19 @@ vsnprintfN(char *       const str,
                 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 = 1;
+                ++p;
+                precision_specified = true;
                 if (*p == '*') {
                     int j = va_arg(ap, int);
                     p++;
                     if (j >= 0) precision = j;
                     else {
-                        precision_specified = 0; precision = 0;
+                        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
@@ -322,7 +346,9 @@ vsnprintfN(char *       const str,
                         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++;
@@ -357,7 +383,7 @@ vsnprintfN(char *       const str,
                     Unix and Linux does not.
                 */
 
-                zero_padding = 0;
+                zero_padding = false;
                     /* turn zero padding off for string conversions */
                 str_arg_l = 1;
                 switch (fmt_spec) {
@@ -387,9 +413,7 @@ vsnprintfN(char *       const str,
                     else {
                         /* memchr on HP does not like n > 2^31  !!! */
                         const char * q =
-                            memchr(str_arg, '\0',
-                                   precision <= 0x7fffffff ?
-                                   precision : 0x7fffffff);
+                            memchr(str_arg, '\0', MIN(precision, 0x7fffffff));
                         str_arg_l = !q ? precision : (q-str_arg);
                     }
                     break;
@@ -483,7 +507,7 @@ vsnprintfN(char *       const str,
                    Perl.
                 */
                 if (precision_specified)
-                    zero_padding = 0;
+                    zero_padding = false;
                 if (fmt_spec == 'd') {
                     if (force_sign && arg_sign >= 0)
                         tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
@@ -549,9 +573,9 @@ vsnprintfN(char *       const str,
                     */
                     if (zero_padding_insertion_ind < str_arg_l &&
                         tmp[zero_padding_insertion_ind] == '-') {
-                        zero_padding_insertion_ind++;
+                        zero_padding_insertion_ind += 1;
                     }
-                    if (zero_padding_insertion_ind+1 < str_arg_l &&
+                    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') ) {
@@ -559,7 +583,7 @@ vsnprintfN(char *       const str,
                     }
                 }
                 {
-                    size_t num_of_digits =
+                    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 */
@@ -576,7 +600,7 @@ vsnprintfN(char *       const str,
                                explicit precision of zero
                             */
                             precision = num_of_digits+1;
-                            precision_specified = 1;
+                            precision_specified = true;
                         }
                     }
                     /* zero padding to specified precision? */
@@ -585,23 +609,37 @@ vsnprintfN(char *       const str,
                 }
                 /* zero padding to specified minimal field width? */
                 if (!justify_left && zero_padding) {
-                    int n =
+                    int const n =
                         min_field_width - (str_arg_l+number_of_zeros_to_pad);
-                    if (n > 0) number_of_zeros_to_pad += n;
+                    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, keep format
-                   string as-is
+                /* Unrecognized conversion specifier.  Discard the
+                   unrecognized conversion, just keep the unrecognized
+                   conversion character.
                 */
-                zero_padding = 0;
+                zero_padding = false;
                     /* turn zero padding off for non-numeric convers. */
                 /* reset flags */
-                justify_left = 1;
+                justify_left = true;
                 min_field_width = 0;
-                /* discard the unrecognized conversion, just keep the
-                   unrecognized conversion character
-                */
                 str_arg = p;
                 str_arg_l = 0;
                 if (*p)
@@ -620,12 +658,12 @@ vsnprintfN(char *       const str,
 
             if (!justify_left) {
                 /* left padding with blank or zero */
-                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                int n = min_field_width - (str_arg_l + number_of_zeros_to_pad);
                 if (n > 0) {
                     if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memset(str+str_l, (zero_padding ? '0' : ' '),
-                                    (n > avail ? avail : n));
+                        size_t const avail = str_m - str_l;
+                        fast_memset(str + str_l, (zero_padding ? '0' : ' '),
+                                    (MIN(n, avail)));
                     }
                     str_l += n;
                 }
@@ -639,40 +677,44 @@ vsnprintfN(char *       const str,
                 */
                 zero_padding_insertion_ind = 0;
             } else {
-                /* insert first part of numerics (sign or '0x') before
-                   zero padding
-                */
-                int n = zero_padding_insertion_ind;
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
+                {
+                    /* 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;
                     }
-                    str_l += n;
                 }
-                /* insert zero padding as requested by the precision
-                   or min field width
-                */
-                n = number_of_zeros_to_pad;
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t avail = str_m - str_l;
-                        fast_memset(str + str_l, '0', (n > avail ? avail : 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;
                     }
-                    str_l += n;
                 }
             }
             /* insert formatted string (or as-is conversion specifier
                for unknown conversions)
             */
             {
-                int n = str_arg_l - zero_padding_insertion_ind;
+                int const n = str_arg_l - zero_padding_insertion_ind;
                 if (n > 0) {
                     if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
+                        size_t const avail = str_m-str_l;
                         fast_memcpy(str + str_l,
                                     str_arg + zero_padding_insertion_ind,
-                                    (n > avail ? avail : n));
+                                    MIN(n, avail));
                     }
                     str_l += n;
                 }
@@ -680,11 +722,12 @@ vsnprintfN(char *       const str,
             /* insert right padding */
             if (justify_left) {
                 /* right blank padding to the field width */
-                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                int const n =
+                    min_field_width - (str_arg_l + number_of_zeros_to_pad);
                 if (n > 0) {
                     if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memset(str+str_l, ' ', (n>avail?avail:n));
+                        size_t const avail = str_m - str_l;
+                        fast_memset(str+str_l, ' ', (MIN(n, avail)));
                     }
                     str_l += n;
                 }
@@ -696,7 +739,7 @@ vsnprintfN(char *       const str,
            of overwriting the last character (shouldn't happen, but
            just in case)
         */
-        str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+        str[MIN(str_l, str_m - 1)] = '\0';
     }
     *sizeP = str_l;
 }
@@ -704,17 +747,17 @@ vsnprintfN(char *       const str,
 
 
 int
-snprintfN(char *       const dest,
-          size_t       const str_m,
-          const char * const fmt,
-          ...) {
+pm_snprintf(char *       const dest,
+            size_t       const str_m,
+            const char * const fmt,
+            ...) {
 
     size_t size;
     va_list ap;
 
     va_start(ap, fmt);
     
-    vsnprintfN(dest, str_m, fmt, ap, &size);
+    pm_vsnprintf(dest, str_m, fmt, ap, &size);
 
     va_end(ap);
 
@@ -726,73 +769,96 @@ snprintfN(char *       const dest,
 
 
 /* When a function that is supposed to return a malloc'ed string cannot
-   get the memory for it, it should return 'strsol'.  That has a much
+   get the memory for it, it should return 'pm_strsol'.  That has a much
    better effect on the caller, if the caller doesn't explicitly allow for
    the out of memory case, than returning NULL.  Note that it is very
    rare for the system not to have enough memory to return a small string,
    so it's OK to have somewhat nonsensical behavior when it happens.  We
    just don't want catastrophic behavior.
 
-   'strsol' is an external symbol, so if Caller wants to detect the
+   'pm_strsol' is an external symbol, so if Caller wants to detect the
    out-of-memory failure, he certainly can.
 */
-const char * const strsol = "NO MEMORY TO CREATE STRING!";
+const char * const pm_strsol = "NO MEMORY TO CREATE STRING!";
+
+
+
+const char *
+pm_strdup(const char * const arg) {
+
+    const char * const dup = strdup(arg);
+
+    return dup ? dup : pm_strsol;
+}
 
 
 
 void PM_GNU_PRINTF_ATTR(2,3)
-asprintfN(const char ** const resultP,
-          const char *  const fmt, 
-          ...) {
+pm_asprintf(const char ** const resultP,
+            const char *  const fmt, 
+            ...) {
 
+    const char * result;
     va_list varargs;
     
+#if HAVE_VASPRINTF
+    int rc;
+    va_start(varargs, fmt);
+    rc = vasprintf((char **)&result, fmt, varargs);
+    va_end(varargs);
+    if (rc < 0)
+        result = pm_strsol;
+#else
     size_t dryRunLen;
     
     va_start(varargs, fmt);
-    
-    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+
+    pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen);
 
     va_end(varargs);
 
     if (dryRunLen + 1 < dryRunLen)
         /* arithmetic overflow */
-        *resultP = strsol;
+        result = pm_strsol;
     else {
         size_t const allocSize = dryRunLen + 1;
-        char * result;
-        result = malloc(allocSize);
-        if (result == NULL)
-            *resultP = strsol;
-        else {
+        char * buffer;
+        buffer = malloc(allocSize);
+        if (buffer != NULL) {
             va_list varargs;
             size_t realLen;
 
             va_start(varargs, fmt);
 
-            vsnprintfN(result, allocSize, fmt, varargs, &realLen);
+            pm_vsnprintf(buffer, allocSize, fmt, varargs, &realLen);
                 
             assert(realLen == dryRunLen);
             va_end(varargs);
 
-            *resultP = result;
+            result = buffer;
         }
     }
+#endif
+
+    if (result == NULL)
+        *resultP = pm_strsol;
+    else
+        *resultP = result;
 }
 
 
 
 void
-strfree(const char * const string) {
+pm_strfree(const char * const string) {
 
-    if (string != strsol)
+    if (string != pm_strsol)
         free((void *) string);
 }
 
 
 
 const char *
-strsepN(char ** const stringP, const char * const delim) {
+pm_strsep(char ** const stringP, const char * const delim) {
     const char * retval;   
 
     if (stringP == NULL || *stringP == NULL)
@@ -824,65 +890,75 @@ strsepN(char ** const stringP, const char * const delim) {
 
 
 int
-stripeq(const char * const comparand,
-        const char * const comparator) {
+pm_stripeq(const char * const comparand,
+           const char * const comparator) {
 /*----------------------------------------------------------------------------
   Compare two strings, ignoring leading and trailing white space.
 
   Return 1 (true) if the strings are identical; 0 (false) otherwise.
 -----------------------------------------------------------------------------*/
-    char *p, *q, *px, *qx;
-    char equal;
+    const char * p;
+    const char * q;
+    const char * px;
+    const char * qx;
+    bool equal;
   
     /* Make p and q point to the first non-blank character in each string.
-     If there are no non-blank characters, make them point to the terminating
-     NULL.
-     */
+       If there are no non-blank characters, make them point to the terminating
+       NUL.
+    */
 
-    p = (char *) comparand;
-    while (ISSPACE(*p)) p++;
-    q = (char *) comparator;
-    while (ISSPACE(*q)) q++;
+    p = &comparand[0];
+    while (ISSPACE(*p))
+        p++;
+    q = &comparator[0];
+    while (ISSPACE(*q))
+        q++;
 
     /* Make px and qx point to the last non-blank character in each string.
        If there are no nonblank characters (which implies the string is
-       null), make them point to the terminating NULL.
+       null), make them point to the terminating NUL.
     */
 
-    if (*p == '\0') px = p;
+    if (*p == '\0')
+        px = p;
     else {
         px = p + strlen(p) - 1;
-        while (ISSPACE(*px)) px--;
+        while (ISSPACE(*px))
+            --px;
     }
 
-    if (*q == '\0') qx = q;
+    if (*q == '\0')
+        qx = q;
     else {
         qx = q + strlen(q) - 1;
-        while (ISSPACE(*qx)) qx--;
+        while (ISSPACE(*qx))
+            --qx;
     }
 
-    equal = 1;   /* initial assumption */
-  
-    /* If the stripped strings aren't the same length, 
-       we know they aren't equal 
-     */
-    if (px - p != qx - q) equal = 0;
-
-    else
-    while (p <= px) {
-        if (*p != *q) equal = 0;
-        p++; q++;
+    if (px - p != qx - q) {
+        /* The stripped strings aren't the same length, so we know they aren't
+           equal.
+        */
+        equal = false;
+    } else {
+        /* Move p and q through the nonblank characters, comparing. */
+        for (equal = true; p <= px; ++p, ++q) {
+            assert(q <= qx);  /* Because stripped strings are same length */
+            if (*p != *q)
+                equal = false;
+        }
     }
-    return equal;
+    return equal ? 1 : 0;
 }
 
 
 
 const void *
-memmemN(const void * const haystackArg,
-        size_t       const haystacklen,
-        const void * const needleArg,
-        size_t       const needlelen) {
+pm_memmem(const void * const haystackArg,
+          size_t       const haystacklen,
+          const void * const needleArg,
+          size_t       const needlelen) {
 
     const unsigned char * const haystack = haystackArg;
     const unsigned char * const needle   = needleArg;
@@ -902,7 +978,7 @@ memmemN(const void * const haystackArg,
 
 
 bool
-strishex(const char * const subject) {
+pm_strishex(const char * const subject) {
 
     bool retval;
     unsigned int i;
@@ -919,12 +995,12 @@ strishex(const char * const subject) {
 
 
 void
-interpret_uint(const char *   const string,
-               unsigned int * const valueP,
-               const char **  const errorP) {
+pm_interpret_uint(const char *   const string,
+                  unsigned int * const valueP,
+                  const char **  const errorP) {
 
     if (string[0] == '\0')
-        asprintfN(errorP, "Null string.");
+        pm_asprintf(errorP, "Null string.");
     else {
         /* strtoul() does a bizarre thing where if the number is out
            of range, it returns a clamped value but tells you about it
@@ -939,13 +1015,13 @@ interpret_uint(const char *   const string,
         ulongValue = strtoul(string, &tail, 10);
 
         if (tail[0] != '\0')
-            asprintfN(errorP, "Non-digit stuff in string: %s", tail);
+            pm_asprintf(errorP, "Non-digit stuff in string: %s", tail);
         else if (errno == ERANGE)
-            asprintfN(errorP, "Number too large");
+            pm_asprintf(errorP, "Number too large");
         else if (ulongValue > UINT_MAX)
-            asprintfN(errorP, "Number too large");
+            pm_asprintf(errorP, "Number too large");
         else if (string[0] == '-')
-            asprintfN(errorP, "Negative number");
+            pm_asprintf(errorP, "Negative number");
             /* Sleazy code; string may have leading spaces. */
         else {
             *valueP = ulongValue;
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 53f1e4c0..7238a76e 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -137,62 +137,68 @@ strncaseeq(const char * const comparand,
      - The returned string is a const char * instead of a char *.  The
        const is more correct.
 
-     - If the function can't get the memory, it returns 'strsol',
+     - If the function can't get the memory, it returns 'pm_strsol',
        which is a string that is in static memory that contains text
        indicating an out of memory failure has occurred, intead of
        NULL.  This makes it much easier for programs to ignore this
        possibility.
 
    strfree() is strictly a Netpbm invention, to allow proper type checking
-   when freeing storage allocated by the Netpbm asprintfN().
+   when freeing storage allocated by the Netpbm pm_asprintf().
 */
 
-extern const char * const strsol;
+extern const char * const pm_strsol;
 
 int
-snprintfN(char *       const dest,
-          size_t       const str_m,
-          const char * const fmt,
-          ...) PM_GNU_PRINTF_ATTR(3,4);
+pm_snprintf(char *       const dest,
+            size_t       const str_m,
+            const char * const fmt,
+            ...) PM_GNU_PRINTF_ATTR(3,4);
 
 void
-vsnprintfN(char *       const str,
-           size_t       const str_m,
-           const char * const fmt,
-           va_list            ap,
-           size_t *     const sizeP);
+pm_vsnprintf(char *       const str,
+             size_t       const str_m,
+             const char * const fmt,
+             va_list            ap,
+             size_t *     const sizeP);
+
+const char *
+pm_strdup(const char * const arg);
 
 void
-asprintfN(const char ** const resultP,
-          const char *  const fmt,
-          ...) PM_GNU_PRINTF_ATTR(2,3);
+pm_asprintf(const char ** const resultP,
+            const char *  const fmt,
+            ...) PM_GNU_PRINTF_ATTR(2,3);
 
 void
-vasprintfN(const char ** const resultP,
-           const char *  const format,
-           va_list             args);
+pm_vasprintf(const char ** const resultP,
+             const char *  const format,
+             va_list             args);
+
+bool
+pm_vasprintf_knows_float(void);
 
 void 
-strfree(const char * const string);
+pm_strfree(const char * const string);
 
 const char *
-strsepN(char ** const stringP, const char * const delim);
+pm_strsep(char ** const stringP, const char * const delim);
 
 int
-stripeq(const char * const comparand,
-        const char * const comparator);
+pm_stripeq(const char * const comparand,
+           const char * const comparator);
 
 const void *
-memmemN(const void * const haystackArg,
-        size_t       const haystacklen,
-        const void * const needleArg,
-        size_t       const needlelen);
+pm_memmem(const void * const haystackArg,
+          size_t       const haystacklen,
+          const void * const needleArg,
+          size_t       const needlelen);
 
 bool
-strishex(const char * const subject);
+pm_strishex(const char * const subject);
 
 void
-interpret_uint(const char *   const string,
+pm_interpret_uint(const char *   const string,
                unsigned int * const valueP,
                const char **  const errorP);
 
diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h
index 07913f30..01a07657 100644
--- a/lib/util/pm_c_util.h
+++ b/lib/util/pm_c_util.h
@@ -1,6 +1,11 @@
 #ifndef PM_C_UTIL_INCLUDED
 #define PM_C_UTIL_INCLUDED
 
+/* Note that for MAX and MIN, if either of the operands is a floating point
+   Not-A-Number, the result is the second operand.  So if you're computing a
+   running maximum and want to ignore the NaNs in the computation, put the
+   running maximum variable second.
+*/
 #undef MAX
 #define MAX(a,b) ((a) > (b) ? (a) : (b))
 #undef MIN
@@ -36,35 +41,49 @@
    use "int".
 */
 
-/* We used to assume that if TRUE was defined, then bool was too.
-   However, we had a report on 2001.09.21 of a Tru64 system that had
-   TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was
-   likewise.  So now we define bool all the time, unless the macro
-   HAVE_BOOL is defined.  If someone is using the Netpbm libraries and
-   also another library that defines bool, he can either make the
-   other library define/respect HAVE_BOOL or just define HAVE_BOOL in
-   the file that includes pm_config.h or with a compiler option.  Note
-   that C++ always has bool.  
+/* We will probably never again see a system that does not have
+   <stdbool.h>, but just in case, we have our own alternative here.
 
-   A preferred way of getting booleans is <stdbool.h>.  But it's not
-   available on all platforms, and it's easy to reproduce what it does
-   here.
+   Evidence shows that the compiler actually produces better code with
+   <stdbool.h> than with bool simply typedefed to int.
 */
+
+#ifdef __cplusplus
+  /* C++ has a bool type and false and true constants built in. */
+#else
+  /* The test for __STDC__ is paranoid.  It is there just in case some
+     nonstandard compiler defines __STDC_VERSION__ in an arbitrary manner.
+  */
+  #if ( defined(__GNUC__) && (__GNUC__ >= 3) ) || \
+      ( defined(__STDC__) && (__STDC__ ==1) && \
+        defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) ) 
+    #include <stdbool.h>
+  #else
+    /* We used to assume that if TRUE was defined, then bool was too.
+       However, we had a report on 2001.09.21 of a Tru64 system that had
+       TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was
+       likewise.  So now we define bool all the time, unless the macro
+       HAVE_BOOL is defined.  If someone is using the Netpbm libraries and
+       also another library that defines bool, he can either make the
+       other library define/respect HAVE_BOOL or just define HAVE_BOOL in
+       the file that includes pm_config.h or with a compiler option.  Note
+       that C++ always has bool.  
+    */
+    #ifndef HAVE_BOOL
+      #define HAVE_BOOL 1
+      typedef int bool;
+    #endif
+    #ifndef true
+      enum boolvalue {false=0, true=1};
+    #endif
+  #endif
+#endif
+
 #ifndef TRUE
-  #define TRUE 1
+  #define TRUE true
   #endif
 #ifndef FALSE
-  #define FALSE 0
-  #endif
-/* C++ has a bool type and false and true constants built in. */
-#ifndef __cplusplus
-  #ifndef HAVE_BOOL
-    #define HAVE_BOOL 1
-    typedef int bool;
-    #endif
-  #ifndef true
-    enum boolvalue {false=0, true=1};
-    #endif
+  #define FALSE false
   #endif
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
diff --git a/lib/util/runlength.c b/lib/util/runlength.c
new file mode 100644
index 00000000..e5c60db0
--- /dev/null
+++ b/lib/util/runlength.c
@@ -0,0 +1,374 @@
+/*=============================================================================
+                                  runlength.c
+===============================================================================
+  "Packbits" run-length encoding and variants.
+
+  Copyright (C) 2015 by Akira Urushibata (afu@wta.att.ne.jp).
+
+  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.
+
+  Functions pm_rlenc_byte() and pm_rlenc_word() are based on algorithm
+  originally by Robert A. Knop (rknop@mop.caltech.edu).
+
+  Those who wish to incorporate the code herein into their own programs are
+  strongly discouraged from removing the comments within borders consisting
+  of "+" marks.  This is a practical consideration based on code inspection
+  and bug fixes of various programs which use run-length compression.
+
+===============================================================================
+
+  Packbits is a simple yet efficient simple run-length compression method.  It
+  has a provision for uncompressed sequences which allows efficient encoding
+  of noisy input.
+
+  A survey of netpbm source code in 2015 found Packbits encoding in the
+  following Netpbm programs: pbmtoescp2, pbmtomacp, pnmtopalm, pnmtopclxl,
+  pnmtops, ppmtoilbm and ppmtopjxl.
+ 
+  Packbits is an option in the TIFF standard; pamtotiff can generate TIFF
+  images that use Packbits compression, but does so via code in the TIFF
+  library (not part of Netpbm).
+
+  Variants of Packbits are employed in pnmtosgi,  pamtopdbimg,  pbmtoppa
+  and  pbmtogo.
+
+  All the above programs formerly did the same compression through different
+  code.  This redundancy bloated the Netpbm package and made maintenance
+  difficult.  This maintenance difficulty surfaced as an issue when tests with
+  valgrind revealed multiple memory-related problems in the above programs.
+  Indeed, just determining which programs do this compression by this method
+  was not simple because of differences in terminology.  "Packbits" is often
+  called "run length encoding."  In ppmtoilbm the name is "byterun1."
+
+  Today, all Netpbm programs that do Packbits compression use the facilities
+  in this file for it.
+=============================================================================*/
+
+#include <string.h>
+
+#include "pm.h"
+#include "pm_c_util.h"
+#include "runlength.h"
+#include "mallocvar.h"
+
+
+
+static const char * const errorUndefinedMode =
+    "Internal error: compression mode %u not supported";
+
+
+
+/*-----------------------------------------------------------------------------
+   Run length encoding
+
+   In this simple run-length encoding scheme, compressed and uncompressed
+   strings follow a single index or "flag" byte N.  There are several
+   variations, differing in the format of the flag byte, maximum string length
+   and element size (byte or word).
+   
+   In the most widely used version, Packbits, the meaning of the flag byte N
+   is defined as follows:
+
+    0-127 means the next N+1 bytes are uncompressed.
+    129-255 means the next byte is to be repeated 257-N times.
+    128 is not used.
+
+   The name "Packbits" is misleading: it packs bytes, not bits.
+-----------------------------------------------------------------------------*/
+
+
+
+void
+pm_rlenc_compressbyte(const unsigned char * const inbuf,
+                      unsigned char       * const outbuf,
+                      enum pm_RleMode       const mode,
+                      size_t                const inSize,
+                      size_t              * const outputSizeP) {
+/*-----------------------------------------------------------------------------
+  Compress the contents of input buffer 'inbuf' with Packbits encoding into
+  output buffer 'outbuf'.  'inSize' is the number of bytes of data in 'inbuf'.
+  Return as *outputSizeP the number of bytes we put in 'outbuf'.
+
+  'outbuf' should be allocated with pm_rlenc_allocoutbuf().
+
+  Always encode 3-byte repeat sequences.
+
+  Encode 2-byte repeat sequences only when they are at the start of the block.
+  This ensures that the output is never unnecessary bloated.
+
+  Original algorithm by Robert A. Knop (rknop@mop.caltech.edu)
+-----------------------------------------------------------------------------*/
+    unsigned int const maxRun = 128;
+
+    size_t inCurs, outCurs;
+
+    if (mode != PM_RLE_PACKBITS)
+        pm_error(errorUndefinedMode, mode);
+
+    for (inCurs = 0, outCurs = 0; inCurs < inSize; ) {
+        if ((inCurs < inSize - 1) && (inbuf[inCurs] == inbuf[inCurs+1])) {
+            /*Begin replicate run*/
+            size_t const hold = inCurs;
+            size_t count;
+            for (count = 0;
+                 inCurs < inSize &&
+                     inbuf[inCurs] == inbuf[hold] &&
+                     count < maxRun; 
+                 ++inCurs, ++count)
+                ;
+
+            outbuf[outCurs++] = (unsigned char)(257 - count);
+            outbuf[outCurs++] = inbuf[hold];
+        } else {
+            /*Do a non-run*/
+            size_t const hold = outCurs;
+            size_t count;
+            ++outCurs;
+            count = 0;
+            while(((inCurs + 2 >= inSize) && (inCurs < inSize) ) || 
+                  ((inCurs + 2 <  inSize) &&
+                   ((inbuf[inCurs] != inbuf[inCurs+1]  )
+                    || (inbuf[inCurs] != inbuf[inCurs+2]  ) ) )    ) {
+                outbuf[outCurs++] = inbuf[inCurs++];
+                if (++count >= 128)
+                    break;
+            }
+            outbuf[hold] = (unsigned char)(count - 1);
+        }
+    }
+    *outputSizeP = outCurs;
+}
+
+
+
+static void
+setFlagElement(void            * const outP,
+               enum pm_RleMode   const mode,
+               bool              const isRepeatRun,
+               size_t            const count) {
+/*---------------------------------------------------------------------------
+  Write the flag byte or word at specified point in the output buffer.
+-----------------------------------------------------------------------------*/
+    switch (mode) {
+    case PM_RLE_SGI16:
+        * (uint16_t *) outP =  (isRepeatRun ? 0x00 : 0x80 ) | count;
+        break;
+    case PM_RLE_PALM16:
+        * (unsigned char *) outP = isRepeatRun ?
+            (unsigned char)(257 - count) : (unsigned char) (count - 1);
+        break;
+    default:
+        pm_error(errorUndefinedMode, mode);
+    }
+}
+
+
+
+void
+pm_rlenc_compressword(const uint16_t   * const inbuf,
+                      unsigned char *    const outbuf,
+                      enum pm_RleMode    const mode,
+                      size_t             const inSize,
+                      size_t           * const outputSizeP) {
+/*---------------------------------------------------------------------------
+   Similar to pm_rlenc_byte(), but this works with two-byte elements.  The
+   difference between SGI16 and PALM16 is the size of the flag.  SGI16 : 16
+   bits; PALM16 : 8 bits
+
+   'inSize' is a number of words,but *outputSizeP is a number of bytes.
+-----------------------------------------------------------------------------*/
+    size_t inCurs, outCurs;
+    size_t flagSz;
+    unsigned int maxRunSz;
+
+    switch (mode) {
+    case PM_RLE_SGI16:
+        flagSz = 2;
+        maxRunSz = 127;
+        break;
+    case PM_RLE_PALM16:
+        flagSz = 1;
+        maxRunSz = 128;
+        break;
+    default:
+        pm_error(errorUndefinedMode, mode);
+    }
+
+    for (inCurs = 0, outCurs = 0; inCurs < inSize; ) {
+        if ((inCurs < inSize - 1) && (inbuf[inCurs] == inbuf[inCurs+1])) {
+            uint16_t const runValue = inbuf[inCurs];
+            size_t count;
+            /* Do a run of 'runValue' values */
+            for (count = 0;
+                 count < maxRunSz &&
+                     inCurs < inSize &&
+                     inbuf[inCurs] == runValue;
+                 ++inCurs, ++count)
+                ;
+            setFlagElement(&outbuf[outCurs], mode, TRUE, count);
+            outCurs += flagSz;
+            * (uint16_t *) &outbuf[outCurs] = runValue;
+            outCurs += 2;
+        } else {
+            /*Do a non run*/
+            size_t const nonrunStart = inCurs;
+            size_t count;
+            count = 0;
+            while (count < maxRunSz &&
+                   ((inCurs + 2 >= inSize && inCurs < inSize) ||
+                    (inCurs + 2 <  inSize &&
+                     (inbuf[inCurs] != inbuf[inCurs+1]
+                      || inbuf[inCurs] != inbuf[inCurs+2])))) {
+                ++inCurs;
+                ++count;
+            }
+            setFlagElement(&outbuf[outCurs], mode, FALSE, count);
+            outCurs += flagSz;
+            memcpy(&outbuf[outCurs], &inbuf[nonrunStart], count * 2);
+            outCurs += count * 2;
+        }
+    }
+    
+    if (mode == PM_RLE_SGI16) {
+        * (uint16_t *) &outbuf[outCurs] = 0;     /* terminator */
+        outCurs += 2;
+    }
+
+    *outputSizeP = outCurs;
+}
+
+
+
+/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+   Worst case output size
+
+   The term "worst-case output size" can mean one of two things depending on
+   whether the encoder is efficient or not.
+
+   Sub-optimal encoder: the output size can be up to twice the input size.
+   This happens (or one may rather say "is achieved by a determined encoder")
+   when input is split into one-byte blocks.
+
+   Efficient encoder: the output is larger than the input by the minimum
+   number of flag bytes.  The worst case happens when there are no repeat
+   sequences in the input.
+
+   The key to an efficient encoder is correct handling of short, inefficient
+   sequences.  The following algorithm (not actually used in this file)
+   describes the idea.
+
+   A run is a maximal set of two or more consecutive identical values in the
+   input.  A nonrun is a maximal set of values in which every value is
+   different from its neighbors.
+
+   A compressed block is one that encodes a sequence of identical values
+   (which could be a run or just part of a run) as a value and a count.
+   count.  An uncompressed block is one that encodes a sequence of any values
+   by listing the values individually.
+
+   Start by encoding the entire input as uncompressed blocks.  Seek runs that
+   can be encoded with compressed blocks, but only if doing so doesn't make
+   the output larger.
+
+   Criteria to avoid bloat:
+
+     - Overhead for a single uncompressed block: 1 byte.
+
+     - Overhead for one uncompressed block and a compressed block: 2 bytes.
+
+     - Overhead for two uncompressed blocks and a compressed block: 3 bytes.
+
+     - New blocks at the edge of any existing block add 1 byte of overhead.
+       New blocks in the middle of existing blocks add 2 bytes of overhead.
+
+     - Whenever savings are larger than the added overhead, encode the run
+       as a compressed block.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+
+
+
+size_t
+pm_rlenc_maxbytes(size_t          const inSize,  /* number of elements */
+                  enum pm_RleMode const mode) {
+/*---------------------------------------------------------------------------
+   Calculate worst case output size from input size and encoding mode.
+
+   'inSize' counts the number of elements, not bytes: input size is (2 *
+   inSize) bytes if input is an array of 16-bit words.
+
+   Return value is the maximum possible output size in bytes regardless of
+   type of input.
+
+   Abort the program if the maximum possible output size is greater than
+   INT_MAX.
+-----------------------------------------------------------------------------*/
+   /* The upper limit could be raised above INT_MAX, but no program needs that
+      today.
+   */
+    size_t blockSize;
+    size_t flagSize;
+    size_t itemSize;
+    size_t miscSize;
+    size_t overhead;
+
+    switch (mode) {
+    case PM_RLE_PACKBITS:
+        blockSize = 128; flagSize = 1; itemSize = 1; miscSize = 0; break;
+    case PM_RLE_SGI8:
+        blockSize = 127; flagSize = 1; itemSize = 1; miscSize = 0; break;
+    case PM_RLE_GRAPHON:  case PM_RLE_PPA:
+        blockSize =  64; flagSize = 1; itemSize = 1; miscSize = 0; break;
+    case PM_RLE_SGI16:
+        blockSize = 127; flagSize = 2; itemSize = 2; miscSize = 2; break;
+    case PM_RLE_PALM16:
+        blockSize = 128; flagSize = 1; itemSize = 2; miscSize = 0; break;
+    default:
+        pm_error(errorUndefinedMode, mode);
+    }
+    
+    overhead = miscSize +
+        (inSize / blockSize + (inSize % blockSize > 0 ? 1 : 0) ) * flagSize;
+
+    if (inSize > INT_MAX / itemSize ||
+        inSize * itemSize > INT_MAX - overhead)
+        pm_error("Cannot do RLE compression.  Input too large.");
+
+    return inSize * itemSize + overhead;
+}
+
+
+
+void
+pm_rlenc_allocoutbuf(unsigned char ** const outbufP,
+                     size_t           const inSize,  /* element count */
+                     enum pm_RleMode  const mode) {
+/*---------------------------------------------------------------------------
+   Allocate an output buffer sufficient for input with inSize elements, using
+   compression mode 'mode'.  Element may be byte or word, whichever 'mode'
+   implies.
+-----------------------------------------------------------------------------*/
+    size_t const size = pm_rlenc_maxbytes(inSize, mode);
+
+    unsigned char * outbuf;
+
+    MALLOCARRAY(outbuf, size);
+    if (outbuf == NULL)
+        pm_error("Out of memory trying to get %u bytes for RLE output buffer",
+                 (unsigned)size);
+
+    *outbufP = outbuf;
+}
+
+
+
+void
+pm_rlenc_freebuf(void * const buf) {
+    free(buf);
+}
+
+
diff --git a/lib/util/runlength.h b/lib/util/runlength.h
new file mode 100644
index 00000000..4857ae61
--- /dev/null
+++ b/lib/util/runlength.h
@@ -0,0 +1,51 @@
+#ifndef RUNLENGTH_INCLUDED
+#define RUNLENGTH_INCLUDED
+
+#include "pm_config.h"
+
+#include <limits.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+enum pm_RleMode { PM_RLE_PACKBITS,          /* most common mode */
+                  PM_RLE_GRAPHON,           /* reserved */ 
+                  PM_RLE_PPA,               /* reserved */
+                  PM_RLE_SGI8,              /* reserved */
+                  PM_RLE_SGI16,
+                  PM_RLE_PALM16
+                };
+
+size_t
+pm_rlenc_maxbytes (size_t          const inSize,
+                   enum pm_RleMode const mode);
+
+void
+pm_rlenc_allocoutbuf(unsigned char ** const outbufP,
+                     size_t           const inSize,
+                     enum pm_RleMode  const mode);
+
+
+void
+pm_rlenc_freebuf(void * const buf);
+
+void
+pm_rlenc_compressbyte(const unsigned char * const inbuf,
+                      unsigned char       * const outbuf,
+                      enum pm_RleMode       const mode,
+                      size_t                const inSize,
+                      size_t              * const outputSizeP);
+
+void
+pm_rlenc_compressword(const uint16_t      * const inbuf,
+                      unsigned char       * const outbuf,
+                      enum pm_RleMode       const mode,
+                      size_t                const itemCnt,
+                      size_t              * const outputSizeP);
+
+#endif
diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c
index 718186fa..ccf2d1eb 100644
--- a/lib/util/shhopt.c
+++ b/lib/util/shhopt.c
@@ -31,6 +31,7 @@
 
 #include "mallocvar.h"
 #include "nstring.h"
+#include "token.h"
 #include "shhopt.h"
 
 /*-----------------------------------------------------------------------+
@@ -76,7 +77,7 @@ optFatalFunc(const char *format, ...)
  |
  |  RETURNS       Number of options in the given array.
  |
- |  DESCRIPTION   Count elements in an optStruct-array. The strcture must
+ |  DESCRIPTION   Count elements in an optStruct-array. The structure must
  |                be ended using an element of type OPT_END.
  */
 static int
@@ -237,6 +238,7 @@ optNeedsArgument(const optEntry opt)
 	|| opt.type == OPT_ULONG
     || opt.type == OPT_FLOAT
     || opt.type == OPT_NAMELIST
+    || opt.type == OPT_STRINGLIST
         ;
 }
 
@@ -281,46 +283,12 @@ getToken(const char *  const tokenStart,
    we return an empty string and *nextP == tokenStart, i.e. *nextP
    doesn't necessarily advance.
 -----------------------------------------------------------------------------*/
-    char * token;
-    const char * cursor;
-    unsigned int charCount;
-
-    /* Run through the token, counting characters */
-
-    charCount = 0;
-    cursor = tokenStart;
-
-    while (*cursor != delimiter && *cursor != '\0') {
-        if (*cursor == '\\') {
-            ++cursor;
-            if (*cursor == '\0')
-                optFatal("string ends with an escape character (\\)");
-        }
-        ++cursor;
-        ++charCount;
-    }
+    const char * error;
     
-    token = malloc(charCount + 1);
-    if (token == NULL)
-        optFatal("Could not allocate %u bytes of memory to parse a string",
-                 charCount + 1);
-
-    /* Go back and do it again, this time copying the characters */
-    charCount = 0;
-    cursor = tokenStart;
-
-    while (*cursor != delimiter && *cursor != '\0') {
-        if (*cursor == '\\')
-            ++cursor;
-
-        assert(*cursor != '\0');
+    pm_gettoken(tokenStart, delimiter, tokenP, nextP, &error);
 
-        token[charCount++] = *cursor++;
-    }
-    token[charCount] = '\0';
-
-    *tokenP = token;
-    *nextP = cursor;
+    if (error)
+        optFatal("error parsing a token: %s", error);
 }
 
 
@@ -375,6 +343,41 @@ parseNameList(const char *           const listText,
 
 
 
+static void
+parseStringList(const char *   const listText,
+                const char *** const listP) {
+
+    unsigned int const maxStringCount = 100;
+
+    const char * cursor;
+    unsigned int stringCount;
+    const char ** list;
+
+    MALLOCARRAY_NOFAIL(list, maxStringCount+1);
+
+    cursor = &listText[0];  /* initial value */
+
+    stringCount = 0;  /* initial value */
+
+    while (stringCount < maxStringCount && *cursor != '\0') {
+        const char * next;
+
+        getToken(cursor, ',', &list[stringCount++], &next);
+
+        cursor = next;
+
+        if (*cursor != '\0') {
+            assert(*cursor == ',');
+            ++cursor;
+        }
+    }
+    list[stringCount] = NULL;
+
+    *listP = list;
+}
+
+
+
 /*------------------------------------------------------------------------
  |  NAME          optExecute
  |
@@ -479,6 +482,15 @@ optExecute(optEntry  const opt, char *arg, int lng)
             parseNameList(arg, (struct optNameValue **)opt.arg);
 
     } break;
+    case OPT_STRINGLIST: {
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+
+        if (opt.arg)
+            parseStringList(arg, (const char ***)opt.arg);
+
+    } break;
     default:
         break;
     }
@@ -502,19 +514,20 @@ optExecute(optEntry  const opt, char *arg, int lng)
  |                        that _must_ abort the program.
  */
 void
-optSetFatalFunc(void (*f)(const char *, ...))
-{
+pm_optSetFatalFunc(void (*f)(const char *, ...)) {
+
     optFatal = f;
 }
 
 
+
 /*------------------------------------------------------------------------
- |  NAME          optParseOptions
+ |  NAME          pm_optParseOptions
  |
  |  FUNCTION      Parse commandline options.
  |
  |  SYNOPSIS      #include "shhopt.h"
- |                void optParseOptions(int *argc, char *argv[],
+ |                void pm_optParseOptions(int *argc, char *argv[],
  |                                     optStruct opt[], int allowNegNum);
  |
  |  INPUT         argc    Pointer to number of options.
@@ -541,7 +554,7 @@ optSetFatalFunc(void (*f)(const char *, ...))
  |                Any error leads to program abortion.
  */
 void
-optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
+pm_optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
 {
     int  ai,        /* argv index. */
          optarg,    /* argv index of option argument, or -1 if none. */
@@ -724,13 +737,13 @@ fatalUnrecognizedLongOption(const char * const optionName,
         const char * entry;
 
         if (optEntryP->longName)
-            asprintfN(&entry, "-%s ", optEntryP->longName);
+            pm_asprintf(&entry, "-%s ", optEntryP->longName);
         else
-            asprintfN(&entry, "-%c ", optEntryP->shortName);
+            pm_asprintf(&entry, "-%c ", optEntryP->shortName);
 
         strncat(optList, entry, sizeof(optList) - strlen(optList) - 1);
 
-        strfree(entry);
+        pm_strfree(entry);
 
         if (strlen(optList) + 1 == sizeof(optList)) {
             /* Buffer is full.  Overwrite end of list with ellipsis */
@@ -809,12 +822,12 @@ parse_long_option(char *   const argv[],
 
 
 /*------------------------------------------------------------------------
- |  NAME          optParseOptions2
+ |  NAME          pm_optParseOptions2
  |
  |  FUNCTION      Parse commandline options.
  |
  |  SYNOPSIS      #include "shhopt.h"
- |                void optParseOptions2(int *argc, char *argv[],
+ |                void pm_optParseOptions2(int *argc, char *argv[],
  |                                      optStruct2 opt, unsigned long flags);
  |
  |  INPUT         argc    Pointer to number of options.
@@ -834,9 +847,9 @@ parse_long_option(char *   const argv[],
  |                extracted and stored in the variables or passed to
  |                functions pointed to by entries in opt.
  |
- |                This differs from optParseOptions in that it accepts
+ |                This differs from pm_optParseOptions in that it accepts
  |                long options with just one hyphen and doesn't accept
- |                any short options.  It also has accomodations for 
+ |                any short options.  It also has accommodations for 
  |                future expansion.
  |
  |                Options and arguments used are removed from the argv-
@@ -845,10 +858,10 @@ parse_long_option(char *   const argv[],
  |                Any error leads to program abortion.
  */
 void
-optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+pm_optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
                  const unsigned long flags)
 /*----------------------------------------------------------------------------
-   This does the same thing as optParseOptions3(), except that there is no
+   This does the same thing as pm_optParseOptions3(), except that there is no
    "specified" return value.  
 
    This function exists for backward compatibility.
@@ -865,7 +878,7 @@ optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt,
         optFatal("Memory allocation failed (trying to allocate space for "
                  "new-format option table)");
     
-    optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags);
+    pm_optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags);
 
     free(opt3.opt_table);
 }
@@ -890,7 +903,7 @@ zero_specified(optEntry opt_table[]) {
 
 
 /*------------------------------------------------------------------------
- |  NAME          optParseOptions3
+ |  NAME          pm_optParseOptions3
  |
  |  FUNCTION      Parse commandline options.
  |
@@ -919,9 +932,9 @@ zero_specified(optEntry opt_table[]) {
  |                extracted and stored in the variables or passed to
  |                functions pointed to by entries in opt.
  |
- |                This differs from optParseOptions in that it accepts
+ |                This differs from pm_optParseOptions in that it accepts
  |                long options with just one hyphen and doesn't accept
- |                any short options.  It also has accomodations for 
+ |                any short options.  It also has accommodations for 
  |                future expansion.
  |
  |                Options and arguments used are removed from the argv-
@@ -930,7 +943,7 @@ zero_specified(optEntry opt_table[]) {
  |                Any error leads to program abortion.
  */
 void
-optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+pm_optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
                  const unsigned int optStructSize, const unsigned long flags)
 {
     int  ai;        /* argv index. */
@@ -996,13 +1009,13 @@ optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt,
 
 
 void
-optDestroyNameValueList(struct optNameValue * const list) {
+pm_optDestroyNameValueList(struct optNameValue * const list) {
 
     unsigned int i;
 
     for (i = 0; list[i].name; ++i) {
-        strfree(list[i].name);
-        strfree(list[i].value);
+        pm_strfree(list[i].name);
+        pm_strfree(list[i].value);
     }
 
     free(list);
diff --git a/lib/util/shhopt.h b/lib/util/shhopt.h
index 99096a76..9a446290 100644
--- a/lib/util/shhopt.h
+++ b/lib/util/shhopt.h
@@ -1,4 +1,5 @@
-/*=============================================================================
+#if 0
+=============================================================================
 HERE IS AN EXAMPLE OF THE USE OF SHHOPT:
 
 
@@ -6,8 +7,9 @@ HERE IS AN EXAMPLE OF THE USE OF SHHOPT:
 int 
 main ( int argc, char **argv ) {
 
-    // initial values here are just to demonstrate what gets set and
-    // what doesn't by the code below.
+    /* initial values here are just to demonstrate what gets set and
+       what doesn't by the code below.
+    */
     int help_flag = 7;
     unsigned int help_spec =7;
     unsigned int height_spec =7;
@@ -20,7 +22,8 @@ main ( int argc, char **argv ) {
     
     optStruct3 opt;
     unsigned int option_def_index = 0;
-    optEntry *option_def = malloc(100*sizeof(option_def[0]));
+    optEntry * option_def;
+    MALLOCARRAY(option_def, 100);
 
     OPTENT3(0,   "help",     OPT_FLAG,     &help_flag,    &help_spec,   0);
     OPTENT3(0,   "height",   OPT_INT,      &height,       &height_spec, 0);
@@ -34,7 +37,7 @@ main ( int argc, char **argv ) {
     opt.allowNegNum = 1;
 
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
     
 
     printf("argc=%d\n", argc);
@@ -67,8 +70,15 @@ Now run this program with something like
   myprog -v /etc/passwd -name=Bryan --hei=4
 
 
-========================================================================*/
+  If your program takes no options (so you have no OPTENT3 macro invocations),
+  you need an OPTENTINIT call to establish the empty option table:
+
+    optEntry * option_def;
+    MALLOCARRAY(option_def, 1);
+    OPTENTINIT;
 
+========================================================================
+#endif /* 0 */
 
 #ifndef SHHOPT_H
 #define SHHOPT_H
@@ -90,13 +100,14 @@ typedef enum {
     OPT_LONG,              /* signed long integer argument. */
     OPT_ULONG,             /* unsigned long integer argument. */
     OPT_FLOAT,             /* floating point argument. */
+    OPT_STRINGLIST,        /* list like "arg1,arg2.arg3" */
     OPT_NAMELIST           /* list like "key1=val1,key2=val2" */
 } optArgType;
 
 
 typedef struct {
     /* This structure describes a single program option in a form for
-     use by the optParseOptions() or optParseOptions2() function.
+     use by the pm_optParseOptions() or pm_optParseOptions2() function.
     */
     char       shortName;  /* short option name. */
     const char *longName;  /* long option name, not including '--'. */
@@ -108,7 +119,7 @@ typedef struct {
     
 typedef struct {
     /* This structure describes a single program option in a form for
-     use by the optParseOptions3() function.
+     use by the pm_optParseOptions3() function.
     */
     char       shortName;  /* short option name. */
     const char *longName;  /* long option name, not including '--' or '-' */
@@ -129,7 +140,7 @@ typedef struct {
 
 typedef struct {
     /* This structure describes the options of a program in a form for
-       use with the optParseOptions2() function.
+       use with the pm_optParseOptions2() function.
        */
     unsigned char short_allowed;  /* boolean */
         /* The syntax may include short (i.e. one-character) options.
@@ -146,7 +157,7 @@ typedef struct {
 } optStruct2;
 
 typedef struct {
-    /* Same as optStruct2, but for optParseOptions3() */
+    /* Same as optStruct2, but for pm_optParseOptions3() */
     unsigned char short_allowed;  
     unsigned char allowNegNum;    
     optEntry *opt_table;
@@ -187,9 +198,10 @@ 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).  It sets it to the number of times that
-   the option appears in the command line.
+   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 the number of times that the option appears in the command
+   line.
 
    Here is an example:
 
@@ -215,18 +227,21 @@ struct optNameValue {
 
 
         
-void optSetFatalFunc(void (*f)(const char *, ...));
-void optParseOptions(int *argc, char *argv[],
-		     optStruct opt[], int allowNegNum);
 void
-optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+pm_optSetFatalFunc(void (*f)(const char *, ...));
+
+void
+pm_optParseOptions(int *argc, char *argv[],
+                   optStruct opt[], int allowNegNum);
+void
+pm_optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
                  const unsigned long flags);
 void
-optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+pm_optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
                  const unsigned int optStructSize, const unsigned long flags);
 
 void
-optDestroyNameValueList(struct optNameValue * const list);
+pm_optDestroyNameValueList(struct optNameValue * const list);
 
 #ifdef __cplusplus
 }
diff --git a/lib/util/token.c b/lib/util/token.c
new file mode 100644
index 00000000..a68a4821
--- /dev/null
+++ b/lib/util/token.c
@@ -0,0 +1,80 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "nstring.h"
+
+#include "token.h"
+
+
+void
+pm_gettoken(const char *  const tokenStart,
+            char          const delimiter,
+            const char ** const tokenP,
+            const char ** const nextP,
+            const char ** const errorP) {
+/*----------------------------------------------------------------------------
+   Find the token starting at 'tokenStart' up to but not including
+   the first 'delimiter' character or end of string.  Return it in newly
+   malloced memory as *tokenP, NUL-terminated.
+
+   Make *nextP point just past the token, i.e. to the delimiter or
+   end of string NUL character.
+
+   Note that if the string is empty, or starts with the delimiter,
+   we return an empty string and *nextP == tokenStart, i.e. *nextP
+   doesn't necessarily advance.
+-----------------------------------------------------------------------------*/
+    char * token;
+    const char * cursor;
+    unsigned int charCount;
+
+    /* Run through the token, counting characters */
+
+    charCount = 0;        /* initial value */
+    cursor = tokenStart;  /* initial value */
+    *errorP = NULL;       /* initial value */
+
+    while (*cursor != delimiter && *cursor != '\0' && !*errorP) {
+        if (*cursor == '\\') {
+            ++cursor;
+            if (*cursor == '\0')
+                pm_asprintf(errorP,
+                            "string ends with an escape character (\\)");
+        } else {
+            ++cursor;
+            ++charCount;
+        }
+    }
+    if (!*errorP) {
+        token = malloc(charCount + 1);
+        if (token == NULL)
+            pm_asprintf(errorP, "Could not allocate %u bytes of memory "
+                        "to parse a string",
+                        charCount + 1);
+        else {
+            /* Go back and do it again, this time copying the characters */
+            charCount = 0;
+            cursor = tokenStart;
+
+            while (*cursor != delimiter && *cursor != '\0') {
+                if (*cursor == '\\')
+                    ++cursor;
+
+                assert(*cursor != '\0');
+
+                token[charCount++] = *cursor++;
+            }
+            token[charCount] = '\0';
+            
+            *tokenP = token;
+            *nextP = cursor;
+        }
+    }
+}
+
+
+
+
+
+
+
diff --git a/lib/util/token.h b/lib/util/token.h
new file mode 100644
index 00000000..292a9164
--- /dev/null
+++ b/lib/util/token.h
@@ -0,0 +1,11 @@
+#ifndef TOKEN_H_INCLUDE
+#define TOKEN_H_INCLUDE
+
+void
+pm_gettoken(const char *  const tokenStart,
+            char          const delimiter,
+            const char ** const tokenP,
+            const char ** const nextP,
+            const char ** const errorP);
+
+#endif
diff --git a/lib/util/vasprintf.c b/lib/util/vasprintf.c
index 47b4079d..a947f763 100644
--- a/lib/util/vasprintf.c
+++ b/lib/util/vasprintf.c
@@ -1,19 +1,21 @@
 #define _GNU_SOURCE
-   /* Due to conditional compilation, this is GNU source only if the C library
-      is GNU.
+   /* Because of conditional compilation, this is GNU source only if the C
+      library is GNU.
    */
 #include <stdlib.h>
 #include <string.h>
 
 #include "pm_config.h"
+#include "pm_c_util.h"
+
 #include "nstring.h"
 
 
 
 void
-vasprintfN(const char ** const resultP,
-           const char *  const format,
-           va_list             varargs) {
+pm_vasprintf(const char ** const resultP,
+             const char *  const format,
+             va_list             varargs) {
 
     char * result;
 
@@ -23,7 +25,7 @@ vasprintfN(const char ** const resultP,
     rc = vasprintf(&result, format, varargs);
 
     if (rc < 0)
-        *resultP = strsol;
+        *resultP = pm_strsol;
     else
         *resultP = result;
 #else
@@ -38,16 +40,22 @@ vasprintfN(const char ** const resultP,
 
        So instead, we just allocate 4K and truncate or waste as
        necessary.
+
+       Note that we don't recognize the floating point specifiers (%f, %e, %g)
+       - we render them as 'f', 'e', and 'g'.  It would be too much work to
+       make this code handle those, just for the few systems on which it runs.
+       Instead, we have pm_vasprintf_knows_float(), and any caller that cares
+       enough can avoid using these specifiers where they don't work.
     */
     size_t const allocSize = 4096;
     result = malloc(allocSize);
     
     if (result == NULL)
-        *resultP = strsol;
+        *resultP = pm_strsol;
     else {
         size_t realLen;
 
-        vsnprintfN(result, allocSize, format, varargs, &realLen);
+        pm_vsnprintf(result, allocSize, format, varargs, &realLen);
         
         if (realLen >= allocSize)
             strcpy(result + allocSize - 15, "<<<TRUNCATED");
@@ -56,3 +64,14 @@ vasprintfN(const char ** const resultP,
     }
 #endif
 }
+
+
+
+bool
+pm_vasprintf_knows_float(void) {
+#if HAVE_VASPRINTF
+    return true;
+#else
+    return false;
+#endif
+}
diff --git a/lib/util/wordaccess.h b/lib/util/wordaccess.h
index 2eaa2b24..df0eaf12 100644
--- a/lib/util/wordaccess.h
+++ b/lib/util/wordaccess.h
@@ -44,33 +44,32 @@
 
 #include "pm_config.h"
 
-#if (!defined(WORDACCESS_GENERIC) && HAVE_GCC_BITCOUNT )
-
-    #if BYTE_ORDER == BIG_ENDIAN    /* See pm_config.h */
-        /* Sun Sparc 64, etc */ 
-        #include "wordaccess_gcc3_be.h"
-
-    #elif (BITS_PER_LONG == 64)
-        /* AMD Athlon 64, Intel x86_64, Intel Itanium, etc. */
-        #include "wordaccess_64_le.h"
-
-    #elif (BITS_PER_LONG == 32)
-        /* Intel x86_32 (80386, 80486, Pentium), etc. */
-        #include "wordaccess_generic.h"
-
-    #else
-        /* Extremely rare case.
-           If long is neither 32 nor 64 bits, (say, 128) it comes here.
-        */
-        #define WORDACCESS_GENERIC
-        #include "wordaccess_generic.h"
-
-    #endif
-
+#if defined(WORDACCESS_GENERIC)
+  /* User wants this, regardless of whether machine can do better */
+  #include "wordaccess_generic.h"
+#elif BYTE_ORDER == BIG_ENDIAN
+  #if UNALIGNED_OK
+     #include "wordaccess_be_unaligned.h"
+  #else
+    /* Sparc */
+    #include "wordaccess_be_aligned.h"
+  #endif
+#elif HAVE_GCC_BITCOUNT
+  #if (BITS_PER_LONG == 64)
+    /* AMD Athlon 64, Intel x86_64, Intel Itanium, etc. */
+    #include "wordaccess_64_le.h"
+  #elif (BITS_PER_LONG == 32)
+    /* Intel x86_32 (80386, 80486, Pentium), etc. */
+      #include "wordaccess_generic.h"
+  #else
+     /* Extremely rare case.
+        If long is neither 32 nor 64 bits, (say, 128) it comes here.
+     */
+     #include "wordaccess_generic.h"
+  #endif
 #else
-    /* Non GCC, GCC prior to v.3.4 or WORDACCESS_GENERIC defined  */
-    #include "wordaccess_generic.h"
-
+  /* Non GCC or GCC prior to v.3.4; little-endian  */
+  #include "wordaccess_generic.h"
 #endif
 
 #endif
diff --git a/lib/util/wordaccess_be_aligned.h b/lib/util/wordaccess_be_aligned.h
new file mode 100644
index 00000000..f3bbb841
--- /dev/null
+++ b/lib/util/wordaccess_be_aligned.h
@@ -0,0 +1,35 @@
+/*=============================================================================
+  This file is the part of wordaccess.h for use under with big-endian
+  machines that require word accesses to be word-aligned.
+*===========================================================================*/
+
+typedef unsigned long int wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+static __inline__ wordint
+bytesToWordint(wordintBytes bytes) {
+    uint16_t const hi = *((uint16_t *) (bytes + 0));
+    uint16_t const mh = *((uint16_t *) (bytes + 2));
+    uint16_t const ml = *((uint16_t *) (bytes + 4));
+    uint16_t const lo = *((uint16_t *) (bytes + 6));
+    return
+        (((wordint) hi) << 48) |
+        (((wordint) mh) << 32) |
+        (((wordint) ml) << 24) |
+        (((wordint) lo) <<  0);
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+    uint16_t const hi = ((wordInt >> 48) & 0xFF);
+    uint16_t const mh = ((wordInt >> 32) & 0xFF);
+    uint16_t const ml = ((wordInt >> 24) & 0xFF);
+    uint16_t const lo = ((wordInt >>  0) & 0xFF);
+    *(uint16_t *)(bytesP + 0) = hi;
+    *(uint16_t *)(bytesP + 2) = mh;
+    *(uint16_t *)(bytesP + 4) = ml;
+    *(uint16_t *)(bytesP + 6) = lo;
+}
diff --git a/lib/util/wordaccess_gcc3_be.h b/lib/util/wordaccess_be_unaligned.h
index 5aa63521..95b68ac7 100644
--- a/lib/util/wordaccess_gcc3_be.h
+++ b/lib/util/wordaccess_be_unaligned.h
@@ -1,9 +1,6 @@
 /*=============================================================================
-  This file is the part of wordaccess.h for use under these
-  conditions:
-
-  * GCC (>=3.4), GLIBC
-  * Big-Endian machines
+  This file is the part of wordaccess.h for use on a big-endian machine
+  that does not require word accesses to be word-aligned.
 *===========================================================================*/
 
 typedef unsigned long int wordint;
diff --git a/lib/util/wordaccess_generic.h b/lib/util/wordaccess_generic.h
index 94cc8124..6e0a20ef 100644
--- a/lib/util/wordaccess_generic.h
+++ b/lib/util/wordaccess_generic.h
@@ -1,11 +1,11 @@
 /*=============================================================================
 
-  This file is the part of wordaccess.h for use under these
+  This file is the part of wordaccess.h for use under any of these
   conditions:
 
-  * Compilers other than GCC
+  * Compiler other than GCC
   * GCC before version 3.4
-  * Specified by the user with WORDACCESS_GENERIC
+  * Requested by the user with WORDACCESS_GENERIC
 =============================================================================*/
 
 #include "intcode.h"