about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2007-06-26 02:05:42 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2007-06-26 02:05:42 +0000
commit4f463b7abea8bc5a6031fe72ab50dc18eeaaa2bf (patch)
treefd67d42be8b0edb1c78da7393ae8c7d95cdc1d69
parent0a7fcd8c888dd70fe961a4b4fc56fa3d0a24b7c3 (diff)
downloadnetpbm-mirror-4f463b7abea8bc5a6031fe72ab50dc18eeaaa2bf.tar.gz
netpbm-mirror-4f463b7abea8bc5a6031fe72ab50dc18eeaaa2bf.tar.xz
netpbm-mirror-4f463b7abea8bc5a6031fe72ab50dc18eeaaa2bf.zip
Rebase advanced branch to 10.39
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@330 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile28
-rw-r--r--Makefile.common6
-rw-r--r--Makefile.config.in11
-rw-r--r--Makefile.version2
-rwxr-xr-xbuildtools/configure.pl246
-rw-r--r--converter/other/Makefile3
-rw-r--r--converter/other/fitstopnm.c806
-rw-r--r--converter/other/pamtofits.c12
-rw-r--r--converter/other/pamtooctave.c241
-rw-r--r--converter/other/svgtopam.c13
-rw-r--r--converter/ppm/picttoppm.c124
-rw-r--r--doc/HISTORY27
-rw-r--r--doc/INSTALL2
-rw-r--r--editor/Makefile2
-rw-r--r--editor/pamdice.c29
-rw-r--r--editor/pamthreshold.c41
-rw-r--r--editor/pamundice.c705
-rw-r--r--editor/pgmmedian.c4
-rw-r--r--editor/pnmremap.c70
-rw-r--r--editor/ppmdraw.c30
-rw-r--r--generator/pbmtext.c160
-rw-r--r--generator/pbmtextps.c38
-rw-r--r--lib/Makefile2
-rw-r--r--lib/fileio.c21
-rw-r--r--lib/libpammap.c8
-rw-r--r--lib/libpbmfont.c818
-rw-r--r--lib/libpm.c6
-rw-r--r--lib/libppmfuzzy.c2
-rw-r--r--lib/pammap.h6
-rw-r--r--lib/pbmfont.h34
-rw-r--r--other/pamx/pamx.c2
-rw-r--r--other/pnmcolormap.c2
32 files changed, 2638 insertions, 863 deletions
diff --git a/GNUmakefile b/GNUmakefile
index a65ac0d4..0103aa9f 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -106,10 +106,8 @@ $(TYPEGEN) $(ENDIANGEN): $(BUILDDIR)/buildtools
 	$(MAKE) -C $(dir $@) -f $(SRCDIR)/buildtools/Makefile \
 	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
 
-DELETEIT = (rm -f $@ || false)
-
 inttypes_netpbm.h: $(TYPEGEN)
-	$(TYPEGEN) >$@ || $(DELETEIT)
+	$(TYPEGEN) >$@
 
 # We run a couple of programs on the build machine in computing the
 # contents of pm_config.h.  We need to give the user a way not to do
@@ -119,27 +117,29 @@ inttypes_netpbm.h: $(TYPEGEN)
 pm_config.h: \
   $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h \
   $(ENDIANGEN)
-	echo '/* pm_config.h GENERATED BY A MAKE RULE */' >$@ || $(DELETEIT)
-	echo '#ifndef PM_CONFIG_H' >>$@ || $(DELETEIT)
-	echo '#define PM_CONFIG_H' >>$@ || $(DELETEIT)
+# Note that this rule depends on the effect of the .DELETE_ON_ERROR
+# target we get from Makefile.common
+	echo '/* pm_config.h GENERATED BY A MAKE RULE */' >$@
+	echo '#ifndef PM_CONFIG_H' >>$@
+	echo '#define PM_CONFIG_H' >>$@
 ifeq ($(INTTYPES_H)x,x)
 	echo '/* Don't need to #include any inttypes.h-type thing */
 else
   ifeq ($(INTTYPES_H),"inttypes_netpbm.h")
-	cat inttypes_netpbm.h >>$@ || $(DELETEIT)
+	cat inttypes_netpbm.h >>$@
   else
-	echo '#include $(INTTYPES_H)' >>$@ || $(DELETEIT)
+	echo '#include $(INTTYPES_H)' >>$@
   endif
 endif
 ifeq ($(HAVE_INT64),Y)
-	echo "#define HAVE_INT64 1" >>$@ || $(DELETEIT)
+	echo "#define HAVE_INT64 1" >>$@
 else
-	echo "#define HAVE_INT64 0" >>$@ || $(DELETEIT)
+	echo "#define HAVE_INT64 0" >>$@
 endif	
-	echo '/* pm_config.h.in FOLLOWS ... */' >>$@ || $(DELETEIT)
-	cat $(SRCDIR)/pm_config.in.h >>$@ || $(DELETEIT)
-	$(ENDIANGEN) >>$@ || $(DELETEIT)
-	echo '#endif' >>$@ || $(DELETEIT)
+	echo '/* pm_config.h.in FOLLOWS ... */' >>$@
+	cat $(SRCDIR)/pm_config.in.h >>$@
+	$(ENDIANGEN) >>$@
+	echo '#endif' >>$@
 
 
 MAJOR := $(NETPBM_MAJOR_RELEASE)
diff --git a/Makefile.common b/Makefile.common
index ed9f2066..16bb58ef 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -72,6 +72,12 @@
 
 include $(SRCDIR)/Makefile.version
 
+# .DELETE_ON_ERROR is a special predefined Make target that says to delete
+# the target if a command in the rule for it fails.  That's important,
+# because we don't want a half-made target sitting around looking like it's
+# fully made.
+.DELETE_ON_ERROR:
+
 NETPBM_INCLUDES := -I importinc -I$(SRCDIR)/$(SUBDIR)
 
 # -I. is needed when builddir != srcdir
diff --git a/Makefile.config.in b/Makefile.config.in
index 84666cb1..4929a6d8 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -248,6 +248,17 @@ LDSHLIB = -shared -Wl,-soname,$(SONAME)
 #LDSHLIB = -shared
 #AIX Visual Age C:
 #LDSHLIB = -qmkshrobj
+#Mac OSX:
+# According to experiments done by Peter A Crowley in May 2007, if
+# libnetpbm goes in a standard place such as /usr/local/lib,
+# programs need not be built with libnetpbm's location included.
+# But if it goes elsewhere, the link-editor must include the
+# location in the executable.  It finds the runtime location by
+# looking inside the library.  The information in the library
+# comes from the install_name option with which the library was
+# built.  It's an alternative to the -rpath option on other systems.
+#LDSHLIB=-dynamiclib
+#LDSHLIB=-dynamiclib -install_name $(NETPBMLIB_RUNTIME_PATH)/libnetpbm.$(MAJ).dylib
 
 # LDRELOC is the command to combine two .o files (relocateable object files)
 # into a single .o file that can later be linked into something else.  NONE
diff --git a/Makefile.version b/Makefile.version
index 194eae42..1c86fb6d 100644
--- a/Makefile.version
+++ b/Makefile.version
@@ -1,4 +1,4 @@
 NETPBM_MAJOR_RELEASE = 10
 NETPBM_MINOR_RELEASE = 38
-NETPBM_POINT_RELEASE = 04
+NETPBM_POINT_RELEASE = 99
 
diff --git a/buildtools/configure.pl b/buildtools/configure.pl
index ab14324a..97e3c33f 100755
--- a/buildtools/configure.pl
+++ b/buildtools/configure.pl
@@ -42,6 +42,18 @@ my ($TRUE, $FALSE) = (1,0);
 
 my $testCc;
 
+##############################################################################
+#
+#  Implementation note:
+#
+#  At one time, we thought we had to add /usr/local/lib and /usr/local/include
+#  to the path on some platforms because they needed it and didn't include
+#  it in the default compiler search path.  But then we had reason to doubt
+#  that was really required, so removed the function on 04.03.15 and never
+#  had any complaint in the next 3 years.
+##############################################################################
+
+
 #******************************************************************************
 #
 #  SUBROUTINES
@@ -167,8 +179,7 @@ sub chooseTestCompiler($$) {
 
 
 
-sub testCflags($) {
-    my ($needLocal) = @_;
+sub testCflags() {
 
     my $cflags;
 
@@ -184,9 +195,6 @@ sub testCflags($) {
         $cflags .= " " . $ENV{"CFLAGS"};
     }
     
-    if ($needLocal) {
-        $cflags .= " -I/usr/local/include";
-    }
     return $cflags;
 }    
 
@@ -724,7 +732,7 @@ sub inttypesDefault() {
         print("(Doing test compiles to choose a default for you -- " .
               "ignore errors)\n");
 
-        my $cflags = testCflags($FALSE);
+        my $cflags = testCflags();
 
         my $works;
 
@@ -814,7 +822,7 @@ sub getInt64($$) {
         print("(Doing test compiles to determine if you have int64 type -- " .
               "ignore errors)\n");
 
-        my $cflags = testCflags($FALSE);
+        my $cflags = testCflags();
 
         my $works;
 
@@ -1225,49 +1233,13 @@ sub gnuOptimizeOpt($) {
 
 
 
-sub needLocal($) {
-#-----------------------------------------------------------------------------
-#  Return wether or not /usr/local paths must be added to compiles and
-#  links.  In a properly configured system, those paths should be in
-#  the compiler and linker default search paths, e.g. the compiler
-#  should search /usr/local/include and then /usr/include without any
-#  -I options needed.  But we've seen some systems where it isn't.
-#
-#  Actually, I have doubts now as to whether these misconfigured systems
-#  really exist.  This subroutine was apparently always broken, because
-#  before 04.03.15, it had "netbsd", etc. in lower case.  So it always
-#  returned false.  I never had a complaint.  Plus, we had a bug in 
-#  Makefile.config.in wherein it wiped out the user's setting of the LDFLAGS
-#  environment variable.  This could explain /usr/local/lib not being in
-#  the path when it should have been.
-#
-#  So I've disabled this function; we'll see if we encounter any truly
-#  misconfigured systems.  04.03.15.
-#-----------------------------------------------------------------------------
-    my ($platform) = @_;
-
-    return $FALSE;  # See comments above.
-
-    my $needLocal;
-    
-    if ($platform eq "NETBSD" || $platform eq "OPENBSD" || 
-        $platform eq "FREEBSD") {
-        $needLocal = $TRUE;
-    } else {
-        $needLocal = $FALSE;
-    }
-    return $needLocal;
-}
-
-
-
 sub findProcessManagement($) {
     my ($dontHaveProcessMgmtR) = @_;
 #-----------------------------------------------------------------------------
 #  Return $TRUE iff the system does not have <sys/wait.h> in its default
 #  search path.
 #-----------------------------------------------------------------------------
-    my $cflags = testCflags($FALSE);
+    my $cflags = testCflags();
 
     my @cSourceCode = (
                        "#include <sys/wait.h>\n",
@@ -1404,13 +1376,13 @@ sub printOldJpegWarning() {
 
 
 
-sub testJpegHdr($$) {
+sub testJpegHdr($) {
 
-    my ($needLocal, $jpeghdr_dir) = @_;
+    my ($jpeghdr_dir) = @_;
 
     if (defined($testCc)) {
 
-        my $generalCflags = testCflags($needLocal);
+        my $generalCflags = testCflags();
 
         my $jpegIOpt = $jpeghdr_dir ? "-I$jpeghdr_dir" : "";
 
@@ -1463,15 +1435,15 @@ sub testCompilePngH($$) {
 
 
 
-sub testPngHdr($$$) {
+sub testPngHdr($$) {
 #-----------------------------------------------------------------------------
 #  Issue a warning if the compiler can't find png.h.
 #-----------------------------------------------------------------------------
-    my ($needLocal, $pnghdr_dir, $zhdr_dir) = @_;
+    my ($pnghdr_dir, $zhdr_dir) = @_;
 
     if (defined($testCc)) {
 
-        my $generalCflags = testCflags($needLocal);
+        my $generalCflags = testCflags();
 
         my $zlibIOpt = $zhdr_dir ? "-I$zhdr_dir" : "";
 
@@ -1495,6 +1467,42 @@ sub testPngHdr($$$) {
 
 
 
+sub printBadPngConfigLdflagsWarning($) {
+    my ($pngLdFlags) = @_;
+
+    print << 'EOF';
+WARNING: 'libpng-config' in this environment (a program in your PATH)
+gives instructions that don't work for linking with the PNG library.
+Our test link failed.
+
+This indicates Libpng is installed incorrectly on this system.  If so,
+your Netpbm build, which uses 'libpng-config', will fail.  But it
+might also just be our test that is broken.
+
+EOF
+
+}
+
+
+
+sub printBadPngConfigCFlagsWarning($) {
+    my ($pngCFlags) = @_;
+
+    print << 'EOF';
+WARNING: 'libpng-config' in this environment (a program in your PATH)
+gives instructions that don't work for compiling for the PNG library.
+Our test compile failed.
+
+This indicates Libpng is installed incorrectly on this system.  If so,
+your Netpbm build, which uses 'libpng-config', will fail.  But it
+might also just be our test that is broken.
+
+EOF
+
+}
+
+
+
 sub testLinkPnglib($$) {
     my ($generalCflags, $pngCflags) = @_;
 
@@ -1523,13 +1531,12 @@ sub testLinkPnglib($$) {
 
 
 
-sub testLibpngConfig($) {
-    my ($needLocal) = @_;
+sub testLibpngConfig() {
 #-----------------------------------------------------------------------------
 #  Issue a warning if the instructions 'libpng-config' give for compiling
 #  with Libpng don't work.
 #-----------------------------------------------------------------------------
-    my $generalCflags = testCflags($needLocal);
+    my $generalCflags = testCflags();
 
     my $pngCflags = qx{libpng-config --cflags};
     chomp($pngCflags);
@@ -1545,19 +1552,126 @@ sub testLibpngConfig($) {
 
 
 
-sub testConfiguration($$$$$$$) {
+sub testCompileXmlreaderH($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if we can see xmlreader.h.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <libxml/xmlreader.h>\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
 
-    my ($needLocal, $jpeglib, $jpeghdr_dir,
+sub printBadXml2CFlagsWarning($) {
+    my ($xml2CFlags) = @_;
+
+    print << 'EOF';
+
+WARNING: 'xml2-config' in this environment (a program in your PATH)
+gives instructions that don't work for compiling for the Libxml2 library.
+Our test compile failed.
+
+This indicates Libxml2 is installed incorrectly on this system.  If so,
+your Netpbm build, which uses 'xml2-config', will fail.  But it
+might also just be our test that is broken.
+
+EOF
+
+}
+
+
+
+sub testCompileXmlReaderTypes($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if xmlreader.h defines xmlReaderTypes,
+#  assuming we can compile with xmlreader.h in general.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <libxml/xmlreader.h>\n",
+                       "xmlReaderTypes dummy;\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
+
+sub printBadXml2XmlReaderTypesWarning($) {
+
+    print << 'EOF';
+
+WARNING: Your libxml2 interface header file does not define the type
+'xmlReaderTypes', which Netpbm needs.  In order to build Netpbm successfully,
+you must install a more recent version of Libxml2.
+
+EOF
+}
+
+
+
+sub printNoLibxml2Warning() {
+
+    print << 'EOF';
+
+WARNING: You appear not to have Libxml2 installed ('xml2-config' does not
+exist in your program search PATH).  If this is the case at build time,
+the build will skip building 'svtgtopam'.
+
+EOF
+}
+
+
+
+sub testLibxml2Hdr() {
+#-----------------------------------------------------------------------------
+#  Issue a warning if the instructions 'xml2-config' give for compiling
+#  with Libxml2 don't work.  In particular, note whether they get us a
+#  modern enough Libxml2 to have the 'xmlReaderTypes' type.
+#-----------------------------------------------------------------------------
+    if (commandExists('xml2-config')) {
+        my $generalCflags = testCflags();
+
+        my $xml2Cflags = qx{xml2-config --cflags};
+        chomp($xml2Cflags);
+
+        testCompileXmlreaderH("$generalCflags $xml2Cflags", \my $success);
+
+        if (!$success) {
+            printBadXml2CflagsWarning($xml2Cflags);
+        } else {
+            testCompileXmlReaderTypes("$generalCflags $xml2Cflags",
+                                      \my $success);
+
+            if (!$success) {
+                printMissingXmlReaderTypesWarning();
+            }
+        }
+    } else {
+        printNoLibxml2Warning();
+    }
+}
+
+
+
+sub testConfiguration($$$$$$) {
+
+    my ($jpeglib, $jpeghdr_dir,
         $pnglib, $pnghdr_dir, $zlib, $zhdr_dir) = @_;
 
     if (defined($jpeglib)) {
-        testJpegHdr($needLocal, $jpeghdr_dir);
+        testJpegHdr($jpeghdr_dir);
     }
     if (defined($pnglib) && defined($zlib)) {
-        testPngHdr($needLocal, $pnghdr_dir, $zhdr_dir);
+        testPngHdr($pnghdr_dir, $zhdr_dir);
     } elsif (commandExists('libpng-config')) {
-        testLibpngConfig($needLocal);
+        testLibpngConfig();
     }
+    testLibxml2Hdr();
 
     # TODO: We ought to validate other libraries too.  But it's not
     # that important, because in the vast majority of cases where the
@@ -1771,8 +1885,7 @@ validateLibraries($jpeglib, $tifflib, $pnglib, $zlib);
 
 warnJpegTiffDependency($jpeglib, $tifflib);
 
-testConfiguration(needLocal($platform), 
-                  $jpeglib, $jpeghdr_dir,
+testConfiguration($jpeglib, $jpeghdr_dir,
                   $pnglib, $pnghdr_dir,
                   $zlib, $zhdr_dir,
                   );
@@ -2029,23 +2142,18 @@ if ($platform eq "GNU") {
     push(@Makefile_config, 'CFLAGS_SHLIB = -fno-common', "\n");
 
     my $installNameOpt;
-    if ($netpbmlib eq '') {
+    if ($netpbmlib_runtime_path eq '') {
         $installNameOpt = '';
     } else {
         $installNameOpt  =
-            '-install_name $(NETPBMLIB_RUNTIME_PATH)/libnetpbm.$(MAJ).dylib', 
-        }
-    push(@Makefile_config, "LDSHLIB = -dynamiclib $installNameOpt\n"
+            '-install_name $(NETPBMLIB_RUNTIME_PATH)/libnetpbm.$(MAJ).dylib';
+    }
+    push(@Makefile_config, "LDSHLIB = -dynamiclib $installNameOpt\n");
 #    push(@Makefile_config, "INSTALL = install\n");
 } else {
     die ("Internal error: invalid value for \$platform: '$platform'\n");
 }
 
-if (needLocal($platform)) {
-    push(@Makefile_config, "CFLAGS += -I/usr/local/include\n");
-    push(@Makefile_config, "LDFLAGS += -L/usr/local/lib\n");
-}
-
 if ($linkViaCompiler) {
     push(@Makefile_config, "LINKERISCOMPILER = Y\n");
 }
diff --git a/converter/other/Makefile b/converter/other/Makefile
index 0273c523..e4eac3ad 100644
--- a/converter/other/Makefile
+++ b/converter/other/Makefile
@@ -80,7 +80,8 @@ endif
 PORTBINARIES =  bmptopnm fitstopnm \
 		gemtopnm giftopnm hdifftopam infotopam \
 		pamtodjvurle pamtofits pamtogif \
-		pamtohdiff pamtohtmltbl pamtopfm pamtopnm pamtouil \
+		pamtohdiff pamtohtmltbl pamtooctave \
+		pamtopfm pamtopnm pamtouil \
 		pamtoxvmini \
 		pbmtopgm pfmtopam \
 	        pgmtopbm pgmtoppm ppmtopgm pnmtoddif \
diff --git a/converter/other/fitstopnm.c b/converter/other/fitstopnm.c
index b143882b..86bdfbb8 100644
--- a/converter/other/fitstopnm.c
+++ b/converter/other/fitstopnm.c
@@ -1,4 +1,4 @@
- /* fitstopnm.c - read a FITS file and produce a portable anymap
+ /* fitstopnm.c - read a FITS file and produce a PNM.
  **
  ** Copyright (C) 1989 by Jef Poskanzer.
  **
@@ -31,15 +31,109 @@
  */
 
 #include <string.h>
+#include <float.h>
 
 #include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
 #include "pnm.h"
 
-/* Do what you have to, to get a sensible value here.  This may not be so */
-/* portable as the rest of the program.  We want to use MAXFLOAT and */
-/* MAXDOUBLE, so you could define them manually if you have to. */
-/* #include <values.h> */
-#include <float.h>
+
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    unsigned int image;  /* zero if unspecified */
+    double max;
+    unsigned int maxSpec;
+    double min;
+    unsigned int minSpec;
+    unsigned int scanmax;
+    unsigned int printmax;
+    unsigned int noraw;
+        /* This is for backward compatibility only.  Use the common option
+           -plain now.  (pnm_init() processes -plain).
+        */
+    unsigned int verbose;
+    unsigned int omaxval;
+    unsigned int omaxvalSpec;
+};
+
+
+
+static void 
+parseCommandLine(int argc, char ** argv, 
+                 struct cmdlineInfo * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int imageSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "image",    OPT_UINT,
+            &cmdlineP->image,   &imageSpec,                            0);
+    OPTENT3(0, "min",      OPT_FLOAT,
+            &cmdlineP->min,     &cmdlineP->minSpec,                    0);
+    OPTENT3(0, "max",      OPT_FLOAT,
+            &cmdlineP->max,     &cmdlineP->maxSpec,                    0);
+    OPTENT3(0, "scanmax",  OPT_FLAG,
+            NULL,               &cmdlineP->scanmax,                    0);
+    OPTENT3(0, "printmax", OPT_FLAG,
+            NULL,               &cmdlineP->printmax,                   0);
+    OPTENT3(0, "noraw",    OPT_FLAG,
+            NULL,               &cmdlineP->noraw,                      0);
+    OPTENT3(0, "verbose",  OPT_FLAG,
+            NULL,               &cmdlineP->verbose,                    0);
+    OPTENT3(0, "omaxval",  OPT_UINT,
+            &cmdlineP->omaxval, &cmdlineP->omaxvalSpec,                0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    /* Set some defaults the lazy way (using multiple setting of variables) */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (imageSpec) {
+        if (cmdlineP->image == 0)
+            pm_error("You may not specify zero for the image number.  "
+                     "Images are numbered starting at 1.");
+    } else
+        cmdlineP->image = 0;
+
+    if (cmdlineP->maxSpec && cmdlineP->minSpec) {
+        if (cmdlineP->max <= cmdlineP->min)
+            pm_error("-max must be greater than -min.  You specified "
+                     "-max=%f, -min=%f", cmdlineP->max, cmdlineP->min);
+    }
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u).  The only non-option argument "
+                     "is the input file name.", argc-1);
+    }
+}
+
+
 
 struct FITS_Header {
   int simple;       /* basic format or not */
@@ -54,10 +148,205 @@ struct FITS_Header {
   double bscale;
 };
 
-static void read_fits_header ARGS(( FILE* fp, struct FITS_Header* hP ));
-static void read_card ARGS(( FILE* fp, char* buf ));
-static void read_val ARGS(( FILE* fp, int bitpix, double* vp ));
-     
+
+
+/*
+ ** This code will deal properly with integers, no matter what the byte order
+ ** or integer size of the host machine.  Sign extension is handled manually
+ ** to prevent problems with signed/unsigned characters.  Floating point
+ ** values will only be read properly when the host architecture is IEEE-754
+ ** conformant.  If you need to tweak this code for other machines, you might
+ ** want to snag a copy of the FITS documentation from nssdca.gsfc.nasa.gov
+ */
+
+static void
+readVal(FILE *   const ifP,
+        int      const bitpix,
+        double * const vP) {
+
+    switch (bitpix) {
+        /* 8 bit FITS integers are unsigned */
+    case 8: {
+        int const ich = getc(ifP);
+        if (ich == EOF)
+            pm_error("EOF / read error");
+        *vP = ich;
+    } break;
+
+    case 16: {
+        int ich;
+        int ival;
+        unsigned char c[8];
+
+        ich = getc(ifP);
+        if (ich == EOF)
+            pm_error("EOF / read error");
+        c[0] = ich;
+        ich = getc(ifP);
+        if (ich == EOF)
+            pm_error("EOF / read error");
+        c[1] = ich;
+        if (c[0] & 0x80)
+            ival = ~0xFFFF | c[0] << 8 | c[1];
+        else
+            ival = c[0] << 8 | c[1];
+        *vP = ival;
+    } break;
+      
+    case 32: {
+        unsigned int i;
+        long int lval;
+        unsigned char c[4];
+
+        for (i = 0; i < 4; ++i) {
+            int const ich = getc(ifP);
+            if (ich == EOF)
+                pm_error("EOF / read error");
+            c[i] = ich;
+        }
+        if (c[0] & 0x80)
+            lval = ~0xFFFFFFFF | c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3];
+        else
+            lval = c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3] << 0;
+        *vP = lval;
+    } break;
+      
+    case -32: {
+        unsigned int i;
+        unsigned char c[4];
+
+        for (i = 0; i < 4; ++i) {
+            int const ich = getc(ifP);
+            if (ich == EOF)
+                pm_error("EOF / read error");
+            c[i] = ich;
+        }
+        *vP = *((float *)c);
+    } break;
+      
+    case -64: {
+        unsigned int i;
+        unsigned char c[8];
+
+        for (i = 0; i < 8; ++i) {
+            int const ich = getc(ifP);
+            if (ich == EOF)
+                pm_error("EOF / read error");
+            c[i] = ich;
+        }
+        *vP = *((double *)c);
+    } break;
+      
+    default:
+        pm_error("Strange bitpix value %d in readVal()", bitpix);
+    }
+}
+
+
+
+static void
+readCard(FILE * const ifP,
+         char * const buf) {
+
+    size_t bytesRead;
+
+    bytesRead = fread(buf, 1, 80, ifP);
+    if (bytesRead == 0)
+        pm_error("error reading header");
+}
+
+
+
+static void
+readFitsHeader(FILE *               const ifP,
+               struct FITS_Header * const hP) {
+
+    int seenEnd;
+  
+    seenEnd = 0;
+    /* Set defaults */
+    hP->simple  = 0;
+    hP->bzer    = 0.0;
+    hP->bscale  = 1.0;
+    hP->datamin = - DBL_MAX;
+    hP->datamax = DBL_MAX;
+  
+    while (!seenEnd) {
+        unsigned int i;
+        for (i = 0; i < 36; ++i) {
+            char buf[80];
+            char c;
+
+            readCard(ifP, buf);
+    
+            if (sscanf(buf, "SIMPLE = %c", &c) == 1) {
+                if (c == 'T' || c == 't')
+                    hP->simple = 1;
+            } else if (sscanf(buf, "BITPIX = %d", &(hP->bitpix)) == 1);
+            else if (sscanf(buf, "NAXIS = %d", &(hP->naxis)) == 1);
+            else if (sscanf(buf, "NAXIS1 = %d", &(hP->naxis1)) == 1);
+            else if (sscanf(buf, "NAXIS2 = %d", &(hP->naxis2)) == 1);
+            else if (sscanf(buf, "NAXIS3 = %d", &(hP->naxis3)) == 1);
+            else if (sscanf(buf, "DATAMIN = %lf", &(hP->datamin)) == 1);
+            else if (sscanf(buf, "DATAMAX = %lf", &(hP->datamax)) == 1);
+            else if (sscanf(buf, "BZERO = %lf", &(hP->bzer)) == 1);
+            else if (sscanf(buf, "BSCALE = %lf", &(hP->bscale)) == 1);
+            else if (strncmp(buf, "END ", 4 ) == 0) seenEnd = 1;
+        }
+    }
+}
+
+
+
+static void
+interpretPlanes(struct FITS_Header const fitsHeader,
+                unsigned int       const imageRequest,
+                bool               const verbose,
+                unsigned int *     const imageCountP,
+                bool *             const multiplaneP,
+                unsigned int *     const desiredImageP) {
+
+    if (fitsHeader.naxis == 2) {
+        *imageCountP   = 1;
+        *multiplaneP   = FALSE;
+        *desiredImageP = 1;
+    } else {
+        if (imageRequest) {
+            if (imageRequest > fitsHeader.naxis3)
+                pm_error("Only %u plane%s in this file.  "
+                         "You requested image %u", 
+                         fitsHeader.naxis3, fitsHeader.naxis3 > 1 ? "s" : "",
+                         imageRequest);
+            else {
+                *imageCountP   = fitsHeader.naxis3;
+                *multiplaneP   = FALSE;
+                *desiredImageP = imageRequest;
+            }
+        } else {
+            if (fitsHeader.naxis3 == 3) {
+                *imageCountP   = 1;
+                *multiplaneP   = TRUE;
+                *desiredImageP = 1;
+            } else if (fitsHeader.naxis3 > 1)
+                pm_error("This FITS file contains multiple (%u) images.  "
+                         "You must specify which one you want with a "
+                         "-image option.", fitsHeader.naxis3);
+            else {
+                *imageCountP   = fitsHeader.naxis3;
+                *multiplaneP   = FALSE;
+                *desiredImageP = 1;
+            }
+        }
+    }
+    if (verbose) {
+        
+        pm_message("FITS stream is %smultiplane", *multiplaneP ? "" : "not ");
+        pm_message("We will take image %u (1 is first) of %u "
+                   "in the FITS stream",
+                   *desiredImageP, *imageCountP);
+    }
+}
+
 
 
 static void
@@ -100,7 +389,7 @@ scanImageForMinMax(FILE *       const ifP,
             unsigned int col;
             for (col = 0; col < cols; ++col) {
                 double val;
-                read_val(ifP, bitpix, &val);
+                readVal(ifP, bitpix, &val);
                 if (image == imagenum || multiplane ) {
                     dmax = MAX(dmax, val);
                     dmin = MIN(dmin, val);
@@ -170,326 +459,225 @@ computeMinMax(FILE *             const ifP,
 
 
 
-int
-main(int argc, char * argv[]) {
-
-    FILE* ifp;
-    int argn, imagenum, image, row;
-    register int col;
-    xelval maxval;
-    double val, frmin, frmax, scale, t;
-    double datamin, datamax;
-    int rows, cols, images, format;
-    struct FITS_Header h;
-    xel** pnmarray;
-    xelval tx, txv[4];
-    const char* fits_name;
-    const char* const usage = "[-image N] [-scanmax] [-printmax] [-min f] [-max f] [FITSfile]";
-
-    int doscan = 0;
-    int forceplain = 0;
-    int forcemin = 0;
-    int forcemax = 0;
-    int printmax = 0;
-    bool multiplane;
-  
-    pnm_init( &argc, argv );
-  
-    argn = 1;
-    imagenum = 0;
-  
-    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-    {
-        if ( pm_keymatch( argv[argn], "-image", 2 ) )
-        {
-            ++argn;
-            if ( argn == argc || sscanf( argv[argn], "%d", &imagenum ) != 1 )
-                pm_usage( usage );
-        }
-        else if ( pm_keymatch( argv[argn], "-max", 3 ) )
-        {
-            ++argn;
-            forcemax = 1;
-            if ( argn == argc || sscanf( argv[argn], "%lf", &frmax ) != 1 )
-                pm_usage( usage );
-        }
-        else if ( pm_keymatch( argv[argn], "-min", 3 ) )
-        {
-            ++argn;
-            forcemin = 1;
-            if ( argn == argc || sscanf( argv[argn], "%lf", &frmin ) != 1 )
-                pm_usage( usage );
-        }
-        else if ( pm_keymatch( argv[argn], "-scanmax", 2 ) )
-            doscan = 1;
-        else if ( pm_keymatch( argv[argn], "-noraw", 2 ) )
-            /* This is for backward compatibility only.  Use the common option
-               -plain now.  (pnm_init() processes -plain).
+static xelval
+determineMaxval(struct cmdlineInfo const cmdline,
+                struct FITS_Header const fitsHeader,
+                double             const datamax,
+                double             const datamin) {
+
+    xelval retval;
+                
+    if (cmdline.omaxvalSpec)
+        retval = cmdline.omaxval;
+    else {
+        if (fitsHeader.bitpix < 0) {
+            /* samples are floating point, which means the resolution
+               could be anything.  So we just pick a convenient maxval
+               of 255.  Before Netpbm 10.20 (January 2004), we did
+               maxval = max - min for floating point as well as
+               integer samples.
             */
-            forceplain = 1;
-        else if ( pm_keymatch( argv[argn], "-printmax", 2 ) )
-            printmax = 1;
-        else
-            pm_usage( usage );
-        ++argn;
-    }
-  
-    if ( argn < argc )
-    {
-        fits_name = argv[argn];
-        ++argn;
+            retval = 255;
+            if (cmdline.verbose)
+                pm_message("FITS image has floating point samples.  "
+                           "Using maxval = %u.", (unsigned int)retval);
+        } else {
+            retval = MAX(1, MIN(PNM_OVERALLMAXVAL, datamax - datamin));
+            if (cmdline.verbose)
+                pm_message("FITS image has samples in the range %d-%d.  "
+                           "Using maxval %u.",
+                           (int)(datamin+0.5), (int)(datamax+0.5),
+                           (unsigned int)retval);
+        }
     }
-    else
-        fits_name = "-";
-
-    if ( argn != argc )
-        pm_usage( usage );
-
-    ifp = pm_openr_seekable(fits_name);
-  
-    read_fits_header( ifp, &h );
-  
-    if ( ! h.simple )
-        pm_error( "FITS file is not in simple format, can't read" );
-    if ( h.naxis != 2 && h.naxis != 3 )
-        pm_message( "Warning: FITS file has %d axes", h.naxis );
-    cols = h.naxis1;
-    rows = h.naxis2;
-    if ( h.naxis == 2 )
-        images = imagenum = 1;
-    else
-        images = h.naxis3;
-    if ( imagenum > images )
-        pm_error( "only %d plane%s in this file", 
-                  images, images > 1 ? "s" : "" );
-    if ( images != 3 && images > 1 && imagenum == 0 )
-        pm_error( "need to specify a plane using the -imagenum flag" );
+    return retval;
+}
 
-    multiplane = ( images == 3 && imagenum == 0 );
 
-    computeMinMax(ifp, images, cols, rows, h, imagenum, multiplane,
-                  forcemin, forcemax, frmin, frmax,
-                  &datamin, &datamax);
 
-    if (h.bitpix < 0) {
-        /* samples are floating point, which means the resolution could be
-           anything.  So we just pick a convenient maxval of 255.  We should
-           have a program option to choose the maxval.  Before Netpbm 10.20
-           (January 2004), we did maxval = max - min for floating point as
-           well as integer samples.
-        */
-        maxval = 255;
-    } else
-        maxval = MAX(1, MIN(PNM_OVERALLMAXVAL, datamax - datamin));
-
-    if ( datamax - datamin == 0 )
-        scale = 1.0;
-    else
-        scale = maxval / ( datamax - datamin );
-
-    /* If printmax option is true, just print and exit. */
-    /* For use in shellscripts.  Ex:                    */
-    /* eval `fitstopnm -printmax $filename | \          */
-    /*   awk '{min = $1; max = $2}\                     */
-    /*         END {print "min=" min; " max=" max}'`    */
-    if (printmax) {
-        printf( "%f %f\n", datamin, datamax);
-        pm_close( ifp );
-        pm_close( stdout );
-        exit( 0 );
-    }
+static void
+convertPgmRaster(FILE *             const ifP,
+                 unsigned int       const cols,
+                 unsigned int       const rows,
+                 xelval             const maxval,
+                 unsigned int       const desiredImage,
+                 unsigned int       const imageCount,
+                 struct FITS_Header const fitsHdr,
+                 double             const scale,
+                 double             const datamin,
+                 xel **             const xels) {
+        
+    unsigned int image;
 
-    if (multiplane)
-        format = PPM_FORMAT;
-    else
-        format = PGM_FORMAT;
+    pm_message("writing PGM file");
 
-    pnmarray = pnm_allocarray( cols, rows );
-
-    switch( PNM_FORMAT_TYPE( format ))
-    {
-    case PGM_TYPE:
-        pm_message( "writing PGM file" );
-        for ( image = 1; image <= imagenum; ++image )
-        {
-            if ( image != imagenum )
-                pm_message( "skipping image plane %d of %d", image, images );
-            else if ( images > 1 )
-                pm_message( "reading image plane %d of %d", image, images );
-            for ( row = 0; row < rows; ++row)
-                for ( col = 0; col < cols; ++col )
+    for (image = 1; image <= desiredImage; ++image) {
+        unsigned int row;
+        if (image != desiredImage)
+            pm_message("skipping image plane %u of %u", image, imageCount);
+        else if (imageCount > 1)
+            pm_message("reading image plane %u of %u", image, imageCount);
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                double val;
+                readVal(ifP, fitsHdr.bitpix, &val);
                 {
-                    read_val (ifp, h.bitpix, &val);
-                    t = scale * ( val * h.bscale + h.bzer - datamin );
-                    tx = MAX( 0, MIN( t, maxval ) );
-                    if ( image == imagenum )
-                        PNM_ASSIGN1( pnmarray[row][col], tx );
+                    double const t = scale *
+                        (val * fitsHdr.bscale + fitsHdr.bzer - datamin);
+                    xelval const tx = MAX(0, MIN(t, maxval));
+                    if (image == desiredImage)
+                        PNM_ASSIGN1(xels[row][col], tx);
                 }
+            }
         }
-        break;
-    case PPM_TYPE:
-        pm_message( "writing PPM file" );
-        for ( image = 1; image <= images; image++ )
-        {
-            pm_message( "reading image plane %d of %d", image, images );
-            for ( row = 0; row < rows; row++ )
-                for ( col = 0; col < cols; col++ )
+    } 
+}
+
+
+
+static void
+convertPpmRaster(FILE *             const ifP,
+                 unsigned int       const cols,
+                 unsigned int       const rows,
+                 xelval             const maxval,
+                 struct FITS_Header const fitsHdr,
+                 double             const scale,
+                 double             const datamin,
+                 xel **             const xels) {
+/*----------------------------------------------------------------------------
+   Read the FITS raster from file *ifP into xels[][].  Image dimensions
+   are 'cols' by 'rows'.  The FITS raster is 3 planes composing one
+   image: a red plane followed by a green plane followed by a blue plane.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    pm_message("writing PPM file");
+
+    for (plane = 0; plane < 3; ++plane) {
+        unsigned int row;
+        pm_message("reading image plane %u (%s)",
+                   plane, plane == 0 ? "red" : plane == 1 ? "green" : "blue");
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                double val;
+                readVal(ifP, fitsHdr.bitpix, &val);
                 {
-                    read_val (ifp, h.bitpix, &val);
-                    txv[1] = PPM_GETR( pnmarray[row][col] );
-                    txv[2] = PPM_GETG( pnmarray[row][col] );
-                    txv[3] = PPM_GETB( pnmarray[row][col] );
-                    t = scale * ( val * h.bscale + h.bzer - datamin );
-                    txv[image] = MAX( 0, MIN( t, maxval ));
-                    PPM_ASSIGN( pnmarray[row][col], txv[1], txv[2], txv[3] );
+                    double const t = scale *
+                        (val * fitsHdr.bscale + fitsHdr.bzer - datamin);
+                    xelval const sample = MAX(0, MIN(t, maxval));
+
+                    switch (plane) {
+                    case 0: PPM_PUTR(xels[row][col], sample); break;
+                    case 1: PPM_PUTG(xels[row][col], sample); break;
+                    case 2: PPM_PUTB(xels[row][col], sample); break;
+                    }
                 }
+            }
         }
-        break;
-    default:
-        pm_error( "can't happen" );
-        break;
     }
-
-    pnm_writepnm( stdout, pnmarray, cols, rows, maxval, format, forceplain );
-    pnm_freearray( pnmarray, rows );
-
-    pm_close( ifp );
-    pm_close( stdout );
-  
-    exit( 0 );
 }
 
-/*
- ** This code will deal properly with integers, no matter what the byte order
- ** or integer size of the host machine.  Sign extension is handled manually
- ** to prevent problems with signed/unsigned characters.  Floating point
- ** values will only be read properly when the host architecture is IEEE-754
- ** conformant.  If you need to tweak this code for other machines, you might
- ** want to snag a copy of the FITS documentation from nssdca.gsfc.nasa.gov
- */
+
 
 static void
-read_val (fp, bitpix, vp)
-    FILE *fp;
-    int bitpix;
-    double *vp;
-
-{
-    int i, ich, ival;
-    long int lval;
-    unsigned char c[8];
-  
-    switch ( bitpix )
-    {
-        /* 8 bit FITS integers are unsigned */
-    case 8:
-        ich = getc( fp );
-        if ( ich == EOF )
-            pm_error( "EOF / read error" );
-        *vp = ich;
-        break;
-      
-    case 16:
-        ich = getc( fp );
-        if ( ich == EOF )
-            pm_error( "EOF / read error" );
-        c[0] = ich;
-        ich = getc( fp );
-        if ( ich == EOF )
-            pm_error( "EOF / read error" );
-        c[1] = ich;
-        if (c[0] & 0x80)
-            ival = ~0xFFFF | c[0]<<8 | c[1];
-        else
-            ival = c[0]<<8 | c[1];
-        *vp = ival;
-        break;
-      
-    case 32:
-        for (i=0; i<4; i++) {
-            ich = getc( fp );
-            if ( ich == EOF )
-                pm_error( "EOF / read error" );
-            c[i] = ich;
-        }
-        if (c[0] & 0x80)
-            lval = ~0xFFFFFFFF |
-                c[0]<<24 | c[1]<<16 | c[2]<<8 | c[3];
-        else
-            lval = c[0]<<24 | c[1]<<16 | c[2]<<8 | c[3];
-        *vp = lval;
-        break;
-      
-    case -32:
-        for (i=0; i<4; i++) {
-            ich = getc( fp );
-            if ( ich == EOF )
-                pm_error( "EOF / read error" );
-            c[i] = ich;
-        }
-        *vp = *( (float *) c);
-        break;
-      
-    case -64:
-        for (i=0; i<8; i++) {
-            ich = getc( fp );
-            if ( ich == EOF )
-                pm_error( "EOF / read error" );
-            c[i] = ich;
-        }
-        *vp = *( (double *) c);
-        break;
-      
-    default:
-        pm_error( "Strange bitpix in read_value" );
+convertRaster(FILE *             const ifP,
+              unsigned int       const cols,
+              unsigned int       const rows,
+              xelval             const maxval,
+              bool               const forceplain,
+              bool               const multiplane,
+              unsigned int       const desiredImage,
+              unsigned int       const imageCount,
+              struct FITS_Header const fitsHdr,
+              double             const scale,
+              double             const datamin) {
+
+    xel ** xels;
+    int format;
+
+    xels = pnm_allocarray(cols, rows);
+
+    if (multiplane) {
+        format = PPM_FORMAT;
+        convertPpmRaster(ifP, cols, rows, maxval, fitsHdr, scale, datamin,
+                         xels);
+    } else {
+        format = PGM_FORMAT;
+        convertPgmRaster(ifP, cols, rows, maxval,
+                         desiredImage, imageCount, fitsHdr, scale, datamin,
+                         xels);
     }
+    pnm_writepnm(stdout, xels, cols, rows, maxval, format, forceplain);
+    pnm_freearray(xels, rows);
 }
 
-static void
-read_fits_header( fp, hP )
-    FILE* fp;
-    struct FITS_Header* hP;
-{
-    char buf[80];
-    int seen_end;
-    int i;
-    char c;
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    unsigned int cols, rows;
+    xelval maxval;
+    double scale;
+    double datamin, datamax;
+    struct FITS_Header fitsHeader;
+
+    unsigned int imageCount;
+    unsigned int desiredImage;
+        /* Plane number (starting at one) of plane that contains the image
+           we want.
+        */
+    bool multiplane;
+        /* This is a one-image multiplane stream; 'desiredImage'
+           is undefined
+        */
   
-    seen_end = 0;
-    hP->simple = 0;
-    hP->bzer = 0.0;
-    hP->bscale = 1.0;
-    hP->datamin = - DBL_MAX;
-    hP->datamax = DBL_MAX;
+    pnm_init( &argc, argv );
   
-    while ( ! seen_end )
-        for ( i = 0; i < 36; ++i )
-        {
-            read_card( fp, buf );
-    
-            if ( sscanf( buf, "SIMPLE = %c", &c ) == 1 )
-            {
-                if ( c == 'T' || c == 't' )
-                    hP->simple = 1;
-            }
-            else if ( sscanf( buf, "BITPIX = %d", &(hP->bitpix) ) == 1 );
-            else if ( sscanf( buf, "NAXIS = %d", &(hP->naxis) ) == 1 );
-            else if ( sscanf( buf, "NAXIS1 = %d", &(hP->naxis1) ) == 1 );
-            else if ( sscanf( buf, "NAXIS2 = %d", &(hP->naxis2) ) == 1 );
-            else if ( sscanf( buf, "NAXIS3 = %d", &(hP->naxis3) ) == 1 );
-            else if ( sscanf( buf, "DATAMIN = %lf", &(hP->datamin) ) == 1 );
-            else if ( sscanf( buf, "DATAMAX = %lf", &(hP->datamax) ) == 1 );
-            else if ( sscanf( buf, "BZERO = %lf", &(hP->bzer) ) == 1 );
-            else if ( sscanf( buf, "BSCALE = %lf", &(hP->bscale) ) == 1 );
-            else if ( strncmp( buf, "END ", 4 ) == 0 ) seen_end = 1;
-        }
-}
+    parseCommandLine(argc, argv, &cmdline);
 
-static void
-read_card( fp, buf )
-    FILE* fp;
-    char* buf;
-{
-    if ( fread( buf, 1, 80, fp ) == 0 )
-        pm_error( "error reading header" );
+    ifP = pm_openr(cmdline.inputFileName);
+
+    readFitsHeader(ifP, &fitsHeader);
+  
+    if (!fitsHeader.simple)
+        pm_error("FITS file is not in simple format, can't read");
+
+    if (fitsHeader.naxis != 2 && fitsHeader.naxis != 3)
+        pm_message("Warning: FITS file has %u axes", fitsHeader.naxis);
+
+    cols = fitsHeader.naxis1;
+    rows = fitsHeader.naxis2;
+
+    interpretPlanes(fitsHeader, cmdline.image, cmdline.verbose,
+                    &imageCount, &multiplane, &desiredImage);
+
+    computeMinMax(ifP, imageCount, cols, rows, fitsHeader,
+                  desiredImage, multiplane,
+                  cmdline.minSpec, cmdline.maxSpec,
+                  cmdline.min, cmdline.max,
+                  &datamin, &datamax);
+
+    maxval = determineMaxval(cmdline, fitsHeader, datamax, datamin);
+
+    if (datamax - datamin == 0)
+        scale = 1.0;
+    else
+        scale = maxval / (datamax - datamin);
+
+    if (cmdline.printmax)
+        printf("%f %f\n", datamin, datamax);
+    else
+        convertRaster(ifP, cols, rows, maxval, cmdline.noraw,
+                      multiplane, desiredImage, imageCount,
+                      fitsHeader, scale, datamin);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
 }
diff --git a/converter/other/pamtofits.c b/converter/other/pamtofits.c
index ec271ff3..04b43c35 100644
--- a/converter/other/pamtofits.c
+++ b/converter/other/pamtofits.c
@@ -39,10 +39,9 @@ struct cmdlineInfo {
 
 
 static void 
-parseCommandLine(int argc, 
-                 char ** argv, 
-                 struct cmdlineInfo  * const cmdlineP) {
-/* --------------------------------------------------------------------------
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*--------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.  
 
@@ -52,8 +51,8 @@ parseCommandLine(int argc,
    Note that the strings we return are stored in the storage that
    was passed to us as the argv array.  We also trash *argv.
 --------------------------------------------------------------------------*/
-    optEntry *option_def;
-    /* Instructions to optParseOptions3 on how to parse our options. */
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options. */
     optStruct3 opt;
 
     unsigned int minSpec;
@@ -98,7 +97,6 @@ parseCommandLine(int argc,
 
 
 
-
 static void
 writeHeaderCard(const char * const s) {
 /*----------------------------------------------------------------------------
diff --git a/converter/other/pamtooctave.c b/converter/other/pamtooctave.c
new file mode 100644
index 00000000..b090281d
--- /dev/null
+++ b/converter/other/pamtooctave.c
@@ -0,0 +1,241 @@
+/* ----------------------------------------------------------------------
+ *
+ * Convert a Netpbm file to the GNU Octave image format
+ * by Scott Pakin <scott+pbm@pakin.org>
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright information is at end of file.
+ * ----------------------------------------------------------------------
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pammap.h"
+
+typedef struct {
+    double comp[3];
+        /* comp[0] is red; comp[1] is green; comp[2] is blue */
+} octaveColor;
+
+typedef struct {
+    struct pam pam;
+    unsigned int nColors;
+    tuplehash hash;
+    unsigned int paletteAlloc;
+        /* 'palette' array has this many slots allocated.  Only the first
+           'nColors' are meaningful.
+        */
+    octaveColor * palette;
+    double normalizer;
+        /* 1/maxval */
+} cmap;
+
+
+
+static void
+initCmap(cmap * const cmapP,
+         sample const maxval) {
+
+    cmapP->pam.size             = sizeof(cmapP->pam.size);
+    cmapP->pam.len              = PAM_STRUCT_SIZE(tuple_type);
+    cmapP->pam.depth            = 3;
+    cmapP->pam.maxval           = maxval;
+    cmapP->pam.bytes_per_sample = pnm_bytespersample(maxval);
+
+    cmapP->normalizer   = 1.0/maxval;
+    cmapP->nColors      = 0;
+    cmapP->paletteAlloc = 0;
+    cmapP->palette      = NULL;
+    cmapP->hash         = pnm_createtuplehash();
+}
+
+
+
+static void
+termCmap(cmap * const cmapP) {
+    pnm_destroytuplehash(cmapP->hash);
+
+    free(cmapP->palette);
+}
+
+
+
+static void
+findOrAddColor(tuple          const color,
+               cmap *         const cmapP,
+               unsigned int * const colorIndexP) {
+/*----------------------------------------------------------------------------
+  Return as *colorIndexP the colormap index of color 'color' in
+  colormap *cmapP.  If the color isn't in the map, give it a new
+  colormap index, put it in the colormap, and return that.
+-----------------------------------------------------------------------------*/
+    bool found;
+    int colorIndex;
+
+    pnm_lookuptuple(&cmapP->pam, cmapP->hash, color, &found, &colorIndex);
+
+    if (!found) {
+        bool fits;
+        unsigned int plane;
+
+        colorIndex = cmapP->nColors++;
+
+        if (cmapP->nColors > cmapP->paletteAlloc) {
+            cmapP->paletteAlloc *= 2;
+            REALLOCARRAY(cmapP->palette, cmapP->nColors);
+        }
+        for (plane = 0; plane < 3; ++plane)
+            cmapP->palette[colorIndex].comp[plane] =
+                color[plane] * cmapP->normalizer;
+
+        pnm_addtotuplehash(&cmapP->pam, cmapP->hash, color, colorIndex, &fits);
+
+        if (!fits)
+            pm_error("Out of memory constructing color map, on %uth color",
+                     cmapP->nColors);
+    }
+    *colorIndexP = colorIndex;
+}
+
+
+
+static void
+outputColormap(FILE * const ofP,
+               cmap   const cmap) {
+/*----------------------------------------------------------------------------
+  Output the colormap as a GNU Octave matrix.
+-----------------------------------------------------------------------------*/
+    unsigned int colorIndex;
+
+    fprintf(ofP, "# name: map\n");
+    fprintf(ofP, "# type: matrix\n");
+    fprintf(ofP, "# rows: %u\n", cmap.nColors);
+    fprintf(ofP, "# columns: 3\n");
+
+    for (colorIndex = 0; colorIndex < cmap.nColors; ++colorIndex) {
+        unsigned int plane;
+
+        assert(cmap.pam.depth == 3);
+
+        for (plane = 0; plane < 3; ++plane)
+            fprintf(ofP, " %.10f", cmap.palette[colorIndex].comp[plane]);
+
+        fprintf(ofP, "\n");
+    }
+}
+
+
+
+static void
+convertToOctave(FILE * const ifP,
+                FILE * const ofP) {
+
+    struct pam inpam;
+    tuple * inRow;
+    unsigned int row;
+    cmap cmap;
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
+
+    pnm_setminallocationdepth(&inpam, 3);
+    
+    /* Output the image as a GNU Octave matrix.  For each row of the
+     * input file we immediately output indexes into the colormap then,
+     * when we're finished, we output the colormap as a second
+     * matrix. */
+    fprintf(ofP, "# name: img\n");
+    fprintf(ofP, "# type: matrix\n");
+    fprintf(ofP, "# rows: %u\n", inpam.height);
+    fprintf(ofP, "# columns: %u\n", inpam.width);
+
+    initCmap(&cmap, inpam.maxval);
+
+    inRow = pnm_allocpamrow(&inpam);
+    for (row = 0; row < inpam.height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(&inpam, inRow);
+
+        pnm_makerowrgb(&inpam, inRow);
+
+        for (col = 0; col < inpam.width; ++col) {
+            unsigned int colorIndex;
+            findOrAddColor(inRow[col], &cmap, &colorIndex);
+            fprintf(ofP, " %u", colorIndex + 1);
+        }
+        fprintf(ofP, "\n");
+    }
+    pm_message("%u colors in palette", cmap.nColors);
+
+    pnm_freepamrow(inRow);
+    outputColormap(ofP, cmap);
+
+    termCmap(&cmap);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    const char * inputName;
+
+    pnm_init(&argc, argv);
+
+    inputName = argc-1 > 0 ? argv[1] : "-";
+
+    ifP = pm_openr(inputName);
+    
+    if (streq(inputName, "-"))
+        fprintf(stdout, "# Created by pamtooctave\n");
+    else
+        fprintf(stdout, "# Created from '%s' by pamtooctave\n", inputName);
+
+    convertToOctave(ifP, stdout);
+    
+    pm_close(ifP);
+
+    return 0;
+}
+
+
+
+/*
+ * Copyright (C) 2007 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------
+ */
diff --git a/converter/other/svgtopam.c b/converter/other/svgtopam.c
index 268515ad..896c5f03 100644
--- a/converter/other/svgtopam.c
+++ b/converter/other/svgtopam.c
@@ -13,6 +13,17 @@
   By Bryan Henderson, San Jose, California.  May 2006
 
   Contributed to the public domain.
+
+==============================================================================
+
+  Implementation notes:
+
+   We've seen a version of <libxml/xmlreader.h> that does not
+   define type 'xmlReaderTypes'.  The system claimed to have
+   Libxml2 2.6.16 installed, but another system that makes that
+   claim _does_ have 'xmlReaderTypes' defined, so I don't know what
+   version actually has the problem.
+
 ============================================================================*/
 
 #include <assert.h>
@@ -561,6 +572,8 @@ static void
 processSubPathNode(xmlTextReaderPtr const xmlReaderP,
                    bool *           const endOfPathP) {
 
+    /* See comment above about xmlReaderTypes not being defined */
+
     xmlReaderTypes const nodeType  = xmlTextReaderNodeType(xmlReaderP);
 
     *endOfPathP = FALSE;  /* initial assumption */
diff --git a/converter/ppm/picttoppm.c b/converter/ppm/picttoppm.c
index 62fb410a..5b61401a 100644
--- a/converter/ppm/picttoppm.c
+++ b/converter/ppm/picttoppm.c
@@ -100,7 +100,7 @@ struct canvas {
 
 typedef void (*transfer_func) (struct RGBColor* src, struct RGBColor* dst);
 
-static const char* stage;
+static const char * stage;
 static struct Rect picFrame;
 static word rowlen;
 static word collen;
@@ -668,46 +668,102 @@ static struct fontinfo** fontlist_ins = &fontlist;
 
 
 
+static void
+tokenize(char *         const s,
+         const char **  const vec,
+         unsigned int   const vecSize,
+         unsigned int * const nTokenP) {
+
+    unsigned int nToken;
+    char * p;
+
+    p = &s[0];   /* start at beginning of string */
+    nToken = 0;  /* no tokens yet */
+
+    while (*p && nToken < vecSize - 1) {
+        if (ISSPACE(*p))
+            *p++ = '\0';
+        else {
+            vec[nToken++] = p;
+            /* Skip to next non-space character or end */
+            while (*p && !ISSPACE(*p))
+                ++p;
+        }
+    }
+    vec[nToken] = NULL;
+
+    *nTokenP = nToken;
+}
+
+
+
+static void
+parseFontLine(const char **      const token,
+              struct fontinfo ** const fontinfoPP) {
+
+    struct fontinfo * fontinfoP;
+
+    MALLOCVAR(fontinfoP);
+    if (fontinfoP == NULL)
+        pm_error("out of memory for font information");
+    MALLOCARRAY(fontinfoP->filename, strlen(token[3] + 1));
+    if (fontinfoP->filename == NULL)
+        pm_error("out of memory for font information file name");
+
+    fontinfoP->font  = atoi(token[0]);
+    fontinfoP->size  = atoi(token[1]);
+    fontinfoP->style = atoi(token[2]);
+    strcpy(fontinfoP->filename, token[3]);
+    fontinfoP->loaded = 0;
+
+    *fontinfoPP = fontinfoP;
+}
+
+
+
 static int 
 load_fontdir(const char * const dirfile) {
 /*----------------------------------------------------------------------------
    Load the font directory from file named 'dirfile'.  Add its contents
    to the global list of fonts 'fontlist'.
 -----------------------------------------------------------------------------*/
-    FILE* fp;
-    int n, nfont;
-    char* arg[5], line[1024];
-    struct fontinfo* fontinfo;
+    int retval;
+    FILE * fp;
 
-    if (!(fp = fopen(dirfile, "rb")))
-        return -1;
-    
-    nfont = 0;
-    while (fgets(line, 1024, fp)) {
-        if ((n = mk_argvn(line, arg, 5)) == 0 || arg[0][0] == '#')
-            continue;
-        if (n != 4)
-            continue;
-        MALLOCVAR(fontinfo);
-        if (fontinfo == NULL)
-            pm_error("out of memory for font information");
-        MALLOCARRAY(fontinfo->filename, strlen(arg[3] + 1));
-        if (fontinfo->filename == NULL)
-            pm_error("out of memory for font information file name");
-
-        fontinfo->font = atoi(arg[0]);
-        fontinfo->size = atoi(arg[1]);
-        fontinfo->style = atoi(arg[2]);
-        strcpy(fontinfo->filename, arg[3]);
-        fontinfo->loaded = 0;
-
-        fontinfo->next = 0;
-        *fontlist_ins = fontinfo;
-        fontlist_ins = &fontinfo->next;
-        nfont++;
-    }
-
-    return nfont;
+    fp = fopen(dirfile, "rb");
+    if (!fp)
+        retval =  -1;
+    else {
+        unsigned int nFont;
+        char line[1024]; 
+
+        nFont = 0;
+        while (fgets(line, 1024, fp) && nFont < INT_MAX) {
+            const char * token[10];
+            unsigned int nToken;
+
+            tokenize(line, token, ARRAY_SIZE(token), &nToken);
+
+            if (nToken == 0) {
+                /* blank line - ignore */
+            } else if (token[0][0] == '#') {
+                /* comment - ignore */
+            } else if (nToken != 4) {
+                /* Unrecognized format - ignore */
+            } else {
+                struct fontinfo * fontinfoP;
+
+                parseFontLine(token, &fontinfoP);
+
+                fontinfoP->next = 0;
+                *fontlist_ins = fontinfoP;
+                fontlist_ins = &fontinfoP->next;
+                ++nFont;
+            }
+        }
+        retval = nFont;
+    }
+    return retval;
 }
 
 
diff --git a/doc/HISTORY b/doc/HISTORY
index 3429a56f..4842f3ec 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,32 +4,39 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-07.06.26 BJH  Release 10.38.04
+07.06.26 BJH  Release 10.39.00
 
-              fitstopnm: fix BITPIX = -32.
+              Add pamtooctave.  Thanks Scott Pakin (scott@pakin.org).
+
+              Add pamundice.
+
+              fitstopnm: add -omaxval.
+
+              pnmremap: add -norand.
+
+              pbmtext: improve error messages about fonts.
 
               pamtofits: fix -min, -max.
 
-07.05.22 BJH  Release 10.38.03
+              fitstopnm: fix BITPIX = -32.
+
+              PAM_STRUCT_SIZE: cast pointer to ulong instead of uint.
 
               pamthreshold: fix totally bogus threshold selection with
               simple thresholding.
 
-              PAM_STRUCT_SIZE: cast pointer to ulong instead of uint.
-
-07.05.09 BJH  Release 10.38.02
+              Configure: do test compile for missing Libxml2 and too old
+              Libxml2.
 
               Configure: fix bug detecting presence of libvga with
               Ldconfig.
 
-07.05.04 BJH  Release 10.38.01
+              Configure: build properly for Mac OSX when user says
+              libnetpbm will be in the default search path.
 
               Build: don't use 'uint' type.  Mac OSX apparently doesn't
               have it.
 
-              Configure: build properly for Mac OSX when user says
-              libnetpbm will be in the default search path.
-
 07.03.30 BJH  Release 10.38.0
     
               Add pamfixtrunc.
diff --git a/doc/INSTALL b/doc/INSTALL
index ae49073a..d18d58c7 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -173,7 +173,7 @@ shared libraries (which is kept in /var/ld/ld.config).  Make sure that
 path includes the directory in which you installed the Netpbm shared
 library.  You can also use the LD_LIBRARY_PATH environment variable.
 
-On AIX, the environment variable LIBPATH affects the share library search
+On AIX, the environment variable LIBPATH affects the shared library search
 path.  On AIX 5.3 and newer, you can use LD_LIBRARY_PATH as well.
 
 Besides the Netpbm shared library, libnetpbm, several of the converter
diff --git a/editor/Makefile b/editor/Makefile
index 10c6ee7b..fd145b53 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -19,7 +19,7 @@ PORTBINARIES = pamaddnoise pambackground pamcomp pamcut \
 	       pamenlarge \
 	       pamflip pamfunc pammasksharpen pammixinterlace \
 	       pamoil pamperspective pampop9 \
-	       pamscale pamstretch pamthreshold \
+	       pamscale pamstretch pamthreshold pamundice \
 	       pbmclean pbmlife pbmmask pbmpscale pbmreduce \
 	       pgmabel pgmbentley pgmdeshadow pgmenhance \
 	       pgmmedian pgmmorphconv \
diff --git a/editor/pamdice.c b/editor/pamdice.c
index 062e05e3..4afb5f29 100644
--- a/editor/pamdice.c
+++ b/editor/pamdice.c
@@ -23,8 +23,8 @@ struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char * inputFilespec;  /* '-' if stdin */
-    char * outstem; 
+    const char * inputFileName;  /* '-' if stdin */
+    const char * outstem; 
         /* null-terminated string, max MAXFILENAMELEN-10 characters */
     unsigned int sliceVertically;    /* boolean */
     unsigned int sliceHorizontally;  /* boolean */
@@ -39,8 +39,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine ( int argc, char ** argv,
-                   struct cmdlineInfo * const cmdlineP ) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP ) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.  
@@ -63,17 +63,17 @@ parseCommandLine ( int argc, char ** argv,
 
     option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0, "width",       OPT_UINT,    &cmdlineP->width,       
-            &cmdlineP->sliceVertically,       0 );
+            &cmdlineP->sliceVertically,       0);
     OPTENT3(0, "height",      OPT_UINT,    &cmdlineP->height,
-            &cmdlineP->sliceHorizontally,     0 );
+            &cmdlineP->sliceHorizontally,     0);
     OPTENT3(0, "hoverlap",    OPT_UINT,    &cmdlineP->hoverlap,
-            &hoverlapSpec,                    0 );
+            &hoverlapSpec,                    0);
     OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
-            &voverlapSpec,                    0 );
+            &voverlapSpec,                    0);
     OPTENT3(0, "outstem",     OPT_STRING,  &cmdlineP->outstem,
-            &outstemSpec,                     0 );
+            &outstemSpec,                     0);
     OPTENT3(0, "verbose",     OPT_FLAG,    NULL,
-            &cmdlineP->verbose,               0 );
+            &cmdlineP->verbose,               0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -104,13 +104,14 @@ parseCommandLine ( int argc, char ** argv,
     if (!outstemSpec)
         pm_error("You must specify the -outstem option to indicate where to "
                  "put the output images.");
+
     if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
+        cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
-        cmdlineP->inputFilespec = argv[1];
+        cmdlineP->inputFileName = argv[1];
     else 
         pm_error("Progam takes at most 1 parameter: the file specification.  "
-                 "You specified %d", argc-1);
+                 "You specified %u", argc-1);
 }
 
 
@@ -451,7 +452,7 @@ main(int argc, char ** argv) {
     
     parseCommandLine(argc, argv, &cmdline);
         
-    ifP = pm_openr(cmdline.inputFilespec);
+    ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
diff --git a/editor/pamthreshold.c b/editor/pamthreshold.c
index 016dde64..f45649bf 100644
--- a/editor/pamthreshold.c
+++ b/editor/pamthreshold.c
@@ -44,10 +44,18 @@ struct cmdlineInfo {
         /* geometry of local subimage.  Defined only if 'local' or 'dual'
            is true.
         */
+    unsigned int verbose;
 };
 
 
 
+static __inline__ bool
+betweenZeroAndOne(float const arg) {
+    return (arg >= 0.0 && arg <= 1.0);
+}
+
+
+
 struct range {
     /* A range of sample values, normalized to [0, 1] */
     samplen min;
@@ -76,6 +84,16 @@ addToRange(struct range * const rangeP,
 
 
 
+static void
+assertRangeValid(struct range const range) {
+
+    assert(betweenZeroAndOne(range.min));
+    assert(betweenZeroAndOne(range.max));
+    assert(range.max >= range.min);
+}
+
+
+
 static float
 spread(struct range const range) {
 
@@ -146,6 +164,8 @@ parseCommandLine(int                 argc,
             &thresholdSpec,         0);
     OPTENT3(0, "contrast",  OPT_FLOAT,  &cmdlineP->contrast,
             &contrastSpec,          0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL,               
+            &cmdlineP->verbose,     0);
 
     /* set the defaults */
     cmdlineP->width = cmdlineP->height = 0U;
@@ -244,6 +264,7 @@ thresholdSimple(struct pam * const inpamP,
 
 static void
 analyzeDistribution(struct pam *          const inpamP,
+                    bool                  const verbose,
                     const unsigned int ** const histogramP,
                     struct range *        const rangeP) {
 /*----------------------------------------------------------------------------
@@ -255,7 +276,8 @@ analyzeDistribution(struct pam *          const inpamP,
    distribution as *histogramP, an array such that histogram[i] is the
    number of pixels that have sample value i.
 
-   Leave the file positioned to the raster.
+   Assume the file is positioned to the raster upon entry and leave
+   it positioned at the same place.
 -----------------------------------------------------------------------------*/
     unsigned int row;
     tuple * inrow;
@@ -295,6 +317,10 @@ analyzeDistribution(struct pam *          const inpamP,
     pnm_freepamrown(inrown);
 
     pm_seek2(inpamP->file, &rasterPos, sizeof(rasterPos));
+
+    if (verbose)
+        pm_message("Pixel values range from %f to %f",
+                   rangeP->min, rangeP->max);
 }
 
 
@@ -342,9 +368,7 @@ computeGlobalThreshold(struct pam *         const inpamP,
                        float *              const thresholdP) {
 /*----------------------------------------------------------------------------
    Compute the proper threshold to use for the image described by
-   *inpamP, whose file is positioned to the raster.
-
-   For our convenience:
+   *inpamP, and:
 
      'histogram' describes the frequency of occurence of the various sample
      values in the image.
@@ -554,7 +578,7 @@ thresholdLocal(struct pam *       const inpamP,
 
     /* global information is needed for dual thresholding */
     if (cmdline.dual) {
-        analyzeDistribution(inpamP, &histogram, &globalRange);
+        analyzeDistribution(inpamP, cmdline.verbose, &histogram, &globalRange);
         computeGlobalThreshold(inpamP, histogram, globalRange,
                                &globalThreshold);
     } else {
@@ -602,13 +626,14 @@ thresholdLocal(struct pam *       const inpamP,
 
 static void
 thresholdIterative(struct pam * const inpamP,
-                   struct pam * const outpamP) {
+                   struct pam * const outpamP,
+                   bool         const verbose) {
 
     const unsigned int * histogram;
     struct range globalRange;
     samplen threshold;
 
-    analyzeDistribution(inpamP, &histogram, &globalRange);
+    analyzeDistribution(inpamP, verbose, &histogram, &globalRange);
 
     computeGlobalThreshold(inpamP, histogram, globalRange, &threshold);
 
@@ -663,7 +688,7 @@ main(int argc, char **argv) {
         else if (cmdline.local || cmdline.dual)
             thresholdLocal(&inpam, &outpam, cmdline);
         else
-            thresholdIterative(&inpam, &outpam);
+            thresholdIterative(&inpam, &outpam, cmdline.verbose);
 
         pnm_nextimage(ifP, &eof);
     }
diff --git a/editor/pamundice.c b/editor/pamundice.c
new file mode 100644
index 00000000..90bac9f6
--- /dev/null
+++ b/editor/pamundice.c
@@ -0,0 +1,705 @@
+/*****************************************************************************
+                                  pamundice
+******************************************************************************
+  Assemble a grid of images into one.
+
+  By Bryan Henderson, San Jose CA 2001.01.31
+
+  Contributed to the public domain.
+
+******************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#define MAXFILENAMELEN 80
+    /* Maximum number of characters we accept in filenames */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilePattern;
+        /* null-terminated string, max MAXFILENAMELEN-10 characters */
+    unsigned int across;
+    unsigned int down;
+    unsigned int hoverlap; 
+    unsigned int voverlap; 
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    
+    unsigned int acrossSpec, downSpec;
+    unsigned int instemSpec, hoverlapSpec, voverlapSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "across",      OPT_UINT,    &cmdlineP->across,
+            &acrossSpec,                      0);
+    OPTENT3(0, "down",        OPT_UINT,    &cmdlineP->down,
+            &downSpec,                        0);
+    OPTENT3(0, "hoverlap",    OPT_UINT,    &cmdlineP->hoverlap,
+            &hoverlapSpec,                    0);
+    OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
+            &voverlapSpec,                    0);
+    OPTENT3(0, "verbose",     OPT_FLAG,    NULL,
+            &cmdlineP->verbose,               0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!acrossSpec)
+        cmdlineP->across = 1;
+    
+    if (!downSpec)
+        cmdlineP->down = 1;
+
+    if (!hoverlapSpec)
+        cmdlineP->hoverlap = 0;
+
+    if (!voverlapSpec)
+        cmdlineP->voverlap = 0;
+
+    if (!instemSpec)
+        pm_error("You must specify the -instem option to indicate where to "
+                 "get the input images.");
+
+    if (argc-1 < 1)
+        pm_error("You must specify one argument: the input file name "
+                 "pattern (e.g. 'myimage%%2a%%2d.pnm')");
+    else {
+        cmdlineP->inputFilePattern = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Progam takes at most one parameter: input file name.  "
+                     "You specified %u", argc-1);
+    }
+}
+
+
+
+/*------------------ string buffer -----------------------------------*/
+struct buffer {
+    char * string;
+    unsigned int allocSize;
+    unsigned int length;
+};
+
+
+static void
+buffer_init(struct buffer * const bufferP) {
+
+    bufferP->length = 0;
+    bufferP->allocSize = 1024;
+    MALLOCARRAY(bufferP->string, bufferP->allocSize);
+
+    if (bufferP->string == NULL)
+        pm_error("Out of memory allocating buffer to compute file name");
+}
+
+
+
+static void
+buffer_term(struct buffer * const bufferP) {
+    
+    free(bufferP->string);
+}
+
+
+
+static void
+buffer_addChar(struct buffer * const bufferP,
+               char            const newChar) {
+
+    if (bufferP->length + 1 + 1 > bufferP->allocSize)
+        pm_error("Ridiculously long input file name.");
+    else {
+        bufferP->string[bufferP->length++] = newChar;
+        bufferP->string[bufferP->length] = '\0';
+    }
+}
+
+
+
+static void
+buffer_addString(struct buffer * const bufferP,
+                 const char *    const newString) {
+
+    if (bufferP->length + 1 + strlen(newString) > bufferP->allocSize)
+        pm_error("Ridiculously long input file name.");
+    else {
+        strcat(&bufferP->string[bufferP->length], newString);
+        bufferP->length += strlen(newString);
+    }
+}
+/*------------------ end of string buffer ----------------------------*/
+
+
+
+/*------------------ computeInputFileName ----------------------------*/
+static unsigned int
+digitValue(char const digitChar) {
+
+    return digitChar - '0';
+}
+
+
+
+static void
+getPrecision(const char *   const pattern,
+             unsigned int   const startInCursor,
+             unsigned int * const precisionP,
+             unsigned int * const newInCursorP) {
+
+    unsigned int precision;
+    unsigned int inCursor;
+
+    inCursor = startInCursor;  /* Start right after the '%' */
+
+    precision = 0;
+                
+    while (isdigit(pattern[inCursor])) {
+        precision = 10 * precision + digitValue(pattern[inCursor]);
+        ++inCursor;
+    }
+
+    if (precision == 0)
+        pm_error("Zero (or no) precision in substitution "
+                 "specification in file name pattern '%s'.  "
+                 "A proper substitution specification is like "
+                 "'%%3a'.", pattern);
+
+    *precisionP = precision;
+    *newInCursorP = inCursor;
+}
+
+
+
+static void
+doSubstitution(const char *    const pattern,
+               unsigned int    const startInCursor,
+               unsigned int    const rank,
+               unsigned int    const file,
+               struct buffer * const bufferP,
+               unsigned int *  const newInCursorP) {
+
+    unsigned int inCursor;
+
+    inCursor = startInCursor;  /* Start right after the '%' */
+
+    if (pattern[inCursor] == '%') {
+        buffer_addChar(bufferP, '%');
+        ++inCursor;
+    } else {
+        unsigned int precision;
+        
+        getPrecision(pattern, inCursor, &precision, &inCursor);
+
+        if (pattern[inCursor] == '\0')
+            pm_error("No format character follows '%%' in input "
+                     "file name pattern '%s'.  A proper substitution "
+                     "specification is like '%%3a'", pattern);
+        else {
+            const char * substString;
+            const char * desc;
+
+            switch (pattern[inCursor]) {
+            case 'a':
+                asprintfN(&substString, "%0*u", precision, file);
+                asprintfN(&desc, "file (across)");
+                break;
+            case 'd':
+                asprintfN(&substString, "%0*u", precision, rank);
+                asprintfN(&desc, "rank (down)");
+                break;
+            default:
+                pm_error("Unknown format specifier '%c' in input file "
+                         "pattern '%s'.  Recognized format specifier s are "
+                         "'%%a' (across) and '%%d (down)'",
+                         pattern[inCursor], pattern);
+            }
+            if (strlen(substString) > precision)
+                pm_error("%s number %u is wider than "
+                         "the %u characters specified in the "
+                         "input file pattern",
+                         desc, strlen(substString), precision);
+            else
+                buffer_addString(bufferP, substString);
+            
+            strfree(desc);
+            strfree(substString);
+
+            ++inCursor;
+        }
+    }
+    *newInCursorP = inCursor;
+}
+
+
+
+static void
+computeInputFileName(const char *  const pattern,
+                     unsigned int  const rank,
+                     unsigned int  const file,
+                     const char ** const fileNameP) {
+
+    struct buffer buffer;
+    unsigned int inCursor, outCursor;
+
+    buffer_init(&buffer);
+
+    inCursor = 0;
+    outCursor = 0;
+
+    while (pattern[inCursor] != '\0') {
+        if (pattern[inCursor] == '%') {
+            ++inCursor;
+
+            doSubstitution(pattern, inCursor, rank, file, &buffer, &inCursor);
+
+        } else
+            buffer_addChar(&buffer, pattern[inCursor++]);
+    }
+
+    asprintfN(fileNameP, "%s", buffer.string);
+
+    buffer_term(&buffer);
+}
+/*------------------ end of computeInputFileName ------------------------*/
+
+
+
+static void
+getCommonInfo(const char *   const inputFilePattern,
+              int *          const formatP,
+              unsigned int * const depthP,
+              sample *       const maxvalP,
+              char *         const tupleType) {
+/*----------------------------------------------------------------------------
+   Get from the top left input image all the information which is common
+   among all input images and the output image.  I.e. everything except
+   width and height.
+-----------------------------------------------------------------------------*/
+    const char * fileName;
+        /* Name of top left input image */
+    FILE * ifP;
+        /* Top left input image stream */
+    struct pam inpam00;
+        /* Description of top left input image */
+
+    computeInputFileName(inputFilePattern, 0, 0, &fileName);
+
+    ifP = pm_openr(fileName);
+
+    pnm_readpaminit(ifP, &inpam00, PAM_STRUCT_SIZE(tuple_type));
+
+    *formatP = inpam00.format;
+    *depthP  = inpam00.depth;
+    *maxvalP = inpam00.maxval;
+    strcpy(tupleType, inpam00.tuple_type);
+
+    pm_close(ifP);
+
+    strfree(fileName);
+}
+
+
+
+static FILE *
+openInputImage(const char * const inputFilePattern,
+               unsigned int const rank,
+               unsigned int const file) {
+
+    FILE * retval;
+    const char * fileName;
+        
+    computeInputFileName(inputFilePattern, rank, file, &fileName);
+
+    retval = pm_openr(fileName);
+    
+    strfree(fileName);
+
+    return retval;
+}
+
+               
+
+static void
+getImageInfo(const char * const inputFilePattern,
+             unsigned int const rank,
+             unsigned int const file,
+             struct pam * const pamP) {
+
+    FILE * ifP;
+
+    ifP = openInputImage(inputFilePattern, rank, file);
+
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(ifP);
+    pamP->file = NULL;  /* for robustness */
+}
+
+
+
+static void
+getOutputWidth(const char * const inputFilePattern,
+               unsigned int const nFile,
+               unsigned int const hoverlap,
+               int *        const widthP) {
+/*----------------------------------------------------------------------------
+   Get the output width by adding up the widths of all 'nFile' images of
+   the top rank, and allowing for overlap of 'hoverlap' pixels.
+-----------------------------------------------------------------------------*/
+    unsigned int totalWidth;
+    unsigned int file;
+
+    for (file = 0, totalWidth = 0; file < nFile; ++file) {
+        struct pam inpam;
+
+        getImageInfo(inputFilePattern, 0, file, &inpam);
+
+        if (inpam.width < hoverlap)
+            pm_error("Rank 0, file %u image has width %u, "
+                     "which is less than the horizontal overlap of %u pixels",
+                     file, inpam.width, hoverlap);
+        else {
+            totalWidth += inpam.width;
+
+            if (file < nFile-1)
+                totalWidth -= hoverlap;
+        }
+    }
+    *widthP = totalWidth;
+}
+
+
+
+static void
+getOutputHeight(const char *  const inputFilePattern,
+                unsigned int  const nRank,
+                unsigned int  const voverlap,
+                int *         const heightP) {
+/*----------------------------------------------------------------------------
+   Get the output height by adding up the widths of all 'nRank' images of
+   the left file, and allowing for overlap of 'voverlap' pixels.
+-----------------------------------------------------------------------------*/
+    unsigned int totalHeight;
+    unsigned int rank;
+
+    for (rank = 0, totalHeight = 0; rank < nRank; ++rank) {
+        struct pam inpam;
+
+        getImageInfo(inputFilePattern, rank, 0, &inpam);
+
+        if (inpam.height < voverlap)
+            pm_error("Rank %u, file 0 image has height %u, "
+                     "which is less than the vertical overlap of %u pixels",
+                     rank, inpam.height, voverlap);
+        
+        totalHeight += inpam.height;
+        
+        if (rank < nRank-1)
+            totalHeight -= voverlap;
+    }
+    *heightP = totalHeight;
+}
+
+
+
+static void
+initOutpam(const char * const inputFilePattern,
+           unsigned int const nFile,
+           unsigned int const nRank,
+           unsigned int const hoverlap,
+           unsigned int const voverlap,
+           FILE *       const ofP,
+           bool         const verbose,
+           struct pam * const outpamP) {
+/*----------------------------------------------------------------------------
+   Figure out the attributes of the output image and return them as
+   *outpamP.
+
+   Do this by examining the top rank and left file of the input images,
+   which are in files named by 'inputFilePattern', 'nFile', and 'nRank'.
+
+   In computing dimensions, assume 'hoverlap' pixels of horizontal
+   overlap and 'voverlap' pixels of vertical overlap.
+
+   We overlook any inconsistencies among the images.  E.g. if two images
+   have different depths, we just return one of them.  If two images in
+   the top rank have different heights, we use just one of them.
+
+   Therefore, Caller must check all the input images to make sure they are
+   consistent with the information we return.
+-----------------------------------------------------------------------------*/
+    assert(nFile >= 1);
+    assert(nRank >= 1);
+
+    outpamP->size        = sizeof(*outpamP);
+    outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file        = ofP;
+    outpamP->plainformat = 0;
+    
+    getCommonInfo(inputFilePattern, &outpamP->format, &outpamP->depth,
+                  &outpamP->maxval, outpamP->tuple_type);
+
+    getOutputWidth(inputFilePattern, nFile, hoverlap, &outpamP->width);
+
+    getOutputHeight(inputFilePattern, nRank, voverlap, &outpamP->height);
+
+    if (verbose) {
+        pm_message("Output width = %u pixels", outpamP->width);
+        pm_message("Output height = %u pixels", outpamP->height);
+    }
+}
+
+
+
+static void
+openInStreams(struct pam         inpam[],
+              unsigned int const rank,
+              unsigned int const fileCount,
+              char         const inputFilePattern[]) {
+/*----------------------------------------------------------------------------
+   Open the input files for a single horizontal slice (there's one file
+   for each vertical slice) and read the Netpbm headers from them.  Return
+   the pam structures to describe each.
+-----------------------------------------------------------------------------*/
+    unsigned int file;
+
+    for (file = 0; file < fileCount; ++file) {
+        FILE * const ifP = openInputImage(inputFilePattern, rank, file);
+
+        pnm_readpaminit(ifP, &inpam[file], PAM_STRUCT_SIZE(tuple_type));
+    }        
+}
+
+
+
+static void
+closeInFiles(struct pam         pam[],
+             unsigned int const fileCount) {
+
+    unsigned int file;
+    
+    for (file = 0; file < fileCount; ++file)
+        pm_close(pam[file].file);
+}
+
+
+
+static void
+assembleRow(tuple              outputRow[], 
+            struct pam         inpam[], 
+            unsigned int const fileCount,
+            unsigned int const hOverlap) {
+/*----------------------------------------------------------------------------
+   Assemble the row outputRow[] from the 'fileCount' input files
+   described out inpam[].
+
+   'hOverlap', which is meaningful only when fileCount is greater than 1,
+   is the amount by which files overlap each other.  We assume every
+   input image is at least that wide.
+
+   We assume that outputRow[] is allocated wide enough to contain the
+   entire assembly.
+-----------------------------------------------------------------------------*/
+    tuple * inputRow;
+    unsigned int file;
+
+    for (file = 0, inputRow = &outputRow[0]; 
+         file < fileCount; 
+         ++file) {
+
+        unsigned int const overlap = file == fileCount - 1 ? 0 : hOverlap;
+
+        assert(hOverlap <= inpam[file].width);
+
+        pnm_readpamrow(&inpam[file], inputRow);
+
+        inputRow += inpam[file].width - overlap;
+    }
+}
+
+
+
+static void
+allocInpam(unsigned int  const rankCount,
+           struct pam ** const inpamArrayP) {
+
+    struct pam * inpamArray;
+
+    MALLOCARRAY(inpamArray, rankCount);
+
+    if (inpamArray == NULL)
+        pm_error("Unable to allocate array for %u input pam structures.",
+                 rankCount);
+
+    *inpamArrayP = inpamArray;
+}
+
+
+
+static void
+verifyRankFileAttributes(struct pam *       const inpam,
+                         unsigned int       const nFile,
+                         const struct pam * const outpamP,
+                         unsigned int       const hoverlap,
+                         unsigned int       const rank) {
+/*----------------------------------------------------------------------------
+   Verify that the 'nFile' images that make up a rank, which are described
+   by inpam[], are consistent with the properties of the assembled image
+   *outpamP.
+
+   I.e. verify that each image has the depth, maxval, format, and tuple
+   type of *outpamP and their total width is the width given by
+   *outpamP.
+
+   Also verify that every image has the same height.
+
+   Abort the program if verification fails.
+-----------------------------------------------------------------------------*/
+    unsigned int file;
+    unsigned int totalWidth;
+
+    for (file = 0, totalWidth = 0; file < nFile; ++file) {
+        struct pam * const inpamP = &inpam[file];
+
+        if (inpamP->depth != outpamP->depth)
+            pm_error("Rank %u, File %u image has depth %u, "
+                     "which differs from others (%u)",
+                     rank, file, inpamP->depth, outpamP->depth);
+        else if (inpamP->maxval != outpamP->maxval)
+            pm_error("Rank %u, File %u image has maxval %lu, "
+                     "which differs from others (%lu)",
+                     rank, file, inpamP->maxval, outpamP->maxval);
+        else if (inpamP->format != outpamP->format)
+            pm_error("Rank %u, File %u image has format 0x%x, "
+                     "which differs from others (0x%x)",
+                     rank, file, inpamP->format, outpamP->format);
+        else if (!streq(inpamP->tuple_type, outpamP->tuple_type))
+            pm_error("Rank %u, File %u image has tuple type '%s', "
+                     "which differs from others ('%s')",
+                     rank, file, inpamP->tuple_type, outpamP->tuple_type);
+
+        else if (inpamP->height != inpam[0].height)
+            pm_error("Rank %u, File %u image has height %u, "
+                     "which differs from that of File 0 in the same rank (%u)",
+                     rank, file, inpamP->height, inpam[0].height);
+        else {
+            totalWidth += inpamP->width;
+        
+            if (file < nFile-1)
+                totalWidth -= hoverlap;
+        }
+    }
+
+    if (totalWidth != outpamP->width)
+        pm_error("Rank %u has a total width (%u) different from that of "
+                 "other ranks (%u)", rank, totalWidth, outpamP->width);
+}
+
+
+
+static void
+assembleTiles(struct pam * const outpamP,
+              const char * const inputFilePattern,
+              unsigned int const nFile,
+              unsigned int const nRank,
+              unsigned int const hoverlap,
+              unsigned int const voverlap,
+              struct pam         inpam[],
+              tuple *      const tuplerow) {
+
+    unsigned int rank;
+        /* Number of the current rank (horizontal slice).  Ranks are numbered
+           sequentially starting at 0.
+        */
+    
+    for (rank = 0; rank < nRank; ++rank) {
+        unsigned int row;
+        unsigned int rankHeight;
+
+        openInStreams(inpam, rank, nFile, inputFilePattern);
+
+        verifyRankFileAttributes(inpam, nFile, outpamP, hoverlap, rank);
+
+        rankHeight = inpam[0].height - (rank == nRank-1 ? 0 : voverlap);
+
+        for (row = 0; row < rankHeight; ++row) {
+            assembleRow(tuplerow, inpam, nFile, hoverlap);
+
+            pnm_writepamrow(outpamP, tuplerow);
+        }
+        closeInFiles(inpam, nFile);
+    }
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam outpam;
+    struct pam * inpam;
+        /* malloc'ed.  inpam[x] is the pam structure that controls the
+           current rank of file x. 
+        */
+    tuple * tuplerow;
+
+    pnm_init(&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+        
+    allocInpam(cmdline.across, &inpam);
+
+    initOutpam(cmdline.inputFilePattern, cmdline.across, cmdline.down,
+               cmdline.hoverlap, cmdline.voverlap, stdout, cmdline.verbose,
+               &outpam);
+    
+    tuplerow = pnm_allocpamrow(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    assembleTiles(&outpam,
+                  cmdline.inputFilePattern, cmdline.across, cmdline.down,
+                  cmdline.hoverlap, cmdline.voverlap, inpam, tuplerow);
+
+    pnm_freepamrow(tuplerow);
+
+    free(inpam);
+
+    return 0;
+}
diff --git a/editor/pgmmedian.c b/editor/pgmmedian.c
index 5878b1e7..9ca2bbb1 100644
--- a/editor/pgmmedian.c
+++ b/editor/pgmmedian.c
@@ -102,9 +102,9 @@ parseCommandLine(int argc, char ** argv,
         cmdlineP->cutoff = 250;
 
     if (typeSpec) {
-        if (STREQ(type, "histogram_sort"))
+        if (streq(type, "histogram_sort"))
             cmdlineP->type = HISTOGRAM_SORT_MEDIAN;
-        else if (STREQ(type, "select"))
+        else if (streq(type, "select"))
             cmdlineP->type = SELECT_MEDIAN;
         else
             pm_error("Invalid value '%s' for -type.  Valid values are "
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index 0260839a..209124a1 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -43,9 +43,9 @@ enum missingMethod {
 #define FS_SCALE 1024
 
 struct fserr {
-    long** thiserr;
-    long** nexterr;
-    bool fsForward;
+    long ** thiserr;
+    long ** nexterr;
+    bool    fsForward;
 };
 
 
@@ -56,6 +56,7 @@ struct cmdlineInfo {
     const char * inputFilespec;  /* Filespec of input file */
     const char * mapFilespec;    /* Filespec of colormap file */
     unsigned int floyd;   /* Boolean: -floyd/-fs option */
+    unsigned int norandom;
     enum missingMethod missingMethod;
     char * missingcolor;      
         /* -missingcolor value.  Null if not specified */
@@ -98,6 +99,8 @@ parseCommandLine (int argc, char ** argv,
             NULL,                       &nofloyd, 0);
     OPTENT3(0,   "nofs",         OPT_FLAG,   
             NULL,                       &nofloyd, 0);
+    OPTENT3(0,   "norandom",     OPT_FLAG,   
+            NULL,                       &cmdlineP->norandom, 0);
     OPTENT3(0,   "firstisdefault", OPT_FLAG,   
             NULL,                       &firstisdefault, 0);
     OPTENT3(0,   "mapfile",      OPT_STRING, 
@@ -270,8 +273,30 @@ computeColorMapFromMap(struct pam *   const mappamP,
 
 
 static void
+randomizeError(long **      const err,
+               unsigned int const width,
+               unsigned int const depth) {
+/*----------------------------------------------------------------------------
+   Set a random error in the range [-1 .. 1] (normalized via FS_SCALE)
+   in the error array err[][].
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+
+    srand(pm_randseed());
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) 
+            err[plane][col] = rand() % (FS_SCALE * 2) - FS_SCALE;
+    }
+}
+
+
+
+static void
 initFserr(struct pam *   const pamP,
-          struct fserr * const fserrP) {
+          struct fserr * const fserrP,
+          bool           const initRandom) {
 /*----------------------------------------------------------------------------
    Initialize the Floyd-Steinberg error vectors
 -----------------------------------------------------------------------------*/
@@ -299,19 +324,9 @@ initFserr(struct pam *   const pamP,
                      "for Plane %u, size %u", plane, fserrSize);
     }
 
-    srand(pm_randseed());
+    if (initRandom)
+        randomizeError(fserrP->thiserr, fserrSize, pamP->depth);
 
-    {
-        int col;
-
-        for (col = 0; col < fserrSize; ++col) {
-            unsigned int plane;
-            for (plane = 0; plane < pamP->depth; ++plane) 
-                fserrP->thiserr[plane][col] = 
-                    rand() % (FS_SCALE * 2) - FS_SCALE;
-                    /* (random errors in [-1 .. 1]) */
-        }
-    }
     fserrP->fsForward = TRUE;
 }
 
@@ -703,14 +718,15 @@ convertRow(struct pam *            const pamP,
 
 
 static void
-copyRaster(struct pam *   const inpamP, 
-           struct pam *   const outpamP,
-           tupletable     const colormap, 
-           unsigned int   const colormapSize,
-           bool           const floyd, 
+copyRaster(struct pam *       const inpamP, 
+           struct pam *       const outpamP,
+           tupletable         const colormap, 
+           unsigned int       const colormapSize,
+           bool               const floyd, 
+           bool               const randomize,
            enum missingMethod const missingMethod,
-           tuple          const defaultColor, 
-           unsigned int * const missingCountP) {
+           tuple              const defaultColor, 
+           unsigned int *     const missingCountP) {
 
     tuplehash const colorhash = pnm_createtuplehash();
     struct colormapFinder * colorFinderP;
@@ -731,7 +747,7 @@ copyRaster(struct pam *   const inpamP,
     createColormapFinder(outpamP, colormap, colormapSize, &colorFinderP);
 
     if (floyd)
-        initFserr(inpamP, &fserr);
+        initFserr(inpamP, &fserr, randomize);
 
     *missingCountP = 0;  /* initial value */
 
@@ -771,6 +787,7 @@ remap(FILE * const ifP,
       tupletable         const colormap, 
       unsigned int       const colormapSize,
       bool               const floyd,
+      bool               const randomize,
       enum missingMethod const missingMethod,
       tuple              const defaultColor,
       bool               const verbose) {
@@ -798,7 +815,7 @@ remap(FILE * const ifP,
         pnm_setminallocationdepth(&inpam, outpam.depth);
     
         copyRaster(&inpam, &outpam, colormap, colormapSize, floyd,
-                   missingMethod, defaultColor, &missingCount);
+                   randomize, missingMethod, defaultColor, &missingCount);
         
         if (verbose)
             pm_message("%u pixels not matched in color map", missingCount);
@@ -858,7 +875,8 @@ main(int argc, char * argv[] ) {
     }
 
     remap(ifP, &outpamCommon, colormap, colormapSize, 
-          cmdline.floyd, cmdline.missingMethod, defaultColor,
+          cmdline.floyd, !cmdline.norandom, cmdline.missingMethod,
+          defaultColor,
           cmdline.verbose);
 
     pnm_freepamtuple(defaultColor);
diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c
index 0dd03bc9..3bd271a6 100644
--- a/editor/ppmdraw.c
+++ b/editor/ppmdraw.c
@@ -515,7 +515,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
         if (drawCommandP == NULL)
             pm_error("Out of memory to parse '%s' command", verb);
 
-        if (STREQ(verb, "setpos")) {
+        if (streq(verb, "setpos")) {
             drawCommandP->verb = VERB_SETPOS;
             if (commandTokens.count < 3)
                 pm_error("Not enough tokens for a 'setpos' command.  "
@@ -524,22 +524,22 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.setposArg.x = atoi(commandTokens.token[1]);
                 drawCommandP->u.setposArg.y = atoi(commandTokens.token[2]);
             }
-        } else if (STREQ(verb, "setlinetype")) {
+        } else if (streq(verb, "setlinetype")) {
             drawCommandP->verb = VERB_SETLINETYPE;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setlinetype' command.  "
                          "Need %u.  Got %u", 2, commandTokens.count);
             else {
                 const char * const typeArg = commandTokens.token[1];
-                if (STREQ(typeArg, "normal"))
+                if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
-                else if (STREQ(typeArg, "normal"))
+                else if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = 
                         PPMD_LINETYPE_NODIAGS;
                 else
                     pm_error("Invalid type");
             }
-        } else if (STREQ(verb, "setlineclip")) {
+        } else if (streq(verb, "setlineclip")) {
             drawCommandP->verb = VERB_SETLINECLIP;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setlineclip' command.  "
@@ -547,7 +547,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setlineclipArg.clip =
                     atoi(commandTokens.token[1]);
-        } else if (STREQ(verb, "setcolor")) {
+        } else if (streq(verb, "setcolor")) {
             drawCommandP->verb = VERB_SETCOLOR;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setcolor' command.  "
@@ -555,7 +555,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setcolorArg.colorName =
                     strdup(commandTokens.token[1]);
-        } else if (STREQ(verb, "setfont")) {
+        } else if (streq(verb, "setfont")) {
             drawCommandP->verb = VERB_SETFONT;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setfont' command.  "
@@ -563,7 +563,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setfontArg.fontFileName =
                     strdup(commandTokens.token[1]);
-        } else if (STREQ(verb, "line")) {
+        } else if (streq(verb, "line")) {
             drawCommandP->verb = VERB_LINE;
             if (commandTokens.count < 5)
                 pm_error("Not enough tokens for a 'line' command.  "
@@ -574,7 +574,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
                 drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
             } 
-        } else if (STREQ(verb, "line_here")) {
+        } else if (streq(verb, "line_here")) {
             drawCommandP->verb = VERB_LINE_HERE;
             if (commandTokens.count < 3)
                 pm_error("Not enough tokens for a 'line_here' command.  "
@@ -585,7 +585,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->right = atoi(commandTokens.token[1]);
                 argP->down = atoi(commandTokens.token[2]);
             } 
-       } else if (STREQ(verb, "spline3")) {
+       } else if (streq(verb, "spline3")) {
             drawCommandP->verb = VERB_SPLINE3;
             if (commandTokens.count < 7)
                 pm_error("Not enough tokens for a 'spline3' command.  "
@@ -600,7 +600,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->x2 = atoi(commandTokens.token[5]);
                 argP->y2 = atoi(commandTokens.token[6]);
             } 
-        } else if (STREQ(verb, "circle")) {
+        } else if (streq(verb, "circle")) {
             drawCommandP->verb = VERB_CIRCLE;
             if (commandTokens.count < 4)
                 pm_error("Not enough tokens for a 'circle' command.  "
@@ -611,7 +611,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
             } 
-        } else if (STREQ(verb, "filledrectangle")) {
+        } else if (streq(verb, "filledrectangle")) {
             drawCommandP->verb = VERB_FILLEDRECTANGLE;
             if (commandTokens.count < 5)
                 pm_error("Not enough tokens for a 'filledrectangle' command.  "
@@ -624,7 +624,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->width  = atoi(commandTokens.token[3]);
                 argP->height = atoi(commandTokens.token[4]);
             } 
-        } else if (STREQ(verb, "text")) {
+        } else if (streq(verb, "text")) {
             drawCommandP->verb = VERB_TEXT;
             if (commandTokens.count < 6)
                 pm_error("Not enough tokens for a 'text' command.  "
@@ -638,7 +638,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 if (drawCommandP->u.textArg.text == NULL)
                     pm_error("Out of storage parsing 'text' command");
             }
-        } else if (STREQ(verb, "text_here")) {
+        } else if (streq(verb, "text_here")) {
             drawCommandP->verb = VERB_TEXT_HERE;
             if (commandTokens.count < 4)
                 pm_error("Not enough tokens for a 'text_here' command.  "
@@ -705,7 +705,7 @@ processToken(const char *      const scriptText,
     memcpy(token, &scriptText[tokenStart], tokenLength);
     token[tokenLength] = '\0';
     
-    if (STREQ(token, ";")) {
+    if (streq(token, ";")) {
         disposeOfCommandTokens(tokenSetP, scriptP);
         free(token);
     } else {
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index 6706598f..2f4d18c7 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -20,27 +20,28 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 
-struct cmdline_info {
+struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *text;    /* text from command line or NULL if none */
-    const char *font;    /* -font option value or NULL if none */
-    const char *builtin; /* -builtin option value or NULL if none */
+    const char * text;    /* text from command line or NULL if none */
+    const char * font;    /* -font option value or NULL if none */
+    const char * builtin; /* -builtin option value or NULL if none */
     unsigned int dump;   
         /* undocumented dump option for installing a new built-in font */
     float space;   /* -space option value or default */
     unsigned int width;     /* -width option value or zero */
     int lspace;    /* lspace option value or default */
     unsigned int nomargins;     /* -nomargins */
+    unsigned int verbose;
 };
 
 
 
 
 static void
-parse_command_line(int argc, char ** argv,
-                   struct cmdline_info *cmdline_p) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -53,30 +54,31 @@ parse_command_line(int argc, char ** argv,
     unsigned int option_def_index;
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "font",      OPT_STRING, &cmdline_p->font, NULL,        0);
-    OPTENT3(0, "builtin",   OPT_STRING, &cmdline_p->builtin, NULL,     0);
-    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdline_p->dump,        0);
-    OPTENT3(0, "space",     OPT_FLOAT,  &cmdline_p->space, NULL,       0);
-    OPTENT3(0, "width",     OPT_UINT,   &cmdline_p->width, NULL,       0);
-    OPTENT3(0, "lspace",    OPT_INT,    &cmdline_p->lspace, NULL,      0);
-    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdline_p->nomargins,   0);
+    OPTENT3(0, "font",      OPT_STRING, &cmdlineP->font, NULL,        0);
+    OPTENT3(0, "builtin",   OPT_STRING, &cmdlineP->builtin, NULL,     0);
+    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdlineP->dump,        0);
+    OPTENT3(0, "space",     OPT_FLOAT,  &cmdlineP->space, NULL,       0);
+    OPTENT3(0, "width",     OPT_UINT,   &cmdlineP->width, NULL,       0);
+    OPTENT3(0, "lspace",    OPT_INT,    &cmdlineP->lspace, NULL,      0);
+    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdlineP->nomargins,   0);
+    OPTENT3(0, "verbose",   OPT_FLAG,   NULL, &cmdlineP->verbose,     0);
 
     /* Set the defaults */
-    cmdline_p->font = NULL;
-    cmdline_p->builtin = NULL;
-    cmdline_p->space = 0.0;
-    cmdline_p->width = 0;
-    cmdline_p->lspace = 0;
+    cmdlineP->font = NULL;
+    cmdlineP->builtin = NULL;
+    cmdlineP->space = 0.0;
+    cmdlineP->width = 0;
+    cmdlineP->lspace = 0;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
     optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (argc-1 == 0)
-        cmdline_p->text = NULL;
+        cmdlineP->text = NULL;
     else {
         char *text;
         int i;
@@ -87,7 +89,7 @@ parse_command_line(int argc, char ** argv,
         text = malloc(totaltextsize);  /* initial allocation */
         text[0] = '\0';
         
-        for (i = 1; i < argc; i++) {
+        for (i = 1; i < argc; ++i) {
             if (i > 1) {
                 totaltextsize += 1;
                 text = realloc(text, totaltextsize);
@@ -101,11 +103,59 @@ parse_command_line(int argc, char ** argv,
                 pm_error("out of memory allocating space for input text");
             strcat(text, argv[i]);
         }
-        cmdline_p->text = text;
+        cmdlineP->text = text;
     }
 }
 
 
+
+static void
+reportFont(struct font * const fontP) {
+
+    unsigned int n;
+    unsigned int c;
+
+    pm_message("FONT:");
+    pm_message("  character dimensions: %uw x %uh",
+               fontP->maxwidth, fontP->maxheight);
+    pm_message("  Additional vert white space: %d pixels", fontP->y);
+
+    for (c = 0, n = 0; c < ARRAY_SIZE(fontP->glyph); ++c)
+        if (fontP->glyph[c])
+            ++n;
+
+    pm_message("  # characters: %u", n);
+}
+
+
+
+static void
+computeFont(struct cmdlineInfo const cmdline,
+            struct font **     const fontPP) {
+
+    struct font * fontP;
+
+    if (cmdline.font)
+        fontP = pbm_loadfont(cmdline.font);
+    else {
+        if (cmdline.builtin)
+            fontP = pbm_defaultfont(cmdline.builtin);
+        else
+            fontP = pbm_defaultfont("bdf");
+    }
+
+    if (cmdline.verbose)
+        reportFont(fontP);
+
+    if (cmdline.dump) {
+        pbm_dumpfont(fontP);
+        exit(0);
+    }
+    *fontPP = fontP;
+}
+
+
+
 struct text {
     char **      textArray;  /* malloc'ed */
     unsigned int allocatedLineCount;
@@ -145,27 +195,39 @@ freeTextArray(struct text const text) {
 
 
 static void
-fix_control_chars(char * const buf, struct font * const fn) {
+fixControlChars(char *        const buf,
+                struct font * const fontP) {
+/*----------------------------------------------------------------------------
+   Make buf[] something that can be rendered as glyphs in the font 'fontP'.
+
+   Expand tabs to spaces.
+
+   Remove any trailing newline.  (But leave intermediate ones as line
+   delimiters).
 
-    int i;
+   Turn anything that isn't a code point in the font to a single space
+   (which isn't guaranteed to be in the font either, of course).
+-----------------------------------------------------------------------------*/
+    unsigned int i;
 
     /* chop off terminating newline */
     if (strlen(buf) >= 1 && buf[strlen(buf)-1] == '\n')
         buf[strlen(buf)-1] = '\0';
     
-    for ( i = 0; buf[i] != '\0'; ++i ) {
-        if ( buf[i] == '\t' ) { 
+    for (i = 0; buf[i] != '\0'; ++i) {
+        if (buf[i] == '\t') { 
             /* Turn tabs into the right number of spaces. */
-            int j, n, l;
-            n = ( i + 8 ) / 8 * 8;
-            l = strlen( buf );
-            for ( j = l; j > i; --j )
-                buf[j + n - i - 1] = buf[j];
-            for ( ; i < n; ++i )
+            unsigned int const nextTabStop = (i + 8) / 8 * 8;
+            unsigned int const nSpaceToInsert = nextTabStop - i;
+            int j;
+            /* Move text right to make room for spaces */
+            for (j = strlen(buf); j > i; --j )
+                buf[j + nSpaceToInsert - 1] = buf[j];
+            /* insert the spaces */
+            for ( ; i < nextTabStop; ++i )
                 buf[i] = ' ';
             --i;
-        }
-        else if ( !fn->glyph[(unsigned char)buf[i]] )
+        } else if (!fontP->glyph[(unsigned char)buf[i]] )
             /* Turn unknown chars into a single space. */
             buf[i] = ' ';
     }
@@ -575,7 +637,7 @@ truncateText(struct text   const inputText,
 
 static void
 getText(const char          cmdline_text[], 
-        struct font * const fn, 
+        struct font * const fontP,
         struct text * const input_textP) {
 
     struct text input_text;
@@ -583,7 +645,7 @@ getText(const char          cmdline_text[],
     if (cmdline_text) {
         allocTextArray(&input_text, 1, strlen(cmdline_text));
         strcpy(input_text.textArray[0], cmdline_text);
-        fix_control_chars(input_text.textArray[0], fn);
+        fixControlChars(input_text.textArray[0], fontP);
         input_text.lineCount = 1;
     } else {
         /* Read text from stdin. */
@@ -601,7 +663,7 @@ getText(const char          cmdline_text[],
         
         lineCount = 0;  /* initial value */
         while (fgets(buf, sizeof(buf), stdin) != NULL) {
-            fix_control_chars(buf, fn);
+            fixControlChars(buf, fontP);
             if (lineCount >= maxlines) {
                 maxlines *= 2;
                 text_array = (char**) realloc((char*) text_array, 
@@ -649,10 +711,10 @@ compute_image_width(struct text         const lp,
 int
 main(int argc, char *argv[]) {
 
-    struct cmdline_info cmdline;
-    bit** bits;
+    struct cmdlineInfo cmdline;
+    bit ** bits;
     int rows, cols;
-    struct font* fontP;
+    struct font * fontP;
     int vmargin, hmargin;
     struct text inputText;
     struct text formattedText;
@@ -660,23 +722,11 @@ main(int argc, char *argv[]) {
         /* width in pixels of the longest line of text in the image */
     int maxleftb;
 
-    pbm_init( &argc, argv );
+    pbm_init(&argc, argv);
 
-    parse_command_line(argc, argv, &cmdline);
+    parseCommandLine(argc, argv, &cmdline);
     
-    if (cmdline.font)
-        fontP = pbm_loadfont(cmdline.font);
-    else {
-        if (cmdline.builtin)
-            fontP = pbm_defaultfont(cmdline.builtin);
-        else
-            fontP = pbm_defaultfont("bdf");
-    }
-
-    if (cmdline.dump) {
-        pbm_dumpfont(fontP);
-        exit(0);
-    }
+    computeFont(cmdline, &fontP);
 
     getText(cmdline.text, fontP, &inputText);
     
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index f4fa8b14..d641a512 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -132,25 +132,37 @@ parseCommandLine(int argc, char ** argv,
 
 
 static const char *
-construct_postscript(struct cmdlineInfo const cmdl) {
+construct_postscript(struct cmdlineInfo const cmdline) {
 
     const char * retval;
     const char * template;
 
-    if (cmdl.stroke <= 0) 
-        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
-            "(%s) show\nshowpage\n";
+    if (cmdline.stroke < 0) 
+        template =
+            "/%s findfont\n"
+            "%d scalefont\n"
+            "setfont\n"
+            "12 36 moveto\n"
+            "(%s) show\n"
+            "showpage\n";
     else 
-        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
-            "%f setlinewidth\n0 setgray\n"
-            "(%s) true charpath\nstroke\nshowpage\n";
-
-    if (cmdl.stroke < 0)
-        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
-                  cmdl.text);
+        template =
+            "/%s findfont\n"
+            "%d scalefont\n"
+            "setfont\n"
+            "12 36 moveto\n"
+            "%f setlinewidth\n"
+            "0 setgray\n"
+            "(%s) true charpath\n"
+            "stroke\n"
+            "showpage\n";
+
+    if (cmdline.stroke < 0)
+        asprintfN(&retval, template, cmdline.font, cmdline.fontsize, 
+                  cmdline.text);
     else
-        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
-                  cmdl.stroke, cmdl.text);
+        asprintfN(&retval, template, cmdline.font, cmdline.fontsize, 
+                  cmdline.stroke, cmdline.text);
 
     return retval;
 }
diff --git a/lib/Makefile b/lib/Makefile
index 0f333c2b..69b3b1d0 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -183,7 +183,7 @@ endif
 # ship a pre-made standardppmfont.c, so this rule will not normally be
 # used.
 standardppmdfont.c:standard.ppmdfont
-	ppmdcfont <$< >$@ || (rm $@ && false)
+	ppmdcfont <$< >$@
 
 # Note that we create a new compile.h only for the first make after a
 # make clean.  This is good enough.  We used to do stamp-date for
diff --git a/lib/fileio.c b/lib/fileio.c
index 01243f9d..d891b05a 100644
--- a/lib/fileio.c
+++ b/lib/fileio.c
@@ -17,23 +17,23 @@
 #include "fileio.h"
 
 char
-pm_getc(FILE * const file) {
+pm_getc(FILE * const fileP) {
     int ich;
     char ch;
 
-    ich = getc(file);
+    ich = getc(fileP);
     if (ich == EOF)
         pm_error("EOF / read error reading a byte");
     ch = (char) ich;
     
     if (ch == '#') {
         do {
-	    ich = getc(file);
-	    if (ich == EOF)
-            pm_error("EOF / read error reading a byte");
-	    ch = (char) ich;
-	    } while (ch != '\n' && ch != '\r');
-	}
+            ich = getc(fileP);
+            if (ich == EOF)
+                pm_error("EOF / read error reading a byte");
+            ch = (char) ich;
+        } while (ch != '\n' && ch != '\r');
+    }
     return ch;
 }
 
@@ -76,7 +76,7 @@ pm_getuint(FILE * const ifP) {
 
     do {
         ch = pm_getc(ifP);
-	} while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
+    } while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
 
     if (ch < '0' || ch > '9')
         pm_error("junk in file where an unsigned integer should be");
@@ -165,6 +165,3 @@ pm_putraw(FILE *       const file,
             pm_error("Error writing %d byte sample to file.", bytes);
     }
 }
-
-
-
diff --git a/lib/libpammap.c b/lib/libpammap.c
index 781f6f88..1ebc639c 100644
--- a/lib/libpammap.c
+++ b/lib/libpammap.c
@@ -438,8 +438,8 @@ pnm_alloctupletable(const struct pam * const pamP,
 
 
 void
-pnm_freetupletable(struct pam * const pamP,
-                   tupletable   const tupletable) {
+pnm_freetupletable(const struct pam * const pamP,
+                   tupletable         const tupletable) {
 
     /* Note that the address 'tupletable' is, to the operating system, 
        the address of a larger block of memory that contains not only 
@@ -453,8 +453,8 @@ pnm_freetupletable(struct pam * const pamP,
 
 
 void
-pnm_freetupletable2(struct pam * const pamP,
-                    tupletable2  const tupletable) {
+pnm_freetupletable2(const struct pam * const pamP,
+                    tupletable2        const tupletable) {
 
     pnm_freetupletable(pamP, tupletable.table);
 }
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
index c7b5a355..708205a2 100644
--- a/lib/libpbmfont.c
+++ b/lib/libpbmfont.c
@@ -14,6 +14,7 @@
 ** implied warranty.
 */
 
+#include <assert.h>
 #include <string.h>
 
 #include "pm_c_util.h"
@@ -667,9 +668,9 @@ pbm_defaultfont(const char * const name) {
         unsigned int row;
 
         if (strcmp(name, "fixed") != 0)
-            pm_error( "built-in font name unknown, try 'bdf' or 'fixed'" );
+            pm_error( "built-in font name unknown, try 'bdf' or 'fixed'");
 
-        defaultfont = pbm_allocarray( DEFAULTFONT_COLS, DEFAULTFONT_ROWS );
+        defaultfont = pbm_allocarray(DEFAULTFONT_COLS, DEFAULTFONT_ROWS);
         for (row =  0; row < DEFAULTFONT_ROWS; ++row) {
             unsigned int col;
             for (col = 0; col < DEFAULTFONT_COLS; col += 32) {
@@ -689,104 +690,200 @@ pbm_defaultfont(const char * const name) {
                 }
             }
         }
-
         retval = 
-            pbm_dissectfont(defaultfont, DEFAULTFONT_ROWS, DEFAULTFONT_COLS);
+            pbm_dissectfont((const bit **)defaultfont,
+                            DEFAULTFONT_ROWS, DEFAULTFONT_COLS);
     }
     return retval;
 }
 
 
+
+static void
+findFirstBlankRow(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const browP) {
+
+    unsigned int row;
+    bool foundBlank;
+
+    for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) {
+        unsigned int col;
+        bit col0Value = font[row][0];
+        bool rowIsBlank;
+        rowIsBlank = true;  /* initial assumption */
+        for (col = 1; col < fcols; ++col)
+            if (font[row][col] != col0Value)
+                rowIsBlank = false;
+
+        if (rowIsBlank) {
+            foundBlank = true;
+            *browP = row;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel row in font");
+}
+
+
+
+static void
+findFirstBlankCol(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const bcolP) {
+
+    unsigned int col;
+    bool foundBlank;
+
+    for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) {
+        unsigned int row;
+        bit row0Value = font[0][col];
+        bool colIsBlank;
+        colIsBlank = true;  /* initial assumption */
+        for (row = 1; row < frows; ++row)
+            if (font[row][col] != row0Value)
+                colIsBlank = false;
+
+        if (colIsBlank) {
+            foundBlank = true;
+            *bcolP = col;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel column in font");
+}
+
+
+
+static void
+computeCharacterSize(const bit **   const font,
+                     unsigned int   const fcols,
+                     unsigned int   const frows,
+                     unsigned int * const cellWidthP,
+                     unsigned int * const cellHeightP,
+                     unsigned int * const charWidthP,
+                     unsigned int * const charHeightP) {
+
+    unsigned int firstBlankRow;
+    unsigned int firstBlankCol;
+    unsigned int heightLast11Rows;
+
+    findFirstBlankRow(font, fcols, frows, &firstBlankRow);
+
+    findFirstBlankCol(font, fcols, frows, &firstBlankCol);
+
+    heightLast11Rows = frows - firstBlankRow;
+
+    if (heightLast11Rows % 11 != 0)
+        pm_error("The rows of characters in the font do not appear to "
+                 "be all the same height.  The last 11 rows are %u pixel "
+                 "rows high (from pixel row %u up to %u), "
+                 "which is not a multiple of 11.",
+                 heightLast11Rows, firstBlankRow, frows);
+    else {
+        unsigned int widthLast15Cols;
+
+        *cellHeightP = heightLast11Rows / 11;
+
+        widthLast15Cols = fcols - firstBlankCol;
+
+        if (widthLast15Cols % 15 != 0)
+            pm_error("The columns of characters in the font do not appear to "
+                     "be all the same width.  "
+                     "The last 15 columns are %u pixel "
+                     "columns wide (from pixel col %u up to %u), "
+                     "which is not a multiple of 15.",
+                     widthLast15Cols, firstBlankCol, fcols);
+        else {
+            *cellWidthP = widthLast15Cols / 15;
+
+            *charWidthP = firstBlankCol;
+            *charHeightP = firstBlankRow;
+        }
+    }
+}
+
+
+
 struct font*
-pbm_dissectfont(bit ** const font,
-                int    const frows,
-                int    const fcols) {
+pbm_dissectfont(const bit ** const font,
+                unsigned int const frows,
+                unsigned int const fcols) {
     /*
-    ** This routine expects a font bitmap representing the following text:
-    **
-    ** (0,0)
-    **    M ",/^_[`jpqy| M
-    **
-    **    /  !"#$%&'()*+ /
-    **    < ,-./01234567 <
-    **    > 89:;<=>?@ABC >
-    **    @ DEFGHIJKLMNO @
-    **    _ PQRSTUVWXYZ[ _
-    **    { \]^_`abcdefg {
-    **    } hijklmnopqrs }
-    **    ~ tuvwxyz{|}~  ~
-    **
-    **    M ",/^_[`jpqy| M
-    **
-    ** The bitmap must be cropped exactly to the edges.
-    **
-    ** The border you see is irrelevant.  The 12 x 8 array in the center is
-    ** the font.  The top left character there belongs to code point 0, and
-    ** the code points increase in standard reading order, so the bottom
-    ** right character is code point 127.  You can't define code points 
-    ** < 32 or > 127 with this font format.
-    **
-    ** The dissection works by finding the first blank row and column; that
-    ** gives the height and width of the maximum-sized character, which is
-    ** not too useful.  But the distance from there to the opposite side is
-    ** an integral multiple of the cell size, and that's what we need.  Then
-    ** it's just a matter of filling in all the coordinates.
-    **
-    ** The difference between cell_height, cell_width and char_height,
-    ** char_width is that the first is the size of the cell including
-    ** spacing, while the second is just the actual maximum-size character.
-    */
-    int cell_width, cell_height, char_width, char_height;
-    int brow, bcol, row, col, d, ch, r, c, i;
-    struct font* fn;
-    struct glyph* glyph;
+       This routine expects a font bitmap representing the following text:
+      
+       (0,0)
+          M ",/^_[`jpqy| M
+      
+          /  !"#$%&'()*+ /
+          < ,-./01234567 <
+          > 89:;<=>?@ABC >
+          @ DEFGHIJKLMNO @
+          _ PQRSTUVWXYZ[ _
+          { \]^_`abcdefg {
+          } hijklmnopqrs }
+          ~ tuvwxyz{|}~  ~
+      
+          M ",/^_[`jpqy| M
+      
+       The bitmap must be cropped exactly to the edges.
+      
+       The characters in the border you see are irrelevant except for
+       character size compuations.  The 12 x 8 array in the center is
+       the font.  The top left character there belongs to code point
+       0, and the code points increase in standard reading order, so
+       the bottom right character is code point 127.  You can't define
+       code points < 32 or > 127 with this font format.
+
+       The characters in the top and bottom border rows must include a
+       character with the lowest reach of any in the font (e.g. "y",
+       "_") and one with the highest reach (e.g. '"').  The characters
+       in the left and right border columns must include characters
+       with the rightmost and leftmost reach of any in the font
+       (e.g. "M" for both).
+
+       The border must be separated from the font by one blank text
+       row or text column.
+      
+       The dissection works by finding the first blank row and column;
+       i.e the lower right corner of the "M" in the upper left corner
+       of the matrix.  That gives the height and width of the
+       maximum-sized character, which is not too useful.  But the
+       distance from there to the opposite side is an integral
+       multiple of the cell size, and that's what we need.  Then it's
+       just a matter of filling in all the coordinates.  */
+    
+    unsigned int cellWidth, cellHeight;
+        /* Dimensions in pixels of each cell of the font -- that
+           includes the glyph and the white space above and to the
+           right of it.  Each cell is a tile of the font image.  The
+           top character row and left character row don't count --
+           those cells are smaller because they are missing the white
+           space.
+        */
+    unsigned int charWidth, charHeight;
+        /* Maximum dimensions of glyph itself, inside its cell */
+
+    int row, col, ch, r, c, i;
+    struct font * fn;
+    struct glyph * glyph;
     char* bmap;
-    bit b;
-
-    /* Find first blank row. */
-    for ( brow = 0; brow < frows / 6; ++brow ) {
-        b = font[brow][0];
-        for ( col = 1; col < fcols; ++col )
-            if ( font[brow][col] != b )
-                goto nextrow;
-        goto gotblankrow;
-    nextrow: ;
-    }
-    pm_error( "couldn't find blank row in font" );
-
- gotblankrow:
-    /* Find first blank col. */
-    for ( bcol = 0; bcol < fcols / 8; ++bcol ) {
-        b = font[0][bcol];
-        for ( row = 1; row < frows; ++row )
-            if ( font[row][bcol] != b )
-                goto nextcol;
-        goto gotblankcol;
-    nextcol: ;
-    }
-    pm_error( "couldn't find blank col in font" );
-
- gotblankcol:
-    /* Now compute character cell size. */
-    d = frows - brow;
-    cell_height = d / 11;
-    if ( cell_height * 11 != d )
-        pm_error( "problem computing character cell height" );
-    d = fcols - bcol;
-    cell_width = d / 15;
-    if ( cell_width * 15 != d )
-        pm_error( "problem computing character cell width" );
-    char_height = brow;
-    char_width = bcol;
+
+    computeCharacterSize(font, fcols, frows,
+                         &cellWidth, &cellHeight, &charWidth, &charHeight);
 
     /* Now convert to a general font */
 
     MALLOCVAR(fn);
-    if ( fn == NULL )
-        pm_error( "out of memory allocating font structure" );
+    if (fn == NULL)
+        pm_error("out of memory allocating font structure");
 
-    fn->maxwidth  = char_width;
-    fn->maxheight = char_height;
+    fn->maxwidth  = charWidth;
+    fn->maxheight = charHeight;
     fn->x = fn->y = 0;
 
     fn->oldfont = font;
@@ -808,8 +905,8 @@ pbm_dissectfont(bit ** const font,
         pm_error( "out of memory allocating glyph data" );
 
     /* Now fill in the 0,0 coords. */
-    row = cell_height * 2;
-    col = cell_width * 2;
+    row = cellHeight * 2;
+    col = cellWidth * 2;
     for (i = 0; i < firstCodePoint; ++i)
         fn->glyph[i] = NULL;
 
@@ -817,7 +914,7 @@ pbm_dissectfont(bit ** const font,
         glyph[ch].width = fn->maxwidth;
         glyph[ch].height = fn->maxheight;
         glyph[ch].x = glyph[ch].y = 0;
-        glyph[ch].xadd = cell_width;
+        glyph[ch].xadd = cellWidth;
 
         for ( r = 0; r < glyph[ch].height; ++r )
             for ( c = 0; c < glyph[ch].width; ++c )
@@ -828,10 +925,10 @@ pbm_dissectfont(bit ** const font,
 
         fn->glyph[firstCodePoint + ch] = &glyph[ch];
 
-        col += cell_width;
-        if ( col >= cell_width * 14 ) {
-            col = cell_width * 2;
-            row += cell_height;
+        col += cellWidth;
+        if ( col >= cellWidth * 14 ) {
+            col = cellWidth * 2;
+            row += cellHeight;
         }
     }
     for (i = firstCodePoint + nCharsInFont; i < 256; ++i)
@@ -868,18 +965,21 @@ pbm_loadfont(const char * const filename)
 
 
 
-struct font* pbm_loadpbmfont(const char * const filename)
-{
-    FILE* ifp;
-    bit** font;
+struct font *
+pbm_loadpbmfont(const char * const filename) {
+
+    FILE * ifP;
+    bit ** font;
     int fcols, frows;
 
-    ifp = pm_openr( filename );
-    font = pbm_readpbm( ifp, &fcols, &frows );
-    pm_close( ifp );
-    return pbm_dissectfont( font, frows, fcols );
+    ifP = pm_openr(filename);
+    font = pbm_readpbm(ifP, &fcols, &frows);
+    pm_close(ifP);
+    return pbm_dissectfont((const bit **)font, frows, fcols);
 }
 
+
+
 void
 pbm_dumpfont( fn )
     struct font* fn;
@@ -971,166 +1071,400 @@ pbm_dumpfont( fn )
 
 /* Routines for loading a BDF font file */
 
-static int readline ARGS((FILE* fp, char* buf, char* arg[]));
-
-#define expect(str) if (readline(fp, line, arg) < 0 || strcmp(arg[0], (str))) \
-    { fclose(fp); return 0; }
 
-struct font* pbm_loadbdffont(const char * const name)
-{
-    FILE* fp;
-    char line[1024], *arg[32], *hex;
-    char *b;
-    int n, numchar, hdig, encoding;
-    struct font* font;
-    struct glyph* glyph;
+static unsigned int
+mk_argvn(char *        const s,
+         const char ** const vec,
+         unsigned int  const mk_max) {
 
-    if (!(fp = fopen(name, "rb")))
-        return 0;
+    int n;
+    char * p;
 
-    expect("STARTFONT");
+    p = &s[0];
+    n = 0;
 
-    MALLOCVAR(font);
-    if (font == NULL)
-        pm_error("no memory for font");
-    font->oldfont = 0;
-    { 
-        /* Initialize all characters to nonexistent; we will fill the ones we
-           find in the bdf file later.
-        */
-        int i;
-        for (i = 0; i < 256; i++) 
-            font->glyph[i] = NULL;
+    while (*p) {
+        if (ISSPACE(*p))
+            *p++ = '\0';
+        else {
+            vec[n++] = p;
+            if (n >= mk_max)
+                break;
+            while (*p && !ISSPACE(*p))
+                ++p;
+        }
     }
+    vec[n] = NULL;
 
-    while (readline(fp, line, arg) >= 0) {
-        if (!strcmp(arg[0], "COMMENT"))
-            continue;
-        if (!strcmp(arg[0], "SIZE"))
-            continue;
-        
-        if (!strcmp(arg[0], "STARTPROPERTIES")) {
-            n = atoi(arg[1]);
-            for (; n > 0 && readline(fp, line, arg) >= 0; n--)
-                ;
-        }
-        else if (!strcmp(arg[0], "FONTBOUNDINGBOX")) {
-            font->maxwidth = atoi(arg[1]);
-            font->maxheight = atoi(arg[2]);
-            font->x = atoi(arg[3]);
-            font->y = atoi(arg[4]);
-        }
-        else if (!strcmp(arg[0], "ENDFONT")) {
-            fclose(fp);
-            return font;
+    return n;
+}
+
+
+
+static int
+readline(FILE *        const fp,
+         char *        const buf,
+         const char ** const arg) {
+
+    int retval;
+    char * rc;
+
+    rc = fgets(buf, 1024, fp);
+    if (rc == NULL)
+        retval = -1;
+    else
+        retval = mk_argvn(buf, arg, 32);
+
+    return retval;
+}
+
+
+
+static void
+readBitmap(FILE *          const fp,
+           unsigned int    const glyphWidth,
+           unsigned int    const glyphHeight,
+           const char *    const charName,
+           unsigned char * const bmap) {
+
+    int n;
+    unsigned int bmapIndex;
+
+    bmapIndex = 0;
+           
+    for (n = glyphHeight; n > 0; --n) {
+        int i;  /* dot counter */
+        int rc;
+        char * hex;
+        char line[1024];
+        const char * arg[32];
+
+        rc = readline(fp, line, arg);
+
+        if (rc < 0)
+            pm_error("End of file in bitmap for character '%s' in BDF "
+                     "font file.", charName);
+
+        hex = line;
+        for (i = glyphWidth; i > 0; i -= 4) {
+            char hdig;
+            unsigned int hdigValue;
+            hdig = *hex++;
+            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;
         }
-        else if (!strcmp(arg[0], "CHARS")) {
-            numchar = atoi(arg[1]);
-            while (numchar > 0) {
-                if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
-                if (!strcmp(arg[0], "COMMENT"))
-                    continue;
-                if (strcmp(arg[0], "STARTCHAR")) { fclose(fp); return 0; }
-                MALLOCVAR(glyph);
-                if (glyph == NULL)
-                    pm_error("no memory for font glyph");
-
-                expect("ENCODING");
-                if ((encoding = atoi(arg[1])) < 0) {
-                    if (arg[2])
-                        encoding = atoi(arg[2]);
-                    else {
-                        while (readline(fp, line, arg) >= 0)
-                            if (!strcmp(arg[0], "ENDCHAR"))
-                                break;
+    }
+}
+
+
+
+static void
+createBmap(unsigned int  const glyphWidth,
+           unsigned int  const glyphHeight,
+           FILE *        const fp,
+           const char *  const charName,
+           const char ** const bmapP) {
+
+    char line[1024];
+    const char * arg[32];
+    unsigned char * bmap;
+    int rc;
+
+    if (UINT_MAX / glyphWidth < glyphHeight)
+        pm_error("Ridiculously large glyph");
+
+    MALLOCARRAY(bmap, glyphWidth * glyphHeight);
+
+    if (!bmap)
+        pm_error("no memory for font glyph byte map");
+
+    rc = readline(fp, line, arg);
+    if (rc < 0)
+        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)
+            pm_error("End of file encountered after ATTRIBUTES in BDF "
+                     "font file.");
+    }                
+    if (!streq(arg[0], "BITMAP"))
+        pm_error("'%s' found where BITMAP expected in definition of "
+                 "character '%s' in BDF font file.", line, charName);
+
+    assert(streq(arg[0], "BITMAP"));
+
+    readBitmap(fp, glyphWidth, glyphHeight, charName, bmap);
+
+    *bmapP = (char *)bmap;
+}
+
+
+
+static void
+expect(FILE *        const fp,
+       const char *  const expected,
+       const char ** const arg) {
+
+    char line[1024];
+    int rc;
+
+    rc = readline(fp, line, arg);
+
+    if (rc < 0)
+        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);
+}
+
+
+
+static void
+skipCharacter(FILE * const fp) {
+/*----------------------------------------------------------------------------
+  In BDF font file 'fp', skip through the end of the character we are
+  presently in.
+-----------------------------------------------------------------------------*/
+    bool endChar;
                         
-                        numchar--;
-                        continue;
-                    }
-                }
-                expect("SWIDTH");
-                expect("DWIDTH");
-                glyph->xadd = atoi(arg[1]);
-                expect("BBX");
-                glyph->width = atoi(arg[1]);
-                glyph->height = atoi(arg[2]);
-                glyph->x = atoi(arg[3]);
-                glyph->y = atoi(arg[4]);
-
-                b = (char*)malloc(glyph->width * glyph->height * sizeof(char));
-                glyph->bmap = b;
-                if (!glyph->bmap)
-                    pm_error("no memory for font glyph byte map");
-
-                if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
-                if (!strcmp(arg[0], "ATTRIBUTES"))
-                    if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
-                
-                for (n = glyph->height; n > 0; n--) {
-                    int i;  /* dot counter */
-                    if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
-                    hex = line;
-                    for (i = glyph->width; i > 0; i -= 4) {
-                        hdig = *hex++;
-                        if (hdig >= '0' && hdig <= '9')
-                            hdig -= '0';
-                        else if (hdig >= 'a' && hdig <= 'f')
-                            hdig -= 'a' - 10;
-                        else if (hdig >= 'A' && hdig <= 'F')
-                            hdig -= 'A' - 10;
+    endChar = FALSE;
                         
-                        *b++ = hdig & 8 ? 1 : 0;
-                        if (i > 1) *b++ = hdig & 4 ? 1 : 0;
-                        if (i > 2) *b++ = hdig & 2 ? 1 : 0;
-                        if (i > 3) *b++ = hdig & 1;
-                    }
-                }
+    while (!endChar) {
+        char line[1024];
+        const char * arg[32];
+        int rc;
+        rc = readline(fp, line, arg);
+        if (rc < 0)
+            pm_error("End of file in the middle of a character (before "
+                     "ENDCHAR) in BDF font file.");
+        endChar = streq(arg[0], "ENDCHAR");
+    }                        
+}
+
+
+
+static void
+validateEncoding(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
+   code, e.g. in ASCII, 48 is '0').
+
+   But if the statement doesn't give an acceptable codepoint return
+   *badCodepointP == TRUE.
+-----------------------------------------------------------------------------*/
+
+    bool gotCodepoint;
+    bool badCodepoint;
+    unsigned int codepoint;
+
+    if (atoi(arg[1]) >= 0) {
+        codepoint = atoi(arg[1]);
+        gotCodepoint = true;
+    } else {
+        if (arg[2]) {
+            codepoint = atoi(arg[2]);
+            gotCodepoint = true;
+        } else
+            gotCodepoint = false;
+    }
+    if (gotCodepoint) {
+        if (codepoint > 255)
+            badCodepoint = true;
+        else
+            badCodepoint = false;
+    } else
+        badCodepoint = true;
+
+    *badCodepointP = badCodepoint;
+    *codepointP    = codepoint;
+}
+
+
+
+
+static void
+processCharsLine(FILE *        const fp,
+                 const char ** const arg,
+                 struct font * const fontP) {
 
-                expect("ENDCHAR");
+    unsigned int const nCharacters = atoi(arg[1]);
 
-                if (encoding < 256)
-                    /* We ignore any characters with codes that don't fit 
-                       in 8 bits.  We may want to change this someday.
-                       */
-                    font->glyph[encoding] = glyph;
+    unsigned int nCharsDone;
 
-                numchar--;
+    nCharsDone = 0;
+
+    while (nCharsDone < nCharacters) {
+        char line[1024];
+        const char * arg[32];
+        int rc;
+
+        rc = readline(fp, line, arg);
+        if (rc < 0)
+            pm_error("End of file after CHARS reading BDF font file");
+
+        if (streq(arg[0], "COMMENT")) {
+            /* ignore */
+        } else if (!streq(arg[0], "STARTCHAR"))
+            pm_error("no STARTCHAR after CHARS in BDF font file");
+        else {
+            const char * const charName = arg[1];
+            struct glyph * glyphP;
+            unsigned int codepoint;
+            bool badCodepoint;
+
+            assert(streq(arg[0], "STARTCHAR"));
+
+            MALLOCVAR(glyphP);
+
+            if (glyphP == NULL)
+                pm_error("no memory for font glyph for '%s' character",
+                         charName);
+
+            {
+                const char * arg[32];
+                expect(fp, "ENCODING", arg);
+
+                validateEncoding(arg, &codepoint, &badCodepoint);
+            }
+            if (badCodepoint)
+                skipCharacter(fp);
+            else {
+                {
+                    const char * arg[32];
+                    expect(fp, "SWIDTH", arg);
+                }
+                {
+                    const char * arg[32];
+                    
+                    expect(fp, "DWIDTH", arg);
+                    glyphP->xadd = atoi(arg[1]);
+                }
+                {
+                    const char * arg[32];
+                    
+                    expect(fp, "BBX", arg);
+                    glyphP->width  = atoi(arg[1]);
+                    glyphP->height = atoi(arg[2]);
+                    glyphP->x      = atoi(arg[3]);
+                    glyphP->y      = atoi(arg[4]);
+                }
+                createBmap(glyphP->width, glyphP->height, fp, charName,
+                           &glyphP->bmap);
+                
+                {
+                    const char * arg[32];
+                    expect(fp, "ENDCHAR", arg);
+                }
+                assert(codepoint < 256); /* Ensured by validateEncoding() */
+
+                fontP->glyph[codepoint] = glyphP;
             }
+            ++nCharsDone;
         }
     }
-    return font;
 }
 
-static int readline(fp, buf, arg)
-FILE* fp;
-char* buf;
-char* arg[];
-{
-    if (!fgets(buf, 1024, fp))
-        return -1;
-    
-    return mk_argvn(buf, arg, 32);
-}
 
-int mk_argvn(s, vec, mk_max)
-char* s;
-char* vec[];
-int mk_max;
-{
-    int n;
 
-    n = 0;
-    while (*s) {
-        if (ISSPACE(*s)) {
-            *s++ = '\0';
-            continue;
+static void
+processFontLine(FILE *        const fp,
+                const char *  const line,
+                const char ** const arg,
+                struct font * const fontP,
+                bool *        const endOfFontP) {
+
+    *endOfFontP = FALSE;  /* initial assumption */
+
+    if (streq(arg[0], "COMMENT")) {
+        /* ignore */
+    } else if (streq(arg[0], "SIZE")) {
+        /* ignore */
+    } else if (streq(arg[0], "STARTPROPERTIES")) {
+        unsigned int const propCount = atoi(arg[1]);
+        unsigned int i;
+        for (i = 0; i < propCount; ++i) {
+            char line[1024];
+            const char * arg[32];
+            int rc;
+            rc = readline(fp, line, arg);
+            if (rc < 0)
+                pm_error("End of file after STARTPROPERTIES in BDF font file");
         }
-        vec[n++] = s;
-        if (n >= mk_max)
-            break;
-        while (*s && !ISSPACE(*s))
-            s++;
+    } else if (streq(arg[0], "FONTBOUNDINGBOX")) {
+        fontP->maxwidth  = atoi(arg[1]);
+        fontP->maxheight = atoi(arg[2]);
+        fontP->x         = atoi(arg[3]);
+        fontP->y         = atoi(arg[4]);
+    } else if (streq(arg[0], "ENDFONT")) {
+        *endOfFontP = true;
+    } else if (!strcmp(arg[0], "CHARS"))
+        processCharsLine(fp, arg, fontP);
+}
+
+
+
+struct font *
+pbm_loadbdffont(const char * const name) {
+
+    FILE * fp;
+    char line[1024];
+    const char * arg[32];
+    struct font * fontP;
+    bool endOfFont;
+
+    fp = fopen(name, "rb");
+    if (!fp)
+        pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
+                 name, errno, strerror(errno));
+
+    expect(fp, "STARTFONT", arg);
+
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
+        pm_error("no memory for font");
+
+    fontP->oldfont = 0;
+    { 
+        /* Initialize all characters to nonexistent; we will fill the ones we
+           find in the bdf file later.
+        */
+        unsigned int i;
+        for (i = 0; i < 256; i++) 
+            fontP->glyph[i] = NULL;
     }
-    vec[n] = 0;
-    return n;
+    fontP->x = fontP->y = 0;
+
+    endOfFont = FALSE;
+
+    while (!endOfFont) {
+        int rc;
+        rc = readline(fp, line, arg);
+        if (rc < 0)
+            pm_error("End of file before ENDFONT statement in BDF font file");
+
+        processFontLine(fp, line, arg, fontP, &endOfFont);
+    }
+    return fontP;
 }
diff --git a/lib/libpm.c b/lib/libpm.c
index 913b1644..115d301c 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -1494,7 +1494,8 @@ pm_tell2(FILE *       const fileP,
     } else
         pm_error("File position size passed to pm_tell() is invalid: %u.  "
                  "Valid sizes are %u and %u", 
-                 fileposSize, sizeof(pm_filepos), sizeof(long));
+                 fileposSize, (unsigned int)sizeof(pm_filepos),
+                 (unsigned int) sizeof(long));
 }
 
 
@@ -1532,7 +1533,8 @@ pm_seek2(FILE *             const fileP,
     } else
         pm_error("File position size passed to pm_seek() is invalid: %u.  "
                  "Valid sizes are %u and %u", 
-                 fileposSize, sizeof(pm_filepos), sizeof(long));
+                 fileposSize, (unsigned int)sizeof(pm_filepos),
+                 (unsigned int) sizeof(long));
 }
 
 
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
index e149b42a..2a54e896 100644
--- a/lib/libppmfuzzy.c
+++ b/lib/libppmfuzzy.c
@@ -425,7 +425,7 @@ ppm_bk_color_from_name(const char * const name) {
     bk_color i;
 
     for (i = 0; i < BKCOLOR_COUNT; ++i) {
-        if (STREQ(name, bkColorNameMap[i]))
+        if (streq(name, bkColorNameMap[i]))
             return i;
     }
     pm_error("Invalid Berlin-Kay color name: '%s'", name);
diff --git a/lib/pammap.h b/lib/pammap.h
index f11b1d7a..a53a9c23 100644
--- a/lib/pammap.h
+++ b/lib/pammap.h
@@ -72,10 +72,12 @@ tupletable
 pnm_alloctupletable(const struct pam * const pamP, unsigned int const size);
 
 void
-pnm_freetupletable(struct pam * const pamP, tupletable const tupletable);
+pnm_freetupletable(const struct pam * const pamP,
+                   tupletable         const tupletable);
 
 void
-pnm_freetupletable2(struct pam * const pamP, tupletable2 const tupletable);
+pnm_freetupletable2(const struct pam * const pamP,
+                    tupletable2        const tupletable);
 
 tuplehash
 pnm_createtuplehash(void);
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
index 432aea3c..8a81c37a 100644
--- a/lib/pbmfont.h
+++ b/lib/pbmfont.h
@@ -13,22 +13,23 @@ struct glyph {
        can be anything, but normally does not have white borders because
        it's more efficient to represent white borders explicitly.
     */
-	int width, height;
+    unsigned int width;
+    unsigned int height;
         /* The dimensions of the central glyph, i.e. the 'bmap' array */
-	int x;
+    unsigned int x;
         /* Width in pixels of the white left border of this glyph.
            This can actually be negative to indicate that the central
            glyph backs up over the previous character in the line.  In
            that case, if there is no previous character in the line, it
            is as if 'x' is 0.
         */
-    int y;
+    unsigned int y;
         /* Height in pixels of the white bottom border of this glyph */
-	int xadd;
+    unsigned int xadd;
         /* Width of glyph -- white left border plus central glyph
            plus white right border
         */
-	const char * bmap;
+    const char * bmap;
         /* The raster of the central glyph.  It is an 'width' by
            'height' array in row major order, with each byte being 1
            for black; 0 for white.  E.g. if 'width' is 20 pixels and
@@ -44,31 +45,30 @@ struct font {
        an code point in the range 0..255, this structure describes the
        glyph for that character.
     */
-	int maxwidth, maxheight;
-	int x;
+    int maxwidth, maxheight;
+    int x;
         /* ?? Not used by Pbmtext */
     int y;
         /* Amount of white space that should be added between lines of
-           this font.
+           this font.  Can be negative.
         */
-	struct glyph * glyph[256];
+    struct glyph * glyph[256];
         /* glyph[i] is the glyph for code point i */
-	bit** oldfont;
-	    /* for compatibility with old pbmtext routines */
-	    /* oldfont is 0 if the font is BDF derived */
-	int fcols, frows;
+    const bit ** oldfont;
+        /* for compatibility with old pbmtext routines */
+        /* oldfont is NULL if the font is BDF derived */
+    int fcols, frows;
 };
 
 struct font* pbm_defaultfont(const char* const which);
 struct font*
-pbm_dissectfont(bit ** const font,
-                int    const frows,
-                int    const fcols);
+pbm_dissectfont(const bit ** const font,
+                unsigned int const frows,
+                unsigned int const fcols);
 struct font* pbm_loadfont(const char * const filename);
 struct font* pbm_loadpbmfont(const char * const filename);
 struct font* pbm_loadbdffont(const char * const filename);
 void pbm_dumpfont ARGS(( struct font* fn ));
-int mk_argvn ARGS(( char* s, char* vec[], int max ));
 
 #ifdef __cplusplus
 }
diff --git a/other/pamx/pamx.c b/other/pamx/pamx.c
index c6503d5e..49aa07c9 100644
--- a/other/pamx/pamx.c
+++ b/other/pamx/pamx.c
@@ -298,7 +298,7 @@ determineTitle(struct cmdlineInfo const cmdline,
     if (cmdline.title)
         title = strdup(cmdline.title);
     else {
-        if (STREQ(cmdline.inputFileName, "-"))
+        if (streq(cmdline.inputFileName, "-"))
             title = NULL;
         else {
             title = pm_basename(cmdline.inputFileName);
diff --git a/other/pnmcolormap.c b/other/pnmcolormap.c
index 1be54ef8..4faf5ab6 100644
--- a/other/pnmcolormap.c
+++ b/other/pnmcolormap.c
@@ -631,7 +631,7 @@ validateCompatibleImage(struct pam * const inpamP,
     if (inpamP->format != firstPamP->format)
         pm_error("Image %u format (%d) is not the same as Image 0 (%d)",
                  imageSeq, inpamP->format, firstPamP->format);
-    if (!STREQ(inpamP->tuple_type, firstPamP->tuple_type))
+    if (!streq(inpamP->tuple_type, firstPamP->tuple_type))
         pm_error("Image %u tuple type (%s) is not the same as Image 0 (%s)",
                  imageSeq, inpamP->tuple_type, firstPamP->tuple_type);
 }