about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /lib
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile278
-rw-r--r--lib/bitio.c207
-rw-r--r--lib/bitio.h89
-rw-r--r--lib/colorname.c208
-rw-r--r--lib/colorname.h43
-rw-r--r--lib/compile.h5
-rw-r--r--lib/fileio.c170
-rw-r--r--lib/fileio.h22
-rw-r--r--lib/libpam.c1098
-rw-r--r--lib/libpam.h17
-rw-r--r--lib/libpamcolor.c102
-rw-r--r--lib/libpammap.c662
-rw-r--r--lib/libpamn.c542
-rw-r--r--lib/libpamread.c277
-rw-r--r--lib/libpamwrite.c397
-rw-r--r--lib/libpbm.h12
-rw-r--r--lib/libpbm1.c62
-rw-r--r--lib/libpbm2.c180
-rw-r--r--lib/libpbm3.c282
-rw-r--r--lib/libpbmfont.c1136
-rw-r--r--lib/libpbmvms.c734
-rw-r--r--lib/libpgm.h24
-rw-r--r--lib/libpgm1.c324
-rw-r--r--lib/libpgm2.c226
-rw-r--r--lib/libpm.c1494
-rw-r--r--lib/libpnm1.c222
-rw-r--r--lib/libpnm2.c128
-rw-r--r--lib/libpnm3.c393
-rw-r--r--lib/libppm.h15
-rw-r--r--lib/libppm1.c328
-rw-r--r--lib/libppm2.c208
-rw-r--r--lib/libppmcmap.c650
-rw-r--r--lib/libppmcolor.c710
-rw-r--r--lib/libppmd.c1177
-rw-r--r--lib/libppmfloyd.c270
-rw-r--r--lib/libppmfuzzy.c434
-rw-r--r--lib/libsystem.c319
-rw-r--r--lib/libsystem_dummy.c47
-rw-r--r--lib/lum.h123
-rw-r--r--lib/mkstemp.c8
-rw-r--r--lib/pam.h490
-rw-r--r--lib/pammap.h124
-rw-r--r--lib/path.c468
-rw-r--r--lib/pbm.h97
-rw-r--r--lib/pbmfont.h75
-rw-r--r--lib/pgm.h136
-rw-r--r--lib/pm.h346
-rw-r--r--lib/pm_gamma.h68
-rw-r--r--lib/pm_system.h43
-rw-r--r--lib/pnm.h134
-rw-r--r--lib/ppm.h300
-rw-r--r--lib/ppmcmap.h111
-rw-r--r--lib/ppmdfont.c133
-rw-r--r--lib/ppmdfont.h74
-rw-r--r--lib/ppmdraw.h303
-rw-r--r--lib/ppmfloyd.h67
-rw-r--r--lib/rgb.txt881
-rw-r--r--lib/standard.ppmdfontbin0 -> 3899 bytes
-rw-r--r--lib/standardppmdfont.c3177
-rw-r--r--lib/util/LICENSE.txt121
-rw-r--r--lib/util/Makefile29
-rw-r--r--lib/util/bitreverse.h46
-rw-r--r--lib/util/filename.c26
-rw-r--r--lib/util/filename.h7
-rw-r--r--lib/util/intcode.h86
-rw-r--r--lib/util/lexheader9
-rw-r--r--lib/util/mallocvar.h107
-rw-r--r--lib/util/nstring.c906
-rw-r--r--lib/util/nstring.h157
-rw-r--r--lib/util/pm_c_util.h62
-rw-r--r--lib/util/shhopt-1.1.6.lsm16
-rw-r--r--lib/util/shhopt.README200
-rw-r--r--lib/util/shhopt.c939
-rw-r--r--lib/util/shhopt.h240
-rw-r--r--lib/util/testnstring.c30
-rw-r--r--lib/util/wordaccess.h65
-rw-r--r--lib/util/wordaccess_64_le.h52
-rw-r--r--lib/util/wordaccess_gcc3_be.h40
-rw-r--r--lib/util/wordaccess_gcc3_le.h54
-rw-r--r--lib/util/wordaccess_generic.h89
80 files changed, 23931 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 00000000..bd8eccae
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,278 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = lib
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+DLLTOOL=dlltool
+include $(BUILDDIR)/Makefile.config
+
+ifeq ($(NETPBMLIBTYPE),unixstatic)
+LIBNETPBM = libnetpbm.$(STATICLIBSUFFIX)
+else
+LIBNETPBM = $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).$(NETPBMLIBSUFFIX)
+endif
+
+ifeq ($(STATICLIB_TOO),y)
+EXTRA_STATICLIB = libnetpbm.$(STATICLIBSUFFIX)
+else
+EXTRA_STATICLIB =
+endif
+
+ifeq ($(DONT_HAVE_PROCESS_MGMT),Y)
+  LIBSYSTEM = libsystem_dummy.o
+else
+  LIBSYSTEM = libsystem.o
+endif
+
+LIBOBJECTS = libpm.o fileio.o bitio.o colorname.o \
+	libpbm1.o libpbm2.o libpbm3.o libpbmfont.o \
+	libpgm1.o libpgm2.o \
+	libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \
+	libppmd.o ppmdfont.o standardppmdfont.o path.o \
+	libppmfloyd.o \
+	libpnm1.o libpnm2.o libpnm3.o \
+	libpam.o libpamread.o libpamwrite.o \
+	libpamn.o libpammap.o libpamcolor.o \
+	$(LIBSYSTEM) \
+
+ifneq (${VMS}x,x)
+LIBOBJECTS += libpbmvms.o
+endif
+# Library objects to be linked but not built by Makefile.common:
+LIBOBJECTS_X = util/shhopt.o util/nstring.o util/filename.o
+
+MANUALS3 = libnetpbm
+MANUALS5 = pbm pgm ppm pnm pam
+
+INTERFACE_HEADERS =  pm.h pbm.h bitio.h pbmfont.h \
+	pgm.h ppm.h ppm.h ppmcmap.h ppmfloyd.h colorname.h \
+	pnm.h pam.h pammap.h util/shhopt.h util/nstring.h util/mallocvar.h \
+	pm_system.h pm_gamma.h
+
+DATAFILES = rgb.txt
+
+.PHONY: all
+all: libnetpbm extra_staticlib
+
+INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
+
+SUBDIRS = util
+SCRIPTS = 
+BINARIES = 
+
+OMIT_LIBRARY_RULE = 1
+include $(SRCDIR)/Makefile.common
+
+# The following must go after Makefile.common because $(LIBNETPBM) may 
+# contain a reference to $(NETPBM_MAJOR_RELEASE).
+.PHONY: libnetpbm
+libnetpbm: $(LIBNETPBM)
+
+.PHONY: extra_staticlib
+extra_staticlib: $(EXTRA_STATICLIB)
+
+#----------------------------------------------------------------------------
+# Following are rules for building shared libraries.
+# Note that the user may specify a shared library as his "main" library
+# type, but request a static library in addition.
+#----------------------------------------------------------------------------
+
+$(LIBOBJECTS): %.o: %.c importinc
+# Note that the user may have configured -I options into CFLAGS.
+	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) $(CFLAGS_SHLIB) \
+	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+MAJ = $(NETPBM_MAJOR_RELEASE)
+MIN = $(NETPBM_MINOR_RELEASE)
+
+SONAME = libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ)
+
+ifeq ($(NETPBMLIBTYPE),irixshared)
+# The libxxx.so link is needed to link the executables.
+libnetpbm.$(NETPBMLIBSUFFIX): $(SONAME)
+	rm -f $@
+	$(SYMLINK) $< $@
+
+PERLPROG = print "sgi$(MAJ)." . join(":sgi$(MAJ) . ", (0..$(MIN))) . "\n"
+
+$(SONAME): \
+    $(LIBOBJECTS) $(LIBOBJECTS_X) 
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+          -lc \
+	  -soname libnetpbm.$(NETPBMLIBSUFFIX) \
+	  -set_version $(shell perl -e '$(PERLPROG)') \
+	  $(LADD)
+endif
+
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# The libxxx.so link is needed to link the executables.
+libnetpbm.$(NETPBMLIBSUFFIX): $(SONAME)
+	rm -f $@
+	$(SYMLINK) $< $@
+# The $(SONAME) link is needed only to test the programs without
+# installing the libraries (in that case, you also need to direct the 
+# dynamic linker to the source directories, e.g. set LD_LIBRARY_PATH).
+$(SONAME): libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN)
+	rm -f $@
+	$(SYMLINK) $< $@
+libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN): $(LIBOBJECTS) $(LIBOBJECTS_X)
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+          $(SHLIB_CLIB) -lm $(LADD)
+endif
+
+ifeq ($(NETPBMLIBTYPE),dll)
+ifeq ($(STATICLIB_TOO),y)
+$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X) libnetpbm.$(STATICLIBSUFFIX)
+else
+$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X)
+endif
+	$(LD) $(LDSHLIB) -Wl,--export-all-symbols \
+            -Wl,-soname,$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll \
+	    -Wl,--output-def,$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).def \
+            -Wl,--out-implib,libnetpbm.dll.a -o $@ $(LDFLAGS) \
+            $(LIBOBJECTS) $(LIBOBJECTS_X) $(LDLIBS) $(LADD) 
+endif
+
+ifeq ($(NETPBMLIBTYPE),dylib)
+libnetpbm.dylib: libnetpbm.$(MAJ).dylib
+	rm -f $@
+	$(SYMLINK) $< $@
+
+libnetpbm.$(MAJ).dylib: libnetpbm.$(MAJ).$(MIN).dylib
+	rm -f $@
+	$(SYMLINK) $< $@
+
+libnetpbm.$(MAJ).$(MIN).dylib: $(LIBOBJECTS) $(LIBOBJECTS_X) 
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+           -lc $(LADD)
+endif
+
+#--------------------------------------------------------------------------
+# The rule for building a static library is below (if needed).  This is
+# tricky because the user can be building the static library as his main
+# library or in addition to some other kind of main library.  In fact,
+# he may have specified it both as the main library type and an 
+# additional library type.  In that case, NETPBMLIBSUFFIX and 
+# STATICLIBSUFFIX are redundant -- we hope they're the same.
+# 
+# We must not include a rule for static libraries if he doesn't want us
+# to build any.  The library name we come up with might conflict with 
+# the name of the library he actually is building.  In fact, in that case
+# STATICLIB_SUFFIX may just be arbitrary.
+#-----------------------------------------------------------------------------
+ifeq ($(NETPBMLIBTYPE),unixstatic)
+  BUILD_STATICLIB = y
+else
+  ifeq ($(STATICLIB_TOO),y)
+    BUILD_STATICLIB = y
+  else
+    BUILD_STATICLIB = n
+  endif
+endif
+
+ifeq ($(BUILD_STATICLIB),y)
+libnetpbm.$(STATICLIBSUFFIX): $(LIBOBJECTS) $(LIBOBJECTS_X)
+	-rm -f $@
+	ar rc $@ $(LIBOBJECTS) $(LIBOBJECTS_X)
+	-$(RANLIB) $@
+endif
+
+
+# To avoid major hassles with having ppmdcfont available here, we just
+# ship a pre-made standardppmfont.c, so this rule will not normally be
+# used.
+standardppmdfont.c:standard.ppmdfont
+	ppmdcfont <$< >$@ || (rm $@ && false)
+
+# 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
+# every build of "all" from the Netpbm top directory, but nowhere
+# else, so it was really sloppy.
+
+compile.h:
+	$(SRCDIR)/buildtools/stamp-date >$@ || rm $@
+
+$(LIBOBJECTS_X): FORCE
+	@if [ ! -d $(dir $@) ] ; then mkdir $(dir $@) ; fi
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+		SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+
+libpm.o: compile.h
+
+# Install a shared library
+#
+.PHONY: install.lib
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# install a Unix-style shared library
+install.lib: $(PKGDIR)/lib $(PKGDIR)/link
+	cd $(PKGDIR)/lib ; rm -f libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).*
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBD) \
+	  libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN)  $(PKGDIR)/lib/
+	cd $(PKGDIR)/lib/ ; \
+          rm -f libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ); \
+          $(SYMLINK) libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN) $(SONAME)
+endif
+ifeq ($(NETPBMLIBTYPE),dll)
+#install a Windows DLL shared library
+#Note that unlike Unix libraries, a Windows DLL must go into a directory
+#that is in the PATH, so we use bin/ instead of lib/
+install.lib: $(PKGDIR)/bin
+	( cd $(PKGDIR)/bin ; rm -f $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll )
+	$(INSTALL) -c $(STRIPFLAG) -m $(INSTALL_PERM_LIBD) \
+          $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll $(PKGDIR)/bin/
+endif
+ifeq ($(NETPBMLIBTYPE),dylib)
+# install a Darwin-style shared library
+install.lib: $(PKGDIR)/lib
+	cd $(PKGDIR)/lib ; rm -f libnetpbm.*.dylib
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBD) libnetpbm.$(MAJ).$(MIN).dylib \
+	   $(PKGDIR)/lib
+	cd $(PKGDIR)/lib ; \
+          rm -f libnetpbm.$(MAJ).dylib; \
+          $(SYMLINK) libnetpbm.$(MAJ).$(MIN).dylib libnetpbm.$(MAJ).dylib
+endif
+
+.PHONY: install.hdr
+install.hdr: $(INTERFACE_HEADERS:%=%_installhdr)
+# You need to install the interface header files only if you are going to
+# compile programs that use the Netpbm libraries.  Alternatively, you may
+# prefer not to "install" them, but just to access the Netpbm source
+# directory when you compile your programs.
+
+%_installhdr: $(PKGDIR)/include
+	$(INSTALL) -c -m $(INSTALL_PERM_HDR) \
+	  $(SRCDIR)/lib/$(@:%_installhdr=%) $(PKGDIR)/include/;
+
+.PHONY: install.staticlib
+install.staticlib: $(PKGDIR)/link
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBS) libnetpbm.$(STATICLIBSUFFIX) \
+	  $(PKGDIR)/link
+
+# Install a shared library stub -- the ".so" file used at link time to
+# prepare a program for dynamically linking a library at run time 
+.PHONY: install.sharedlibstub
+install.sharedlibstub: $(PKGDIR)/link
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# install the link-time (.so) links to the runtime libraries
+	cd $(PKGDIR)/link ; \
+          rm -f libnetpbm.$(NETPBMLIBSUFFIX); \
+          $(SYMLINK) ../lib/libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ) \
+            libnetpbm.$(NETPBMLIBSUFFIX)
+endif
+ifeq ($(NETPBMLIBTYPE),dll)
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBS) libnetpbm.dll.a $(PKGDIR)/link
+endif
+ifeq ($(NETPBMLIBTYPE),dylib)
+	cd $(PKGDIR)/link/ ; \
+          rm -f libnetpbm.dylib; \
+	$(SYMLINK) ../lib/libnetpbm.$(MAJ).$(MIN).dylib libnetpbm.dylib
+endif
+
+clean: localclean
+
+.PHONY: localclean
+localclean:
+	rm -f compile.h
+
+FORCE:
diff --git a/lib/bitio.c b/lib/bitio.c
new file mode 100644
index 00000000..ca1b55f9
--- /dev/null
+++ b/lib/bitio.c
@@ -0,0 +1,207 @@
+/*\
+ * $Id: bitio.c,v 1.5 1992/11/24 19:36:46 dws Exp dws $
+ *
+ * bitio.c - bitstream I/O
+ *
+ * Works for (sizeof(unsigned long)-1)*8 bits.
+ *
+ * Copyright (C) 1992 by David W. Sanderson.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ *
+ * $Log: bitio.c,v $
+ * Revision 1.5  1992/11/24  19:36:46  dws
+ * Added copyright.
+ *
+ * Revision 1.4  1992/11/17  03:37:50  dws
+ * updated comment
+ *
+ * Revision 1.3  1992/11/10  23:15:16  dws
+ * Removed superfluous code.
+ *
+ * Revision 1.2  1992/11/10  23:11:22  dws
+ * Generalized to handle more than one bitstream at once.
+ *
+ * Revision 1.1  1992/11/10  18:33:21  dws
+ * Initial revision
+ *
+\*/
+
+#include <string.h>
+
+#include "bitio.h"
+
+struct bitstream
+{
+    FILE *
+        f;      /* bytestream */
+    unsigned long
+        bitbuf;     /* bit buffer */
+    int
+        nbitbuf;    /* number of bits in 'bitbuf' */
+    char
+        mode;
+};
+
+#define Mask(n)     ((1<<(n))-1)
+
+#define BitPut(b,ul,n)  ((b)->bitbuf = (((b)->bitbuf<<(n))  \
+                    |((ul)&Mask(n))),   \
+            (b)->nbitbuf += (n))
+
+#define BitGet(b,n) (((b)->bitbuf>>((b)->nbitbuf-=(n))) & Mask(n))
+
+/*
+ * pm_bitinit() - allocate and return a struct bitstream * for the
+ * given FILE*.
+ *
+ * mode must be one of "r" or "w", according to whether you will be
+ * reading from or writing to the struct bitstream *.
+ *
+ * Returns 0 on error.
+ */
+
+struct bitstream *
+pm_bitinit(FILE * const f, const char * const mode) {
+
+    struct bitstream * ans;
+
+    if (!f || !mode)
+        ans = NULL;
+    else if (strcmp(mode, "r") != 0 && strcmp(mode, "w") != 0)
+        ans = NULL;
+    else {
+        ans = (struct bitstream *)calloc(1, sizeof(struct bitstream));
+        if (ans != NULL) {
+            ans->f = f;
+            ans->mode = *mode;
+        }
+    }
+    return ans;
+}
+
+/*
+ * pm_bitfini() - deallocate the given struct bitstream *.
+ *
+ * You must call this after you are done with the struct bitstream *.
+ * 
+ * It may flush some bits left in the buffer.
+ *
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitfini(b)
+    struct bitstream *b;
+{
+    int     nbyte = 0;
+
+    if(!b)
+        return -1;
+
+    /* flush the output */
+    if(b->mode == 'w')
+    {
+        /* flush the bits */
+        if (b->nbitbuf < 0 || b->nbitbuf >= 8)
+        {
+            /* pm_bitwrite() didn't work */
+            return -1;
+        }
+
+        /*
+         * If we get to here, nbitbuf is 0..7
+         */
+        if(b->nbitbuf)
+        {
+            char    c;
+
+            BitPut(b, 0, (long)8-(b->nbitbuf));
+            c = (char) BitGet(b, (long)8);
+            if(putc(c, b->f) == EOF)
+            {
+                return -1;
+            }
+            nbyte++;
+        }
+    }
+
+    free(b);
+    return nbyte;
+}
+
+/*
+ * pm_bitread() - read the next nbits into *val from the given file.
+ * 
+ * The last pm_bitread() must be followed by a call to pm_bitfini().
+ * 
+ * Returns the number of bytes read, -1 on error.
+ */
+
+int 
+pm_bitread(b, nbits, val)
+    struct bitstream *b;
+    unsigned long   nbits;
+    unsigned long  *val;
+{
+    int     nbyte = 0;
+    int     c;
+
+    if(!b)
+        return -1;
+
+    while (b->nbitbuf < nbits)
+    {
+        if((c = getc(b->f)) == EOF)
+        {
+            return -1;
+        }
+        nbyte++;
+
+        BitPut(b, c, (long) 8);
+    }
+
+    *val = BitGet(b, nbits);
+    return nbyte;
+}
+
+/*
+ * pm_bitwrite() - write the low nbits of val to the given file.
+ * 
+ * The last pm_bitwrite() must be followed by a call to pm_bitfini().
+ * 
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitwrite(b, nbits, val)
+    struct bitstream *b;
+    unsigned long   nbits;
+    unsigned long   val;
+{
+    int     nbyte = 0;
+    char        c;
+
+    if(!b)
+        return -1;
+
+    BitPut(b, val, nbits);
+
+    while (b->nbitbuf >= 8)
+    {
+        c = (char) BitGet(b, (long)8);
+
+        if(putc(c, b->f) == EOF)
+        {
+            return -1;
+        }
+        nbyte++;
+    }
+
+    return nbyte;
+}
diff --git a/lib/bitio.h b/lib/bitio.h
new file mode 100644
index 00000000..15fe0e8a
--- /dev/null
+++ b/lib/bitio.h
@@ -0,0 +1,89 @@
+/*\
+ * $Id: bitio.h,v 1.4 1992/11/24 19:37:02 dws Exp dws $
+ *
+ * bitio.h - bitstream I/O
+ *
+ * Works for (sizeof(unsigned long)-1)*8 bits.
+ *
+ * Copyright (C) 1992 by David W. Sanderson.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ *
+ * $Log: bitio.h,v $
+ * Revision 1.4  1992/11/24  19:37:02  dws
+ * Added copyright
+ *
+ * Revision 1.3  1992/11/17  03:37:59  dws
+ * updated comment
+ *
+ * Revision 1.2  1992/11/10  23:10:22  dws
+ * Generalized to handle more than one bitstream at a time.
+ *
+ * Revision 1.1  1992/11/10  18:33:51  dws
+ * Initial revision
+ *
+\*/
+
+#ifndef _BITIO_H_
+#define _BITIO_H_
+
+#include "pm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef struct bitstream	*BITSTREAM;
+
+struct bitstream *
+pm_bitinit(FILE * const f, const char * const mode);
+
+/*
+ * pm_bitfini() - deallocate the given BITSTREAM.
+ *
+ * You must call this after you are done with the BITSTREAM.
+ * 
+ * It may flush some bits left in the buffer.
+ *
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitfini(BITSTREAM b);
+
+/*
+ * pm_bitread() - read the next nbits into *val from the given file.
+ * 
+ * Returns the number of bytes read, -1 on error.
+ */
+
+int
+pm_bitread(BITSTREAM       b,
+           unsigned long   nbits,
+           unsigned long * val);
+
+/*
+ * pm_bitwrite() - write the low nbits of val to the given file.
+ * 
+ * The last pm_bitwrite() must be followed by a call to pm_bitflush().
+ * 
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitwrite(BITSTREAM     b,
+            unsigned long nbits,
+            unsigned long val);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _BITIO_H_ */
diff --git a/lib/colorname.c b/lib/colorname.c
new file mode 100644
index 00000000..dcda3ab8
--- /dev/null
+++ b/lib/colorname.c
@@ -0,0 +1,208 @@
+/* colorname.c - colorname routines, not dependent on Netpbm formats
+**
+** Taken from libppm4.c May 2002.
+
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "pm_c_util.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "nstring.h"
+#include "colorname.h"
+
+static int lineNo;
+
+void 
+pm_canonstr(char * const str) {
+
+    char * p;
+    for (p = str; *p; ) {
+        if (ISSPACE(*p)) {
+            strcpy(p, &(p[1]));
+        } else {
+            if (ISUPPER(*p))
+                *p = tolower(*p);
+            ++p;
+        }
+    }
+}
+
+
+
+FILE *
+pm_openColornameFile(const char * const fileName, const int must_open) {
+/*----------------------------------------------------------------------------
+   Open the colorname dictionary file.  Its file name is 'fileName', unless
+   'fileName' is NULL.  In that case, its file name is the value of the
+   environment variable whose name is RGB_ENV (e.g. "RGBDEF").  Except
+   if that environment variable is not set, it is RGB_DB1, RGB_DB2,
+   or RGB_DB3 (e.g. "/usr/lib/X11/rgb.txt"), whichever exists.
+   
+   'must_open' is a logical: we must get the file open or die.  If
+   'must_open' is true and we can't open the file (e.g. it doesn't
+   exist), exit the program with an error message.  If 'must_open' is
+   false and we can't open the file, just return a null pointer.
+-----------------------------------------------------------------------------*/
+    const char *rgbdef;
+    FILE *f;
+
+    if (fileName == NULL) {
+        if ((rgbdef = getenv(RGBENV))==NULL) {
+            /* The environment variable isn't set, so try the hardcoded
+               default color name dictionary locations.
+            */
+            if ((f = fopen(RGB_DB1, "r")) == NULL &&
+                (f = fopen(RGB_DB2, "r")) == NULL &&
+                (f = fopen(RGB_DB3, "r")) == NULL && must_open) {
+                pm_error("can't open color names dictionary file named "
+                         "%s, %s, or %s "
+                         "and Environment variable %s not set.  Set %s to "
+                         "the pathname of your rgb.txt file or don't use "
+                         "color names.", 
+                         RGB_DB1, RGB_DB2, RGB_DB3, RGBENV, RGBENV);
+            }
+        } else {            
+            /* The environment variable is set */
+            if ((f = fopen(rgbdef, "r")) == NULL && must_open)
+                pm_error("Can't open the color names dictionary file "
+                         "named %s, per the %s environment variable.  "
+                         "errno = %d (%s)",
+                         rgbdef, RGBENV, errno, strerror(errno));
+        }
+    } else {
+        f = fopen(fileName, "r");
+        if (f == NULL && must_open)
+            pm_error("Can't open the color names dictionary file '%s'.  "
+                     "errno = %d (%s)", fileName, errno, strerror(errno));
+        
+    }
+    lineNo = 0;
+    return(f);
+}
+
+
+
+struct colorfile_entry
+pm_colorget(FILE * const f) {
+/*----------------------------------------------------------------------------
+   Get next color entry from the color name dictionary file 'f'.
+
+   If eof or error, return a color entry with NULL for the color name.
+
+   Otherwise, return color name in static storage within.
+-----------------------------------------------------------------------------*/
+    char buf[200];
+    static char colorname[200];
+    bool gotOne;
+    bool eof;
+    struct colorfile_entry retval;
+    char * rc;
+    
+    gotOne = FALSE;  /* initial value */
+    eof = FALSE;
+    while (!gotOne && !eof) {
+        lineNo++;
+        rc = fgets(buf, sizeof(buf), f);
+        if (rc == NULL)
+            eof = TRUE;
+        else {
+            if (buf[0] != '#' && buf[0] != '\n' && buf[0] != '!' &&
+                buf[0] != '\0') {
+                if (sscanf(buf, "%ld %ld %ld %[^\n]", 
+                           &retval.r, &retval.g, &retval.b, colorname) 
+                    == 4 )
+                    gotOne = TRUE;
+                else {
+                    if (buf[strlen(buf)-1] == '\n')
+                        buf[strlen(buf)-1] = '\0';
+                    pm_message("can't parse color names dictionary Line %d:  "
+                               "'%s'", 
+                               lineNo, buf);
+                }
+            }
+        }
+    }
+    if (gotOne)
+        retval.colorname = colorname;
+    else
+        retval.colorname = NULL;
+    return retval;
+}
+
+
+
+void
+pm_parse_dictionary_name(char    const colorname[], 
+                         pixval  const maxval,
+                         int     const closeOk,
+                         pixel * const colorP) {
+
+    FILE* f;
+    bool gotit;
+    bool colorfileExhausted;
+    struct colorfile_entry colorfile_entry;
+    char * canoncolor;
+    pixval r,g,b;
+
+    f = pm_openColornameFile(NULL, TRUE);  /* exits if error */
+    canoncolor = strdup(colorname);
+    pm_canonstr(canoncolor);
+    gotit = FALSE;
+    colorfileExhausted = FALSE;
+    while (!gotit && !colorfileExhausted) {
+        colorfile_entry = pm_colorget(f);
+        if (colorfile_entry.colorname) {
+            pm_canonstr(colorfile_entry.colorname);
+            if (strcmp( canoncolor, colorfile_entry.colorname) == 0)
+                gotit = TRUE;
+        } else
+            colorfileExhausted = TRUE;
+    }
+    fclose(f);
+    
+    if (!gotit)
+        pm_error("unknown color '%s'", colorname);
+    
+    /* Rescale from [0..255] if necessary. */
+    if (maxval != 255) {
+        r = colorfile_entry.r * maxval / 255;
+        g = colorfile_entry.g * maxval / 255;
+        b = colorfile_entry.b * maxval / 255;
+
+        if (!closeOk) {
+            if (r * 255 / maxval != colorfile_entry.r ||
+                g * 255 / maxval != colorfile_entry.g ||
+                b * 255 / maxval != colorfile_entry.b)
+                pm_message("WARNING: color '%s' cannot be represented "
+                           "exactly with a maxval of %u.  "
+                           "Approximating as (%u,%u,%u).  "
+                           "The color dictionary uses maxval 255, so that "
+                           "maxval will always work.",
+                           colorname, maxval, r, g, b);
+        }
+    } else {
+        r = colorfile_entry.r;
+        g = colorfile_entry.g;
+        b = colorfile_entry.b;
+    }
+    free(canoncolor);
+
+    PPM_ASSIGN(*colorP, r, g, b);
+}
+
+
+
diff --git a/lib/colorname.h b/lib/colorname.h
new file mode 100644
index 00000000..d33980e4
--- /dev/null
+++ b/lib/colorname.h
@@ -0,0 +1,43 @@
+#ifndef COLORNAME_H
+#define COLORNAME_H
+
+#include <string.h>
+#include <stdio.h>
+#include "ppm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+enum colornameFormat {PAM_COLORNAME_ENGLISH = 0,
+                      PAM_COLORNAME_HEXOK   = 1};
+
+struct colorfile_entry {
+    long r, g, b;
+    char * colorname;
+};
+
+
+
+void 
+pm_canonstr(char * const str);
+
+FILE *
+pm_openColornameFile(const char * const fileName, const int must_open);
+
+struct colorfile_entry
+pm_colorget(FILE * const f);
+
+void
+pm_parse_dictionary_name(const char       colorname[], 
+                         pixval     const maxval,
+                         int        const closeOk,
+                         pixel *    const colorP);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/compile.h b/lib/compile.h
new file mode 100644
index 00000000..4626f1ea
--- /dev/null
+++ b/lib/compile.h
@@ -0,0 +1,5 @@
+/* This file tells the package when it was compiled */
+/* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY */
+/* Created by the program 'stamp-date'  */
+#define COMPILE_TIME "Thu Aug 17 03:34:22 GMT 2006"
+#define COMPILED_BY "bryanh"
diff --git a/lib/fileio.c b/lib/fileio.c
new file mode 100644
index 00000000..01243f9d
--- /dev/null
+++ b/lib/fileio.c
@@ -0,0 +1,170 @@
+/* fileio.c - routines to read elements from Netpbm image files
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <limits.h>
+
+#include "pm.h"
+#include "fileio.h"
+
+char
+pm_getc(FILE * const file) {
+    int ich;
+    char ch;
+
+    ich = getc(file);
+    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');
+	}
+    return ch;
+}
+
+
+
+/* This is useful for PBM files.  It used to be used for PGM and PPM files
+   too, since the sample size was always one byte.  Now, use pbm_getrawsample()
+   for PGM and PPM files.
+*/
+
+unsigned char
+pm_getrawbyte(FILE * const file) {
+    int iby;
+
+    iby = getc(file);
+    if (iby == EOF)
+        pm_error("EOF / read error reading a one-byte sample");
+    return (unsigned char) iby;
+}
+
+
+
+unsigned int
+pm_getuint(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+   Read an unsigned integer in ASCII decimal from the file stream
+   represented by 'ifP' and return its value.
+
+   If there is nothing at the current position in the file stream that
+   can be interpreted as an unsigned integer, issue an error message
+   to stderr and abort the program.
+
+   If the number at the current position in the file stream is too
+   great to be represented by an 'int' (Yes, I said 'int', not
+   'unsigned int'), issue an error message to stderr and abort the
+   program.
+-----------------------------------------------------------------------------*/
+    char ch;
+    unsigned int i;
+
+    do {
+        ch = pm_getc(ifP);
+	} while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
+
+    if (ch < '0' || ch > '9')
+        pm_error("junk in file where an unsigned integer should be");
+
+    i = 0;
+    do {
+        unsigned int const digitVal = ch - '0';
+
+        if (i > INT_MAX/10 - digitVal)
+            pm_error("ASCII decimal integer in file is "
+                     "too large to be processed.  ");
+        i = i * 10 + digitVal;
+        ch = pm_getc(ifP);
+    } while (ch >= '0' && ch <= '9');
+
+    return i;
+}
+
+
+
+unsigned int
+pm_getraw(FILE *       const file, 
+          unsigned int const bytes) {
+
+    unsigned int value;  /* our return value */
+
+    if (bytes == 1) {
+        /* Here's a speedup for the common 1-byte sample case: */
+        value = getc(file);
+        if (value == EOF)
+            pm_error("EOF/error reading 1 byte sample from file.");
+    } else {
+        /* This code works for bytes == 1..4 */
+        /* We could speed this up by exploiting knowledge of the format of
+           an unsigned integer (i.e. endianness).  Then we could just cast
+           the value as an array of characters instead of shifting and
+           masking.
+           */
+        int shift;
+        unsigned char inval[4];
+        int cursor;
+        int n_read;
+
+        n_read = fread(inval, bytes, 1, file);
+        if (n_read < 1) 
+            pm_error("EOF/error reading %d byte sample from file.", bytes);
+        value = 0;  /* initial value */
+        cursor = 0;
+        for (shift = (bytes-1)*8; shift >= 0; shift-=8) 
+            value += inval[cursor++] << shift;
+    }
+    return(value);
+}
+
+
+
+void
+pm_putraw(FILE *       const file, 
+          unsigned int const value, 
+          unsigned int const bytes) {
+
+    if (bytes == 1) {
+        /* Here's a speedup for the common 1-byte sample case: */
+        int rc;
+        rc = fputc(value, file);
+        if (rc == EOF)
+            pm_error("Error writing 1 byte sample to file.");
+    } else {
+        /* This code works for bytes == 1..4 */
+        /* We could speed this up by exploiting knowledge of the format of
+           an unsigned integer (i.e. endianness).  Then we could just cast
+           the value as an array of characters instead of shifting and
+           masking.
+           */
+        int shift;
+        unsigned char outval[4];
+        int cursor;
+        int n_written;
+
+        cursor = 0;
+        for (shift = (bytes-1)*8; shift >= 0; shift-=8) {
+            outval[cursor++] = (value >> shift) & 0xFF;
+        }
+        n_written = fwrite(&outval, bytes, 1, file);
+        if (n_written == 0) 
+            pm_error("Error writing %d byte sample to file.", bytes);
+    }
+}
+
+
+
diff --git a/lib/fileio.h b/lib/fileio.h
new file mode 100644
index 00000000..158da10a
--- /dev/null
+++ b/lib/fileio.h
@@ -0,0 +1,22 @@
+#ifndef _NETPBM_FILEIO_H_
+#define _NETPBM_FILEIO_H_
+
+char
+pm_getc(FILE * const file);
+
+unsigned char
+pm_getrawbyte(FILE * const file);
+
+unsigned int
+pm_getuint(FILE * const file);
+
+unsigned int
+pm_getraw(FILE *       const file, 
+          unsigned int const bytes);
+
+void
+pm_putraw(FILE *       const file, 
+          unsigned int const value, 
+          unsigned int const bytes);
+
+#endif
diff --git a/lib/libpam.c b/lib/libpam.c
new file mode 100644
index 00000000..bbfa7e32
--- /dev/null
+++ b/lib/libpam.c
@@ -0,0 +1,1098 @@
+/*----------------------------------------------------------------------------
+                                  libpam.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with the PAM (Portable Arbitrary Format) image format.
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "ppm.h"
+#include "libpbm.h"
+#include "libpgm.h"
+#include "libppm.h"
+#include "colorname.h"
+#include "fileio.h"
+
+#include "libpam.h"
+
+
+
+static unsigned int
+allocationDepth(const struct pam * const pamP) {
+
+    unsigned int retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(allocation_depth)) {
+        if (pamP->allocation_depth == 0)
+            retval = pamP->depth;
+        else {
+            if (pamP->depth > pamP->allocation_depth)
+                pm_error("'allocationDepth' (%u) is smaller than 'depth' (%u)",
+                         pamP->allocation_depth, pamP->depth);
+            retval = pamP->allocation_depth;
+        }
+    } else
+        retval = pamP->depth;
+    return retval;
+}
+
+
+
+static const char **
+pamCommentP(const struct pam * const pamP) {
+
+    const char ** retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(comment_p))
+        retval = pamP->comment_p;
+    else
+        retval = NULL;
+
+    return retval;
+}
+
+
+static void
+validateComputableSize(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus arithmetic,
+   so if your values are too big, the result is not what you expect.
+   That failed expectation can be disastrous if you use it to allocate
+   memory.
+
+   It is very normal to allocate space for a tuplerow, so we make sure
+   the size of a tuple row, in bytes, can be represented by an 'int'.
+
+   Another common operation is adding 1 or 2 to the highest row, column,
+   or plane number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = allocationDepth(pamP);
+
+    if (depth > INT_MAX/sizeof(sample))
+        pm_error("image depth (%u) too large to be processed", depth);
+    else if (depth * sizeof(sample) > INT_MAX/pamP->width)
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    else if (pamP->width * (depth * sizeof(sample)) >
+             INT_MAX - depth * sizeof(tuple *))
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    
+    if (depth > INT_MAX - 2)
+        pm_error("image depth (%u) too large to be processed", depth);
+    if (pamP->width > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", pamP->width);
+    if (pamP->height > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", pamP->height);
+}
+
+
+
+tuple
+pnm_allocpamtuple(const struct pam * const pamP) {
+
+    tuple retval;
+
+    retval = malloc(allocationDepth(pamP) * sizeof(retval[0]));
+
+    if (retval == NULL)
+        pm_error("Out of memory allocating %u-plane tuple", 
+                 allocationDepth(pamP));
+
+    return retval;
+}
+
+
+
+int
+pnm_tupleequal(const struct pam * const pamP, 
+               tuple              const comparand, 
+               tuple              const comparator) {
+
+    unsigned int plane;
+    bool equal;
+
+    equal = TRUE;  /* initial value */
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        if (comparand[plane] != comparator[plane])
+            equal = FALSE;
+
+    return equal;
+}
+
+
+
+
+void
+pnm_assigntuple(const struct pam * const pamP,
+                tuple              const dest,
+                tuple              const source) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        dest[plane] = source[plane];
+    }
+}
+
+
+
+static void
+scaleTuple(const struct pam * const pamP,
+           tuple              const dest,
+           tuple              const source, 
+           sample             const newmaxval) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        dest[plane] = pnm_scalesample(source[plane], pamP->maxval, newmaxval);
+}
+
+
+
+void
+pnm_scaletuple(const struct pam * const pamP,
+               tuple              const dest,
+               tuple              const source, 
+               sample             const newmaxval) {
+
+    scaleTuple(pamP, dest, source, newmaxval);
+}
+
+
+
+void
+pnm_createBlackTuple(const struct pam * const pamP, 
+                     tuple *            const blackTupleP) {
+/*----------------------------------------------------------------------------
+   Create a "black" tuple.  By that we mean a tuple all of whose elements
+   are zero.  If it's an RGB, grayscale, or b&w pixel, that means it's black.
+-----------------------------------------------------------------------------*/
+    *blackTupleP = pnm_allocpamtuple(pamP);
+    if (pamP->format == PAM_FORMAT) {
+        /* In this format, we don't know the meaning of "black", so we
+           just punt.
+           */
+        int i;
+        for (i = 0; i < pamP->depth; i++) 
+            (*blackTupleP)[i] = 0;
+    } else {
+        xel black_xel;
+        black_xel = pnm_blackxel(pamP->maxval, pamP->format);
+        (*blackTupleP)[0] = PPM_GETR(black_xel);
+        (*blackTupleP)[1] = PPM_GETG(black_xel);
+        (*blackTupleP)[2] = PPM_GETB(black_xel);
+    }
+}
+
+
+
+void
+createBlackTuple(const struct pam * const pamP, 
+                 tuple *            const blackTupleP) {
+
+/* This is poorly named, because it lacks the "pnm" prefix.  But for some
+   reason, this is how we originally named this.  So to maintain backward
+   compatibility with binaries that refer to "createBlackTuple", we define
+   this.  The preferred name, pnm_createBlackTuple() was new in Netpbm 10.20,
+   January 2004.  We should eventually retire createBlackTuple().
+*/
+    pnm_createBlackTuple(pamP, blackTupleP);
+}
+
+
+
+static tuple *
+allocPamRow(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    /* The tuple row data structure starts with 'width' pointers to
+       the tuples, immediately followed by the 'width' tuples
+       themselves.  Each tuple consists of 'depth' samples.  
+    */
+
+    int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
+    tuple * tuplerow;
+
+    tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple));
+                      
+    if (tuplerow != NULL) {
+        /* Now we initialize the pointers to the individual tuples
+           to make this a regulation C two dimensional array.  
+        */
+        char * p;
+        unsigned int col;
+        
+        p = (char*) (tuplerow + pamP->width);  /* location of Tuple 0 */
+        for (col = 0; col < pamP->width; ++col) {
+                tuplerow[col] = (tuple) p;
+                p += bytesPerTuple;
+        }
+    }
+    return tuplerow;
+}
+
+
+
+tuple *
+pnm_allocpamrow(const struct pam * const pamP) {
+
+
+    tuple * const tuplerow = allocPamRow(pamP);
+
+    if (tuplerow == NULL)
+        pm_error("Out of memory allocating space for a tuple row of\n"
+                 "%d tuples by %d samples per tuple by %d bytes per sample.",
+                 pamP->width, allocationDepth(pamP), sizeof(sample));
+
+    return tuplerow;
+}
+
+
+
+static unsigned int 
+rowimagesize(const struct pam * const pamP) {
+
+    /* If repeatedly calculating this turns out to be a significant
+       performance problem, we could keep this in struct pam like
+       bytes_per_sample.
+    */
+
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        return pbm_packed_bytes(pamP->width);
+    else 
+        return (pamP->width * pamP->bytes_per_sample * pamP->depth);
+}
+
+
+
+unsigned char *
+pnm_allocrowimage(const struct pam * const pamP) {
+
+    unsigned int const rowsize = rowimagesize(pamP);
+    unsigned int const overrunSpaceNeeded = 8;
+        /* This is the number of extra bytes of space libnetpbm needs to have
+           at the end of the buffer so it can use fast, lazy algorithms.
+        */
+    unsigned int const size = rowsize + overrunSpaceNeeded;
+
+    unsigned char * retval;
+
+    retval = malloc(size);
+
+    if (retval == NULL)
+        pm_error("Unable to allocate %u bytes for a row image buffer",
+                 size);
+
+    return retval;
+}
+
+
+
+void
+pnm_freerowimage(unsigned char * const rowimage) {
+    free(rowimage);
+}
+
+
+
+void 
+pnm_scaletuplerow(const struct pam * const pamP,
+                  tuple *            const destRow,
+                  tuple *            const sourceRow,
+                  sample             const newMaxval) {
+
+    if (pamP->maxval == newMaxval) {
+        /* Fast path for common case: no scaling needed */
+        if (destRow != sourceRow) {
+            /* Fast path for common case: it's already what it needs to be */
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                pnm_assigntuple(pamP, destRow[col], sourceRow[col]);
+        }
+    } else {        
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col) 
+            scaleTuple(pamP, destRow[col], sourceRow[col], newMaxval);
+    }
+}
+
+ 
+
+tuple **
+pnm_allocpamarray(const struct pam * const pamP) {
+    
+    tuple **tuplearray;
+
+    /* If the speed of this is ever an issue, it might be sped up a little
+       by allocating one large chunk.
+    */
+    
+    MALLOCARRAY(tuplearray, pamP->height);
+    if (tuplearray == NULL) 
+        pm_error("Out of memory allocating the row pointer section of "
+                 "a %u row array", pamP->height);
+    else {
+        int row;
+        bool outOfMemory;
+
+        outOfMemory = FALSE;
+        for (row = 0; row < pamP->height && !outOfMemory; ++row) {
+            tuplearray[row] = allocPamRow(pamP);
+            if (tuplearray[row] == NULL) {
+                unsigned int freerow;
+                outOfMemory = TRUE;
+                
+                for (freerow = 0; freerow < row; ++freerow)
+                    pnm_freepamrow(tuplearray[row]);
+            }
+        }
+        if (outOfMemory) {
+            free(tuplearray);
+            
+            pm_error("Out of memory allocating the %u rows %u columns wide by "
+                     "%u planes deep", pamP->height, pamP->width,
+                     allocationDepth(pamP));
+        }
+    }
+    return tuplearray;
+}
+
+
+
+void
+pnm_freepamarray(tuple ** const tuplearray, const struct pam * const pamP) {
+
+    int row;
+    for (row = 0; row < pamP->height; row++)
+        pnm_freepamrow(tuplearray[row]);
+
+    free(tuplearray);
+}
+
+
+
+void 
+pnm_setminallocationdepth(struct pam * const pamP,
+                          unsigned int const allocationDepth) {
+
+    if (pamP->len < PAM_STRUCT_SIZE(allocation_depth))
+        pm_error("Can't set minimum allocation depth in pam structure, "
+                 "because the structure is only %u bytes long, and to "
+                 "have an allocation_depth field, it must bea at least %u",
+                 pamP->len, PAM_STRUCT_SIZE(allocation_depth));
+
+    pamP->allocation_depth = MAX(allocationDepth, pamP->depth);
+        
+    validateComputableSize(pamP);
+}
+
+
+
+void
+pnm_setpamrow(const struct pam * const pamP,
+              tuple *            const tuplerow, 
+              sample             const value) {
+
+    int col;
+    for (col = 0; col < pamP->width; ++col) {
+        int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) 
+            tuplerow[col][plane] = value;
+    }
+}
+
+
+
+
+#define MAX_LABEL_LENGTH 8
+#define MAX_VALUE_LENGTH 255
+
+static void
+parse_header_line(const char buffer[], char label[MAX_LABEL_LENGTH+1], 
+                  char value[MAX_VALUE_LENGTH+1]) {
+    
+    int buffer_curs;
+
+    buffer_curs = 0;
+    /* Skip initial white space */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    {
+        /* Read off label, put as much as will fit into label[] */
+        int label_curs;
+        label_curs = 0;
+        while (!ISSPACE(buffer[buffer_curs]) && buffer[buffer_curs] != '\0') {
+            if (label_curs < MAX_LABEL_LENGTH) 
+                label[label_curs++] = buffer[buffer_curs];
+            buffer_curs++;
+        }
+        label[label_curs] = '\0';  /* null terminate it */
+    }    
+
+    /* Skip white space between label and value */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    /* copy value into value[] */
+    strncpy(value, buffer+buffer_curs, MAX_VALUE_LENGTH+1);
+
+    {
+        /* Remove trailing white space from value[] */
+        int value_curs;
+        value_curs = strlen(value)-1;
+        while (value_curs >= 0 && ISSPACE(value[value_curs])) 
+            value[value_curs--] = '\0';
+    }
+}
+
+
+
+struct headerSeen {
+/*----------------------------------------------------------------------------
+   This structure tells what we've seen so far in our progress through the
+   PAM header
+-----------------------------------------------------------------------------*/
+    bool width;
+    bool height;
+    bool depth;
+    bool maxval;
+    bool endhdr;
+};
+
+
+
+static void
+process_header_line(char                const buffer[],
+                    struct pam *        const pamP,
+                    struct headerSeen * const headerSeenP) {
+/*----------------------------------------------------------------------------
+   Process a line from the PAM header.  The line is buffer[], and it is not
+   a comment or blank.
+
+   Put the value that the line defines in *pamP (unless it's ENDHDR).
+
+   Update *headerSeenP with whatever we see.
+-----------------------------------------------------------------------------*/
+    char label[MAX_LABEL_LENGTH+1];
+    char value[MAX_VALUE_LENGTH+1];
+
+    parse_header_line(buffer, label, value);
+
+    if (strcmp(label, "ENDHDR") == 0)
+        headerSeenP->endhdr = TRUE;
+    else {
+        if (strcmp(label, "WIDTH") == 0 ||
+            strcmp(label, "HEIGHT") == 0 ||
+            strcmp(label, "DEPTH") == 0 ||
+            strcmp(label, "MAXVAL") == 0) {
+
+            if (strlen(value) == 0)
+                pm_error("Missing value for %s in PAM file header.",
+                         label);
+            else {
+                char *endptr;
+                long int numeric_value;
+                errno = 0;  /* Clear errno so we can detect strtol() failure */
+                numeric_value = strtol(value, &endptr, 10);
+                if (errno != 0)
+                    pm_error("Too-large value for %s in "
+                             "PAM file header: '%s'", label, value);
+                if (*endptr != '\0') 
+                    pm_error("Non-numeric value for %s in "
+                             "PAM file header: '%s'", label, value);
+                else if (numeric_value < 0) 
+                    pm_error("Negative value for %s in "
+                             "PAM file header: '%s'", label, value);
+            }
+        }
+    
+        if (strcmp(label, "WIDTH") == 0) {
+            pamP->width = atoi(value);
+            headerSeenP->width = TRUE;
+        } else if (strcmp(label, "HEIGHT") == 0) {
+            pamP->height = atoi(value);
+            headerSeenP->height = TRUE;
+        } else if (strcmp(label, "DEPTH") == 0) {
+            pamP->depth = atoi(value);
+            headerSeenP->depth = TRUE;
+        } else if (strcmp(label, "MAXVAL") == 0) {
+            pamP->maxval = atoi(value);
+            headerSeenP->maxval = TRUE;
+        } else if (strcmp(label, "TUPLTYPE") == 0) {
+            int len = strlen(pamP->tuple_type);
+            if (len + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
+                pm_error("TUPLTYPE value too long in PAM header");
+            if (len == 0)
+                strcpy(pamP->tuple_type, value);
+            else {
+                strcat(pamP->tuple_type, "\n");
+                strcat(pamP->tuple_type, value);
+            }
+            pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
+        } else 
+            pm_error("Unrecognized header line: '%s'.  "
+                     "Possible missing ENDHDR line?", label);
+    }
+}
+
+
+
+static void
+appendComment(char **      const commentsP,
+              const char * const commentHeader) {
+
+    const char * const commentLine = &commentHeader[1];
+
+    size_t commentLen = strlen(*commentsP) + strlen(commentLine) + 1;
+
+    assert(commentHeader[0] == '#');
+
+    REALLOCARRAY(*commentsP, commentLen);
+
+    if (*commentsP == NULL)
+        pm_error("Couldn't get storage for %u characters of comments from "
+                 "the PAM header", commentLen);
+
+    strcat(*commentsP, commentLine);
+}
+
+
+
+static void
+disposeOfComments(const struct pam * const pamP,
+                  const char *       const comments) {
+
+    const char ** const retP = pamCommentP(pamP);
+
+    if (retP)
+        *retP = comments;
+    else
+        strfree(comments);
+}
+
+
+
+static void
+readpaminitrest(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line -- the magic
+   number line).  Fill in all the information in *pamP.
+-----------------------------------------------------------------------------*/
+    struct headerSeen headerSeen;
+    char * comments;
+
+    headerSeen.width  = FALSE;
+    headerSeen.height = FALSE;
+    headerSeen.depth  = FALSE;
+    headerSeen.maxval = FALSE;
+    headerSeen.endhdr = FALSE;
+
+    pamP->tuple_type[0] = '\0';
+
+    comments = strdup("");
+
+    { 
+        int c;
+        /* Read off rest of 1st line -- probably just the newline after the 
+           magic number 
+        */
+        while ((c = getc(pamP->file)) != -1 && c != '\n');
+    }    
+
+    while (!headerSeen.endhdr) {
+        char buffer[256];
+        char * rc;
+        rc = fgets(buffer, sizeof(buffer), pamP->file);
+        if (rc == NULL)
+            pm_error("EOF or error reading file while trying to read the "
+                     "PAM header");
+        else {
+            buffer[256-1-1] = '\n';  /* In case fgets() truncated */
+            if (buffer[0] == '#')
+                appendComment(&comments, buffer);
+            else if (stripeq(buffer, ""));
+                /* Ignore it; it's a blank line */
+            else 
+                process_header_line(buffer, pamP, &headerSeen);
+        }
+    }
+
+    disposeOfComments(pamP, comments);
+
+    if (!headerSeen.height)
+        pm_error("No HEIGHT header line in PAM header");
+    if (!headerSeen.width)
+        pm_error("No WIDTH header line in PAM header");
+    if (!headerSeen.depth)
+        pm_error("No DEPTH header line in PAM header");
+    if (!headerSeen.maxval)
+        pm_error("No MAXVAL header line in PAM header");
+
+    if (pamP->height == 0) 
+        pm_error("HEIGHT value is zero in PAM header");
+    if (pamP->width == 0) 
+        pm_error("WIDTH value is zero in PAM header");
+    if (pamP->depth == 0) 
+        pm_error("DEPTH value is zero in PAM header");
+    if (pamP->maxval == 0) 
+        pm_error("MAXVAL value is zero in PAM header");
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        pm_error("MAXVAL value (%lu) in PAM header is greater than %u",
+                 pamP->maxval, PAM_OVERALL_MAXVAL);
+}
+
+
+
+void
+pnm_readpaminitrestaspnm(FILE * const fileP, 
+                         int *  const colsP, 
+                         int *  const rowsP, 
+                         gray * const maxvalP,
+                         int *  const formatP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line) and return
+   information as if it were PPM or PGM.
+
+   Die if it isn't a PAM of the sort we can treat as PPM or PGM.
+-----------------------------------------------------------------------------*/
+    struct pam pam;
+
+    pam.size        = PAM_STRUCT_SIZE(tuple_type);
+    pam.file        = fileP;
+    pam.len         = sizeof(struct pam);
+    pam.format      = PAM_FORMAT;
+
+    readpaminitrest(&pam);
+
+
+    /* A PAM raster of depth 1 is identical to a PGM raster.  A PAM
+       raster of depth 3 is identical to PPM raster.  So
+       ppm_readppmrow() will be able to read the PAM raster as long as
+       the format it thinks it is (PGM or PPM) corresponds to the PAM
+       depth.  Similar for pgm_readpgmrow().
+    */
+    switch (pam.depth) {
+    case 3:
+        *formatP = RPPM_FORMAT;
+        break;
+    case 1:
+        *formatP = RPGM_FORMAT;
+        break;
+    default: {
+        pm_error("Cannot treat PAM image as PPM or PGM, "
+                 "because its depth (%u) "
+                 "is not 1 or 3.", pam.depth);
+    }
+    }
+
+    *colsP   = pam.width;
+    *rowsP   = pam.height;
+    *maxvalP = (gray)pam.maxval;
+}
+
+
+                
+unsigned int
+pnm_bytespersample(sample const maxval) {
+
+    assert(sizeof(maxval) * 8 <= 32);
+
+    if      (maxval >>  8 == 0) return 1;
+    else if (maxval >> 16 == 0) return 2;
+    else if (maxval >> 24 == 0) return 3;
+    else                        return 4;
+}
+
+
+
+void 
+pnm_readpaminit(FILE *       const file, 
+                struct pam * const pamP, 
+                int          const size) {
+
+    if (size < PAM_STRUCT_SIZE(tuple_type)) 
+        pm_error("pam object passed to pnm_readpaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up to the "
+                 "'tuple_type' member, but according\n"
+                 "to the 'size' argument, it is only %d bytes long.", 
+                 size);
+
+    pamP->size = size;
+    pamP->file = file;
+    pamP->len = MIN(pamP->size, sizeof(struct pam));
+
+    if (size >= PAM_STRUCT_SIZE(allocation_depth))
+        pamP->allocation_depth = 0;
+
+    /* Get magic number. */
+    pamP->format = pm_readmagicnumber(file);
+
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: 
+        readpaminitrest(pamP);
+        break;
+    case PPM_TYPE: {
+        pixval maxval;
+        ppm_readppminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 3;
+        strcpy(pamP->tuple_type, PAM_PPM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PGM_TYPE: {
+        gray maxval;
+        pgm_readpgminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PGM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PBM_TYPE:
+        pbm_readpbminitrest(pamP->file, &pamP->width,&pamP->height);
+        pamP->maxval = (sample) 1;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PBM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+        break;
+        
+    default:
+        pm_error("bad magic number - not a PAM, PPM, PGM, or PBM file");
+    }
+    
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    pamP->plainformat = FALSE;
+        /* See below for complex explanation of why this is FALSE. */
+
+    validateComputableSize(pamP);
+}
+
+
+
+static void
+writeComments(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Write comments for a PAM header, insofar as *pamP specifies comments.
+-----------------------------------------------------------------------------*/
+    const char ** const commentP = pamCommentP(pamP);
+
+    if (commentP) {
+        const char * const comment = *commentP;
+
+        const char * p;
+        bool startOfLine;
+
+        for (p = &comment[0], startOfLine = TRUE; *p; ++p) {
+            if (startOfLine)
+                fputc('#', pamP->file);
+                
+            fputc(*p, pamP->file);
+
+            if (*p == '\n')
+                startOfLine = TRUE;
+            else
+                startOfLine = FALSE;
+        }
+        if (!startOfLine)
+            fputc('\n', pamP->file);
+    }
+}
+
+
+
+/* About the 'plainformat' member on image input operations:
+
+   'plainformat' is meaningless when reading an image, but we always
+   set it FALSE anyway.
+
+   That's because it is common for a program to copy a pam structure
+   used for input as a pam structure for output, and just update the
+   few fields it cares about -- mainly 'file'.  We want a program like
+   that to write a raw format image, and 'plainformat' in an output
+   pam structure is what determines whether it is raw or plain.  So we
+   set it false here so that it is false in the copied output pam
+   structure.
+   
+   Before 10.32, we set 'plainformat' according to the
+   plainness of the input image, and thought it was a good
+   idea for a program that reads a plain format input image to
+   write a plain format output image.  But that is not what
+   the older libnetpbm facilities (e.g. pnm_writepnm()) do, so
+   for compatibility, the pam facility shouldn't either.  This
+   came to light as we converted programs from the pnm/pbm/ppm
+   facilities to pam.
+*/
+
+void 
+pnm_writepaminit(struct pam * const pamP) {
+
+    const char * tupleType;
+
+    if (pamP->size < pamP->len)
+        pm_error("pam object passed to pnm_writepaminit() is smaller "
+                 "(%d bytes, according to its 'size' element) "
+                 "than the amount of data in it "
+                 "(%d bytes, according to its 'len' element).",
+                 pamP->size, pamP->len);
+
+    if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample))
+        pm_error("pam object passed to pnm_writepaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up through the "
+                 "'bytes_per_sample' member, but according\n"
+                 "to its 'len' member, it is only %d bytes long.", 
+                 pamP->len);
+
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        pm_error("maxval (%lu) passed to pnm_writepaminit() "
+                 "is greater than %u", pamP->maxval, PAM_OVERALL_MAXVAL);
+
+    if (pamP->len < PAM_STRUCT_SIZE(tuple_type))
+        tupleType = "";
+    else
+        tupleType = pamP->tuple_type;
+
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE:
+        if (pm_plain_output)
+            pm_error("There is no plain version of PAM.  -plain option "
+                     "is not allowed");
+        fprintf(pamP->file, "P7\n");
+        writeComments(pamP);
+        fprintf(pamP->file, "WIDTH %u\n",   (unsigned)pamP->width);
+        fprintf(pamP->file, "HEIGHT %u\n",  (unsigned)pamP->height);
+        fprintf(pamP->file, "DEPTH %u\n",   pamP->depth);
+        fprintf(pamP->file, "MAXVAL %lu\n", pamP->maxval);
+        if (!stripeq(tupleType, ""))
+            fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type);
+        fprintf(pamP->file, "ENDHDR\n");
+        break;
+    case PPM_TYPE:
+        /* The depth must be exact, because pnm_writepamrow() is controlled
+           by it, without regard to format.
+        */
+        if (pamP->depth != 3)
+            pm_error("pnm_writepaminit() got PPM format, but depth = %d "
+                     "instead of 3, as required for PPM.", 
+                     pamP->depth);
+        if (pamP->maxval > PPM_OVERALLMAXVAL) 
+            pm_error("pnm_writepaminit() got PPM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PPM: %d", 
+                     pamP->maxval, PPM_OVERALLMAXVAL);
+        ppm_writeppminit(pamP->file, pamP->width, pamP->height, 
+                         (pixval) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PGM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PGM format, but depth = %d "
+                     "instead of 1, as required for PGM.",
+                     pamP->depth);
+        if (pamP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("pnm_writepaminit() got PGM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PGM: %d", 
+                     pamP->maxval, PGM_OVERALLMAXVAL);
+        pgm_writepgminit(pamP->file, pamP->width, pamP->height, 
+                         (gray) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PBM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PBM format, but depth = %d "
+                     "instead of 1, as required for PBM.",
+                     pamP->depth);
+        if (pamP->maxval != 1) 
+            pm_error("pnm_writepaminit() got PBM format, but maxval = %ld "
+                     "instead of 1, as required for PBM.", pamP->maxval);
+        pbm_writepbminit(pamP->file, pamP->width, pamP->height, 
+                         pamP->plainformat);
+        break;
+
+    default:
+        pm_error("Invalid format passed to pnm_writepaminit(): %d",
+                 pamP->format);
+    }
+}
+
+
+
+void
+pnm_checkpam(const struct pam *   const pamP, 
+             enum pm_check_type   const checkType, 
+             enum pm_check_code * const retvalP) {
+
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP) *retvalP = PM_CHECK_UNKNOWN_TYPE;
+    } else switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: {
+        pm_filepos const need_raster_size = 
+            pamP->width * pamP->height * pamP->depth * pamP->bytes_per_sample;
+        pm_check(pamP->file, checkType, need_raster_size, retvalP);
+    }
+        break;
+    case PPM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PGM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PBM_TYPE:
+        pbm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, retvalP);
+        break;
+    default:
+        if (retvalP) *retvalP = PM_CHECK_UNCHECKABLE;
+    }
+}
+
+
+
+void 
+pnm_maketuplergb(const struct pam * const pamP,
+                 tuple              const tuple) {
+
+    if (allocationDepth(pamP) < 3)
+        pm_error("allocation depth %u passed to pnm_maketuplergb().  "
+                 "Must be at least 3.", allocationDepth(pamP));
+
+    if (pamP->depth < 3)
+        tuple[2] = tuple[1] = tuple[0];
+}
+
+
+
+void 
+pnm_makerowrgb(const struct pam * const pamP,
+               tuple *            const tuplerow) {
+    
+    if (pamP->depth < 3) {
+        unsigned int col;
+
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makerowrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        if (strncmp(pamP->tuple_type, "RGB", 3) != 0) {
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void 
+pnm_makearrayrgb(const struct pam * const pamP,
+                 tuple **           const tuples) {
+
+    if (pamP->depth < 3) {
+        unsigned int row;
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makearrayrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        for (row = 0; row < pamP->height; ++row) {
+            tuple * const tuplerow = tuples[row];
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void
+pnm_getopacity(const struct pam * const pamP,
+               bool *             const haveOpacityP,
+               unsigned int *     const opacityPlaneP) {
+
+    /* Design note; If use of this information proliferates, we should
+       probably add it to struct pam as convenience values analogous to
+       bytes_per_sample.
+    */
+    if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_TRN_PLANE;
+    } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_GRAY_TRN_PLANE;
+    } else
+        *haveOpacityP = FALSE;
+}
+
+
+
+/*=============================================================================
+   pm_system() Standard Input feeder and Standard Output accepter functions.   
+=============================================================================*/
+
+void
+pm_feed_from_pamtuples(int    const pipeToFeedFd,
+                       void * const feederParm) {
+
+    struct pamtuples * const inputTuplesP = feederParm;
+
+    struct pam outpam;
+
+    outpam = *inputTuplesP->pamP;
+    outpam.file = fdopen(pipeToFeedFd, "w");
+
+    /* The following signals (and normally kills) the process with
+       SIGPIPE if the pipe does not take all 'inputLength' bytes.
+    */
+    pnm_writepam(&outpam, *inputTuplesP->tuplesP);
+
+    pm_close(outpam.file);
+}
+
+
+
+void
+pm_accept_to_pamtuples(int    const pipeToSuckFd,
+                       void * const accepterParm ) {
+
+    struct pamtuples * const outputTuplesP = accepterParm;
+
+    struct pam inpam;
+
+    *outputTuplesP->tuplesP =
+        pnm_readpam(fdopen(pipeToSuckFd, "r"), &inpam, sizeof(inpam));
+
+    pm_close(inpam.file);
+}
diff --git a/lib/libpam.h b/lib/libpam.h
new file mode 100644
index 00000000..4b8e6693
--- /dev/null
+++ b/lib/libpam.h
@@ -0,0 +1,17 @@
+/* This is the intra-libnetpbm interface header file for libpam.c
+*/
+
+#ifndef LIBPAM_H_INCLUDED
+#define LIBPAM_H_INCLUDED
+
+#include "pam.h"
+
+void
+pnm_readpaminitrestaspnm(FILE * const fileP, 
+                         int *  const colsP, 
+                         int *  const rowsP, 
+                         gray * const maxvalP,
+                         int *  const formatP);
+
+#endif
+
diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c
new file mode 100644
index 00000000..e11415ca
--- /dev/null
+++ b/lib/libpamcolor.c
@@ -0,0 +1,102 @@
+/*----------------------------------------------------------------------------
+                                  libpamcolor.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with colors in the PAM image format.
+-----------------------------------------------------------------------------*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <limits.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+#include "ppm.h"
+
+
+tuple
+pnm_parsecolor(const char * const colorname,
+               sample       const maxval) {
+
+    tuple retval;
+    pixel color;
+    struct pam pam;
+
+    pam.len = PAM_STRUCT_SIZE(bytes_per_sample);
+    pam.depth = 3;
+    pam.maxval = maxval;
+    pam.bytes_per_sample = pnm_bytespersample(maxval);
+
+    retval = pnm_allocpamtuple(&pam);
+
+    color = ppm_parsecolor(colorname, maxval);
+
+    retval[PAM_RED_PLANE] = PPM_GETR(color);
+    retval[PAM_GRN_PLANE] = PPM_GETG(color);
+    retval[PAM_BLU_PLANE] = PPM_GETB(color);
+
+    return retval;
+}
+
+
+
+double pnm_lumin_factor[3] = {PPM_LUMINR, PPM_LUMING, PPM_LUMINB};
+
+void
+pnm_YCbCrtuple(tuple    const tuple, 
+               double * const YP, 
+               double * const CbP, 
+               double * const CrP) {
+/*----------------------------------------------------------------------------
+   Assuming that the tuple 'tuple' is of tupletype RGB, return the 
+   Y/Cb/Cr representation of the color represented by the tuple.
+-----------------------------------------------------------------------------*/
+    int const red = (int)tuple[PAM_RED_PLANE];
+    int const grn = (int)tuple[PAM_GRN_PLANE];
+    int const blu = (int)tuple[PAM_BLU_PLANE];
+    
+    *YP  = (+ PPM_LUMINR * red + PPM_LUMING * grn + PPM_LUMINB * blu);
+    *CbP = (- 0.16874 * red - 0.33126 * grn + 0.50000 * blu);
+    *CrP = (+ 0.50000 * red - 0.41869 * grn - 0.08131 * blu);
+}
+
+
+
+void 
+pnm_YCbCr_to_rgbtuple(const struct pam * const pamP,
+                      tuple              const tuple,
+                      double             const Y,
+                      double             const Cb, 
+                      double             const Cr,
+                      int *              const overflowP) {
+
+    double rgb[3];
+    unsigned int plane;
+    bool overflow;
+
+    rgb[PAM_RED_PLANE] = Y + 1.4022 * Cr + .5;
+    rgb[PAM_GRN_PLANE] = Y - 0.7145 * Cr - 0.3456 * Cb + .5;
+    rgb[PAM_BLU_PLANE] = Y + 1.7710 * Cb + .5;
+
+    overflow = FALSE;  /* initial assumption */
+
+    for (plane = 0; plane < 3; ++plane) {
+        if (rgb[plane] > pamP->maxval) {
+            overflow = TRUE;
+            tuple[plane] = pamP->maxval;
+        } else if (rgb[plane] < 0.0) {
+            overflow = TRUE;
+            tuple[plane] = 0;
+        } else 
+            tuple[plane] = (sample)rgb[plane];
+    }
+    if (overflowP)
+        *overflowP = overflow;
+}
+
+
+
diff --git a/lib/libpammap.c b/lib/libpammap.c
new file mode 100644
index 00000000..9e90ade0
--- /dev/null
+++ b/lib/libpammap.c
@@ -0,0 +1,662 @@
+/******************************************************************************
+                                  libpammap.c
+*******************************************************************************
+
+  These are functions that deal with tuple hashes and tuple tables.
+
+  Both tuple hashes and tuple tables let you associate an arbitrary
+  integer with a tuple value.  A tuple hash lets you look up the one
+  integer value (if any) associated with a given tuple value, having
+  the low memory and execution time characteristics of a hash table.
+  A tuple table lets you scan all the values, being a table of elements
+  that consist of an ordered pair of a tuple value and integer.
+
+******************************************************************************/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pam.h"
+#include "pammap.h"
+
+
+#define HASH_SIZE 20023
+
+unsigned int
+pnm_hashtuple(struct pam * const pamP, tuple const tuple) {
+/*----------------------------------------------------------------------------
+   Return the hash value of the tuple 'tuple' -- i.e. an index into a hash
+   table.
+-----------------------------------------------------------------------------*/
+    int i;
+    unsigned int hash;
+    const unsigned int hash_factor[] = {33023, 30013, 27011};
+
+    hash = 0;  /* initial value */
+    for (i = 0; i < MIN(pamP->depth, 3); ++i) {
+        hash += tuple[i] * hash_factor[i];  /* May overflow */
+    }
+    hash %= HASH_SIZE;
+    return hash;
+}
+
+
+
+
+tuplehash
+pnm_createtuplehash(void) {
+/*----------------------------------------------------------------------------
+   Create an empty tuple hash -- i.e. a hash table of zero length hash chains.
+-----------------------------------------------------------------------------*/
+    tuplehash retval;
+    unsigned int i;
+
+    MALLOCARRAY(retval, HASH_SIZE);
+
+    if (retval == NULL)
+        pm_error("Out of memory allocating tuple hash of size %u",
+                 HASH_SIZE);
+
+    for (i = 0; i < HASH_SIZE; ++i) 
+        retval[i] = NULL;
+
+    return retval;
+}
+
+
+
+void
+pnm_destroytuplehash(tuplehash const tuplehash) {
+
+    int i;
+
+    /* Free the chains */
+
+    for (i = 0; i < HASH_SIZE; ++i) {
+        struct tupleint_list_item * p;
+        struct tupleint_list_item * next;
+        
+        /* Walk this chain, freeing each element */
+        for (p = tuplehash[i]; p; p = next) {
+            next = p->next;
+
+            free(p);
+        }            
+    }
+
+    /* Free the table of chains */
+    free(tuplehash);
+}
+
+
+
+
+static struct tupleint_list_item * 
+allocTupleIntListItem(struct pam * const pamP) {
+
+
+    /* This is complicated by the fact that the last element of a 
+       tupleint_list_item is of variable length, because the last element
+       of _it_ is of variable length 
+    */
+    struct tupleint_list_item * retval;
+
+    unsigned int const size = 
+        sizeof(*retval) - sizeof(retval->tupleint.tuple) 
+        + pamP->depth * sizeof(sample);
+
+    retval = (struct tupleint_list_item *) malloc(size);
+
+    return retval;
+}
+
+
+
+void
+pnm_addtotuplehash(struct pam *   const pamP,
+                   tuplehash      const tuplehash, 
+                   tuple          const tupletoadd,
+                   int            const value,
+                   int *          const fitsP) {
+/*----------------------------------------------------------------------------
+   Add a tuple value to the hash -- assume it isn't already there.
+
+   Allocate new space for the tuple value and the hash chain element.
+
+   If we can't allocate space for the new hash chain element, don't
+   change anything and return *fitsP = FALSE;
+-----------------------------------------------------------------------------*/
+    struct tupleint_list_item * const listItemP = allocTupleIntListItem(pamP);
+    if (listItemP == NULL)
+        *fitsP = FALSE;
+    else {
+        unsigned int const hashvalue = pnm_hashtuple(pamP, tupletoadd);
+    
+        *fitsP = TRUE;
+
+        pnm_assigntuple(pamP, listItemP->tupleint.tuple, tupletoadd);
+        listItemP->tupleint.value = value;
+        listItemP->next = tuplehash[hashvalue];
+        tuplehash[hashvalue] = listItemP;
+    }
+}
+
+
+
+void
+pnm_lookuptuple(struct pam *    const pamP, 
+                const tuplehash       tuplehash, 
+                const tuple           searchval, 
+                int *           const foundP, 
+                int *           const retvalP) {
+    
+    unsigned int const hashvalue = pnm_hashtuple(pamP, searchval);
+    struct tupleint_list_item * p;
+    struct tupleint_list_item * found;
+
+    found = NULL;  /* None found yet */
+    for (p = tuplehash[hashvalue]; p && !found; p = p->next)
+        if (pnm_tupleequal(pamP, p->tupleint.tuple, searchval)) {
+            found = p;
+        }
+
+    if (found) {
+        *foundP = TRUE;
+        *retvalP = found->tupleint.value;
+    } else
+        *foundP = FALSE;
+}
+
+
+
+static void
+addColorOccurrenceToHash(tuple          const color, 
+                         tuplehash      const tuplefreqhash,
+                         struct pam *   const pamP,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP,
+                         bool *         const fullP) {
+               
+    unsigned int const hashvalue = pnm_hashtuple(pamP, color);
+            
+    struct tupleint_list_item *p;
+
+    for (p = tuplefreqhash[hashvalue]; 
+         p && !pnm_tupleequal(pamP, p->tupleint.tuple, color);
+         p = p->next);
+
+    if (p) {
+        /* It's in the hash; just tally one more occurence */
+        ++p->tupleint.value;
+        *fullP = FALSE;
+    } else {
+        /* It's not in the hash yet, so add it (if allowed) */
+        ++(*sizeP);
+        if (maxsize > 0 && *sizeP > maxsize) 
+            *fullP = TRUE;
+        else {
+            *fullP = FALSE;
+            p = allocTupleIntListItem(pamP);
+            if (p == NULL)
+                pm_error("out of memory computing hash table");
+            pnm_assigntuple(pamP, p->tupleint.tuple, color);
+            p->tupleint.value = 1;
+            p->next = tuplefreqhash[hashvalue];
+            tuplefreqhash[hashvalue] = p;
+        }
+    }
+}
+
+
+
+void
+pnm_addtuplefreqoccurrence(struct pam *   const pamP,
+                           tuple          const value,
+                           tuplehash      const tuplefreqhash,
+                           int *          const firstOccurrenceP) {
+
+    unsigned int const hashvalue = pnm_hashtuple(pamP, value);
+            
+    struct tupleint_list_item * p;
+
+    for (p = tuplefreqhash[hashvalue]; 
+         p && !pnm_tupleequal(pamP, p->tupleint.tuple, value);
+         p = p->next);
+
+    if (p) {
+        /* It's in the hash; just tally one more occurence */
+        ++p->tupleint.value;
+        *firstOccurrenceP = FALSE;
+    } else {
+        struct tupleint_list_item * p;
+
+        /* It's not in the hash yet, so add it */
+        *firstOccurrenceP = TRUE;
+
+        p = allocTupleIntListItem(pamP);
+        if (p == NULL)
+            pm_error("out of memory computing hash table");
+
+        pnm_assigntuple(pamP, p->tupleint.tuple, value);
+        p->tupleint.value = 1;
+        p->next = tuplefreqhash[hashvalue];
+        tuplefreqhash[hashvalue] = p;
+    }
+}
+
+
+
+static void
+computehashrecoverable(struct pam *   const pamP,
+                       tuple **       const tupleArray, 
+                       unsigned int   const maxsize, 
+                       sample         const newMaxval,
+                       unsigned int * const sizeP,
+                       tuplehash *    const tuplefreqhashP,
+                       tuple **       const rowbufferP,
+                       tuple *        const colorP) {
+/*----------------------------------------------------------------------------
+   This is computetuplefreqhash(), only it leaves a trail so that if it
+   happens to longjmp out because of a failed memory allocation, the
+   setjmp'er can cleanup whatever it had done so far.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    struct pam freqPam;
+    bool full;
+
+    freqPam = *pamP;
+    freqPam.maxval = newMaxval;
+
+    *tuplefreqhashP = pnm_createtuplehash();
+    *sizeP = 0;   /* initial value */
+    
+    *rowbufferP = pnm_allocpamrow(pamP);
+    
+    *colorP = pnm_allocpamtuple(&freqPam);
+    
+    full = FALSE;  /* initial value */
+    
+    /* Go through the entire raster, building a hash table of
+       tuple values. 
+    */
+    for (row = 0; row < pamP->height && !full; ++row) {
+        int col;
+        const tuple * tuplerow;  /* The row of tuples we are processing */
+        
+        if (tupleArray)
+            tuplerow = tupleArray[row];
+        else {
+            pnm_readpamrow(pamP, *rowbufferP);
+            tuplerow = *rowbufferP;
+        }
+        for (col = 0; col < pamP->width && !full; ++col) {
+            pnm_scaletuple(pamP, *colorP, tuplerow[col], freqPam.maxval);
+            addColorOccurrenceToHash(
+                *colorP, *tuplefreqhashP, &freqPam, maxsize, sizeP, &full);
+        }
+    }
+
+    pnm_freepamtuple(*colorP); *colorP = NULL;
+    pnm_freepamrow(*rowbufferP); *rowbufferP = NULL;
+
+    if (full) {
+        pnm_destroytuplehash(*tuplefreqhashP);
+        *tuplefreqhashP = NULL;
+    }
+}
+
+
+
+static tuplehash
+computetuplefreqhash(struct pam *   const pamP,
+                     tuple **       const tupleArray, 
+                     unsigned int   const maxsize, 
+                     sample         const newMaxval,
+                     unsigned int * const sizeP) {
+/*----------------------------------------------------------------------------
+  Compute a tuple frequency hash from a PAM.  This is a hash that gives
+  you the number of times a given tuple value occurs in the PAM.  You can
+  supply the input PAM in one of two ways:
+
+  1) a two-dimensional array of tuples tupleArray[][];  In this case,
+     'tupleArray' is non-NULL.
+
+  2) an open PAM file, positioned to the raster.  In this case,
+     'tupleArray' is NULL.  *pamP contains the file descriptor.
+  
+     We return with the file still open and its position undefined.  
+
+  In either case, *pamP contains parameters of the tuple array.
+
+  Return the number of unique tuple values found as *sizeP.
+
+  However, if the number of unique tuple values is greater than 'maxsize', 
+  return a null return value and *sizeP undefined.
+
+  The tuple values that index the hash are scaled to a new maxval of
+  'newMaxval'.  E.g.  if the input has maxval 100 and 'newMaxval' is
+  50, and a particular tuple has sample value 50, it would be counted
+  as sample value 25 in the hash.
+-----------------------------------------------------------------------------*/
+    tuplehash tuplefreqhash;
+    tuple * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+    tuple color;  
+        /* The color currently being added, scaled to the new maxval */
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    
+    /* Initialize to "none" for purposes of error recovery */
+    tuplefreqhash = NULL;
+    rowbuffer = NULL;
+    color = NULL;
+
+    if (setjmp(jmpbuf) == 0) {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
+                               &tuplefreqhash, &rowbuffer, &color);
+        pm_setjmpbuf(origJmpbufP);
+    } else {
+        if (color) 
+            pnm_freepamtuple(color);
+        if (rowbuffer)
+            pnm_freepamrow(rowbuffer);
+        if (tuplefreqhash)
+            pnm_destroytuplehash(tuplefreqhash);
+        pm_longjmp();
+    }
+    return tuplefreqhash;
+}
+
+
+
+tuplehash
+pnm_computetuplefreqhash(struct pam *   const pamP,
+                         tuple **       const tupleArray,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP) {
+/*----------------------------------------------------------------------------
+   Compute the tuple frequency hash for the tuple array tupleArray[][].
+-----------------------------------------------------------------------------*/
+    return computetuplefreqhash(pamP, tupleArray, maxsize, pamP->maxval, 
+                                sizeP);
+}
+
+
+
+tupletable
+pnm_alloctupletable(const struct pam * const pamP, 
+                    unsigned int       const size) {
+    
+    tupletable retval;
+
+    if (UINT_MAX / sizeof(struct tupleint) < size)
+        pm_error("size %u is too big for arithmetic", size);
+    else {
+        unsigned int const mainTableSize = size * sizeof(struct tupleint *);
+        unsigned int const tupleIntSize = 
+            sizeof(struct tupleint) - sizeof(sample) 
+            + pamP->depth * sizeof(sample);
+
+        /* To save the enormous amount of time it could take to allocate
+           each individual tuple, we do a trick here and allocate everything
+           as a single malloc block and suballocate internally.
+        */
+        if ((UINT_MAX - mainTableSize) / tupleIntSize < size)
+            pm_error("size %u is too big for arithmetic", size);
+        else {
+            void * pool;
+            unsigned int i;
+    
+            pool = malloc(mainTableSize + size * tupleIntSize);
+    
+            retval = (tupletable) pool;
+
+            for (i = 0; i < size; ++i)
+                retval[i] = (struct tupleint *)
+                    ((char*)pool + mainTableSize + i * tupleIntSize);
+        }
+    }
+    return retval;
+}
+
+
+
+void
+pnm_freetupletable(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 
+       tupletable, but all the samples to which it points (e.g.
+       tupletable[0].tuple[0])
+    */
+
+    free(tupletable);
+}
+
+
+
+void
+pnm_freetupletable2(struct pam * const pamP,
+                    tupletable2  const tupletable) {
+
+    pnm_freetupletable(pamP, tupletable.table);
+}
+
+
+
+static tupletable
+tuplehashtotable(const struct pam * const pamP,
+                 tuplehash          const tuplehash,
+                 unsigned int       const allocsize) {
+/*----------------------------------------------------------------------------
+   Create a tuple table containing the info from a tuple hash.  Allocate
+   space in the table for 'allocsize' elements even if there aren't that
+   many tuple values in the input hash.  That's so the caller has room
+   for expansion.
+
+   Caller must ensure that 'allocsize' is at least as many tuple values
+   as there are in the input hash.
+
+   We allocate new space for all the table contents; there are no pointers
+   in the table to tuples or anything else in existing space.
+-----------------------------------------------------------------------------*/
+    tupletable tupletable;
+
+    tupletable = pnm_alloctupletable(pamP, allocsize);
+
+    if (tupletable != NULL) {
+        unsigned int i, j;
+        /* Loop through the hash table. */
+        j = 0;
+        for (i = 0; i < HASH_SIZE; ++i) {
+            /* Walk this hash chain */
+            struct tupleint_list_item * p;
+            for (p = tuplehash[i]; p; p = p->next) {
+                assert(j < allocsize);
+                tupletable[j]->value = p->tupleint.value;
+                pnm_assigntuple(pamP, tupletable[j]->tuple, p->tupleint.tuple);
+                ++j;
+            }
+        }
+    }
+    return tupletable;
+}
+
+
+
+tupletable
+pnm_tuplehashtotable(const struct pam * const pamP,
+                     tuplehash          const tuplehash,
+                     unsigned int       const allocsize) {
+
+    tupletable tupletable;
+
+    tupletable = tuplehashtotable(pamP, tuplehash, allocsize);
+
+    if (tupletable == NULL)
+        pm_error("out of memory generating tuple table");
+
+    return tupletable;
+}
+
+
+
+tuplehash
+pnm_computetupletablehash(struct pam * const pamP, 
+                          tupletable   const tupletable,
+                          unsigned int const tupletableSize) {
+/*----------------------------------------------------------------------------
+   Create a tuple hash containing indices into the tuple table
+   'tupletable'.  The hash index for the hash is the value of a tuple;
+   the hash value is the tuple table index for the element in the
+   tuple table that contains that tuple value.
+
+   Assume there are no duplicate tuple values in the tuple table.
+
+   We allocate space for the main hash table and all the elements of the
+   hash chains.
+-----------------------------------------------------------------------------*/
+    tuplehash tupletablehash;
+    unsigned int i;
+    bool fits;
+    
+    tupletablehash = pnm_createtuplehash();
+
+    fits = TRUE;  /* initial assumption */
+    for (i = 0; i < tupletableSize && fits; ++i) {
+        pnm_addtotuplehash(pamP, tupletablehash, 
+                           tupletable[i]->tuple, i, &fits);
+    }
+    if (!fits) {
+        pnm_destroytuplehash(tupletablehash);
+        pm_error("Out of memory computing tuple hash from tuple table");
+    }
+    return tupletablehash;
+}
+
+
+
+tupletable
+pnm_computetuplefreqtable2(struct pam *   const pamP,
+                           tuple **       const tupleArray,
+                           unsigned int   const maxsize,
+                           sample         const newMaxval,
+                           unsigned int * const countP) {
+/*----------------------------------------------------------------------------
+   Compute a tuple frequency table from a PAM image.  This is an
+   array that tells how many times each tuple value occurs in the
+   image.
+
+   Except for the format of the output, this function is the same as
+   computetuplefreqhash().
+
+   If there are more than 'maxsize' unique tuple values in tupleArray[][],
+   give up.
+
+   Return the array in newly malloc'ed storage.  Allocate space for
+   'maxsize' entries even if there aren't that many distinct tuple
+   values in tupleArray[].  That's so the caller has room for
+   expansion.
+
+   If 'maxsize' is zero, allocate exactly as much space as there are
+   distinct tuple values in tupleArray[], and don't give up no matter
+   how many tuple values we find (except, of course, we abort if we
+   can't get enough memory).
+
+   Return the number of unique tuple values in tupleArray[][] as
+   *countP.
+
+
+   Scale the tuple values to a new maxval of 'newMaxval' before
+   processing them.  E.g. if the input has maxval 100 and 'newMaxval'
+   is 50, and a particular tuple has sample value 50, it would be
+   listed as sample value 25 in the output table.  This makes the
+   output table smaller and the processing time less.
+-----------------------------------------------------------------------------*/
+    tuplehash tuplefreqhash;
+    tupletable tuplefreqtable;
+    unsigned int uniqueCount;
+
+    tuplefreqhash = computetuplefreqhash(pamP, tupleArray, maxsize, 
+                                         newMaxval, &uniqueCount);
+    if (tuplefreqhash == NULL)
+        tuplefreqtable = NULL;
+    else {
+        unsigned int tableSize = (maxsize == 0 ? uniqueCount : maxsize);
+        assert(tableSize >= uniqueCount);
+        tuplefreqtable = tuplehashtotable(pamP, tuplefreqhash, tableSize);
+        pnm_destroytuplehash(tuplefreqhash);
+        if (tuplefreqtable == NULL)
+            pm_error("Out of memory generating tuple table");
+    }
+    *countP = uniqueCount;
+
+    return tuplefreqtable;
+}
+
+
+
+tupletable
+pnm_computetuplefreqtable(struct pam *   const pamP,
+                          tuple **       const tupleArray,
+                          unsigned int   const maxsize,
+                          unsigned int * const sizeP) {
+
+    return pnm_computetuplefreqtable2(pamP, tupleArray, maxsize, pamP->maxval,
+                                      sizeP);
+}
+
+
+
+char*
+pam_colorname(struct pam *         const pamP, 
+              tuple                const color, 
+              enum colornameFormat const format) {
+
+    unsigned int r, g, b;
+    FILE* f;
+    static char colorname[200];
+
+    r = pnm_scalesample(color[PAM_RED_PLANE], pamP->maxval, 255);
+    g = pnm_scalesample(color[PAM_GRN_PLANE], pamP->maxval, 255);
+    b = pnm_scalesample(color[PAM_BLU_PLANE], pamP->maxval, 255);
+
+    f = pm_openColornameFile(NULL, format == PAM_COLORNAME_ENGLISH);
+    if (f != NULL) {
+        unsigned int best_diff;
+        bool done;
+
+        best_diff = 32767;
+        done = FALSE;
+        while (!done) {
+            struct colorfile_entry const ce = pm_colorget(f);
+            if (ce.colorname) {
+                unsigned int const this_diff = 
+                    abs((int)r - (int)ce.r) + 
+                    abs((int)g - (int)ce.g) + 
+                    abs((int)b - (int)ce.b);
+
+                if (this_diff < best_diff) {
+                    best_diff = this_diff;
+                    strcpy(colorname, ce.colorname);
+                }
+            } else
+                done = TRUE;
+        }
+        fclose(f);
+        if (best_diff != 32767 && 
+            (best_diff == 0 || format == PAM_COLORNAME_ENGLISH))
+            return colorname;
+    }
+
+    /* Color lookup failed, but caller is willing to take an X11-style
+       hex specifier, so return that.
+    */
+    sprintf(colorname, "#%02x%02x%02x", r, g, b);
+    return colorname;
+}
diff --git a/lib/libpamn.c b/lib/libpamn.c
new file mode 100644
index 00000000..c7610100
--- /dev/null
+++ b/lib/libpamn.c
@@ -0,0 +1,542 @@
+/*----------------------------------------------------------------------------
+                                  libpamn.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with the PAM image format via maxval-normalized, floating point
+   sample values.
+-----------------------------------------------------------------------------*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pam.h"
+#include "fileio.h"
+#include "pm_gamma.h"
+
+#define EPSILON 1e-7
+
+
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    const int bytes_per_tuple = pamP->depth * sizeof(samplen);
+    tuplen * tuplerown;
+
+    /* The tuple row data structure starts with 'width' pointers to
+       the tuples, immediately followed by the 'width' tuples
+       themselves.  Each tuple consists of 'depth' samples.
+    */
+
+    tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
+    if (tuplerown == NULL)
+        pm_error("Out of memory allocating space for a tuple row of\n"
+                 "%d tuples by %d samples per tuple by %d bytes per sample.",
+                 pamP->width, pamP->depth, sizeof(samplen));
+
+    {
+        /* Now we initialize the pointers to the individual tuples to make this
+           a regulation C two dimensional array.
+        */
+        
+        char *p;
+        int i;
+        
+        p = (char*) (tuplerown + pamP->width);  /* location of Tuple 0 */
+        for (i = 0; i < pamP->width; i++) {
+            tuplerown[i] = (tuplen) p;
+            p += bytes_per_tuple;
+        }
+    }
+    return(tuplerown);
+}
+
+
+
+void 
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
+        int col;
+        bit *bitrow;
+        if (pamP->depth != 1)
+            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                     "It says PBM format, but 'depth' member is not 1.");
+        bitrow = pbm_allocrow(pamP->width);
+        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
+        for (col = 0; col < pamP->width; col++)
+            tuplenrow[col][0] = 
+                bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+        pbm_freerow(bitrow);
+    } else {
+        float const scaler = 1.0 / pamP->maxval;
+            /* Note: multiplication is faster than division, so we divide
+               once here so we can multiply many times later.
+            */
+        int col;
+        tuple * tuplerow;
+
+        tuplerow = pnm_allocpamrow(pamP);
+
+        pnm_readpamrow(pamP, tuplerow);
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane)
+                tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
+        }
+        pnm_freepamrow(tuplerow);
+    }
+}
+
+
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
+        int col;
+        bit *bitrow;
+        bitrow = pbm_allocrow(pamP->width);
+        for (col = 0; col < pamP->width; col++)
+            bitrow[col] = 
+                tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
+        pbm_writepbmrow(pamP->file, bitrow, pamP->width, 
+                        pamP->format == PBM_FORMAT);
+        pbm_freerow(bitrow);
+    } else {
+        tuple * tuplerow;
+        int col;
+
+        tuplerow = pnm_allocpamrow(pamP);
+
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane)
+                tuplerow[col][plane] = (sample)
+                    (tuplenrow[col][plane] * pamP->maxval + 0.5);
+        }    
+        pnm_writepamrow(pamP, tuplerow);
+        pnm_freepamrow(tuplerow);
+    }
+}
+
+
+
+tuplen **
+pnm_allocpamarrayn(const struct pam * const pamP) {
+    
+    tuplen **tuplenarray;
+    int row;
+
+    /* If the speed of this is ever an issue, it might be sped up a little
+       by allocating one large chunk.
+    */
+    
+    MALLOCARRAY(tuplenarray, pamP->height);
+    if (tuplenarray == NULL) 
+        pm_error("Out of memory allocating the row pointer section of "
+                 "a %u row array", pamP->height);
+
+    for (row = 0; row < pamP->height; row++) {
+        tuplenarray[row] = pnm_allocpamrown(pamP);
+    }
+    return(tuplenarray);
+}
+
+
+
+void
+pnm_freepamarrayn(tuplen **          const tuplenarray, 
+                  const struct pam * const pamP) {
+
+    int row;
+    for (row = 0; row < pamP->height; row++)
+        pnm_freepamrown(tuplenarray[row]);
+
+    free(tuplenarray);
+}
+
+
+
+tuplen** 
+pnm_readpamn(FILE *       const file, 
+             struct pam * const pamP, 
+             int          const size) {
+
+    tuplen **tuplenarray;
+    int row;
+
+    pnm_readpaminit(file, pamP, size);
+    
+    tuplenarray = pnm_allocpamarrayn(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_readpamrown(pamP, tuplenarray[row]);
+
+    return(tuplenarray);
+}
+
+
+
+void 
+pnm_writepamn(struct pam * const pamP, 
+              tuplen **    const tuplenarray) {
+
+    int row;
+
+    pnm_writepaminit(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_writepamrown(pamP, tuplenarray[row]);
+}
+
+
+
+void
+pnm_normalizetuple(struct pam * const pamP,
+                   tuple        const tuple,
+                   tuplen       const tuplen) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        tuplen[plane] = (samplen)tuple[plane] / pamP->maxval;
+}
+
+
+
+void
+pnm_unnormalizetuple(struct pam * const pamP,
+                     tuplen       const tuplen,
+                     tuple        const tuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        tuple[plane] = tuplen[plane] * pamP->maxval + 0.5;
+}
+
+
+
+void
+pnm_normalizeRow(struct pam *             const pamP,
+                 const tuple *            const tuplerow,
+                 const pnm_transformMap * const transform,
+                 tuplen *                 const tuplenrow) {
+
+    float const scaler = 1.0 / pamP->maxval;
+        /* Note: multiplication is faster than division, so we divide
+           once here so we can multiply many times later.
+        */
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (transform && transform[plane]) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                sample const sample = tuplerow[col][plane];
+                tuplenrow[col][plane] = transform[plane][sample];
+            }
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
+        }
+    }
+}
+
+
+
+static sample
+reversemap(samplen          const samplen,
+           pnm_transformMap const transformMap,
+           sample           const maxval) {
+/*----------------------------------------------------------------------------
+   Find the integer sample value that maps to the normalized samplen value
+   'samplen' through the map 'transformMap'.  We interpret the map as
+   mapping the value N+1 to all the values transformMap[N] through 
+   transformMap[N+1], and we expect transformMap[N+1] to be greater than
+   transformMap[N] for all N.
+-----------------------------------------------------------------------------*/
+    /* Do a binary search, since the values are in sorted (increasing)
+       order
+    */
+    
+    sample low, high;
+
+    low = 0; high = maxval;  /* Consider whole range to start */
+
+    while (low < high) {
+        unsigned int const middle = (low + high) / 2;
+
+        if (samplen < transformMap[middle])
+            /* Restrict  our consideration to the lower half of the range */
+            high = middle;
+        else
+            /* Restrict our consideration to the upper half of the range */
+            low = middle + 1;
+    }
+    return low;
+}
+
+
+
+void
+pnm_unnormalizeRow(struct pam *             const pamP,
+                   const tuplen *           const tuplenrow,
+                   const pnm_transformMap * const transform,
+                   tuple *                  const tuplerow) {
+
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (transform && transform[plane]) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][plane] = 
+                    reversemap(tuplenrow[col][plane], 
+                               transform[plane], pamP->maxval);
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][plane] = 
+                    tuplenrow[col][plane] * pamP->maxval + 0.5;
+        }
+    }
+}
+
+
+
+typedef samplen (*gammaFunction)(samplen);
+
+static void
+gammaCommon(struct pam *  const pamP,
+            tuplen *      const tuplenrow,
+            gammaFunction       gammafn) {
+
+    unsigned int plane;
+    unsigned int opacityPlane;
+    bool haveOpacity;
+    
+    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (haveOpacity && plane == opacityPlane) {
+            /* It's an opacity (alpha) plane, which means there is
+               no gamma adjustment in it.  
+            */
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplenrow[col][plane] = gammafn(tuplenrow[col][plane]);
+        }
+    }
+}
+
+
+
+void
+pnm_gammarown(struct pam * const pamP,
+              tuplen *     const tuplenrow) {
+
+    gammaCommon(pamP, tuplenrow, &pm_gamma709);
+}
+
+
+
+void
+pnm_ungammarown(struct pam * const pamP,
+                tuplen *     const tuplenrow) {
+
+    gammaCommon(pamP, tuplenrow, &pm_ungamma709);
+}
+
+
+
+enum applyUnapply {OPACITY_APPLY, OPACITY_UNAPPLY};
+
+static void
+applyopacityCommon(enum applyUnapply const applyUnapply,
+                   struct pam *      const pamP,
+                   tuplen *          const tuplenrow) {
+
+    unsigned int opacityPlane;
+    bool haveOpacity;
+    
+    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+    if (haveOpacity) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            if (plane != opacityPlane) {
+                unsigned int col;
+                for (col = 0; col < pamP->width; ++col) {
+                    tuplen const thisTuple = tuplenrow[col];
+
+                    switch (applyUnapply) {
+                    case OPACITY_APPLY:
+                        thisTuple[plane] *= thisTuple[opacityPlane];
+                        break;
+                    case OPACITY_UNAPPLY:
+                        if (thisTuple[opacityPlane] < EPSILON) {
+                            /* There is no foreground here at all.  So
+                               the color plane values must be zero and
+                               as output it makes absolutely no
+                               difference what they are (they must be
+                               multiplied by the opacity -- zero -- to
+                               be used).
+                            */
+                            assert(thisTuple[plane] < EPSILON);
+                        } else
+                            thisTuple[plane] /= thisTuple[opacityPlane];
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+void
+pnm_applyopacityrown(struct pam * const pamP,
+                     tuplen *     const tuplenrow) {
+
+    applyopacityCommon(OPACITY_APPLY, pamP, tuplenrow);
+
+}
+
+
+
+void
+pnm_unapplyopacityrown(struct pam * const pamP,
+                       tuplen *     const tuplenrow) {
+
+    applyopacityCommon(OPACITY_UNAPPLY, pamP, tuplenrow);
+}
+
+
+
+static void
+fillInMap(pnm_transformMap const ungammaTransformMap,
+          sample           const maxval,
+          float            const offset) {
+
+    float const scaler = 1.0/maxval;  /* divide only once, it's slow */
+
+    sample sample;
+
+    /* Fill in the map */
+    for (sample = 0; sample <= maxval; ++sample) {
+        samplen const samplen = (sample + offset) * scaler;
+        ungammaTransformMap[sample] = pm_ungamma709(samplen);
+    }
+}
+
+
+
+static pnm_transformMap *
+createUngammaMapOffset(const struct pam * const pamP,
+                       float              const offset) {
+/*----------------------------------------------------------------------------
+   Create a transform table that computes ungamma(arg+offset) for arg
+   in [0..maxval]; So with offset == 0, you get a function that can be
+   used in converting integer sample values to normalized ungamma'ed
+   samplen values.  But with offset == 0.5, you get a function that
+   can be used in a reverse lookup to convert normalized ungamma'ed
+   samplen values to integer sample values.  The 0.5 effectively does
+   the rounding.
+-----------------------------------------------------------------------------*/
+    pnm_transformMap * retval;
+    pnm_transformMap ungammaTransformMap;
+
+    MALLOCARRAY(retval, pamP->depth);
+
+    if (retval != NULL) {
+        MALLOCARRAY(ungammaTransformMap, pamP->maxval+1);
+
+        if (ungammaTransformMap != NULL) {
+            bool haveOpacity;
+            unsigned int opacityPlane;
+            unsigned int plane;
+
+            pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+            for (plane = 0; plane < pamP->depth; ++plane) {
+                if (haveOpacity && plane == opacityPlane)
+                    retval[plane] = NULL;
+                else
+                    retval[plane] = ungammaTransformMap;
+            }            
+            fillInMap(ungammaTransformMap, pamP->maxval, offset);
+        } else {
+            free(retval);
+            retval = NULL;
+        }
+    }
+    return retval;
+}
+
+
+
+pnm_transformMap *
+pnm_createungammatransform(const struct pam * const pamP) {
+
+    return createUngammaMapOffset(pamP, 0.0);
+}
+
+
+
+pnm_transformMap *
+pnm_creategammatransform(const struct pam * const pamP) {
+
+    /* Since we're creating a map to be used backwards (you search for
+       the normalized value in the array, and the result is the array
+       index at which you found it), the gamma transform map is almost
+       identical to the ungamma transform map -- just with a 0.5 offset
+       to effect rounding.
+    */
+    return createUngammaMapOffset(pamP, 0.5);
+}
+
+
+
+void
+pnm_freegammatransform(const pnm_transformMap * const transform,
+                       const struct pam *       const pamP) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        if (transform[plane])
+            free(transform[plane]);
+
+    free((void*)transform);
+}
diff --git a/lib/libpamread.c b/lib/libpamread.c
new file mode 100644
index 00000000..1b65745a
--- /dev/null
+++ b/lib/libpamread.c
@@ -0,0 +1,277 @@
+/*----------------------------------------------------------------------------
+                                  libpamread.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with reading the PAM (Portable Arbitrary Format) image format
+   raster (not the header).
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "fileio.h"
+
+
+static void
+readPbmRow(const struct pam * const pamP,
+           tuple *            const tuplerow) {
+
+    unsigned char *bitrow;
+    if (pamP->depth != 1)
+        pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                 "It says PBM format, but 'depth' member is not 1.");
+    
+    bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
+    pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width, pamP->format);
+    
+    if (tuplerow) {
+        int col;
+        for (col = 0; col < pamP->width; ++col) {
+            tuplerow[col][0] = 
+                ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
+                ? PAM_PBM_BLACK : PAM_PBM_WHITE
+                ;
+        }
+    }   
+    pbm_freerow(bitrow);
+}
+
+
+
+static void
+readPlainNonPbmRow(const struct pam * const pamP,
+                   tuple *            const tuplerow) {
+
+    int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            if (tuplerow)
+                tuplerow[col][plane] = pm_getuint(pamP->file);
+            else
+                pm_getuint(pamP->file);  /* read data and discard */
+    }
+}
+
+
+
+/* Though it is possible to simplify the bytesToSampleN() and
+   parsexNBpsRow() functions into a single routine that handles all
+   sample widths, we value efficiency higher here.  Earlier versions
+   of Netpbm (before 10.25) did that, with a loop, and performance
+   suffered visibly.
+*/
+
+static __inline__ sample
+bytes2ToSample(unsigned char buff[2]) {
+
+    return (buff[0] << 8) + buff[1];
+}
+
+
+
+static __inline__ sample
+bytes3ToSample(unsigned char buff[3]) {
+    return (buff[0] << 16) + (buff[1] << 8) + buff[2];
+}
+
+
+
+static __inline__ sample
+bytes4ToSample(unsigned char buff[4]) {
+
+    return (buff[0] << 24) + (buff[1] << 16) + (buff[2] << 8) + buff[3];
+}
+
+
+
+static void
+parse1BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane]= inbuf[bufferCursor++];
+    }
+}
+
+
+
+static void
+parse2BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[2] = (unsigned char (*)[2]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes2ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+parse3BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[3] = (unsigned char (*)[3]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes3ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+parse4BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[4] = (unsigned char (*)[4]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes4ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+readRawNonPbmRow(const struct pam * const pamP,
+                 tuple *            const tuplerow) {
+
+    unsigned int const rowImageSize = 
+        pamP->width * pamP->bytes_per_sample * pamP->depth;
+
+    unsigned char * inbuf;
+    size_t bytesRead;
+
+    inbuf = pnm_allocrowimage(pamP);
+    
+    bytesRead = fread(inbuf, 1, rowImageSize, pamP->file);
+
+    if (bytesRead != rowImageSize) {
+        if (feof(pamP->file))
+            pm_error("End of file encountered when trying to read a row from "
+                     "input file.");
+        else 
+            pm_error("Error reading a row from input file.  "
+                     "fread() fails with errno=%d (%s)",
+                     errno, strerror(errno));
+    }
+    if (tuplerow) {
+        switch (pamP->bytes_per_sample) {
+        case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
+        case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
+        case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
+        case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
+        default:
+            pm_error("invalid bytes per sample passed to "
+                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+        }
+    }
+    pnm_freerowimage(inbuf);
+}
+
+
+
+void 
+pnm_readpamrow(const struct pam * const pamP, 
+               tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Read a row from the Netpbm image file into tuplerow[], at the
+   current file position.  If 'tuplerow' is NULL, advance the file
+   pointer to the next row, but don't return the contents of the
+   current one.
+   
+   We assume the file is positioned to the beginning of a row of the
+   image's raster.
+-----------------------------------------------------------------------------*/
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */  
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+
+    switch (pamP->format) {
+    case PAM_FORMAT:
+    case RPPM_FORMAT:
+    case RPGM_FORMAT:
+        readRawNonPbmRow(pamP, tuplerow);
+        break;
+    case PPM_FORMAT:
+    case PGM_FORMAT:
+        readPlainNonPbmRow(pamP, tuplerow);
+        break;
+    case RPBM_FORMAT:
+    case PBM_FORMAT:
+        readPbmRow(pamP, tuplerow);
+        break;
+    default:
+        pm_error("Invalid 'format' member in PAM structure: %u", pamP->format);
+    }
+}
+
+
+
+tuple ** 
+pnm_readpam(FILE *       const fileP,
+            struct pam * const pamP, 
+            int          const size) {
+
+    tuple **tuplearray;
+    int row;
+
+    pnm_readpaminit(fileP, pamP, size);
+    
+    tuplearray = pnm_allocpamarray(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_readpamrow(pamP, tuplearray[row]);
+
+    return tuplearray;
+}
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
new file mode 100644
index 00000000..9184a4b5
--- /dev/null
+++ b/lib/libpamwrite.c
@@ -0,0 +1,397 @@
+/*----------------------------------------------------------------------------
+                                  libpamwrite.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with writing the PAM (Portable Arbitrary Format) image format
+   raster (not the header).
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <math.h>
+
+#include "pam.h"
+
+
+static __inline__ unsigned int
+samplesPerPlainLine(sample       const maxval, 
+                    unsigned int const depth, 
+                    unsigned int const lineLength) {
+/*----------------------------------------------------------------------------
+   Return the minimum number of samples that should go in a line
+   'lineLength' characters long in a plain format non-PBM PNM image
+   with depth 'depth' and maxval 'maxval'.
+
+   Note that this number is just for aesthetics; the Netpbm formats allow
+   any number of samples per line.
+-----------------------------------------------------------------------------*/
+    unsigned int const digitsForMaxval = (unsigned int)
+        (log(maxval + 0.1 ) / log(10.0));
+        /* Number of digits maxval has in decimal */
+        /* +0.1 is an adjustment to overcome precision problems */
+    unsigned int const fit = lineLength / (digitsForMaxval + 1);
+        /* Number of maxval-sized samples that fit in a line */
+    unsigned int const retval = (fit > depth) ? (fit - (fit % depth)) : fit;
+        /* 'fit', rounded down to a multiple of depth, if possible */
+
+    return retval;
+}
+
+
+
+static void
+writePamPlainPbmRow(const struct pam *  const pamP,
+                    const tuple *       const tuplerow) {
+
+    int col;
+    unsigned int const samplesPerLine = 70;
+
+    for (col = 0; col < pamP->width; ++col)
+        fprintf(pamP->file,  
+                ((col+1) % samplesPerLine == 0 || col == pamP->width-1)
+                    ? "%1u\n" : "%1u",
+                tuplerow[col][0] == PAM_PBM_BLACK ? PBM_BLACK : PBM_WHITE);
+}
+
+
+
+static void
+writePamPlainRow(const struct pam *  const pamP,
+                    const tuple *       const tuplerow) {
+
+    int const samplesPerLine = 
+        samplesPerPlainLine(pamP->maxval, pamP->depth, 79);
+
+    int col;
+    unsigned int samplesInCurrentLine;
+        /* number of samples written from start of line  */
+    
+    samplesInCurrentLine = 0;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane){
+            fprintf(pamP->file, "%lu ",tuplerow[col][plane]);
+
+            ++samplesInCurrentLine;
+
+            if (samplesInCurrentLine >= samplesPerLine) {
+                fprintf(pamP->file, "\n");
+                samplesInCurrentLine = 0;
+            }            
+        }
+    }
+    fprintf(pamP->file, "\n");
+}
+
+
+
+static void
+formatPbmRow(const struct pam * const pamP,
+             const tuple *      const tuplerow,
+             unsigned char *    const outbuf,
+             unsigned int *     const rowSizeP) {
+
+    unsigned char accum;
+    int col;
+    
+    accum = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        accum |= 
+            (tuplerow[col][0] == PAM_PBM_BLACK ? PBM_BLACK : PBM_WHITE)
+                << (7-col%8);
+        if (col%8 == 7) {
+                outbuf[col/8] = accum;
+                accum = 0;
+        }
+    }
+    if (pamP->width % 8 != 0) {
+        unsigned int const lastByteIndex = pamP->width/8;
+        outbuf[lastByteIndex] = accum;
+        *rowSizeP = lastByteIndex + 1;
+    } else
+        *rowSizeP = pamP->width/8;
+}
+
+
+
+/* Though it is possible to simplify the sampleToBytesN() and
+   formatNBpsRow() functions into a single routine that handles all
+   sample widths, we value efficiency higher here.  Earlier versions
+   of Netpbm (before 10.25) did that, with a loop, and performance
+   suffered visibly.
+*/
+
+static __inline__ void
+sampleToBytes2(unsigned char       buf[2], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 8) & 0xff;
+    buf[1] = (sampleval >> 0) & 0xff;
+}
+
+
+
+static __inline__ void
+sampleToBytes3(unsigned char       buf[3], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 16) & 0xff;
+    buf[1] = (sampleval >>  8) & 0xff;
+    buf[2] = (sampleval >>  0) & 0xff;
+}
+
+
+
+static __inline__ void
+sampleToBytes4(unsigned char       buf[4], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 24 ) & 0xff;
+    buf[1] = (sampleval >> 16 ) & 0xff;
+    buf[2] = (sampleval >>  8 ) & 0xff;
+    buf[3] = (sampleval >>  0 ) & 0xff;
+}
+
+
+
+static __inline__ void
+format1BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+   Create the image of a row in the raster of a raw format Netpbm
+   image that has one byte per sample (ergo not PBM).
+
+   Put the image at *outbuf; put the number of bytes of it at *rowSizeP.
+-----------------------------------------------------------------------------*/
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane=0; plane < pamP->depth; ++plane)
+            outbuf[bufferCursor++] = (unsigned char)tuplerow[col][plane];
+    }
+    *rowSizeP = pamP->width * 1 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format2BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[2] = (unsigned char (*)[2]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes2(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 2 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format3BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[3] = (unsigned char (*)[3]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes3(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 3 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format4BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[4] = (unsigned char (*)[4]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes4(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 4 * pamP->depth;
+}
+
+
+
+void
+pnm_formatpamrow(const struct pam * const pamP,
+                 const tuple *      const tuplerow,
+                 unsigned char *    const outbuf,
+                 unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+   Create the image of a row in the raster of a raw (not plain) format
+   Netpbm image, as described by *pamP and tuplerow[].  Put the image
+   at *outbuf.
+
+   'outbuf' must be the address of space allocated with pnm_allocrowimage().
+   
+   We return as *rowSizeP the number of bytes in the row image.
+-----------------------------------------------------------------------------*/
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        formatPbmRow(pamP, tuplerow, outbuf, rowSizeP);
+    else {
+        switch(pamP->bytes_per_sample){
+        case 1: format1BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 2: format2BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 3: format3BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 4: format4BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        default:
+            pm_error("invalid bytes per sample passed to "
+                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+        }
+    }
+}
+
+
+
+static void
+writePamRawRow(const struct pam * const pamP,
+               const tuple *      const tuplerow,
+               unsigned int       const count) {
+/*----------------------------------------------------------------------------
+   Write mutiple ('count') copies of the same row ('tuplerow') to the file,
+   in raw (not plain) format.
+-----------------------------------------------------------------------------*/
+    unsigned int rowImageSize;
+
+    unsigned char * outbuf;  /* malloc'ed */
+    unsigned int i;
+
+    outbuf = pnm_allocrowimage(pamP);
+
+    pnm_formatpamrow(pamP, tuplerow, outbuf, &rowImageSize);
+
+    for (i = 0; i < count; ++i) {
+        size_t bytesWritten;
+
+        bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
+        if (bytesWritten != rowImageSize)
+            pm_error("fwrite() failed to write an image row to the file.  "
+                     "errno=%d (%s)", errno, strerror(errno));
+    }
+    pnm_freerowimage(outbuf);
+}
+
+
+
+void 
+pnm_writepamrow(const struct pam * const pamP, 
+                const tuple *      const tuplerow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    
+    if (pm_plain_output || pamP->plainformat) {
+        switch (PAM_FORMAT_TYPE(pamP->format)) {
+        case PBM_TYPE:
+            writePamPlainPbmRow(pamP, tuplerow);
+            break;
+        case PGM_TYPE:
+        case PPM_TYPE:
+            writePamPlainRow(pamP, tuplerow);
+            break;
+        case PAM_TYPE:
+            /* pm_plain_output is impossible here due to assumption stated
+               above about pnm_writepaminit() having checked it.  The
+               pamP->plainformat is meaningless for PAM.
+            */
+            writePamRawRow(pamP, tuplerow, 1);
+            break;
+        default:
+            pm_error("Invalid 'format' value %u in pam structure", 
+                     pamP->format);
+        }
+    } else
+        writePamRawRow(pamP, tuplerow, 1);
+}
+
+
+
+void
+pnm_writepamrowmult(const struct pam * const pamP, 
+                    const tuple *      const tuplerow,
+                    unsigned int       const count) {
+/*----------------------------------------------------------------------------
+   Write mutiple ('count') copies of the same row ('tuplerow') to the file.
+-----------------------------------------------------------------------------*/
+   if (pm_plain_output || pamP->plainformat) {
+       unsigned int i;
+       for (i = 0; i < count; ++i)
+           pnm_writepamrow(pamP, tuplerow);
+   } else
+       /* Simple common case - use fastpath */
+       writePamRawRow(pamP, tuplerow, count);
+}
+
+
+
+void 
+pnm_writepam(struct pam * const pamP, 
+             tuple **     const tuplearray) {
+
+    int row;
+
+    pnm_writepaminit(pamP);
+    
+    for (row = 0; row < pamP->height; ++row) 
+        pnm_writepamrow(pamP, tuplearray[row]);
+}
diff --git a/lib/libpbm.h b/lib/libpbm.h
new file mode 100644
index 00000000..827c9d7e
--- /dev/null
+++ b/lib/libpbm.h
@@ -0,0 +1,12 @@
+/* This is the intra-libnetpbm interface header file for libpbm*.c
+*/
+
+#ifndef LIBPBM_H_INCLUDED
+#define LIBPBM_H_INCLUDED
+
+void
+pbm_readpbminitrest(FILE * file,
+                    int  * colsP,
+                    int *  rowsP);
+
+#endif
diff --git a/lib/libpbm1.c b/lib/libpbm1.c
new file mode 100644
index 00000000..8dd491a7
--- /dev/null
+++ b/lib/libpbm1.c
@@ -0,0 +1,62 @@
+/* libpbm1.c - pbm utility library part 1
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <stdio.h>
+#include "pbm.h"
+#include "libpbm.h"
+#include "shhopt.h"
+
+void
+pbm_init(int *argcP, char *argv[]) {
+    pm_proginit(argcP, argv);
+}
+
+
+
+void
+pbm_nextimage(FILE *file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+void
+pbm_check(FILE * file, const enum pm_check_type check_type, 
+          const int format, const int cols, const int rows,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to pbm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to pbm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (format != RPBM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = (cols+7)/8;
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+#ifdef LARGEFILEDEBUG
+        pm_message("pm_filepos passed to pm_check() is %u bytes",
+                   sizeof(pm_filepos));
+#endif
+        pm_check(file, check_type, need_raster_size, retval_p);
+    }
+}
+
diff --git a/lib/libpbm2.c b/lib/libpbm2.c
new file mode 100644
index 00000000..d04328ef
--- /dev/null
+++ b/lib/libpbm2.c
@@ -0,0 +1,180 @@
+/* libpbm2.c - pbm utility library part 2
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "libpbm.h"
+#include "fileio.h"
+
+static bit 
+getbit (FILE * const file) {
+    char ch;
+
+    do {
+        ch = pm_getc( file );
+    } while ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );
+
+    if ( ch != '0' && ch != '1' )
+        pm_error( "junk in file where bits should be" );
+    
+    return ( ch == '1' ) ? 1 : 0;
+}
+
+
+
+void
+pbm_readpbminitrest( file, colsP, rowsP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    {
+    /* Read size. */
+    *colsP = (int)pm_getuint( file );
+    *rowsP = (int)pm_getuint( file );
+
+    /* *colsP and *rowsP really should be unsigned int, but they come
+       from the time before unsigned ints (or at least from a person
+       trained in that tradition), so they are int.  We could simply
+       consider negative numbers to mean values > INT_MAX/2 and much
+       code would just automatically work.  But some code would fail
+       miserably.  So we consider values that won't fit in an int to
+       be unprocessable.
+    */
+    if (*colsP < 0)
+        pm_error("Number of columns in header is too large.");
+    if (*rowsP < 0)
+        pm_error("Number of columns in header is too large.");
+    }
+
+void
+pbm_readpbminit( file, colsP, rowsP, formatP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    int* formatP;
+    {
+    /* Check magic number. */
+    *formatP = pm_readmagicnumber( file );
+    switch ( PBM_FORMAT_TYPE(*formatP) )
+    {
+        case PBM_TYPE:
+    pbm_readpbminitrest( file, colsP, rowsP );
+    break;
+
+    default:
+    pm_error( "bad magic number - not a pbm file" );
+    }
+    }
+
+void
+pbm_readpbmrow( file, bitrow, cols, format )
+    FILE* file;
+    bit* bitrow;
+    int cols, format;
+    {
+    register int col, bitshift;
+    register bit* bP;
+
+    switch ( format )
+    {
+    case PBM_FORMAT:
+    for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+        *bP = getbit( file );
+    break;
+
+    case RPBM_FORMAT: {
+        register unsigned char item;
+        bitshift = -1;  item = 0;  /* item's value is meaningless here */
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+          {
+              if ( bitshift == -1 )
+                {
+                    item = pm_getrawbyte( file );
+                    bitshift = 7;
+                }
+              *bP = ( item >> bitshift ) & 1;
+              --bitshift;
+          }
+    }
+    break;
+
+    default:
+    pm_error( "can't happen" );
+    }
+    }
+
+
+
+void
+pbm_readpbmrow_packed(FILE *          const file, 
+                      unsigned char * const packed_bits,
+                      int             const cols, 
+                      int             const format) {
+
+    switch(format) {
+    case PBM_FORMAT: {
+        unsigned int col;
+        unsigned int byteIndex;
+
+        /* We first clear the return buffer, then set ones where needed */
+        for (byteIndex = 0; byteIndex < pbm_packed_bytes(cols); ++byteIndex)
+            packed_bits[byteIndex] = 0x00;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned char mask;
+            mask = getbit(file) << (7 - col % 8);
+            packed_bits[col / 8] |= mask;
+        }
+    }
+    break;
+
+    case RPBM_FORMAT: {
+        int bytes_read;
+        bytes_read = fread(packed_bits, 1, pbm_packed_bytes(cols), file);
+             
+        if (bytes_read < pbm_packed_bytes(cols)) {
+            if (feof(file)) 
+                if (bytes_read == 0) 
+                    pm_error("Attempt to read a raw PBM image row, but "
+                             "no more rows left in file.");
+                else
+                    pm_error("EOF in the middle of a raw PBM row.");
+            else 
+                pm_error("I/O error reading raw PBM row");
+        }
+    }
+    break;
+    
+    default:
+        pm_error( "Internal error in pbm_readpbmrow_packed." );
+    }
+}
+
+
+
+bit**
+pbm_readpbm( file, colsP, rowsP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    {
+    register bit** bits;
+    int format, row;
+
+    pbm_readpbminit( file, colsP, rowsP, &format );
+
+    bits = pbm_allocarray( *colsP, *rowsP );
+
+    for ( row = 0; row < *rowsP; ++row )
+        pbm_readpbmrow( file, bits[row], *colsP, format );
+
+    return bits;
+    }
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
new file mode 100644
index 00000000..f1717f37
--- /dev/null
+++ b/lib/libpbm3.c
@@ -0,0 +1,282 @@
+/* libpbm3.c - pbm utility library part 3
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "libpbm.h"
+#include "bitreverse.h"
+
+#if defined(__GNUC__) && (__GNUC__ >=3) && (__GNUC_MINOR__ >=1) && defined (__SSE__)
+/* intel MMX-SSE enhancement for pbm_writepbmowraw() */
+/* GCC only.  Turn on with -msse */
+
+#define HAVE_MMX_SSE 1
+#else
+#define HAVE_MMX_SSE 0
+#endif
+
+
+void
+pbm_writepbminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 int    const forceplain) {
+
+    if (!forceplain && !pm_plain_output) {
+        fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, RPBM_MAGIC2, cols, rows);
+#ifdef VMS
+        set_outfile_binary();
+#endif
+    } else
+        fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, PBM_MAGIC2, cols, rows);
+}
+
+
+
+static void
+writePackedRawRow(FILE *                const fileP,
+                  const unsigned char * const packed_bits,
+                  int                   const cols) {
+
+    int bytesWritten;
+    bytesWritten = fwrite(packed_bits, 1, pbm_packed_bytes(cols), fileP);
+    if (bytesWritten < pbm_packed_bytes(cols)) 
+        pm_error("I/O error writing packed row to raw PBM file.");
+} 
+
+
+
+static void
+packBitsWithMmxSse(FILE *          const fileP,
+                   const bit *     const bitrow,
+                   unsigned char * const packedBits,
+                   int             const cols,
+                   int *           const nextColP) {
+/*----------------------------------------------------------------------------
+   Pack the bits of bitrow[] into bytes at 'packedBits'.  Going left to right,
+   stop when there aren't enough bits left to fill a whole byte.  Return
+   as *nextColP the number of the next column after the rightmost one we
+   packed.
+
+   Use the Pentium MMX and SSE facilities to pack the bits quickly, but
+   perform the exact same function as the simpler packBitsGeneric().
+-----------------------------------------------------------------------------*/
+#if HAVE_MMX_SSE
+    /*
+      We use MMX/SSE facilities that operate on 8 bytes at once to pack
+      the bits quickly.
+    
+      We use 2 MMX registers (no SSE registers).
+    
+      The key machine instructions are:
+    
+    
+      PCMPEQB  Packed CoMPare EQual Byte
+    
+        Compares 8 bytes in parallel
+        Result is x00 if equal, xFF if unequal for each byte       
+    
+      PMOVMSKB Packed MOVe MaSK Byte 
+    
+        Result is a byte of the MSBs of 8 bytes
+        x00 xFF x00 xFF xFF xFF x00 x00 --> 01011100B = 0x5C     
+    
+    
+      EMMS     Empty MMx State
+    
+        Free MMX registers  
+    
+    
+      Here's a one-statement version of the code in our foor loop.  It's harder 
+      to read, but if we find out this generates more efficient code, we could 
+      use this.
+    
+        packedBits[col/8] 
+          = bitreverse [ ~ (unsigned char) __builtin_ia32_pmovmskb (
+            __builtin_ia32_pcmpeqb ( *(v8qi*) (&bitrow[col]), *(v8qi*) &zero64)
+            ) ];
+    */
+
+    typedef int v8qi __attribute__ ((mode(V8QI)));
+    typedef int di __attribute__ ((mode(DI)));
+    int col;
+
+    di const zero64 = 0;        /* to clear with PXOR */
+
+    for (col = 0; col < cols-7; col += 8) {
+        v8qi const compare =
+            __builtin_ia32_pcmpeqb(*(v8qi*) (&bitrow[col]), *(v8qi*) &zero64);
+        unsigned char const backwardWhiteMask = (unsigned char)
+            __builtin_ia32_pmovmskb(compare);
+        unsigned char const backwardBlackMask = ~backwardWhiteMask;
+        unsigned char const blackMask = bitreverse[backwardBlackMask];
+
+        packedBits[col/8] = blackMask;
+    }
+    *nextColP = col;
+
+    __builtin_ia32_emms();
+
+#else
+    if (bitreverse == bitreverse) {}; /* avoid unused vbl compiler warning */
+#endif
+}
+
+
+
+static void
+packBitsGeneric(FILE *          const fileP,
+                const bit *     const bitrow,
+                unsigned char * const packedBits,
+                int             const cols,
+                int *           const nextColP) {
+/*----------------------------------------------------------------------------
+   Pack the bits of bitrow[] into byts at 'packedBits'.  Going left to right,
+   stop when there aren't enough bits left to fill a whole byte.  Return
+   as *nextColP the number of the next column after the rightmost one we
+   packed.
+
+   Don't use any special CPU facilities to do the packing.
+-----------------------------------------------------------------------------*/
+    int col;
+
+    #define iszero(x) ((x) == 0 ? 0 : 1)
+
+    for (col = 0; col < cols-7; col += 8)
+        packedBits[col/8] = (
+            iszero(bitrow[col+0]) << 7 |
+            iszero(bitrow[col+1]) << 6 |
+            iszero(bitrow[col+2]) << 5 |
+            iszero(bitrow[col+3]) << 4 |
+            iszero(bitrow[col+4]) << 3 |
+            iszero(bitrow[col+5]) << 2 |
+            iszero(bitrow[col+6]) << 1 |
+            iszero(bitrow[col+7]) << 0
+            );
+    *nextColP = col;
+}
+
+
+
+static void
+writePbmRowRaw(FILE *      const fileP,
+               const bit * const bitrow,
+               int         const cols) {
+
+    int nextCol;
+
+    unsigned char * const packedBits = pbm_allocrow_packed(cols);
+
+    if (HAVE_MMX_SSE)
+        packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
+    else 
+        packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+
+    /* routine for partial byte at the end of packed_bits[]
+       Prior to addition of the above enhancement,
+       this method was used for the entire process
+     */                   
+
+    if (cols % 8 > 0) {
+        int col;
+        int bitshift;
+        unsigned char item;
+
+        bitshift = 7;  /* initial value */
+        item = 0;      /* initial value */
+        for (col = nextCol; col < cols; ++col, --bitshift )
+            if (bitrow[col] !=0)
+                item |= 1 << bitshift
+                ;
+        
+        packedBits[col/8] = item;
+    }
+    
+    writePackedRawRow(fileP, packedBits, cols);
+    
+    pbm_freerow_packed(packedBits);
+}
+
+
+
+static void
+writePbmRowPlain(FILE * const fileP,
+                 bit *  const bitrow, 
+                 int    const cols) {
+    
+    int col, charcount;
+
+    charcount = 0;
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 70) {
+            putc('\n', fileP);
+            charcount = 0;
+        }
+        putc(bitrow[col] ? '1' : '0', fileP);
+        ++charcount;
+    }
+    putc('\n', fileP);
+}
+
+
+
+void
+pbm_writepbmrow(FILE * const fileP, 
+                bit *  const bitrow, 
+                int    const cols, 
+                int    const forceplain) {
+
+    if (!forceplain && !pm_plain_output)
+        writePbmRowRaw(fileP, bitrow, cols);
+    else
+        writePbmRowPlain(fileP, bitrow, cols);
+}
+
+
+
+void
+pbm_writepbmrow_packed(FILE *                const fileP, 
+                       const unsigned char * const packed_bits,
+                       int                   const cols, 
+                       int                   const forceplain) {
+
+    if (!forceplain && !pm_plain_output)
+        writePackedRawRow(fileP, packed_bits, cols);
+    else {
+        bit *bitrow;
+        int col;
+
+        bitrow = pbm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col) 
+            bitrow[col] = 
+                packed_bits[col/8] & (0x80 >> (col%8)) ? PBM_BLACK : PBM_WHITE;
+        writePbmRowPlain(fileP, bitrow, cols);
+        pbm_freerow(bitrow);
+    }
+}
+
+
+
+void
+pbm_writepbm(FILE * const fileP, 
+             bit ** const bits, 
+             int    const cols, 
+             int    const rows, 
+             int    const forceplain) {
+
+    int row;
+
+    pbm_writepbminit(fileP, cols, rows, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        pbm_writepbmrow(fileP, bits[row], cols, forceplain);
+}
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
new file mode 100644
index 00000000..c7b5a355
--- /dev/null
+++ b/lib/libpbmfont.c
@@ -0,0 +1,1136 @@
+/* libpbm5.c - pbm utility library part 5
+**
+** Font routines.
+**
+** BDF font code Copyright 1993 by George Phillips.
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pbm.h"
+#include "pbmfont.h"
+#include "mallocvar.h"
+
+static unsigned int const firstCodePoint = 32;
+    /* This is the code point of the first character in a pbmfont font.
+       In ASCII, it is a space.
+    */
+
+static unsigned int const nCharsInFont = 96;
+    /* The number of characters in a pbmfont font.  A pbmfont font defines
+       characters at position 32 (ASCII space) through 127, so that's 96.
+    */
+
+/* The default font, packed in hex so this source file doesn't get huge.
+** You can replace this with your own font using pbm_dumpfont().
+*/
+#define DEFAULTFONT_ROWS 155
+#define DEFAULTFONT_COLS 112
+static unsigned long defaultfont_bits[DEFAULTFONT_ROWS][(DEFAULTFONT_COLS+31)/32] = {
+    {0x00000000L,0x20000c00L,0x10000000L,0x00000000L},
+    {0xc600a000L,0x42000810L,0x00000002L,0x00000063L},
+    {0x6c00a000L,0x45000810L,0x00000002L,0x00000036L},
+    {0x6c00a000L,0x88800808L,0xf2e1dee2L,0x00000036L},
+    {0x54000000L,0x80000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122282L,0x0000002aL},
+    {0x44000102L,0x00000800L,0x11122382L,0x00000022L},
+    {0xee000102L,0x00000800L,0x11e1e102L,0x00000077L},
+    {0x00000204L,0x00000800L,0x11002102L,0x00000000L},
+    {0x00000000L,0x00000c00L,0x11002102L,0x00000000L},
+    {0x00000000L,0x003f8000L,0xe3807600L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x02000080L,0x00040000L,0x00120000L,0x00000001L},
+    {0x04000082L,0x828e1838L,0x20210100L,0x00000002L},
+    {0x04000082L,0x82912448L,0x20210100L,0x00000002L},
+    {0x08000082L,0x8fd01940L,0x404087c2L,0x00000004L},
+    {0x08000080L,0x050c0622L,0x00408102L,0x00000004L},
+    {0x10000080L,0x05061874L,0x0040828fL,0x00008008L},
+    {0x10000080L,0x1f912688L,0x00408002L,0x00000008L},
+    {0x20000000L,0x0a11098cL,0x00408002L,0x00000010L},
+    {0x20000080L,0x0a0e0672L,0x00210000L,0x00000010L},
+    {0x40000000L,0x00040000L,0x00210000L,0x00000020L},
+    {0x00000000L,0x00000000L,0x00120000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x004e0838L,0x7023e1cfL,0x00008000L},
+    {0x00000000L,0x00913844L,0x88620208L,0x00008000L},
+    {0x08000000L,0x00910844L,0x08a20401L,0x00000004L},
+    {0x10000000L,0x01110844L,0x08a20401L,0x00000008L},
+    {0x20000000L,0x01110808L,0x3123c781L,0x00000010L},
+    {0x400003e0L,0x02110810L,0x0a202441L,0x00000020L},
+    {0x20000000L,0x02110820L,0x0bf02442L,0x00000010L},
+    {0x10008000L,0x04110844L,0x88242442L,0x00000008L},
+    {0x08008002L,0x040e3e7cL,0x7073c382L,0x00000004L},
+    {0x00010000L,0x08000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x0000e1c0L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00011220L,0x00000000L,0x70e38f87L,0x00000000L},
+    {0x20011220L,0x00020020L,0x89108448L,0x00008010L},
+    {0x10011220L,0x00040010L,0x09314448L,0x00008008L},
+    {0x0800e221L,0x02083e08L,0x11514788L,0x00000004L},
+    {0x040111e0L,0x00100004L,0x2153e448L,0x00000002L},
+    {0x08011020L,0x00083e08L,0x213a2448L,0x00008004L},
+    {0x10011040L,0x02040010L,0x01022448L,0x00008008L},
+    {0x2000e381L,0x02020020L,0x20e77f87L,0x00000010L},
+    {0x00000000L,0x04000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x3803e7efL,0xc73bbe3dL,0xdb863ce7L,0x0000001cL},
+    {0x44011224L,0x48910808L,0x91036648L,0x00008022L},
+    {0x4c011285L,0x48910808L,0xa1036648L,0x00008026L},
+    {0x54011387L,0x081f0808L,0xc102a548L,0x0000802aL},
+    {0x54011285L,0x09910808L,0xe102a548L,0x0000802aL},
+    {0x4e011204L,0x08910848L,0x9112a4c8L,0x00008027L},
+    {0x40011224L,0x08910848L,0x891224c8L,0x00008020L},
+    {0x3803e7efL,0x073bbe31L,0xcff77e47L,0x0000001cL},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000003L,0x00000000L},
+    {0x0003e1cfL,0x87bff7efL,0xdfbf77c2L,0x00000000L},
+    {0x00013224L,0x48a4a244L,0x89122442L,0x00000000L},
+    {0x00011224L,0x4824a244L,0xa8a14482L,0x00000000L},
+    {0x00013227L,0x8e04226cL,0xa8414102L,0x00000000L},
+    {0x0001e224L,0x83842228L,0xa8a08102L,0x00000000L},
+    {0x00010224L,0x40842228L,0xd8a08242L,0x00000000L},
+    {0x00010224L,0x48843638L,0x51108442L,0x00000000L},
+    {0x0003c1ceL,0x6f1f1c10L,0x53b9c7c2L,0x00000000L},
+    {0x00000060L,0x00000000L,0x00000002L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000003L,0x00000000L},
+    {0xfe000000L,0x00000000L,0x00000000L,0x0000007fL},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00010180L,0x000000c0L,0x003001c0L,0x00000000L},
+    {0x08008081L,0x00040040L,0x00100200L,0x00000004L},
+    {0x10008082L,0x80040040L,0x00100200L,0x00000008L},
+    {0x10004084L,0x40023c78L,0x70f1c7c7L,0x00004008L},
+    {0x10004080L,0x00000244L,0x89122208L,0x00008008L},
+    {0x20002080L,0x00001e44L,0x8113e208L,0x00008010L},
+    {0x10002080L,0x00002244L,0x81120208L,0x00008008L},
+    {0x10001080L,0x00002244L,0x89122208L,0x00008008L},
+    {0x10001080L,0x00001db8L,0x70e9c787L,0x00008008L},
+    {0x10000880L,0x00000000L,0x00000000L,0x00008008L},
+    {0x08000180L,0x00000000L,0x00000000L,0x00008004L},
+    {0x00000000L,0x1fc00000L,0x00000007L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00030080L,0x981c0000L,0x00000000L,0x00000000L},
+    {0x20010000L,0x08040000L,0x00000000L,0x00000010L},
+    {0x10010000L,0x08040000L,0x00000000L,0x00000008L},
+    {0x10016387L,0x898474b8L,0x72e1d5c7L,0x00000008L},
+    {0x10019080L,0x8a042a64L,0x89122208L,0x00008008L},
+    {0x08011080L,0x8c042a44L,0x89122207L,0x00000004L},
+    {0x10011080L,0x8a042a44L,0x89122200L,0x00008008L},
+    {0x10011080L,0x89042a44L,0x89122208L,0x00008008L},
+    {0x1003bbe0L,0x98dfebe6L,0x71e1e787L,0x00000008L},
+    {0x10000000L,0x80000000L,0x01002000L,0x00000008L},
+    {0x20000000L,0x80000000L,0x01002000L,0x00000010L},
+    {0x00000007L,0x00000000L,0x03807000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00008000L,0x00000000L,0x10410000L,0x00000000L},
+    {0x00008000L,0x00000000L,0x20408000L,0x00000000L},
+    {0x0001f66eL,0xfdfbf77cL,0x20408000L,0x00000000L},
+    {0x24008224L,0x488a2248L,0x20408240L,0x00000012L},
+    {0x54008224L,0x4a842210L,0x40404540L,0x0000002aL},
+    {0x48008222L,0x8a8a1420L,0x20408480L,0x00000024L},
+    {0x00008a23L,0x85111c44L,0x20408000L,0x00000000L},
+    {0x000071d1L,0x0531887cL,0x20408000L,0x00000000L},
+    {0x00000000L,0x00000800L,0x20408000L,0x00000000L},
+    {0x00000000L,0x00000800L,0x10410000L,0x00000000L},
+    {0x00000000L,0x00003000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x20000c00L,0x10000000L,0x00000000L},
+    {0xc600a000L,0x42000810L,0x00000002L,0x00000063L},
+    {0x6c00a000L,0x45000810L,0x00000002L,0x00000036L},
+    {0x6c00a000L,0x88800808L,0xf2e1dee2L,0x00000036L},
+    {0x54000000L,0x80000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122282L,0x0000002aL},
+    {0x44000102L,0x00000800L,0x11122382L,0x00000022L},
+    {0xee000102L,0x00000800L,0x11e1e102L,0x00000077L},
+    {0x00000204L,0x00000800L,0x11002102L,0x00000000L},
+    {0x00000000L,0x00000c00L,0x11002102L,0x00000000L},
+    {0x00000000L,0x003f8000L,0xe3807600L,0x00000000L}
+    };
+
+/* A default BDF font */
+/* Not as nicely compacted as the PBM font, oh well. */
+
+static struct glyph _g[190] = {
+ { 1, 1, 0, 0, 3, "\0" },
+ { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\0\1" },
+ { 3, 3, 1, 6, 5, "\1\0\1\1\0\1\1\0\1" },
+ { 5, 8, 0, 0, 6, "\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\1\0\1\0\0\1\0\1\0" },
+ { 5, 11, 0, -1, 6, "\0\0\1\0\0\0\1\1\1\0\1\0\1\0\1\1\0\1\0\0\0\1\1\0\0\0\0\1\1\0\0\0\1\0\1\0\0\1\0\1\1\0\1\0\1\0\1\1\1\0\0\0\1\0\0" },
+ { 8, 9, 0, 0, 9, "\0\1\1\0\0\0\1\1\1\0\0\1\1\1\1\0\1\0\0\1\0\1\0\0\0\1\1\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\1\0\1\1\0\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\0" },
+ { 9, 9, 0, 0, 10, "\0\0\0\1\1\0\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\0\1\1\0\1\1\1\0\1\1\1\1\0\0\1\0\1\1\0\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\0\0\1\1\1\0\1\0\1\1\1\1\0\1\1\0" },
+ { 2, 3, 1, 6, 4, "\1\1\0\1\1\0" },
+ { 3, 12, 1, -3, 5, "\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\0\1" },
+ { 3, 12, 0, -3, 5, "\1\0\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0" },
+ { 5, 5, 0, 4, 6, "\0\0\1\0\0\1\0\1\0\1\0\1\1\1\0\1\0\1\0\1\0\0\1\0\0" },
+ { 5, 5, 1, 1, 7, "\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0" },
+ { 2, 3, 0, -2, 3, "\0\1\0\1\1\0" },
+ { 5, 1, 1, 3, 8, "\1\1\1\1\1" },
+ { 1, 1, 1, 0, 3, "\1" },
+ { 3, 9, 0, 0, 3, "\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\1\0\1\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\1\0\1\1\0\1\1\1\0" },
+ { 4, 9, 0, 0, 6, "\0\0\1\0\0\1\1\0\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\1\1" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\1\1\1\1\1" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\1\1\1\0\0\0\0\0\1\0\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\0\1\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\0\0\1\0\1\0\1\0\0\1\0\1\1\1\1\1\0\0\0\1\0\0\0\0\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\1\1\1\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\1\1\0\0\1\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\0\1\1\0\1\1\0\0\0\1\0\0\0\1\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\0\1\0\0\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\1\0\0\0\1\0\0\0\1\1\0\1\1\0\0\0" },
+ { 1, 6, 1, 0, 3, "\1\0\0\0\0\1" },
+ { 2, 8, 0, -2, 3, "\0\1\0\0\0\0\0\0\0\0\0\1\0\1\1\0" },
+ { 6, 5, 0, 1, 8, "\0\0\0\0\1\1\0\0\1\1\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1" },
+ { 5, 3, 1, 2, 7, "\1\1\1\1\1\0\0\0\0\0\1\1\1\1\1" },
+ { 6, 5, 1, 1, 8, "\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\1\1\0\0\1\1\0\0\0\0" },
+ { 4, 9, 0, 0, 5, "\0\1\1\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\1\0\0" },
+ { 10, 11, 1, -2, 11, "\0\0\0\0\1\1\1\1\0\0\0\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\1\1\0\1\0\1\1\0\0\1\0\0\1\0\0\1\1\0\1\0\0\0\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\1\0\1\1\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\0\0\0" },
+ { 9, 9, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\1\1\1\1\1\1\0" },
+ { 7, 9, 0, 0, 8, "\0\0\1\1\1\0\1\0\1\1\0\0\1\1\0\1\0\0\0\0\1\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\0\1\1\0\0\1\1\0\0\1\1\1\1\0" },
+ { 8, 9, 0, 0, 9, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\1\1\1\1\1\1\0\0" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\1\0\0\0" },
+ { 8, 9, 0, 0, 9, "\0\0\1\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\0\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 9, 0, 0, 9, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\1\1\1\0\0\1\1\1" },
+ { 3, 9, 0, 0, 4, "\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 4, 9, 0, 0, 4, "\0\1\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\0\1\0\1\1\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\1\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\1\1\1\0\0\1\1\1" },
+ { 6, 9, 0, 0, 7, "\1\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\1\1\1\1\1\1\1" },
+ { 11, 9, 0, 0, 11, "\1\1\0\0\0\0\0\0\0\1\1\0\1\1\0\0\0\0\0\1\1\0\0\1\1\0\0\0\0\0\1\1\0\0\1\0\1\0\0\0\1\0\1\0\0\1\0\1\0\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\1\1\1\0\0\1\0\0\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\0\0\0\0\1\1\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\0\0\0\1\0" },
+ { 8, 9, 0, 0, 9, "\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0" },
+ { 8, 11, 0, -2, 9, "\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\0\0\1\1" },
+ { 8, 9, 0, 0, 8, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\0\1\1\1\1\1\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\1\1\1\0\0\0\1\1" },
+ { 6, 9, 0, 0, 7, "\0\1\1\1\0\1\1\0\0\0\1\1\1\0\0\0\0\1\0\1\1\0\0\0\0\0\1\1\1\0\0\0\0\0\1\1\1\0\0\0\0\1\1\1\0\0\1\1\1\0\1\1\1\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\1\1\1\1\1\0\0\1\0\0\1\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0" },
+ { 12, 9, 0, 0, 12, "\1\1\1\0\1\1\1\0\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\1\0\0\0\0\0\1\0\1\0\1\0\1\0\0\0\0\0\1\1\0\0\1\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\1\0\1\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\0\1\0\1\1\1\0\0\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\0\0\0" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\0\1\0\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 3, 12, 1, -3, 5, "\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1" },
+ { 3, 12, 0, -3, 5, "\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1" },
+ { 5, 5, 0, 4, 6, "\0\0\1\0\0\0\1\0\1\0\0\1\0\1\0\1\0\0\0\1\1\0\0\0\1" },
+ { 6, 1, 0, -3, 6, "\1\1\1\1\1\1" },
+ { 2, 3, 1, 6, 4, "\0\1\1\0\1\1" },
+ { 5, 6, 1, 0, 6, "\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 0, 0, 6, "\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0" },
+ { 4, 6, 1, 0, 5, "\0\1\1\0\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\1\0\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\1\0\0\0\0\1\0\0\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 6, 1, 0, 6, "\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 3, 9, 0, 0, 3, "\0\0\1\0\1\0\0\1\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0" },
+ { 5, 9, 1, -3, 6, "\0\1\1\1\1\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\0\1\0\0\0\0\1\1\1\0\1\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 6, 9, 0, 0, 6, "\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 2, 12, 0, -3, 3, "\0\1\0\0\0\0\1\1\0\1\0\1\0\1\0\1\0\1\0\1\0\1\1\0" },
+ { 6, 9, 0, 0, 6, "\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\0\0\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\1" },
+ { 3, 9, 0, 0, 3, "\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 9, 6, 0, 0, 9, "\1\0\1\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1\0\1\1" },
+ { 6, 6, 0, 0, 6, "\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 4, 6, 1, 0, 6, "\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 9, 0, -3, 6, "\1\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\0" },
+ { 5, 9, 1, -3, 6, "\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\1\1\1" },
+ { 4, 6, 0, 0, 4, "\1\0\1\1\0\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\1\1\1\0" },
+ { 4, 6, 1, 0, 6, "\0\1\1\1\1\0\0\1\1\1\0\0\0\0\1\1\1\0\0\1\1\1\1\0" },
+ { 4, 7, 0, 0, 4, "\0\1\0\0\1\1\1\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\1" },
+ { 6, 6, 0, 0, 6, "\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 6, 0, 0, 6, "\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0" },
+ { 9, 6, 0, 0, 9, "\1\1\1\0\1\1\0\1\1\0\1\0\0\1\0\0\1\0\0\1\1\0\1\0\1\1\0\0\0\1\0\1\0\1\0\0\0\0\1\1\0\1\0\0\0\0\0\1\0\0\1\0\0\0" },
+ { 5, 6, 1, 0, 6, "\1\1\0\1\1\0\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\1\0\1\1\0\1\1" },
+ { 6, 9, 0, -3, 6, "\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" },
+ { 4, 6, 1, 0, 6, "\1\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\1" },
+ { 4, 12, 1, -3, 6, "\0\0\1\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\1" },
+ { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\1\1" },
+ { 4, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\1\0\0" },
+ { 6, 2, 0, 3, 7, "\0\1\1\0\0\1\1\0\0\1\1\0" },
+ { 1, 9, 1, -3, 4, "\1\0\1\1\1\1\1\1\1" },
+ { 5, 8, 1, -1, 6, "\0\0\0\0\1\0\1\1\1\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\1\0\0\1\0\1\1\1\0\1\0\0\0\0" },
+ { 5, 9, 0, 0, 6, "\0\0\1\1\0\0\1\0\0\1\0\1\0\0\0\0\1\0\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\1\1\1\0\1\1" },
+ { 6, 7, 1, 1, 7, "\1\0\0\0\0\1\0\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\0\1\0\0\0\0\1" },
+ { 5, 9, 0, 0, 6, "\1\0\0\0\1\1\0\0\0\1\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\1\1\0" },
+ { 1, 9, 1, 0, 3, "\1\1\1\0\0\1\1\1\1" },
+ { 4, 12, 1, -3, 6, "\0\1\1\1\1\0\0\1\1\1\0\0\0\1\1\0\1\0\1\1\1\0\0\1\1\0\0\1\1\1\0\1\0\1\1\0\0\0\1\1\1\0\0\1\1\1\1\0" },
+ { 3, 1, 0, 7, 3, "\1\0\1" },
+ { 9, 9, 1, 0, 11, "\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\1\1\0\1\0\1\0\1\0\0\1\0\0\1\1\0\1\0\0\0\0\0\1\1\0\1\0\0\1\0\0\1\0\1\0\1\1\1\0\1\0\0\1\1\0\0\0\1\1\0\0\0\0\1\1\1\0\0\0" },
+ { 3, 6, 1, 3, 5, "\1\1\0\0\0\1\1\1\1\1\0\1\0\0\0\1\1\1" },
+ { 5, 5, 1, 0, 7, "\0\0\1\0\1\0\1\0\1\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1" },
+ { 6, 4, 1, 1, 8, "\1\1\1\1\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1" },
+ { 4, 1, 1, 3, 6, "\1\1\1\1" },
+ { 9, 9, 1, 0, 11, "\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\1\1\0\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\1\1\0\0\1\1\0\0\1\0\1\0\0\1\1\1\0\1\0\1\0\1\0\0\1\1\0\0\0\1\1\0\0\0\1\1\1\1\0\0\0" },
+ { 4, 1, 0, 7, 4, "\1\1\1\1" },
+ { 4, 4, 0, 5, 5, "\0\1\1\0\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 7, 1, 0, 7, "\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\1\1" },
+ { 4, 5, 0, 4, 4, "\0\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1" },
+ { 3, 5, 0, 4, 4, "\1\1\1\0\0\1\0\1\0\0\0\1\1\1\0" },
+ { 2, 2, 1, 7, 4, "\0\1\1\0" },
+ { 6, 9, 0, -3, 6, "\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\1\0\0\0\0\0\1\0\0\0\0\0\1\1\0\0\0" },
+ { 6, 12, 0, -3, 7, "\0\1\1\1\1\1\1\1\1\0\1\0\1\1\1\0\1\0\1\1\1\0\1\0\1\1\1\0\1\0\0\1\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0" },
+ { 1, 1, 1, 3, 3, "\1" },
+ { 3, 3, 0, -3, 3, "\0\1\0\0\0\1\1\1\1" },
+ { 3, 5, 0, 4, 4, "\0\1\0\1\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 6, 1, 3, 5, "\0\1\0\1\0\1\1\0\1\0\1\0\0\0\0\1\1\1" },
+ { 5, 5, 0, 0, 7, "\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\1\0\1\0\1\0\1\0\0" },
+ { 9, 9, 0, 0, 9, "\0\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\1\0\1\0\0\0\1\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0" },
+ { 9, 9, 0, 0, 9, "\0\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\1\0\1\1\0\0\0\0\1\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\1\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\1\0\1\0\0\0\1\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0" },
+ { 4, 9, 0, -3, 5, "\0\0\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\1\1\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 11, 0, 0, 9, "\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 10, 9, 0, 0, 11, "\0\0\1\1\1\1\1\1\1\1\0\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\1\1\1\1\0\0\1\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\0\0\1\1\1\1\1\1" },
+ { 7, 12, 0, -3, 8, "\0\0\1\1\1\0\1\0\1\1\0\0\1\1\0\1\0\0\0\0\1\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\0\1\1\0\0\1\1\0\0\1\1\1\1\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\1\1\0\0" },
+ { 7, 12, 0, 0, 8, "\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 12, 0, 0, 8, "\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 11, 0, 0, 8, "\0\0\1\0\1\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 3, 12, 0, 0, 4, "\1\0\0\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 12, 0, 0, 4, "\0\0\1\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 12, 0, 0, 4, "\0\1\0\1\0\1\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 11, 0, 0, 4, "\1\0\1\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 8, 9, 0, 0, 9, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\1\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\1\1\1\1\1\1\0\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\1\1\0\0\0\0\1\1\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\0\0\0\1\0" },
+ { 8, 12, 0, 0, 9, "\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 11, 0, 0, 9, "\0\0\1\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 5, 5, 1, 1, 7, "\1\0\0\0\1\0\1\0\1\0\0\0\1\0\0\0\1\0\1\0\1\0\0\0\1" },
+ { 9, 10, 0, 0, 9, "\0\0\0\0\0\0\0\0\1\0\0\1\1\1\1\0\1\0\0\1\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\1\0\0\1\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\0\1\1\0\0\1\0\1\1\1\1\0\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 11, 0, 0, 8, "\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\0\0\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\0\0\0\0\0\1\0\0\0\0\0\0\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\0\1\1\1\0\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\1\0\1\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 8, 1, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 8, 6, 1, 0, 9, "\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\1\1\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\0\1\1\0\1\1\1\0" },
+ { 4, 9, 1, -3, 5, "\0\1\1\0\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\1\0\1\1\0\0\1\0\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 8, 1, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 3, 9, 0, 0, 3, "\1\0\0\0\1\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\1\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\1\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 8, 0, 0, 3, "\1\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 4, 9, 1, 0, 6, "\0\1\0\0\0\1\1\1\1\0\1\0\0\1\1\1\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\1\0\0\1\0\1\0\0\0\0\0\0\0\0\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 4, 9, 1, 0, 6, "\0\1\0\0\0\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\0\1\0\0\1\0\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\0\1\0\0\1\0\1\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\1\0\1\1\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 8, 1, 0, 6, "\1\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 5, 1, 1, 7, "\0\0\1\0\0\0\0\0\0\0\1\1\1\1\1\0\0\0\0\0\0\0\1\0\0" },
+ { 6, 7, 0, -1, 6, "\0\0\1\1\0\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\0\0\1\0\0\0\0\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 9, 0, 0, 6, "\0\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 8, 0, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 12, 0, -3, 6, "\0\0\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" },
+ { 5, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\0" },
+ { 6, 11, 0, -3, 6, "\0\1\0\0\1\0\0\0\0\0\0\0\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" }
+};
+
+static struct font default_bdffont = { 14, 15, -1, -3, {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ _g + 0,
+ _g + 1,
+ _g + 2,
+ _g + 3,
+ _g + 4,
+ _g + 5,
+ _g + 6,
+ _g + 7,
+ _g + 8,
+ _g + 9,
+ _g + 10,
+ _g + 11,
+ _g + 12,
+ _g + 13,
+ _g + 14,
+ _g + 15,
+ _g + 16,
+ _g + 17,
+ _g + 18,
+ _g + 19,
+ _g + 20,
+ _g + 21,
+ _g + 22,
+ _g + 23,
+ _g + 24,
+ _g + 25,
+ _g + 26,
+ _g + 27,
+ _g + 28,
+ _g + 29,
+ _g + 30,
+ _g + 31,
+ _g + 32,
+ _g + 33,
+ _g + 34,
+ _g + 35,
+ _g + 36,
+ _g + 37,
+ _g + 38,
+ _g + 39,
+ _g + 40,
+ _g + 41,
+ _g + 42,
+ _g + 43,
+ _g + 44,
+ _g + 45,
+ _g + 46,
+ _g + 47,
+ _g + 48,
+ _g + 49,
+ _g + 50,
+ _g + 51,
+ _g + 52,
+ _g + 53,
+ _g + 54,
+ _g + 55,
+ _g + 56,
+ _g + 57,
+ _g + 58,
+ _g + 59,
+ _g + 60,
+ _g + 61,
+ _g + 62,
+ _g + 63,
+ _g + 64,
+ _g + 65,
+ _g + 66,
+ _g + 67,
+ _g + 68,
+ _g + 69,
+ _g + 70,
+ _g + 71,
+ _g + 72,
+ _g + 73,
+ _g + 74,
+ _g + 75,
+ _g + 76,
+ _g + 77,
+ _g + 78,
+ _g + 79,
+ _g + 80,
+ _g + 81,
+ _g + 82,
+ _g + 83,
+ _g + 84,
+ _g + 85,
+ _g + 86,
+ _g + 87,
+ _g + 88,
+ _g + 89,
+ _g + 90,
+ _g + 91,
+ _g + 92,
+ _g + 93,
+ _g + 94,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ _g + 95,
+ _g + 96,
+ _g + 97,
+ _g + 98,
+ _g + 99,
+ _g + 100,
+ _g + 101,
+ _g + 102,
+ _g + 103,
+ _g + 104,
+ _g + 105,
+ _g + 106,
+ _g + 107,
+ _g + 108,
+ _g + 109,
+ _g + 110,
+ _g + 111,
+ _g + 112,
+ _g + 113,
+ _g + 114,
+ _g + 115,
+ _g + 116,
+ _g + 117,
+ _g + 118,
+ _g + 119,
+ _g + 120,
+ _g + 121,
+ _g + 122,
+ _g + 123,
+ _g + 124,
+ _g + 125,
+ _g + 126,
+ _g + 127,
+ _g + 128,
+ _g + 129,
+ _g + 130,
+ _g + 131,
+ _g + 132,
+ _g + 133,
+ _g + 134,
+ _g + 135,
+ _g + 136,
+ _g + 137,
+ _g + 138,
+ _g + 139,
+ _g + 140,
+ _g + 141,
+ _g + 142,
+ _g + 143,
+ _g + 144,
+ _g + 145,
+ _g + 146,
+ _g + 147,
+ _g + 148,
+ _g + 149,
+ _g + 150,
+ _g + 151,
+ _g + 152,
+ _g + 153,
+ _g + 154,
+ _g + 155,
+ _g + 156,
+ _g + 157,
+ _g + 158,
+ _g + 159,
+ _g + 160,
+ _g + 161,
+ _g + 162,
+ _g + 163,
+ _g + 164,
+ _g + 165,
+ _g + 166,
+ _g + 167,
+ _g + 168,
+ _g + 169,
+ _g + 170,
+ _g + 171,
+ _g + 172,
+ _g + 173,
+ _g + 174,
+ _g + 175,
+ _g + 176,
+ _g + 177,
+ _g + 178,
+ _g + 179,
+ _g + 180,
+ _g + 181,
+ _g + 182,
+ _g + 183,
+ _g + 184,
+ _g + 185,
+ _g + 186,
+ _g + 187,
+ _g + 188,
+ _g + 189
+ }
+};
+
+
+
+struct font *
+pbm_defaultfont(const char * const name) {
+/*----------------------------------------------------------------------------
+   Generate the built-in font with name 'name'.
+-----------------------------------------------------------------------------*/
+    struct font * retval;
+
+    if (strcmp(name, "bdf") == 0)
+        retval = &default_bdffont;
+    else {
+        bit** defaultfont;
+        unsigned int row;
+
+        if (strcmp(name, "fixed") != 0)
+            pm_error( "built-in font name unknown, try 'bdf' or 'fixed'" );
+
+        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) {
+                int scol;
+                unsigned long l;
+
+                l = defaultfont_bits[row][col / 32]; /* initial value */
+
+                for (scol = MIN( col + 32, DEFAULTFONT_COLS) - 1;
+                     scol >= (int)col; 
+                     --scol) {
+                    if (l & 0x01)
+                        defaultfont[row][scol] = 1;
+                    else
+                        defaultfont[row][scol] = 0;
+                    l >>= 1;
+                }
+            }
+        }
+
+        retval = 
+            pbm_dissectfont(defaultfont, DEFAULTFONT_ROWS, DEFAULTFONT_COLS);
+    }
+    return retval;
+}
+
+
+struct font*
+pbm_dissectfont(bit ** const font,
+                int    const frows,
+                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;
+    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;
+
+    /* Now convert to a general font */
+
+    MALLOCVAR(fn);
+    if ( fn == NULL )
+        pm_error( "out of memory allocating font structure" );
+
+    fn->maxwidth  = char_width;
+    fn->maxheight = char_height;
+    fn->x = fn->y = 0;
+
+    fn->oldfont = font;
+    fn->frows = frows;
+    fn->fcols = fcols;
+    
+    /* Initialize all character positions to "undefined."  Those that
+       are defined in the font will be filled in below.
+    */
+    for (i = 0; i < 256; i++)
+        fn->glyph[i] = NULL;
+
+    MALLOCARRAY(glyph, nCharsInFont);
+    if ( glyph == NULL )
+        pm_error( "out of memory allocating glyphs" );
+    
+    bmap = (char*) malloc( fn->maxwidth * fn->maxheight * nCharsInFont );
+    if ( bmap == (char*) 0)
+        pm_error( "out of memory allocating glyph data" );
+
+    /* Now fill in the 0,0 coords. */
+    row = cell_height * 2;
+    col = cell_width * 2;
+    for (i = 0; i < firstCodePoint; ++i)
+        fn->glyph[i] = NULL;
+
+    for ( ch = 0; ch < nCharsInFont; ++ch ) {
+        glyph[ch].width = fn->maxwidth;
+        glyph[ch].height = fn->maxheight;
+        glyph[ch].x = glyph[ch].y = 0;
+        glyph[ch].xadd = cell_width;
+
+        for ( r = 0; r < glyph[ch].height; ++r )
+            for ( c = 0; c < glyph[ch].width; ++c )
+                bmap[r * glyph[ch].width + c] = font[row + r][col + c];
+    
+        glyph[ch].bmap = bmap;
+        bmap += glyph[ch].width * glyph[ch].height;
+
+        fn->glyph[firstCodePoint + ch] = &glyph[ch];
+
+        col += cell_width;
+        if ( col >= cell_width * 14 ) {
+            col = cell_width * 2;
+            row += cell_height;
+        }
+    }
+    for (i = firstCodePoint + nCharsInFont; i < 256; ++i)
+        fn->glyph[i] = NULL;
+    
+    return fn;
+}
+
+
+
+struct font*
+pbm_loadfont(const char * const filename)
+{
+    FILE* fp;
+    struct font* fn;
+    char line[256];
+
+    fp = pm_openr( filename );
+    fgets(line, 256, fp);
+    pm_close( fp );
+
+    if (line[0] == PBM_MAGIC1 && 
+        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
+        return pbm_loadpbmfont( filename );
+    } else if (!strncmp(line, "STARTFONT", 9)) {
+        if (!(fn = pbm_loadbdffont( filename )))
+          pm_error( "could not load BDF font file" );
+        return fn;
+    } else {
+      pm_error( "font file not in a recognized format ");
+      return NULL;  /* should never reach here */
+  }
+}
+
+
+
+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 );
+}
+
+void
+pbm_dumpfont( fn )
+    struct font* fn;
+{
+    /* Dump out font as C source code. */
+    int row, col, scol, lperrow;
+    unsigned long l;
+
+    if (fn->oldfont) {
+    printf( "#define DEFAULTFONT_ROWS %d\n", fn->frows );
+    printf( "#define DEFAULTFONT_COLS %d\n", fn->fcols );
+    printf( "static unsigned long defaultfont_bits[DEFAULTFONT_ROWS][(DEFAULTFONT_COLS+31)/32] = {\n" );
+    for ( row = 0; row < fn->frows; ++row )
+        {
+        lperrow = 0;
+        for ( col = 0; col < fn->fcols; col += 32 )
+        {
+        if ( lperrow == 0 )
+            printf( "    {" );
+        else if ( lperrow % 6 == 0 )
+            {
+            printf( ",\n     " );
+            lperrow = 0;
+            }
+        else
+            printf( "," );
+        l = 0;
+        for ( scol = col; scol < MIN( col + 32, fn->fcols ); ++scol )
+            {
+            l <<= 1;
+            if ( fn->oldfont[row][scol] )
+            l |= 1;
+            }
+        printf( "0x%08lxL", l );
+        ++lperrow;
+        }
+        printf( "}%s\n", row == fn->frows - 1 ? "" : "," );
+        }
+    printf( "    };\n" );
+    }
+    else {
+    struct glyph* glyph;
+    int i, j, ng;
+
+    ng = 0;
+    for (i = 0; i < 256; i++)
+        if (fn->glyph[i])
+            ng++;
+
+    printf("static struct glyph _g[%d] = {\n", ng);
+    for (i = 0; i < 256; i++) {
+        if (!(glyph = fn->glyph[i]))
+            continue;
+
+        printf(" { %d, %d, %d, %d, %d, \"", glyph->width, glyph->height,
+            glyph->x, glyph->y, glyph->xadd);
+
+        for (j = 0; j < glyph->width * glyph->height; j++)
+            if (glyph->bmap[j])
+                printf("\\1");
+            else
+                printf("\\0");
+        
+        ng--;
+        printf("\" }%s\n", ng ? "," : "");
+    }
+    printf("};\n");
+
+    printf("static struct font default_bdffont = { %d, %d, %d, %d, {\n",
+        fn->maxwidth, fn->maxheight, fn->x, fn->y);
+
+    for (i = 0; i < 256; i++) {
+        if (fn->glyph[i])
+            printf(" _g + %d", ng++);
+        else
+            printf(" 0");
+        
+        if (i != 255) printf(",");
+        printf("\n");
+    }
+
+    printf(" }\n};\n");
+    exit(0);
+
+    }
+
+}
+
+
+/* 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;
+
+    if (!(fp = fopen(name, "rb")))
+        return 0;
+
+    expect("STARTFONT");
+
+    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 (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;
+        }
+        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;
+                        
+                        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;
+                        
+                        *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;
+                    }
+                }
+
+                expect("ENDCHAR");
+
+                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;
+
+                numchar--;
+            }
+        }
+    }
+    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;
+        }
+        vec[n++] = s;
+        if (n >= mk_max)
+            break;
+        while (*s && !ISSPACE(*s))
+            s++;
+    }
+    vec[n] = 0;
+    return n;
+}
diff --git a/lib/libpbmvms.c b/lib/libpbmvms.c
new file mode 100644
index 00000000..8a2a54f8
--- /dev/null
+++ b/lib/libpbmvms.c
@@ -0,0 +1,734 @@
+/***************************************************************************
+  This file contains library routines needed to build Netpbm for VMS.
+  However, as of 2000.05.26, when these were split out of libpbm1.c
+  (where they were all switched by #ifdef VMS), there is no evidence
+  that anyone is building Netpbm for VMS.
+****************************************************************************/
+
+/*
+ * @(#)argproc.c 1.0 89/02/01		Mark Pizzolato (mark@infopiz.uucp)	
+ */
+
+#ifndef lint
+char argproc_version[] = "@(#)argproc.c VMS uucp Version infopiz-1.0";
+#endif
+
+#include "includes.h"		/* System include files, system dependent */
+
+
+/*
+ * getredirection() is intended to aid in porting C programs
+ * to VMS (Vax-11 C) which does not have '>' and '<'
+ * I/O redirection, along with a command line pipe mechanism
+ * using the '|' AND background command execution '&'.
+ * The piping mechanism will probably work with almost any 'filter' type
+ * of program.  With suitable modification, it may useful for other
+ * portability problems as well.
+ *
+ * Author:  Mark Pizzolato	mark@infopiz.UUCP
+ */
+struct list_item
+    {
+    struct list_item *next;
+    char *value;
+    };
+
+int
+getredirection(ac, av)
+int		*ac;
+char		***av;
+/*
+ * Process vms redirection arg's.  Exit if any error is seen.
+ * If getredirection() processes an argument, it is erased
+ * from the vector.  getredirection() returns a new argc and argv value.
+ * In the event that a background command is requested (by a trailing "&"),
+ * this routine creates a background subprocess, and simply exits the program.
+ *
+ * Warning: do not try to simplify the code for vms.  The code
+ * presupposes that getredirection() is called before any data is
+ * read from stdin or written to stdout.
+ *
+ * Normal usage is as follows:
+ *
+ *	main(argc, argv)
+ *	int		argc;
+ *    	char		*argv[];
+ *	{
+ *		getredirection(&argc, &argv);
+ *	}
+ */
+{
+    int			argc = *ac;	/* Argument Count	  */
+    char		**argv = *av;	/* Argument Vector	  */
+    char		*ap;   		/* Argument pointer	  */
+    int	       		j;		/* argv[] index		  */
+    extern int		errno;		/* Last vms i/o error 	  */
+    int			item_count = 0;	/* Count of Items in List */
+    int			new_file;	/* flag, true if '>' used */
+    struct list_item 	*list_head = 0;	/* First Item in List	    */
+    struct list_item	*list_tail;	/* Last Item in List	    */
+    char 		*in = NULL;	/* Input File Name	    */
+    char 		*out = NULL;	/* Output File Name	    */
+    char 		*outmode = "w";	/* Mode to Open Output File */
+    int			cmargc = 0;    	/* Piped Command Arg Count  */
+    char		**cmargv = NULL;/* Piped Command Arg Vector */
+    stat_t		statbuf;	/* fstat buffer		    */
+
+    /*
+     * First handle the case where the last thing on the line ends with
+     * a '&'.  This indicates the desire for the command to be run in a
+     * subprocess, so we satisfy that desire.
+     */
+    ap = argv[argc-1];
+    if (0 == strcmp("&", ap))
+	exit(background_process(--argc, argv));
+    if ('&' == ap[strlen(ap)-1])
+	{
+	ap[strlen(ap)-1] = '\0';
+	exit(background_process(argc, argv));
+	}
+    /*
+     * Now we handle the general redirection cases that involve '>', '>>',
+     * '<', and pipes '|'.
+     */
+    for (new_file = 0, j = 0; j < argc; ++j)
+	{
+	if (0 == strcmp("<", argv[j]))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EINVAL;
+		perror("No input file");
+		exit(EXIT_ERR);
+		}
+	    in = argv[++j];
+	    continue;
+	    }
+	if ('<' == *(ap = argv[j]))
+	    {
+	    in = 1 + ap;
+	    continue;
+	    }
+	if (0 == strcmp(">", ap))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EINVAL;
+		perror("No output file");
+		exit(EXIT_ERR);
+		}
+	    out = argv[++j];
+	    new_file = 1;
+	    continue;
+	    }
+	if ('>' == *ap)
+	    {
+	    if ('>' == ap[1])
+		{
+		outmode = "a";
+		if ('\0' == ap[2])
+		    out = argv[++j];
+		else
+		    out = 2 + ap;
+		}
+	    else
+		{ out = 1 + ap;  new_file = 1; }
+	    continue;
+	    }
+	if (0 == strcmp("|", argv[j]))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EPIPE;
+		perror("No command to Pipe to");
+		exit(EXIT_ERR);
+		}
+	    cmargc = argc-(j+1);
+	    cmargv = &argv[j+1];
+	    argc = j;
+	    outmode = "wb";	/* pipes are binary mode devices */
+	    continue;
+	    }
+	if ('|' == *(ap = argv[j]))
+	    {
+	    ++argv[j];
+	    cmargc = argc-j;
+	    cmargv = &argv[j];
+	    argc = j;
+	    outmode = "wb";	/* pipes are binary mode devices */
+	    continue;
+	    }
+	expand_wild_cards(ap, &list_head, &list_tail, &item_count);
+	}
+    /*
+     * Allocate and fill in the new argument vector, Some Unix's terminate
+     * the list with an extra null pointer.
+     */
+    argv = *av = calloc(item_count+1, sizeof(char *));
+    for (j = 0; j < item_count; ++j, list_head = list_head->next)
+	argv[j] = list_head->value;
+    *ac = item_count;
+    if (cmargv != NULL)
+	{
+	char subcmd[1024];
+	static char *pipe_and_fork();
+
+	if (out != NULL)
+	    {
+	    errno = EINVAL;
+	    perror("Invalid '|' and '>' specified");
+	    exit(EXIT_ERR);
+	    }
+	strcpy(subcmd, cmargv[0]);
+	for (j = 1; j < cmargc; ++j)
+	    {
+	    strcat(subcmd, " \"");
+	    strcat(subcmd, cmargv[j]);
+	    strcat(subcmd, "\"");
+	    }
+	out = pipe_and_fork(subcmd);
+	outmode = "wb";
+	}
+	
+    /* Check for input from a pipe (mailbox) */
+
+    if(fstat(0, &statbuf) == 0){
+	if(strncmp(statbuf.st_dev, "MB", 2) == 0 || 
+	    strncmp(statbuf.st_dev, "_MB", 3) == 0){
+
+	    /* Input from a pipe, reopen it in binary mode to disable	*/
+	    /* carriage control processing.				*/
+
+	    if (in != NULL){
+		errno = EINVAL;
+		perror("Invalid '|' and '<' specified");
+		exit(EXIT_ERR);
+		}
+	    freopen(statbuf.st_dev, "rb", stdin);
+	    }
+	}
+    else {
+	perror("fstat failed");
+	exit(EXIT_ERR);
+	}
+
+#ifdef __ALPHA
+        /*, "mbc=32", "mbf=2"))) blows up on the ALPHA cbm 11/08/92 */
+    if ((in != NULL) && (NULL == freopen(in, "r", stdin)))
+        {
+#else
+    if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2")))
+	{
+#endif
+	perror(in);    	       	/* Can't find file		*/
+	exit(EXIT_ERR);		/* Is a fatal error		*/
+	}
+#ifdef __ALPHA
+    if ((out != NULL) && (NULL == freopen(out, outmode, stdout)))
+        {
+#else
+    if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2")))
+	{	
+#endif
+	perror(ap);		/* Error, can't write or append	*/
+	exit(EXIT_ERR);		/* Is a fatal error		*/
+	}
+
+     if ( new_file ) {
+	/*
+	 * We are making an explicit output file, fstat the file and
+         * declare exit handler to be able change the file to fixed length
+	 * records if necessary. 
+	 */
+	char fname[256];
+	static int outfile_rundown(), check_outfile_filetype();
+	static stat_t ofile_stat;
+	static struct exit_control_block {
+    	    struct exit_control_block *flink;
+    	    int	(*exit_routine)();
+	    int arg_count;
+	    int *status_address;	/* arg 1 */
+	    stat_t *stat;		/* arg 2 */
+	    int exit_status;
+	    int skew[128];
+	} exit_block = 
+	    { 0, outfile_rundown, 2, &exit_block.exit_status, &ofile_stat, 0 };
+
+	if ( fstat ( fileno(stdout), &ofile_stat ) == 0 )
+	     sys$dclexh ( &exit_block );
+	else fprintf(stderr,"Error fstating stdout - %s\n",
+		strerror(errno,vaxc$errno) );
+
+	if ( fgetname ( stdout, fname, 0 ) ) check_outfile_filetype ( fname );
+     }
+#ifdef DEBUG
+    fprintf(stderr, "Arglist:\n");
+    for (j = 0; j < *ac;  ++j)
+	fprintf(stderr, "argv[%d] = '%s'\n", j, argv[j]);
+#endif
+}
+
+static int binary_outfile = 0;
+void set_outfile_binary() { binary_outfile = 1; return; }
+
+/*
+ * Check if output file should be set to binary (fixed 512) on exit based
+ * upon the filetype.
+ */
+static int check_outfile_filetype ( name )
+    char *name;
+{
+    char *binary_filetypes, *ext, *p, *t;
+    binary_filetypes = (char *) getenv ( "PBM_BINARY_FILETYPES" );
+    if ( binary_filetypes == NULL ) return 0;
+
+    ext = strchr ( name, '.' );  if ( ext == NULL ) return 0;
+    ext++;
+    t = strrchr ( name, '.' );   if ( t != NULL ) *t = '\0';
+
+    for ( p = binary_filetypes; *p != '\0'; p++ ) {
+	for ( t = p;
+	      (*p != '\0' ) && (strchr ( ", 	", *p ) == NULL); 
+	     p++ ) *p = toupper(*p);
+	*p = '\0';
+
+	if ( strcmp ( t, ext ) == 0 ) {
+	    binary_outfile = 1;
+	    break;
+	}
+    }
+    return binary_outfile;
+}
+
+
+/*
+ * Exit handler to set output file to binary on image exit.
+ */
+static int outfile_rundown ( reason, statbuf )
+    int *reason;
+    stat_t *statbuf;
+{
+    int code, channel, status, LIB$GETDVI(), sys$assign(), sys$qiow();
+    long int fib_desc[2], devchar;
+    short int iosb[4];
+    $DESCRIPTOR(device, statbuf->st_dev);
+    struct fibdef fib;		/* File information block (XQP) */
+    struct atrdef atr[2];
+    struct fat {
+      unsigned char rtype, ratattrib;
+      unsigned short int rsize, hiblk[2], efblk[2], ffbyte, maxrec, defext, gbc;
+      unsigned short int reserved[4], versions;
+    } rat;
+
+    if ( !binary_outfile ) return 1;	/* leave file alone */
+    /*
+     * Assign channel to device listed in stat block and test if it is
+     * a directory structured device, returning if not.
+     */
+    device.dsc$w_length = strlen ( statbuf->st_dev );
+    status = sys$assign ( &device, &channel, 0, 0 );
+    if ((status & 1) == 0) { fprintf(stderr, 
+	"assign error to %s (%d)\n", device.dsc$a_pointer, status);
+		return status; }
+    code = DVI$_DEVCHAR;
+    status = LIB$GETDVI ( &code, &channel, 0, &devchar );
+    if ((status & 1) == 0) { fprintf(stderr, "getdvi error: %d\n", status);
+		return status; }
+    if ( (devchar & DEV$M_DIR) == 0 ) return 1;
+    /*
+     * Build File Information Block and Atrribute block.
+     */
+#ifdef __ALPHA
+    fib.fib$w_fid[0] = statbuf->st_ino[0];
+    fib.fib$w_fid[1] = statbuf->st_ino[1];
+    fib.fib$w_fid[2] = statbuf->st_ino[2];
+#else
+    fib.fib$r_fid_overlay.fib$w_fid[0] = statbuf->st_ino[0];
+    fib.fib$r_fid_overlay.fib$w_fid[1] = statbuf->st_ino[1];
+    fib.fib$r_fid_overlay.fib$w_fid[2] = statbuf->st_ino[2];
+#endif
+
+    atr[0].atr$w_size = sizeof(rat);
+    atr[0].atr$w_type = ATR$C_RECATTR;
+    atr[0].atr$l_addr = &rat;
+    atr[1].atr$w_size = 0; atr[1].atr$w_type = 0;
+    /*
+     * Perform access function with read_attributes sub-function.
+     */
+    freopen ( "SYS$OUTPUT:", "a", stdout );
+    fib_desc[0] = 10; fib_desc[1] = (long int) &fib;
+#ifdef __ALPHA
+    fib.fib$l_acctl = FIB$M_WRITE;
+#else
+    fib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_WRITE;
+#endif
+    status = sys$qiow ( 0, channel, IO$_ACCESS|IO$M_ACCESS,
+		 &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
+    /*
+     * If status successful, update record byte and perform a MODIFY.
+     */
+    if ( (status&1) == 1 ) status = iosb[0];
+    if ( (status&1) == 1 ) {
+      rat.rtype = 1;		/* fixed length records */
+      rat.rsize = 512;  	/* 512 byte block recrods */
+      rat.ratattrib = 0;        /* Record attributes: none */
+
+     status = sys$qiow
+	( 0, channel, IO$_MODIFY, &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
+       if ( (status&1) == 1 ) status = iosb[0];
+   }
+   sys$dassgn ( channel );
+   if ( (status & 1) == 0 ) fprintf ( stderr,
+	"Failed to convert output file to binary format, status: %d\n", status);
+   return status;
+}
+
+
+static add_item(head, tail, value, count)
+struct list_item **head;
+struct list_item **tail;
+char *value;
+int *count;
+{
+    if (*head == 0)
+	{
+	if (NULL == (*head = calloc(1, sizeof(**head))))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	*tail = *head;
+	}
+    else
+	if (NULL == ((*tail)->next = calloc(1, sizeof(**head))))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	else
+	    *tail = (*tail)->next;
+    (*tail)->value = value;
+    ++(*count);
+}
+
+static expand_wild_cards(item, head, tail, count)
+char *item;
+struct ltem_list **head;
+struct ltem_list **tail;
+int *count;
+{
+int expcount = 0;
+int context = 0;
+int status;
+int status_value;
+int had_version;
+$DESCRIPTOR(filespec, item);
+$DESCRIPTOR(defaultspec, "SYS$DISK:[]*.*;");
+$DESCRIPTOR(resultspec, "");
+
+    if (strcspn(item, "*%") == strlen(item))
+	{
+	add_item(head, tail, item, count);
+	return;
+	}
+    resultspec.dsc$b_dtype = DSC$K_DTYPE_T;
+    resultspec.dsc$b_class = DSC$K_CLASS_D;
+    resultspec.dsc$a_pointer = NULL;
+    filespec.dsc$w_length = strlen(item);
+    /*
+     * Only return version specs, if the caller specified a version
+     */
+    had_version = strchr(item, ';');
+    while (1 == (1&lib$find_file(&filespec, &resultspec, &context,
+    				 &defaultspec, 0, &status_value, &0)))
+	{
+	char *string;
+	char *c;
+
+	if (NULL == (string = calloc(1, resultspec.dsc$w_length+1)))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length);
+	string[resultspec.dsc$w_length] = '\0';
+	if (NULL == had_version)
+	    *((char *)strrchr(string, ';')) = '\0';
+	/*
+	 * Be consistent with what the C RTL has already done to the rest of
+	 * the argv items and lowercase all of these names.
+	 */
+	for (c = string; *c; ++c)
+	    if (isupper(*c))
+		*c = tolower(*c);
+	add_item(head, tail, string, count);
+	++expcount;
+	}
+    if (expcount == 0)
+	add_item(head, tail, item, count);
+    lib$sfree1_dd(&resultspec);
+    lib$find_file_end(&context);
+}
+
+static int child_st[2];	/* Event Flag set when child process completes	*/
+
+static short child_chan;/* I/O Channel for Pipe Mailbox			*/
+
+static exit_handler(status)
+int *status;
+{
+short iosb[4];
+
+    if (0 == child_st[0])
+	{
+#ifdef DEBUG
+	fprintf(stderr, "Waiting for Child Process to Finnish . . .\n");
+#endif
+	fflush(stdout);	    /* Have to flush pipe for binary data to	*/
+			    /* terminate properly -- <tp@mccall.com>	*/
+#ifdef DEBUG
+	fprintf(stderr, "    stdout flushed. . .\n");
+#endif
+	sys$qio(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0);
+#ifdef DEBUG
+	fprintf(stderr, "    Pipe terminated. . .\n");
+#endif
+	fclose(stdout);
+#ifdef DEBUG
+	fprintf(stderr, "    stdout closed. . .\n");
+#endif
+	sys$synch(0, child_st);
+	sys$dassgn(child_chan);
+	}
+#ifdef DEBUG
+	fprintf(stderr, "    sync done. . .\n");
+#endif
+}
+
+#include <syidef>		/* System Information Definitions	*/
+
+static sig_child(chan)
+int chan;
+{
+#ifdef DEBUG
+    fprintf(stderr, "Child Completion AST, st: %x\n", child_st[0] );
+#endif
+    if (child_st[0] == 0)
+	{ child_st[0] = 1; }
+    sys$setef ( 0 );
+}
+
+static struct exit_control_block
+    {
+    struct exit_control_block *flink;
+    int	(*exit_routine)();
+    int arg_count;
+    int *status_address;
+    int exit_status;
+    } exit_block =
+    {
+    0,
+    exit_handler,
+    1,
+    &exit_block.exit_status,
+    0
+    };
+
+static char *pipe_and_fork(cmd)
+char *cmd;
+{
+    $DESCRIPTOR(cmddsc, cmd);
+    static char mbxname[64], ef = 0;
+    $DESCRIPTOR(mbxdsc, mbxname);
+    short iosb[4];
+    int status;
+    int pid;
+    struct
+	{
+	short dna_buflen;
+	short dna_itmcod;
+	char *dna_buffer;
+	short *dna_retlen;
+	int listend;
+	} itmlst =
+	{
+	sizeof(mbxname),
+	DVI$_DEVNAM,
+	mbxname,
+	&mbxdsc.dsc$w_length,
+	0
+	};
+    int mbxsize;
+    struct
+	{
+	short mbf_buflen;
+	short mbf_itmcod;
+	int *mbf_maxbuf;
+	short *mbf_retlen;
+	int listend;
+	} syiitmlst =
+	{
+	sizeof(mbxsize),
+	SYI$_MAXBUF,
+	&mbxsize,
+	0,
+	0
+	};
+
+    cmddsc.dsc$w_length = strlen(cmd);
+    /*
+     * Get the SYSGEN parameter MAXBUF, and the smaller of it and 2048 as
+     * the size of the 'pipe' mailbox.
+     */
+    if (1 == (1&(vaxc$errno = sys$getsyiw(0, 0, 0, &syiitmlst, iosb, 0, 0, 0))))
+	vaxc$errno = iosb[0];
+    if (0 == (1&vaxc$errno))
+	{
+ 	errno = EVMSERR;
+	perror("Can't get SYSGEN parameter value for MAXBUF");
+	exit(EXIT_ERR);
+	}
+    if (mbxsize > 2048)
+	mbxsize = 2048;
+    if (0 == (1&(vaxc$errno = sys$crembx(0, &child_chan, mbxsize, mbxsize, 0, 0, 0))))
+	{
+	errno = EVMSERR;
+	perror("Can't create pipe mailbox");
+	exit(EXIT_ERR);
+	}
+    if (1 == (1&(vaxc$errno = sys$getdviw(0, child_chan, 0, &itmlst, iosb,
+    					  0, 0, 0))))
+	vaxc$errno = iosb[0];
+    if (0 == (1&vaxc$errno))
+	{
+ 	errno = EVMSERR;
+	perror("Can't get pipe mailbox device name");
+	exit(EXIT_ERR);
+	}
+    mbxname[mbxdsc.dsc$w_length] = '\0';
+#ifdef DEBUG
+    fprintf(stderr, "Pipe Mailbox Name = '%s'\n", mbxname);
+#endif
+    if (0 == (1&(vaxc$errno = lib$spawn(&cmddsc, &mbxdsc, 0, &1,
+    					0, &pid, child_st, &ef, sig_child,
+    					&child_chan))))
+	{
+	errno = EVMSERR;
+	perror("Can't spawn subprocess");
+	exit(EXIT_ERR);
+	}
+#ifdef DEBUG
+    fprintf(stderr, "Subprocess's Pid = %08X\n", pid);
+#endif
+    sys$dclexh(&exit_block);
+    return(mbxname);
+}
+
+background_process(argc, argv)
+int argc;
+char **argv;
+{
+char command[2048] = "$";
+$DESCRIPTOR(value, command);
+$DESCRIPTOR(cmd, "BACKGROUND$COMMAND");
+$DESCRIPTOR(null, "NLA0:");
+int pid;
+
+    strcat(command, argv[0]);
+    while (--argc)
+	{
+	strcat(command, " \"");
+	strcat(command, *(++argv));
+	strcat(command, "\"");
+	}
+    value.dsc$w_length = strlen(command);
+    if (0 == (1&(vaxc$errno = lib$set_symbol(&cmd, &value))))
+	{
+	errno = EVMSERR;
+	perror("Can't create symbol for subprocess command");
+	exit(EXIT_ERR);
+	}
+    if (0 == (1&(vaxc$errno = lib$spawn(&cmd, &null, 0, &17, 0, &pid))))
+	{
+	errno = EVMSERR;
+	perror("Can't spawn subprocess");
+	exit(EXIT_ERR);
+	}
+#ifdef DEBUG
+    fprintf(stderr, "%s\n", command);
+#endif
+    fprintf(stderr, "%08X\n", pid);
+    return(EXIT_OK);
+}
+
+/* got this off net.sources */
+
+#ifdef	VMS
+#define	index	strchr
+#endif	/*VMS*/
+
+/*
+ * get option letter from argument vector
+ */
+int	opterr = 1,		/* useless, never set or used */
+	optind = 1,		/* index into parent argv vector */
+	optopt;			/* character checked for validity */
+char	*optarg;		/* argument associated with option */
+
+#define BADCH	(int)'?'
+#define EMSG	""
+#define tell(s)	fputs(progname,stderr);fputs(s,stderr); \
+		fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);
+
+getopt(nargc,nargv,ostr)
+int	nargc;
+char	**nargv,
+	*ostr;
+{
+	static char	*place = EMSG;	/* option letter processing */
+	register char	*oli;		/* option letter list index */
+	char	*index();
+	char *progname;
+
+	if(!*place) {			/* update scanning pointer */
+		if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF);
+		if (*place == '-') {	/* found "--" */
+			++optind;
+			return(EOF);
+		}
+	}				/* option letter okay? */
+	if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
+		if(!*place) ++optind;
+#ifdef VMS
+		progname = strrchr(nargv[0],']');
+#else
+		progname = rindex(nargv[0],'/');
+#endif
+		if (!progname) progname = nargv[0]; else progname++;
+		tell(": illegal option -- ");
+	}
+	if (*++oli != ':') {		/* don't need argument */
+		optarg = NULL;
+		if (!*place) ++optind;
+	}
+	else {				/* need an argument */
+		if (*place) optarg = place;	/* no white space */
+		else if (nargc <= ++optind) {	/* no arg */
+			place = EMSG;
+#ifdef VMS
+			progname = strrchr(nargv[0],']');
+#else
+			progname = rindex(nargv[0],'/');
+#endif
+			if (!progname) progname = nargv[0]; else progname++;
+			tell(": option requires an argument -- ");
+		}
+	 	else optarg = nargv[optind];	/* white space */
+		place = EMSG;
+		++optind;
+	}
+	return(optopt);			/* dump back option letter */
+}
diff --git a/lib/libpgm.h b/lib/libpgm.h
new file mode 100644
index 00000000..ba51f9a9
--- /dev/null
+++ b/lib/libpgm.h
@@ -0,0 +1,24 @@
+/* This is the intra-libnetpbm interface header file for libpgm*.c
+*/
+
+#ifndef LIBPGM_H_INCLUDED
+#define LIBPGM_H_INCLUDED
+
+#include "pgm.h"
+
+void
+pgm_readpgminitrest(FILE * const file, 
+                    int *  const colsP, 
+                    int *  const rowsP, 
+                    gray * const maxvalP);
+
+gray
+pgm_getrawsample(FILE * const file,
+                 gray   const maxval);
+
+void
+pgm_writerawsample(FILE * const fileP,
+                   gray   const val,
+                   gray   const maxval);
+
+#endif
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
new file mode 100644
index 00000000..75fa365b
--- /dev/null
+++ b/lib/libpgm1.c
@@ -0,0 +1,324 @@
+/* libpgm1.c - pgm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "pgm.h"
+#include "libpgm.h"
+#include "pbm.h"
+#include "libpbm.h"
+#include "pam.h"
+#include "libpam.h"
+#include "fileio.h"
+#include "mallocvar.h"
+
+
+gray *
+pgm_allocrow(unsigned int const cols) {
+
+    gray * grayrow;
+
+    MALLOCARRAY(grayrow, cols);
+
+    if (grayrow == NULL)
+        pm_error("Unable to allocate space for a %u-column gray row", cols);
+
+    return grayrow;
+}
+
+
+
+void 
+pgm_init(int *   const argcP,
+         char ** const argv) {
+
+    pbm_init( argcP, argv );
+}
+
+
+
+void
+pgm_nextimage(FILE * const file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+void
+pgm_readpgminitrest(FILE * const fileP, 
+                    int *  const colsP, 
+                    int *  const rowsP, 
+                    gray * const maxvalP) {
+
+    gray maxval;
+    
+    /* Read size. */
+    *colsP = (int)pm_getuint(fileP);
+    *rowsP = (int)pm_getuint(fileP);
+
+    /* Read maxval. */
+    maxval = pm_getuint(fileP);
+    if (maxval > PGM_OVERALLMAXVAL)
+        pm_error("maxval of input image (%u) is too large.  "
+                 "The maximum allowed by PGM is %u.", 
+                 maxval, PGM_OVERALLMAXVAL);
+    if (maxval == 0)
+        pm_error("maxval of input image is zero.");
+
+    *maxvalP = maxval;
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+pgm_readpgminit(FILE * const fileP,
+                int *  const colsP, 
+                int *  const rowsP,
+                gray * const maxvalP,
+                int *  const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PGM_TYPE:
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+        
+    case PBM_TYPE:
+        *formatP = realFormat;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        
+        /* Mathematically, it makes the most sense for the maxval of a PBM
+           file seen as a PGM to be 1.  But we tried this for a while and
+           found that it causes unexpected results and frequent need for a
+           Pnmdepth stage to convert the maxval to 255.  You see, when you
+           transform a PGM file in a way that causes interpolated gray shades,
+           there's no in-between value to use when maxval is 1.  It's really
+           hard even to discover that your lack of Pnmdepth is your problem.
+           So we pick 255, which is the most common PGM maxval, and the highest
+           resolution you can get without increasing the size of the PGM 
+           image.
+
+           So this means some programs that are capable of exploiting the
+           bi-level nature of a PBM file must be PNM programs instead of PGM
+           programs.
+        */
+        
+        *maxvalP = PGM_MAXMAXVAL;
+        break;
+
+    case PAM_TYPE:
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, maxvalP, formatP);
+
+        if (PAM_FORMAT_TYPE(*formatP) != PGM_TYPE)
+            pm_error("Format of PAM input is not consistent with PGM");
+
+        break;
+
+    default:
+        pm_error("bad magic number - not a pgm or pbm file");
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+gray
+pgm_getrawsample(FILE * const file,
+                 gray   const maxval) {
+
+    if (maxval < 256) {
+        /* The sample is just one byte.  Read it. */
+        return(pm_getrawbyte(file));
+    } else {
+        /* The sample is two bytes.  Read both. */
+        unsigned char byte_pair[2];
+        size_t pairs_read;
+
+        pairs_read = fread(&byte_pair, 2, 1, file);
+        if (pairs_read == 0) 
+            pm_error("EOF /read error while reading a long sample");
+        /* This could be a few instructions faster if exploited the internal
+           format (i.e. endianness) of a pixval.  Then we might be able to
+           skip the shifting and oring.
+           */
+        return((byte_pair[0]<<8) | byte_pair[1]);
+    }
+}
+
+
+
+void
+pgm_readpgmrow(FILE * const file,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format) {
+
+    switch (format) {
+    case PGM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            grayrow[col] = pm_getuint(file);
+            if (grayrow[col] > maxval)
+                pm_error("value out of bounds (%u > %u)",
+                         grayrow[col], maxval);
+        }
+    }
+    break;
+        
+    case RPGM_FORMAT: {
+        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+        int          const bytesPerRow    = cols * bytesPerSample;
+
+        unsigned char * rowBuffer;
+        ssize_t rc;
+
+        MALLOCARRAY(rowBuffer, bytesPerRow);
+        if (rowBuffer == NULL)
+            pm_error("Unable to allocate memory for row buffer "
+                     "for %u columns", cols);
+
+        rc = fread(rowBuffer, 1, bytesPerRow, file);
+        if (rc == 0)
+            pm_error("Error reading row.  fread() errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            pm_error("Error reading row.  Short read of %u bytes "
+                     "instead of %u", rc, bytesPerRow);
+
+        if (maxval < 256) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col)
+                grayrow[col] = (gray)rowBuffer[col];
+        } else {
+            unsigned int bufferCursor;
+            unsigned int col;
+
+            bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
+
+            for (col = 0; col < cols; ++col) {
+                gray g;
+
+                g = rowBuffer[bufferCursor++] << 8;
+                g |= rowBuffer[bufferCursor++];
+
+                grayrow[col] = g;
+            }
+        }
+        free(rowBuffer);
+    }
+        break;
+    
+    case PBM_FORMAT:
+    case RPBM_FORMAT: {
+        bit * bitrow;
+        int col;
+
+        bitrow = pbm_allocrow(cols);
+        pbm_readpbmrow( file, bitrow, cols, format );
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
+        pbm_freerow(bitrow);
+    }
+        break;
+        
+    default:
+        pm_error( "can't happen" );
+    }
+}
+
+
+
+gray **
+pgm_readpgm(FILE * const file,
+            int *  const colsP,
+            int *  const rowsP, 
+            gray * const maxvalP) {
+
+    gray** grays;
+    int row;
+    int format;
+
+    pgm_readpgminit( file, colsP, rowsP, maxvalP, &format );
+    
+    grays = pgm_allocarray( *colsP, *rowsP );
+    
+    for ( row = 0; row < *rowsP; ++row )
+        pgm_readpgmrow( file, grays[row], *colsP, *maxvalP, format );
+    
+    return grays;
+}
+
+
+
+void
+pgm_check(FILE *               const file, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          gray                 const maxval,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to pgm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to pgm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (PGM_FORMAT_TYPE(format) == PBM_TYPE) {
+        pbm_check(file, check_type, format, cols, rows, retval_p);
+    } else if (format != RPGM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = cols * (maxval > 255 ? 2 : 1);
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+        
+        pm_check(file, check_type, need_raster_size, retval_p);
+        
+    }
+}
diff --git a/lib/libpgm2.c b/lib/libpgm2.c
new file mode 100644
index 00000000..7a04f09b
--- /dev/null
+++ b/lib/libpgm2.c
@@ -0,0 +1,226 @@
+/* libpgm2.c - pgm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pgm.h"
+#include "libpgm.h"
+
+
+
+void
+pgm_writepgminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 gray   const maxval, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    if (maxval > PGM_OVERALLMAXVAL && !plainFormat) 
+        pm_error("too-large maxval passed to ppm_writepgminit(): %d.\n"
+                 "Maximum allowed by the PGM format is %d.",
+                 maxval, PGM_OVERALLMAXVAL);
+
+    fprintf(fileP, "%c%c\n%d %d\n%d\n", 
+            PGM_MAGIC1, 
+            plainFormat || maxval >= 1<<16 ? PGM_MAGIC2 : RPGM_MAGIC2, 
+            cols, rows, maxval );
+#ifdef VMS
+    if (!plainFormat)
+        set_outfile_binary();
+#endif
+}
+
+
+
+static void
+putus(unsigned short const n, 
+      FILE *         const fileP) {
+
+    if (n >= 10)
+        putus(n / 10, fileP);
+    putc('0' + n % 10, fileP);
+}
+
+
+
+void
+pgm_writerawsample(FILE * const fileP,
+                   gray   const val,
+                   gray   const maxval) {
+
+    if (maxval < 256) {
+        /* Samples fit in one byte, so write just one byte */
+        int rc;
+        rc = putc(val, fileP);
+        if (rc == EOF)
+            pm_error("Error writing single byte sample to file");
+    } else {
+        /* Samples are too big for one byte, so write two */
+        int n_written;
+        unsigned char outval[2];
+        /* We could save a few instructions if we exploited the internal
+           format of a gray, i.e. its endianness.  Then we might be able
+           to skip the shifting and anding.
+           */
+        outval[0] = val >> 8;
+        outval[1] = val & 0xFF;
+        n_written = fwrite(&outval, 2, 1, fileP);
+        if (n_written == 0) 
+            pm_error("Error writing double byte sample to file");
+    }
+}
+
+
+
+static void
+format1bpsRow(const gray *    const grayrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* single byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col)
+        rowBuffer[bufferCursor++] = grayrow[col];
+}
+
+
+
+
+static void
+format2bpsRow(const gray    * const grayrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* two byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        gray const val = grayrow[col];
+        
+        rowBuffer[bufferCursor++] = val >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char) val;
+    }
+}
+
+
+
+static void
+writepgmrowraw(FILE *       const fileP,
+               const gray * const grayrow,
+               unsigned int const cols,
+               gray         const maxval) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * bytesPerSample;
+
+    unsigned char * rowBuffer;
+    ssize_t rc;
+
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+
+    if (rowBuffer == NULL)
+        pm_error("Unable to allocate memory for row buffer "
+                 "for %u columns", cols);
+
+    if (maxval < 256)
+        format1bpsRow(grayrow, cols, rowBuffer);
+    else
+        format2bpsRow(grayrow, cols, rowBuffer);
+
+    rc = fwrite(rowBuffer, 1, bytesPerRow, fileP);
+
+    if (rc < 0)
+        pm_error("Error writing row.  fwrite() errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc != bytesPerRow)
+        pm_error("Error writing row.  Short write of %u bytes "
+                 "instead of %u", rc, bytesPerRow);
+
+    free(rowBuffer);
+}
+
+
+
+static void
+writepgmrowplain(FILE *       const fileP,
+                 const gray * const grayrow, 
+                 unsigned int const cols, 
+                 gray         const maxval) {
+
+    int col, charcount;
+
+    charcount = 0;
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 65) {
+            putc('\n', fileP);
+            charcount = 0;
+        } else if (charcount > 0) {
+            putc(' ', fileP);
+            ++charcount;
+        }
+#ifdef DEBUG
+        if (grayrow[col] > maxval)
+            pm_error("value out of bounds (%u > %u)", grayrow[col], maxval);
+#endif /*DEBUG*/
+        putus((unsigned short)grayrow[col], fileP);
+        charcount += 3;
+    }
+    if (charcount > 0)
+        putc('\n', fileP);
+}
+
+
+
+void
+pgm_writepgmrow(FILE *       const fileP, 
+                const gray * const grayrow, 
+                int          const cols, 
+                gray         const maxval, 
+                int          const forceplain) {
+
+    if (forceplain || pm_plain_output || maxval >= 1<<16)
+        writepgmrowplain(fileP, grayrow, cols, maxval);
+    else
+        writepgmrowraw(fileP, grayrow, cols, maxval);
+}
+
+
+
+void
+pgm_writepgm(FILE *  const fileP,
+             gray ** const grays,
+             int     const cols,
+             int     const rows,
+             gray    const maxval,
+             int     const forceplain) {
+
+    unsigned int row;
+
+    pgm_writepgminit(fileP, cols, rows, maxval, forceplain);
+
+    for (row = 0; row < rows; ++row)
+        pgm_writepgmrow(fileP, grays[row], cols, maxval, forceplain);
+}
diff --git a/lib/libpm.c b/lib/libpm.c
new file mode 100644
index 00000000..2e563a09
--- /dev/null
+++ b/lib/libpm.c
@@ -0,0 +1,1494 @@
+/**************************************************************************
+                                  libpm.c
+***************************************************************************
+  This is the most fundamental Netpbm library.  It contains routines
+  not specific to any particular Netpbm format.
+
+  Some of the subroutines in this library are intended and documented
+  for use by Netpbm users, but most of them are just used by other
+  Netpbm library subroutines.
+
+  Before May 2001, this function was served by the libpbm library
+  (in addition to being the library for handling the PBM format).
+
+**************************************************************************/
+#define _SVID_SOURCE
+    /* Make sure P_tmpdir is defined in GNU libc 2.0.7 (_XOPEN_SOURCE 500
+       does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
+       in some environments.
+    */
+#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE_SOURCE 1  /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE64_SOURCE 1 
+#define _FILE_OFFSET_BITS 64
+    /* This means ftello() is really ftello64() and returns a 64 bit file
+       position.  Unless the C library doesn't have ftello64(), in which 
+       case ftello() is still just ftello().
+
+       Likewise for all the other C library file functions.
+
+       And off_t and fpos_t are 64 bit types instead of 32.  Consequently,
+       pm_filepos_t might be 64 bits instead of 32.
+    */
+#define _LARGE_FILES  
+    /* This does for AIX what _FILE_OFFSET_BITS=64 does for GNU */
+#define _LARGE_FILE_API
+    /* This makes the the x64() functions available on AIX */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <setjmp.h>
+#ifdef __DJGPP__
+  #include <io.h>
+#endif
+
+#include "pm_c_util.h"
+#include "version.h"
+#include "compile.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pm.h"
+
+/* The following are set by pm_init(), then used by subsequent calls to other
+   pm_xxx() functions.
+   */
+static const char* pm_progname;
+static bool pm_showmessages;  
+    /* Programs should display informational messages (because the user didn't
+       specify the --quiet option).
+    */
+
+int pm_plain_output;
+    /* Boolean: programs should produce output in plain format */
+
+static jmp_buf * pm_jmpbufP = NULL;
+    /* A description of the point to which the program should hyperjump
+       if a libnetpbm function encounters an error (libnetpbm functions
+       don't normally return in that case).
+
+       User sets this to something in his own extra-library context.
+       Libnetpbm routines that have something that needs to be cleaned up
+       preempt it.
+
+       NULL, which is the default value, means when a libnetpbm function
+       encounters an error, it causes the process to exit.
+    */
+
+
+
+void
+pm_setjmpbuf(jmp_buf * const jmpbufP) {
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_setjmpbufsave(jmp_buf *  const jmpbufP,
+                 jmp_buf ** const oldJmpbufPP) {
+
+    *oldJmpbufPP = pm_jmpbufP;
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_longjmp(void) {
+
+    if (pm_jmpbufP)
+        longjmp(*pm_jmpbufP, 1);
+    else
+        exit(1);
+}
+
+
+
+void
+pm_usage(const char usage[]) {
+    pm_error("usage:  %s %s", pm_progname, usage);
+}
+
+
+
+void
+pm_perror(const char reason[] ) {
+
+    if (reason != NULL && strlen(reason) != 0)
+        pm_error("%s - errno=%d (%s)", reason, errno, strerror(errno));
+    else
+        pm_error("Something failed with errno=%d (%s)", 
+                 errno, strerror(errno));
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_message(const char format[], ...) {
+
+    va_list args;
+
+    va_start(args, format);
+
+    if (pm_showmessages) {
+        fprintf(stderr, "%s: ", pm_progname);
+        vfprintf(stderr, format, args);
+        fputc('\n', stderr);
+    }
+    va_end(args);
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error(const char format[], ...) {
+    va_list args;
+
+    va_start(args, format);
+
+    fprintf(stderr, "%s: ", pm_progname);
+    vfprintf(stderr, format, args);
+    fputc('\n', stderr);
+    va_end(args);
+
+    pm_longjmp();
+}
+
+
+/* Variable-sized arrays. */
+
+char *
+pm_allocrow(unsigned int const cols,
+            unsigned int const size) {
+
+    char * itrow;
+
+    if (UINT_MAX / cols < size)
+        pm_error("Arithmetic overflow multiplying %u by %u to get the "
+                 "size of a row to allocate.", cols, size);
+
+    itrow = malloc(cols * size);
+    if (itrow == NULL)
+        pm_error("out of memory allocating a row");
+
+    return itrow;
+}
+
+
+
+void
+pm_freerow(char * const itrow) {
+    free(itrow);
+}
+
+
+
+char**
+pm_allocarray(int const cols, int const rows, int const size )  {
+/*----------------------------------------------------------------------------
+   Allocate an array of 'rows' rows of 'cols' columns each, with each
+   element 'size' bytes.
+
+   We use a special format where we tack on an extra element to the row
+   index to indicate the format of the array.
+
+   We have two ways of allocating the space: fragmented and
+   unfragmented.  In both, the row index (plus the extra element) is
+   in one block of memory.  In the fragmented format, each row is
+   also in an independent memory block, and the extra row pointer is
+   NULL.  In the unfragmented format, all the rows are in a single
+   block of memory called the row heap and the extra row pointer is
+   the address of that block.
+
+   We use unfragmented format if possible, but if the allocation of the
+   row heap fails, we fall back to fragmented.
+-----------------------------------------------------------------------------*/
+    char** rowIndex;
+    char * rowheap;
+
+    MALLOCARRAY(rowIndex, rows + 1);
+    if (rowIndex == NULL)
+        pm_error("out of memory allocating row index (%u rows) for an array",
+                 rows);
+    rowheap = malloc(rows * cols * size);
+    if (rowheap == NULL) {
+        /* We couldn't get the whole heap in one block, so try fragmented
+           format.
+        */
+        unsigned int row;
+        
+        rowIndex[rows] = NULL;   /* Declare it fragmented format */
+
+        for (row = 0; row < rows; ++row) {
+            rowIndex[row] = pm_allocrow(cols, size);
+            if (rowIndex[row] == NULL)
+                pm_error("out of memory allocating Row %u "
+                         "(%u columns, %u bytes per tuple) "
+                         "of an array", row, cols, size);
+        }
+    } else {
+        /* It's unfragmented format */
+        unsigned int row;
+        rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+
+        for (row = 0; row < rows; ++row)
+            rowIndex[row] = &(rowheap[row * cols * size]);
+    }
+    return rowIndex;
+}
+
+
+
+void
+pm_freearray(char ** const rowIndex, 
+             int     const rows) {
+
+    void * const rowheap = rowIndex[rows];
+
+    if (rowheap != NULL)
+        free(rowheap);
+    else {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pm_freerow(rowIndex[row]);
+    }
+    free(rowIndex);
+}
+
+
+
+/* Case-insensitive keyword matcher. */
+
+int
+pm_keymatch(char *       const strarg, 
+            const char * const keywordarg, 
+            int          const minchars) {
+    int len;
+    const char *keyword;
+    char *str;
+
+    str = strarg;
+    keyword = keywordarg;
+
+    len = strlen( str );
+    if ( len < minchars )
+        return 0;
+    while ( --len >= 0 )
+        {
+        register char c1, c2;
+
+        c1 = *str++;
+        c2 = *keyword++;
+        if ( c2 == '\0' )
+            return 0;
+        if ( ISUPPER( c1 ) )
+            c1 = tolower( c1 );
+        if ( ISUPPER( c2 ) )
+            c2 = tolower( c2 );
+        if ( c1 != c2 )
+            return 0;
+        }
+    return 1;
+}
+
+
+/* Log base two hacks. */
+
+int
+pm_maxvaltobits(int const maxval) {
+    if ( maxval <= 1 )
+        return 1;
+    else if ( maxval <= 3 )
+        return 2;
+    else if ( maxval <= 7 )
+        return 3;
+    else if ( maxval <= 15 )
+        return 4;
+    else if ( maxval <= 31 )
+        return 5;
+    else if ( maxval <= 63 )
+        return 6;
+    else if ( maxval <= 127 )
+        return 7;
+    else if ( maxval <= 255 )
+        return 8;
+    else if ( maxval <= 511 )
+        return 9;
+    else if ( maxval <= 1023 )
+        return 10;
+    else if ( maxval <= 2047 )
+        return 11;
+    else if ( maxval <= 4095 )
+        return 12;
+    else if ( maxval <= 8191 )
+        return 13;
+    else if ( maxval <= 16383 )
+        return 14;
+    else if ( maxval <= 32767 )
+        return 15;
+    else if ( (long) maxval <= 65535L )
+        return 16;
+    else
+        pm_error( "maxval of %d is too large!", maxval );
+        return -1;  /* Should never come here */
+}
+
+int
+pm_bitstomaxval(int const bits) {
+    return ( 1 << bits ) - 1;
+}
+
+
+unsigned int PURE_FN_ATTR
+pm_lcm(unsigned int const x, 
+       unsigned int const y,
+       unsigned int const z,
+       unsigned int const limit) {
+/*----------------------------------------------------------------------------
+  Compute the least common multiple of 'x', 'y', and 'z'.  If it's bigger than
+  'limit', though, just return 'limit'.
+-----------------------------------------------------------------------------*/
+    unsigned int biggest;
+    unsigned int candidate;
+
+    if (x == 0 || y == 0 || z == 0)
+        pm_error("pm_lcm(): Least common multiple of zero taken.");
+
+    biggest = MAX(x, MAX(y,z));
+
+    candidate = biggest;
+    while (((candidate % x) != 0 ||       /* not a multiple of x */
+            (candidate % y) != 0 ||       /* not a multiple of y */
+            (candidate % z) != 0 ) &&     /* not a multiple of z */
+           candidate <= limit)
+        candidate += biggest;
+
+    if (candidate > limit) 
+        candidate = limit;
+
+    return candidate;
+}
+
+
+/* Initialization. */
+
+
+#ifdef VMS
+static const char *
+vmsProgname(int * const argcP, char * argv[]) {   
+    char **temp_argv = argv;
+    int old_argc = *argcP;
+    int i;
+    const char * retval;
+    
+    getredirection( argcP, &temp_argv );
+    if (*argcP > old_argc) {
+        /* Number of command line arguments has increased */
+        fprintf( stderr, "Sorry!! getredirection() for VMS has "
+                 "changed the argument list!!!\n");
+        fprintf( stderr, "This is intolerable at the present time, "
+                 "so we must stop!!!\n");
+        exit(1);
+    }
+    for (i=0; i<*argcP; i++)
+        argv[i] = temp_argv[i];
+    retval = strrchr( argv[0], '/');
+    if ( retval == NULL ) retval = rindex( argv[0], ']');
+    if ( retval == NULL ) retval = rindex( argv[0], '>');
+
+    return retval;
+}
+#endif
+
+
+
+void
+pm_init(const char * const progname, unsigned int const flags) {
+/*----------------------------------------------------------------------------
+   Initialize static variables that Netpbm library routines use.
+
+   Any user of Netpbm library routines is expected to call this at the
+   beginning of this program, before any other Netpbm library routines.
+
+   A program may call this via pm_proginit() instead, though.
+-----------------------------------------------------------------------------*/
+    pm_setMessage(FALSE, NULL);
+
+    pm_progname = progname;
+
+#ifdef O_BINARY
+#ifdef HAVE_SETMODE
+    /* Set the stdin and stdout mode to binary.  This means nothing on Unix,
+       but matters on Windows.
+       
+       Note that stdin and stdout aren't necessarily image files.  In
+       particular, stdout is sometimes text for human consumption,
+       typically printed on the terminal.  Binary mode isn't really
+       appropriate for that case.  We do this setting here without
+       any knowledge of how stdin and stdout are being used because it is
+       easy.  But we do make an exception for the case that we know the
+       file is a terminal, to get a little closer to doing the right
+       thing.  
+    */
+    if (!isatty(0)) setmode(0,O_BINARY);  /* Standard Input */
+    if (!isatty(1)) setmode(1,O_BINARY);  /* Standard Output */
+#endif /* HAVE_SETMODE */
+#endif /* O_BINARY */
+}
+
+
+
+static void
+showVersion(void) {
+    pm_message( "Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION );
+#if defined(COMPILE_TIME) && defined(COMPILED_BY)
+    pm_message( "Compiled %s by user \"%s\"",
+                COMPILE_TIME, COMPILED_BY );
+#endif
+#ifdef BSD
+    pm_message( "BSD defined" );
+#endif /*BSD*/
+#ifdef SYSV
+#ifdef VMS
+    pm_message( "VMS & SYSV defined" );
+#else
+    pm_message( "SYSV defined" );
+#endif
+#endif /*SYSV*/
+#ifdef MSDOS
+    pm_message( "MSDOS defined" );
+#endif /*MSDOS*/
+#ifdef AMIGA
+    pm_message( "AMIGA defined" );
+#endif /* AMIGA */
+    {
+        const char * rgbdef;
+        pm_message( "RGB_ENV='%s'", RGBENV );
+        rgbdef = getenv(RGBENV);
+        if( rgbdef )
+            pm_message( "RGBENV= '%s' (env vbl set to '%s')", 
+                        RGBENV, rgbdef );
+        else
+            pm_message( "RGBENV= '%s' (env vbl is unset)", RGBENV);
+    }
+}
+
+
+
+static void
+showNetpbmHelp(const char progname[]) {
+/*----------------------------------------------------------------------------
+  Tell the user where to get help for this program, assuming it is a Netpbm
+  program (a program that comes with the Netpbm package, as opposed to a 
+  program that just uses the Netpbm libraries).
+
+  Tell him to go to the URL listed in the Netpbm configuration file.
+  The Netpbm configuration file is the file named by the NETPBM_CONF
+  environment variable, or /etc/netpbm if there is no such environment
+  variable.
+
+  If the configuration file doesn't exist or can't be read, or doesn't
+  contain a DOCURL value, tell him to go to a hardcoded source for
+  documentation.
+-----------------------------------------------------------------------------*/
+    const char * netpbmConfigFileName;
+    FILE * netpbmConfigFile;
+    char * docurl;
+
+    if (getenv("NETPBM_CONF"))
+        netpbmConfigFileName = getenv("NETPBM_CONF");
+    else 
+        netpbmConfigFileName = "/etc/netpbm";
+    
+    netpbmConfigFile = fopen(netpbmConfigFileName, "r");
+    if (netpbmConfigFile == NULL) {
+        pm_message("Unable to open Netpbm configuration file '%s'.  "
+                   "Errno = %d (%s).  "
+                   "Use the NETPBM_CONF environment variable "
+                   "to control the identity of the Netpbm configuration file.",
+                   netpbmConfigFileName,errno, strerror(errno));
+        docurl = NULL;
+    } else {
+        docurl = NULL;  /* default */
+        while (!feof(netpbmConfigFile) && !ferror(netpbmConfigFile)) {
+            char line[80+1];
+            fgets(line, sizeof(line), netpbmConfigFile);
+            if (line[0] != '#') {
+                sscanf(line, "docurl=%s", docurl);
+            }
+        }
+        if (docurl == NULL)
+            pm_message("No 'docurl=' line in Netpbm configuration file '%s'.",
+                       netpbmConfigFileName);
+    }
+    if (docurl == NULL)
+        pm_message("We have no reliable indication of where the Netpbm "
+                   "documentation is, but try "
+                   "http://netpbm.sourceforge.net or email "
+                   "Bryan Henderson (bryanh@giraffe-data.com) for help.");
+    else
+        pm_message("This program is part of the Netpbm package.  Find "
+                   "documentation for it at %s/%s\n", docurl, progname);
+}
+
+
+
+void
+pm_proginit(int * const argcP, char * argv[]) {
+/*----------------------------------------------------------------------------
+   Do various initialization things that all programs in the Netpbm package,
+   and programs that emulate such programs, should do.
+
+   This includes processing global options.
+
+   This includes calling pm_init() to initialize the Netpbm libraries.
+-----------------------------------------------------------------------------*/
+    int argn, i;
+    const char * progname;
+    bool showmessages;
+    bool show_version;
+        /* We're supposed to just show the version information, then exit the
+           program.
+        */
+    bool show_help;
+        /* We're supposed to just tell user where to get help, then exit the
+           program.
+        */
+    
+    /* Extract program name. */
+#ifdef VMS
+    progname = vmsProgname(argcP, argv);
+#else
+    progname = strrchr( argv[0], '/');
+#endif
+    if (progname == NULL)
+        progname = argv[0];
+    else
+        ++progname;
+
+    pm_init(progname, 0);
+
+    /* Check for any global args. */
+    showmessages = TRUE;
+    show_version = FALSE;
+    show_help = FALSE;
+    pm_plain_output = FALSE;
+    for (argn = 1; argn < *argcP; ++argn) {
+        if (pm_keymatch(argv[argn], "-quiet", 6) ||
+            pm_keymatch(argv[argn], "--quiet", 7)) 
+            showmessages = FALSE;
+        else if (pm_keymatch(argv[argn], "-version", 8) ||
+                   pm_keymatch(argv[argn], "--version", 9)) 
+            show_version = TRUE;
+        else if (pm_keymatch(argv[argn], "-help", 5) ||
+                 pm_keymatch(argv[argn], "--help", 6) ||
+                 pm_keymatch(argv[argn], "-?", 2)) 
+            show_help = TRUE;
+        else if (pm_keymatch(argv[argn], "-plain", 6) ||
+                 pm_keymatch(argv[argn], "--plain", 7))
+            pm_plain_output = TRUE;
+        else
+            continue;
+        for (i = argn + 1; i <= *argcP; ++i)
+            argv[i - 1] = argv[i];
+        --(*argcP);
+    }
+
+    pm_setMessage((unsigned int) showmessages, NULL);
+
+    if (show_version) {
+        showVersion();
+        exit( 0 );
+    } else if (show_help) {
+        pm_error("Use 'man %s' for help.", progname);
+        /* If we can figure out a way to distinguish Netpbm programs from 
+           other programs using the Netpbm libraries, we can do better here.
+        */
+        if (0)
+            showNetpbmHelp(progname);
+        exit(0);
+    }
+}
+
+
+void
+pm_setMessage(int const newState, int * const oldStateP) {
+    
+    if (oldStateP)
+        *oldStateP = pm_showmessages;
+
+    pm_showmessages = !!newState;
+}
+
+
+char *
+pm_arg0toprogname(const char arg0[]) {
+/*----------------------------------------------------------------------------
+   Given a value for argv[0] (a command name or file name passed to a 
+   program in the standard C calling sequence), return the name of the
+   Netpbm program to which is refers.
+
+   In the most ordinary case, this is simply the argument itself.
+
+   But if the argument contains a slash, it is the part of the argument 
+   after the last slash, and if there is a .exe on it (as there is for
+   DJGPP), that is removed.
+
+   The return value is in static storage within.  It is null-terminated,
+   but truncated at 64 characters.
+-----------------------------------------------------------------------------*/
+    static char retval[64+1];
+    char *slash_pos;
+
+    /* Chop any directories off the left end */
+    slash_pos = strrchr(arg0, '/');
+
+    if (slash_pos == NULL) {
+        strncpy(retval, arg0, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    } else {
+        strncpy(retval, slash_pos +1, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    }
+
+    /* Chop any .exe off the right end */
+    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
+        retval[strlen(retval)-4] = 0;
+
+    return(retval);
+}
+
+
+
+/* File open/close that handles "-" as stdin/stdout and checks errors. */
+
+FILE*
+pm_openr(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdin;
+    else {
+#ifndef VMS
+        f = fopen(name, "rb");
+#else
+        f = fopen(name, "r", "ctx=stm");
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for reading.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+FILE*
+pm_openw(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdout;
+    else {
+#ifndef VMS
+        f = fopen(name, "wb");
+#else
+        f = fopen(name, "w", "mbc=32", "mbf=2");  /* set buffer factors */
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for writing.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+static const char *
+tmpDir(void) {
+/*----------------------------------------------------------------------------
+   Return the name of the directory in which we should create temporary
+   files.
+
+   The name is a constant in static storage.
+-----------------------------------------------------------------------------*/
+    const char * tmpdir;
+        /* running approximation of the result */
+
+    tmpdir = getenv("TMPDIR");   /* Unix convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TMP");  /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TEMP"); /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = TMPDIR;
+
+    return tmpdir;
+}
+
+
+
+static int
+mkstempx(char * const filenameBuffer) {
+/*----------------------------------------------------------------------------
+  This is meant to be equivalent to POSIX mkstemp().
+
+  On some old systems, mktemp() is a security hazard that allows a hacker
+  to read or write our temporary file or cause us to read or write some
+  unintended file.  On other systems, mkstemp() does not exist.
+
+  A Windows/mingw environment is one which doesn't have mkstemp()
+  (2006.06.15).
+
+  We assume that if a system doesn't have mkstemp() that its mktemp()
+  is safe, or that the total situation is such that the problems of
+  mktemp() are not a problem for the user.
+-----------------------------------------------------------------------------*/
+    int retval;
+    int fd;
+    unsigned int attempts;
+    bool gotFile;
+    bool error;
+
+    for (attempts = 0, gotFile = FALSE, error = FALSE;
+         !gotFile && !error && attempts < 100;
+         ++attempts) {
+
+        char * rc;
+        rc = mktemp(filenameBuffer);
+
+        if (rc == NULL)
+            error = TRUE;
+        else {
+            int rc;
+
+            rc = open(filenameBuffer, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+
+            if (rc == 0) {
+                fd = rc;
+                gotFile = TRUE;
+            } else {
+                if (errno == EEXIST) {
+                    /* We'll just have to keep trying */
+                } else 
+                    error = TRUE;
+            }
+        }
+    }    
+    if (gotFile)
+        retval = fd;
+    else
+        retval = -1;
+
+    return retval;
+}
+
+
+
+static int
+mkstemp2(char * const filenameBuffer) {
+
+#if HAVE_MKSTEMP
+    if (0)
+        mkstempx(NULL);  /* defeat compiler unused function warning */
+    return mkstemp(filenameBuffer);
+#else
+    return mkstempx(filenameBuffer);
+#endif
+}
+
+
+
+void
+pm_make_tmpfile(FILE **       const filePP,
+                const char ** const filenameP) {
+
+    int fd;
+    FILE * fileP;
+    const char * filenameTemplate;
+    char * filenameBuffer;  /* malloc'ed */
+    unsigned int fnamelen;
+    const char * tmpdir;
+    const char * dirseparator;
+
+    fnamelen = strlen (pm_progname) + 10; /* "/" + "_XXXXXX\0" */
+
+    tmpdir = tmpDir();
+
+    if (tmpdir[strlen(tmpdir) - 1] == '/')
+        dirseparator = "";
+    else
+        dirseparator = "/";
+    
+    asprintfN(&filenameTemplate, "%s%s%s%s", 
+              tmpdir, dirseparator, pm_progname, "_XXXXXX");
+
+    if (filenameTemplate == NULL)
+        pm_error("Unable to allocate storage for temporary file name");
+
+    filenameBuffer = strdup(filenameTemplate);
+
+    fd = mkstemp2(filenameBuffer);
+
+    if (fd < 0)
+        pm_error("Unable to create temporary file according to name "
+                 "pattern '%s'.  mkstemp() failed with "
+                 "errno %d (%s)", filenameTemplate, errno, strerror(errno));
+    else {
+        fileP = fdopen(fd, "w+b");
+
+        if (fileP == NULL)
+            pm_error("Unable to create temporary file.  fdopen() failed "
+                     "with errno %d (%s)", errno, strerror(errno));
+    }
+    strfree(filenameTemplate);
+
+    *filenameP = filenameBuffer;
+    *filePP = fileP;
+}
+
+
+
+FILE * 
+pm_tmpfile(void) {
+
+    FILE * fileP;
+    const char * tmpfile;
+
+    pm_make_tmpfile(&fileP, &tmpfile);
+
+    unlink(tmpfile);
+
+    strfree(tmpfile);
+
+    return fileP;
+}
+
+
+
+FILE *
+pm_openr_seekable(const char name[]) {
+/*----------------------------------------------------------------------------
+  Open the file named by name[] such that it is seekable (i.e. it can be
+  rewound and read in multiple passes with fseek()).
+
+  If the file is actually seekable, this reduces to the same as
+  pm_openr().  If not, we copy the named file to a temporary file
+  and return that file's stream descriptor.
+
+  We use a file that the operating system recognizes as temporary, so
+  it picks the filename and deletes the file when Caller closes it.
+-----------------------------------------------------------------------------*/
+    int stat_rc;
+    int seekable;  /* logical: file is seekable */
+    struct stat statbuf;
+    FILE * original_file;
+    FILE * seekable_file;
+
+    original_file = pm_openr((char *) name);
+
+    /* I would use fseek() to determine if the file is seekable and 
+       be a little more general than checking the type of file, but I
+       don't have reliable information on how to do that.  I have seen
+       streams be partially seekable -- you can, for example seek to
+       0 if the file is positioned at 0 but you can't actually back up
+       to 0.  I have seen documentation that says the errno for an
+       unseekable stream is EBADF and in practice seen ESPIPE.
+
+       On the other hand, regular files are always seekable and even if
+       some other file is, it doesn't hurt much to assume it isn't.
+    */
+
+    stat_rc = fstat(fileno(original_file), &statbuf);
+    if (stat_rc == 0 && S_ISREG(statbuf.st_mode))
+        seekable = TRUE;
+    else 
+        seekable = FALSE;
+
+    if (seekable) {
+        seekable_file = original_file;
+    } else {
+        seekable_file = pm_tmpfile();
+        
+        /* Copy the input into the temporary seekable file */
+        while (!feof(original_file) && !ferror(original_file) 
+               && !ferror(seekable_file)) {
+            char buffer[4096];
+            int bytes_read;
+            bytes_read = fread(buffer, 1, sizeof(buffer), original_file);
+            fwrite(buffer, 1, bytes_read, seekable_file);
+        }
+        if (ferror(original_file))
+            pm_error("Error reading input file into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        if (ferror(seekable_file))
+            pm_error("Error writing input into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        pm_close(original_file);
+        {
+            int seek_rc;
+            seek_rc = fseek(seekable_file, 0, SEEK_SET);
+            if (seek_rc != 0)
+                pm_error("fseek() failed to rewind temporary file.  "
+                         "Errno = %s (%d)", strerror(errno), errno);
+        }
+    }
+    return seekable_file;
+}
+
+
+
+void
+pm_close(FILE * const f) {
+    fflush(f);
+    if (ferror(f))
+        pm_message("A file read or write error occurred at some point");
+    if (f != stdin)
+        if (fclose(f) != 0)
+            pm_error("close of file failed with errno %d (%s)",
+                     errno, strerror(errno));
+}
+
+
+
+/* The pnmtopng package uses pm_closer() and pm_closew() instead of 
+   pm_close(), apparently because the 1999 Pbmplus package has them.
+   I don't know what the difference is supposed to be.
+*/
+
+void
+pm_closer(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+void
+pm_closew(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+/* Endian I/O.
+
+   Before Netpbm 10.27 (March 2005), these would return failure on EOF
+   or I/O failure.  For backward compatibility, they still have the return
+   code, but it is always zero and the routines abort the program in case
+   of EOF or I/O failure.  A program that wants to handle failure differently
+   must use lower level (C library) interfaces.  But that level of detail
+   is uncharacteristic of a Netpbm program; the ease of programming that
+   comes with not checking a return code is more Netpbm.
+
+   It is also for historical reasons that these return signed values,
+   when clearly unsigned would make more sense.
+*/
+
+
+
+static void
+abortWithReadError(FILE * const ifP) {
+
+    if (feof(ifP))
+        pm_error("Unexpected end of input file");
+    else
+        pm_error("Error (not EOF) reading file.");
+}
+
+
+
+void
+pm_readchar(FILE * const ifP,
+            char * const cP) {
+    
+    int c;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+
+    *cP = c;
+}
+
+
+
+void
+pm_writechar(FILE * const ofP,
+             char   const c) {
+
+    putc(c, ofP);
+}
+
+
+
+int
+pm_readbigshort(FILE *  const ifP, 
+                short * const sP) {
+    int c;
+
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = (c & 0xff) << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= c & 0xff;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writebigshort(FILE * const ofP, 
+                 short  const s) {
+
+    putc((s >> 8) & 0xff, ofP);
+    putc(s & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readbiglong(FILE * const ifP, 
+               long * const lP) {
+
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c << 24;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writebiglong(FILE * const ofP, 
+                long   const l) {
+
+    putc((l >> 24) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >>  0) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittleshort(FILE *  const ifP, 
+                   short * const sP) {
+    int c;
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = c & 0xff;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= (c & 0xff) << 8;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittleshort(FILE * const ofP, 
+                    short  const s) {
+
+    putc((s >> 0) & 0xff, ofP);
+    putc((s >> 8) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittlelong(FILE * const ifP, 
+                  long * const lP) {
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 24;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittlelong(FILE * const ofP, 
+                   long   const l) {
+
+    putc((l >>  0) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >> 24) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int 
+pm_readmagicnumber(FILE * const ifP) {
+
+    int ich1, ich2;
+
+    ich1 = getc(ifP);
+    ich2 = getc(ifP);
+    if (ich1 == EOF || ich2 == EOF)
+        pm_error( "Error reading magic number from Netpbm image stream.  "
+                  "Most often, this "
+                  "means your input file is empty." );
+
+    return ich1 * 256 + ich2;
+}
+
+
+
+/* Read a file of unknown size to a buffer. Return the number of bytes
+   read. Allocate more memory as we need it. The calling routine has
+   to free() the buffer.
+
+   Oliver Trepte, oliver@fysik4.kth.se, 930613 */
+
+#define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
+                                   double this until we reach PM_MAX_BUF_INC */
+#define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
+                                   than this. */
+
+char *
+pm_read_unknown_size(FILE * const file, 
+                     long * const nread) {
+    long nalloc;
+    char * buf;
+    bool eof;
+
+    *nread = 0;
+    nalloc = PM_BUF_SIZE;
+    MALLOCARRAY(buf, nalloc);
+
+    eof = FALSE;  /* initial value */
+
+    while(!eof) {
+        int val;
+
+        if (*nread >= nalloc) { /* We need a larger buffer */
+            if (nalloc > PM_MAX_BUF_INC)
+                nalloc += PM_MAX_BUF_INC;
+            else
+                nalloc += nalloc;
+            REALLOCARRAY_NOFAIL(buf, nalloc);
+        }
+
+        val = getc(file);
+        if (val == EOF)
+            eof = TRUE;
+        else 
+            buf[(*nread)++] = val;
+    }
+    return buf;
+}
+
+
+
+union cheat {
+    uint32n l;
+    short s;
+    unsigned char c[4];
+};
+
+
+
+short
+pm_bs_short(short const s) {
+    union cheat u;
+    unsigned char t;
+
+    u.s = s;
+    t = u.c[0];
+    u.c[0] = u.c[1];
+    u.c[1] = t;
+    return u.s;
+}
+
+
+
+long
+pm_bs_long(long const l) {
+    union cheat u;
+    unsigned char t;
+
+    u.l = l;
+    t = u.c[0];
+    u.c[0] = u.c[3];
+    u.c[3] = t;
+    t = u.c[1];
+    u.c[1] = u.c[2];
+    u.c[2] = t;
+    return u.l;
+}
+
+
+
+void
+pm_tell2(FILE *       const fileP, 
+         void *       const fileposP,
+         unsigned int const fileposSize) {
+/*----------------------------------------------------------------------------
+   Return the current file position as *filePosP, which is a buffer
+   'fileposSize' bytes long.  Abort the program if error, including if
+   *fileP isn't a file that has a position.
+-----------------------------------------------------------------------------*/
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    pm_filepos const filepos = FTELLO(fileP);
+    if (filepos < 0)
+        pm_error("ftello() to get current file position failed.  "
+                 "Errno = %s (%d)\n", strerror(errno), errno);
+
+    if (fileposSize == sizeof(pm_filepos)) {
+        pm_filepos * const fileposP_filepos = fileposP;
+        *fileposP_filepos = filepos;
+    } else if (fileposSize == sizeof(long)) {
+        if (sizeof(pm_filepos) > sizeof(long) &&
+            filepos >= (pm_filepos) 1 << (sizeof(long)*8))
+            pm_error("File size is too large to represent in the %u bytes "
+                     "that were provided to pm_tell2()", fileposSize);
+        else {
+            long * const fileposP_long = fileposP;
+            *fileposP_long = (long)filepos;
+        }
+    } 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));
+}
+
+
+
+unsigned int
+pm_tell(FILE * const fileP) {
+    
+    long filepos;
+
+    pm_tell2(fileP, &filepos, sizeof(filepos));
+
+    return filepos;
+}
+
+
+
+void
+pm_seek2(FILE *             const fileP, 
+         const pm_filepos * const fileposP,
+         unsigned int       const fileposSize) {
+/*----------------------------------------------------------------------------
+   Position file *fileP to position *fileposP.  Abort if error, including
+   if *fileP isn't a seekable file.
+-----------------------------------------------------------------------------*/
+    if (fileposSize == sizeof(pm_filepos)) 
+        /* Note: FSEEKO() is either fseeko() or fseek(), depending on the
+           capabilities of the underlying C library.  It is defined in
+           pm_config.h.  fseeko(), in turn, may be either fseek() or
+           fseeko64(), as implemented by the C library.
+        */
+        FSEEKO(fileP, *fileposP, SEEK_SET);
+    else if (fileposSize == sizeof(long)) {
+        long const fileposLong = *(long *)fileposP;
+        fseek(fileP, fileposLong, SEEK_SET);
+    } 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));
+}
+
+
+
+void
+pm_seek(FILE * const fileP, unsigned long filepos) {
+/*----------------------------------------------------------------------------
+-----------------------------------------------------------------------------*/
+
+    pm_filepos fileposBuff;
+
+    fileposBuff = filepos;
+
+    pm_seek2(fileP, &fileposBuff, sizeof(fileposBuff));
+}
+
+
+
+void
+pm_nextimage(FILE * const file, int * const eofP) {
+/*----------------------------------------------------------------------------
+   Position the file 'file' to the next image in the stream, assuming it is
+   now positioned just after the current image.  I.e. read off any white
+   space at the end of the current image's raster.  Note that the raw formats
+   don't permit such white space, but this routine tolerates it anyway, 
+   because the plain formats do permit white space after the raster.
+
+   Iff there is no next image, return *eofP == TRUE.
+
+   Note that in practice, we will not normally see white space here in
+   a plain PPM or plain PGM stream because the routine to read a
+   sample from the image reads one character of white space after the
+   sample in order to know where the sample ends.  There is not
+   normally more than one character of white space (a newline) after
+   the last sample in the raster.  But plain PBM is another story.  No white
+   space is required between samples of a plain PBM image.  But the raster
+   normally ends with a newline nonetheless.  Since the sample reading code
+   will not have read that newline, it is there for us to read now.
+-----------------------------------------------------------------------------*/
+    bool eof;
+    bool nonWhitespaceFound;
+
+    eof = FALSE;
+    nonWhitespaceFound = FALSE;
+
+    while (!eof && !nonWhitespaceFound) {
+        int c;
+        c = getc(file);
+        if (c == EOF) {
+            if (feof(file)) 
+                eof = TRUE;
+            else
+                pm_error("File error on getc() to position to image");
+        } else {
+            if (!isspace(c)) {
+                int rc;
+
+                nonWhitespaceFound = TRUE;
+
+                /* Have to put the non-whitespace character back in
+                   the stream -- it's part of the next image.  
+                */
+                rc = ungetc(c, file);
+                if (rc == EOF) 
+                    pm_error("File error doing ungetc() "
+                             "to position to image.");
+            }
+        }
+    }
+    *eofP = eof;
+}
+
+
+
+void
+pm_check(FILE *               const file, 
+         enum pm_check_type   const check_type, 
+         pm_filepos           const need_raster_size,
+         enum pm_check_code * const retval_p) {
+/*----------------------------------------------------------------------------
+   This is not defined for use outside of libnetpbm.
+-----------------------------------------------------------------------------*/
+    struct stat statbuf;
+    pm_filepos curpos;  /* Current position of file; -1 if none */
+    int rc;
+
+#ifdef LARGEFILEDEBUG
+    pm_message("pm_filepos received by pm_check() is %u bytes.",
+               sizeof(pm_filepos));
+#endif
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    curpos = FTELLO(file);
+    if (curpos >= 0) {
+        /* This type of file has a current position */
+            
+        rc = fstat(fileno(file), &statbuf);
+        if (rc != 0) 
+            pm_error("fstat() failed to get size of file, though ftello() "
+                     "successfully identified\n"
+                     "the current position.  Errno=%s (%d)",
+                     strerror(errno), errno);
+        else if (!S_ISREG(statbuf.st_mode)) {
+            /* Not a regular file; we can't know its size */
+            if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        } else {
+            pm_filepos const have_raster_size = statbuf.st_size - curpos;
+            
+            if (have_raster_size < need_raster_size)
+                pm_error("File has invalid format.  The raster should "
+                         "contain %u bytes, but\n"
+                         "the file ends after only %u bytes.",
+                         (unsigned int) need_raster_size, 
+                         (unsigned int) have_raster_size);
+            else if (have_raster_size > need_raster_size) {
+                if (retval_p) *retval_p = PM_CHECK_TOO_LONG;
+            } else {
+                if (retval_p) *retval_p = PM_CHECK_OK;
+            }
+        }
+    } else
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+}
+
+
+
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
new file mode 100644
index 00000000..82f99b93
--- /dev/null
+++ b/lib/libpnm1.c
@@ -0,0 +1,222 @@
+/* libpnm1.c - pnm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+#include "pam.h"
+#include "libpam.h"
+
+#include "mallocvar.h"
+
+
+
+xel *
+pnm_allocrow(unsigned int const cols) {
+
+    xel * xelrow;
+
+    MALLOCARRAY(xelrow, cols);
+
+    if (xelrow == NULL)
+        pm_error("Unable to allocate space for a %u-column xel row", cols);
+
+    return xelrow;
+}
+
+
+
+void
+pnm_init( argcP, argv )
+    int* argcP;
+    char* argv[];
+    {
+    ppm_init( argcP, argv );
+    }
+
+void
+pnm_nextimage(FILE *file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+pnm_readpnminit(FILE *   const fileP,
+                int *    const colsP,
+                int *    const rowsP,
+                xelval * const maxvalP,
+                int *    const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PPM_TYPE: {
+        pixval maxval;
+        *formatP = realFormat;
+        ppm_readppminitrest(fileP, colsP, rowsP, &maxval);
+        *maxvalP = maxval;
+    }
+    break;
+
+    case PGM_TYPE: {
+        gray maxval;
+
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, &maxval);
+        *maxvalP = maxval;
+    }
+    break;
+
+    case PBM_TYPE:
+        *formatP = realFormat;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        *maxvalP = 1;
+    break;
+
+    case PAM_TYPE: {
+        gray maxval;
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, &maxval, formatP);
+        *maxvalP = maxval;
+    }
+    break;
+
+    default:
+        pm_error("bad magic number - not a ppm, pgm, or pbm file");
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+void
+pnm_readpnmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_readppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, format);
+        break;
+
+    case PGM_TYPE: {
+        gray * grayrow;
+        unsigned int col;
+
+        grayrow = pgm_allocrow(cols);
+        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], grayrow[col]);
+        pgm_freerow(grayrow);
+    }
+    break;
+        
+    case PBM_TYPE: {
+        bit * bitrow;
+        unsigned int col;
+        bitrow = pbm_allocrow(cols);
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0: maxval);
+        pbm_freerow(bitrow);
+    }
+    break;
+
+    default:
+        pm_error("INTERNAL ERROR.  Impossible format.");
+    }
+}
+
+
+
+xel **
+pnm_readpnm(FILE *   const fileP,
+            int *    const colsP,
+            int *    const rowsP,
+            xelval * const maxvalP,
+            int *    const formatP) {
+
+    xel ** xels;
+    int row;
+
+    pnm_readpnminit(fileP, colsP, rowsP, maxvalP, formatP);
+
+    xels = pnm_allocarray(*colsP, *rowsP);
+
+    for (row = 0; row < *rowsP; ++row)
+        pnm_readpnmrow(fileP, xels[row], *colsP, *maxvalP, *formatP);
+
+    return xels;
+}
+
+
+
+void
+pnm_check(FILE *               const fileP,
+          enum pm_check_type   const check_type, 
+          int                  const format,
+          int                  const cols,
+          int                  const rows,
+          int                  const maxval,
+          enum pm_check_code * const retvalP) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        pbm_check(fileP, check_type, format, cols, rows, retvalP);
+        break;
+    case PGM_TYPE: 
+        pgm_check(fileP, check_type, format, cols, rows, maxval, retvalP);
+        break;
+    case PPM_TYPE:
+        ppm_check(fileP, check_type, format, cols, rows, maxval, retvalP);
+        break;
+    default:
+        pm_error("pnm_check() called with invalid format parameter");
+    }
+}
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
new file mode 100644
index 00000000..aae78d52
--- /dev/null
+++ b/lib/libpnm2.c
@@ -0,0 +1,128 @@
+/* libpnm2.c - pnm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+void
+pnm_writepnminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 xelval const maxval, 
+                 int    const format, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_writeppminit(fileP, cols, rows, (pixval) maxval, plainFormat);
+        break;
+
+    case PGM_TYPE:
+        pgm_writepgminit(fileP, cols, rows, (gray) maxval, plainFormat);
+        break;
+
+    case PBM_TYPE:
+        pbm_writepbminit(fileP, cols, rows, plainFormat);
+    break;
+
+    default:
+        pm_error("invalid format argument received by pnm_writepnminit(): %d"
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
+    }
+}
+
+
+
+void
+pnm_writepnmrow(FILE * const fileP, 
+                xel *  const xelrow, 
+                int    const cols, 
+                xelval const maxval, 
+                int    const format, 
+                int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+    
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_writeppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, 
+                        plainFormat);
+        break;
+
+    case PGM_TYPE: {
+        gray* grayrow;
+        unsigned int col;
+
+        grayrow = pgm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = PNM_GET1(xelrow[col]);
+
+        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
+
+        pgm_freerow( grayrow );
+    }
+    break;
+
+    case PBM_TYPE: {
+        bit* bitrow;
+        unsigned int col;
+
+        bitrow = pbm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col)
+            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
+
+        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+
+        pbm_freerow(bitrow);
+    }    
+    break;
+    
+    default:
+        pm_error("invalid format argument received by pnm_writepnmrow(): %d"
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
+    }
+}
+
+
+
+void
+pnm_writepnm(FILE * const fileP,
+             xel ** const xels,
+             int    const cols,
+             int    const rows,
+             xelval const maxval,
+             int    const format,
+             int    const forceplain) {
+
+    unsigned int row;
+
+    pnm_writepnminit(fileP, cols, rows, maxval, format, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        pnm_writepnmrow(fileP, xels[row], cols, maxval, format, forceplain);
+}
diff --git a/lib/libpnm3.c b/lib/libpnm3.c
new file mode 100644
index 00000000..c9c9a1b0
--- /dev/null
+++ b/lib/libpnm3.c
@@ -0,0 +1,393 @@
+/* libpnm3.c - pnm utility library part 3
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+#if __STDC__
+xel
+pnm_backgroundxel( xel** xels, int cols, int rows, xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_backgroundxel( xels, cols, rows, maxval, format )
+    xel** xels;
+    int cols, rows, format;
+    xelval maxval;
+#endif /*__STDC__*/
+    {
+    xel bgxel, ul, ur, ll, lr;
+
+    /* Guess a good background value. */
+    ul = xels[0][0];
+    ur = xels[0][cols-1];
+    ll = xels[rows-1][0];
+    lr = xels[rows-1][cols-1];
+
+    /* First check for three corners equal. */
+    if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, ll ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ul, ll ) && PNM_EQUAL( ll, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ur, ll ) && PNM_EQUAL( ll, lr ) )
+    bgxel = ur;
+    /* Nope, check for two corners equal. */
+    else if ( PNM_EQUAL( ul, ur ) || PNM_EQUAL( ul, ll ) ||
+          PNM_EQUAL( ul, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ur, ll ) || PNM_EQUAL( ur, lr ) )
+    bgxel = ur;
+    else if ( PNM_EQUAL( ll, lr ) )
+    bgxel = ll;
+    else
+    {
+    /* Nope, we have to average the four corners.  This breaks the
+    ** rules of pnm, but oh well.  Let's try to do it portably. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PPM_TYPE:
+        PPM_ASSIGN( bgxel,
+        PPM_GETR(ul) + PPM_GETR(ur) + PPM_GETR(ll) + PPM_GETR(lr) / 4,
+        PPM_GETG(ul) + PPM_GETG(ur) + PPM_GETG(ll) + PPM_GETG(lr) / 4,
+        PPM_GETB(ul) + PPM_GETB(ur) + PPM_GETB(ll) + PPM_GETB(lr) / 4 );
+        break;
+
+        case PGM_TYPE:
+        {
+        gray gul, gur, gll, glr;
+        gul = (gray) PNM_GET1( ul );
+        gur = (gray) PNM_GET1( ur );
+        gll = (gray) PNM_GET1( ll );
+        glr = (gray) PNM_GET1( lr );
+        PNM_ASSIGN1( bgxel, ( ( gul + gur + gll + glr ) / 4 ) );
+        break;
+        }
+
+        case PBM_TYPE:
+        pm_error(
+        "pnm_backgroundxel: four bits no two of which equal each other??" );
+
+        default:
+        pm_error( "Invalid format passed to pnm_backgroundxel()");
+        }
+    }
+
+    return bgxel;
+    }
+
+#if __STDC__
+xel
+pnm_backgroundxelrow( xel* xelrow, int cols, xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_backgroundxelrow( xelrow, cols, maxval, format )
+    xel* xelrow;
+    int cols, format;
+    xelval maxval;
+#endif /*__STDC__*/
+    {
+    xel bgxel, l, r;
+
+    /* Guess a good background value. */
+    l = xelrow[0];
+    r = xelrow[cols-1];
+
+    /* First check for both corners equal. */
+    if ( PNM_EQUAL( l, r ) )
+    bgxel = l;
+    else
+    {
+    /* Nope, we have to average the two corners.  This breaks the
+    ** rules of pnm, but oh well.  Let's try to do it portably. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PPM_TYPE:
+        PPM_ASSIGN( bgxel, PPM_GETR(l) + PPM_GETR(r) / 2,
+        PPM_GETG(l) + PPM_GETG(r) / 2, PPM_GETB(l) + PPM_GETB(r) / 2 );
+        break;
+
+        case PGM_TYPE:
+        {
+        gray gl, gr;
+        gl = (gray) PNM_GET1( l );
+        gr = (gray) PNM_GET1( r );
+        PNM_ASSIGN1( bgxel, ( ( gl + gr ) / 2 ) );
+        break;
+        }
+
+        case PBM_TYPE:
+        {
+        int col, blacks;
+
+        /* One black, one white.  Gotta count. */
+        for ( col = 0, blacks = 0; col < cols; ++col )
+        {
+        if ( PNM_GET1( xelrow[col] ) == 0 )
+            ++blacks;
+        }
+        if ( blacks >= cols / 2 )
+        PNM_ASSIGN1( bgxel, 0 );
+        else
+        PNM_ASSIGN1( bgxel, maxval );
+        break;
+        }
+
+        default:
+        pm_error( "Invalid format passed to pnm_backgroundxelrow()");
+        }
+    }
+
+    return bgxel;
+    }
+
+#if __STDC__
+xel
+pnm_whitexel( xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_whitexel( maxval, format )
+    xelval maxval;
+    int format;
+#endif /*__STDC__*/
+    {
+    xel x;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PPM_TYPE:
+    PPM_ASSIGN( x, maxval, maxval, maxval );
+    break;
+
+    case PGM_TYPE:
+    PNM_ASSIGN1( x, maxval );
+    break;
+
+    case PBM_TYPE:
+    PNM_ASSIGN1( x, maxval );
+    break;
+
+    default:
+    pm_error( "Invalid format passed to pnm_whitexel()");
+    }
+
+    return x;
+    }
+
+#if __STDC__
+xel
+pnm_blackxel( xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_blackxel( maxval, format )
+    xelval maxval;
+    int format;
+#endif /*__STDC__*/
+    {
+    xel x;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PPM_TYPE:
+    PPM_ASSIGN( x, 0, 0, 0 );
+    break;
+
+    case PGM_TYPE:
+    PNM_ASSIGN1( x, (xelval) 0 );
+    break;
+
+    case PBM_TYPE:
+    PNM_ASSIGN1( x, (xelval) 0 );
+    break;
+
+    default:
+    pm_error( "Invalid format passed to pnm_blackxel(): %d", format);
+    }
+
+    return x;
+    }
+
+void
+pnm_invertxel(xel*   const xP, 
+              xelval const maxval, 
+              int    const format) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        PPM_ASSIGN(*xP, 
+                   maxval - PPM_GETR(*xP),
+                   maxval - PPM_GETG(*xP), 
+                   maxval - PPM_GETB(*xP));
+        break;
+
+    case PGM_TYPE:
+        PNM_ASSIGN1(*xP, maxval - PNM_GET1(*xP));
+        break;
+
+    case PBM_TYPE:
+        PNM_ASSIGN1(*xP, (PNM_GET1(*xP) == 0) ? maxval : 0);
+        break;
+
+    default:
+        pm_error("Invalid format passed to pnm_invertxel()");
+    }
+}
+
+
+
+#if __STDC__
+void
+pnm_promoteformat( xel** xels, int cols, int rows, xelval maxval, int format, xelval newmaxval, int newformat )
+#else /*__STDC__*/
+void
+pnm_promoteformat( xels, cols, rows, maxval, format, newmaxval, newformat )
+    xel** xels;
+    xelval maxval, newmaxval;
+    int cols, rows, format, newformat;
+#endif /*__STDC__*/
+    {
+    int row;
+
+    for ( row = 0; row < rows; ++row )
+    pnm_promoteformatrow(
+        xels[row], cols, maxval, format, newmaxval, newformat );
+    }
+
+#if __STDC__
+void
+pnm_promoteformatrow( xel* xelrow, int cols, xelval maxval, int format, xelval newmaxval, int newformat )
+#else /*__STDC__*/
+void
+pnm_promoteformatrow( xelrow, cols, maxval, format, newmaxval, newformat )
+    xel* xelrow;
+    xelval maxval, newmaxval;
+    int cols, format, newformat;
+#endif /*__STDC__*/
+    {
+    register int col;
+    register xel* xP;
+
+    if ( ( PNM_FORMAT_TYPE(format) == PPM_TYPE &&
+       ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE ||
+         PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) ) ||
+     ( PNM_FORMAT_TYPE(format) == PGM_TYPE &&
+       PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) )
+    pm_error( "pnm_promoteformatrow: can't promote downwards!" );
+
+    /* Are we promoting to the same type? */
+    if ( PNM_FORMAT_TYPE(format) == PNM_FORMAT_TYPE(newformat) )
+    {
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
+        return;
+    if ( newmaxval < maxval )
+        pm_error(
+       "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
+    if ( newmaxval == maxval )
+        return;
+    /* Increase maxval. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PGM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        PNM_ASSIGN1(
+            *xP, (int) PNM_GET1(*xP) * newmaxval / maxval );
+        break;
+
+        case PPM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        PPM_DEPTH( *xP, *xP, maxval, newmaxval );
+        break;
+
+        default:
+        pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
+        }
+    return;
+    }
+
+    /* We must be promoting to a higher type. */
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PBM_TYPE:
+    switch ( PNM_FORMAT_TYPE(newformat) )
+        {
+        case PGM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        if ( PNM_GET1(*xP) == 0 )
+            PNM_ASSIGN1( *xP, 0 );
+        else
+            PNM_ASSIGN1( *xP, newmaxval );
+        break;
+
+        case PPM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        if ( PNM_GET1(*xP) == 0 )
+            PPM_ASSIGN( *xP, 0, 0, 0 );
+        else
+            PPM_ASSIGN( *xP, newmaxval, newmaxval, newmaxval );
+        break;
+
+        default:
+        pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
+        }
+    break;
+
+    case PGM_TYPE:
+    switch ( PNM_FORMAT_TYPE(newformat) )
+        {
+        case PPM_TYPE:
+        if ( newmaxval < maxval )
+        pm_error(
+       "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
+        if ( newmaxval == maxval )
+        {
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            PPM_ASSIGN(
+            *xP, PNM_GET1(*xP), PNM_GET1(*xP), PNM_GET1(*xP) );
+        }
+        else
+        { /* Increase maxval. */
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            PPM_ASSIGN(
+            *xP, (int) PNM_GET1(*xP) * newmaxval / maxval,
+            (int) PNM_GET1(*xP) * newmaxval / maxval,
+            (int) PNM_GET1(*xP) * newmaxval / maxval );
+        }
+        break;
+
+        default:
+        pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
+        }
+    break;
+
+    default:
+        pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
+    }
+    }
+
+
+pixel
+xeltopixel(xel const inputxel) {
+    
+    pixel outputpixel;
+
+    PPM_ASSIGN(outputpixel, 
+               PNM_GET1(inputxel), PNM_GET1(inputxel), PNM_GET1(inputxel));
+    return outputpixel;
+}
diff --git a/lib/libppm.h b/lib/libppm.h
new file mode 100644
index 00000000..ec2a173d
--- /dev/null
+++ b/lib/libppm.h
@@ -0,0 +1,15 @@
+/* This is the intra-libnetpbm interface header file for libppm*.c
+*/
+
+#ifndef LIBPPM_H_INCLUDED
+#define LIBPPM_H_INCLUDED
+
+#include "ppm.h"
+
+void
+ppm_readppminitrest(FILE *   const file, 
+                    int *    const colsP, 
+                    int *    const rowsP, 
+                    pixval * const maxvalP);
+
+#endif
diff --git a/lib/libppm1.c b/lib/libppm1.c
new file mode 100644
index 00000000..57a1db7d
--- /dev/null
+++ b/lib/libppm1.c
@@ -0,0 +1,328 @@
+/* libppm1.c - ppm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include "ppm.h"
+#include "libppm.h"
+#include "pgm.h"
+#include "libpgm.h"
+#include "pbm.h"
+#include "libpbm.h"
+#include "pam.h"
+#include "libpam.h"
+#include "fileio.h"
+#include "mallocvar.h"
+
+
+pixel *
+ppm_allocrow(unsigned int const cols) {
+
+    pixel * pixelrow;
+
+    MALLOCARRAY(pixelrow, cols);
+
+    if (pixelrow == 0)
+        pm_error("Unable to allocate space for a %u-column pixel row", cols);
+
+    return pixelrow;
+}
+
+
+
+void
+ppm_init( argcP, argv )
+    int* argcP;
+    char* argv[];
+    {
+    pgm_init( argcP, argv );
+    }
+
+void
+ppm_nextimage(FILE * const fileP, 
+              int *  const eofP) {
+    pm_nextimage(fileP, eofP);
+}
+
+
+
+void
+ppm_readppminitrest(FILE *   const fileP, 
+                    int *    const colsP, 
+                    int *    const rowsP, 
+                    pixval * const maxvalP) {
+    unsigned int maxval;
+
+    /* Read size. */
+    *colsP = (int)pm_getuint(fileP);
+    *rowsP = (int)pm_getuint(fileP);
+
+    /* Read maxval. */
+    maxval = pm_getuint(fileP);
+    if (maxval > PPM_OVERALLMAXVAL)
+        pm_error("maxval of input image (%u) is too large.  "
+                 "The maximum allowed by the PPM is %u.",
+                 maxval, PPM_OVERALLMAXVAL); 
+    if (maxval == 0)
+        pm_error("maxval of input image is zero.");
+
+    *maxvalP = maxval;
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+ppm_readppminit(FILE *   const fileP, 
+                int *    const colsP, 
+                int *    const rowsP, 
+                pixval * const maxvalP, 
+                int *    const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PPM_TYPE:
+        *formatP = realFormat;
+        ppm_readppminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+
+    case PGM_TYPE:
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+
+    case PBM_TYPE:
+        *formatP = realFormat;
+        *maxvalP = 1;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        break;
+
+    case PAM_TYPE:
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, maxvalP, formatP);
+        break;
+
+    default:
+        pm_error("bad magic number %d - not a PPM, PGM, PBM, or PAM file",
+                 PAM_FORMAT_TYPE(*formatP));
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+void
+ppm_readppmrow(FILE*  const fileP, 
+               pixel* const pixelrow, 
+               int    const cols, 
+               pixval const maxval, 
+               int    const format) {
+
+    switch (format) {
+    case PPM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            pixval const r = pm_getuint(fileP);
+            pixval const g = pm_getuint(fileP);
+            pixval const b = pm_getuint(fileP);
+
+            if (r > maxval)
+                pm_error("Red sample value %u is greater than maxval (%u)",
+                         r, maxval);
+            if (g > maxval)
+                pm_error("Green sample value %u is greater than maxval (%u)",
+                         g, maxval);
+            if (b > maxval)
+                pm_error("Blue sample value %u is greater than maxval (%u)",
+                         b, maxval);
+
+            PPM_ASSIGN(pixelrow[col], r, g, b);
+        }
+    }
+    break;
+
+    /* For PAM, we require a depth of 3, which means the raster format
+       is identical to Raw PPM!  How convenient.
+    */
+    case PAM_FORMAT:
+    case RPPM_FORMAT: {
+        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+        unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
+        
+        unsigned int bufferCursor;
+        unsigned char * rowBuffer;
+        ssize_t rc;
+
+        MALLOCARRAY(rowBuffer, bytesPerRow);
+        
+        if (rowBuffer == NULL)
+            pm_error("Unable to allocate memory for row buffer "
+                     "for %u columns", cols);
+
+        rc = fread(rowBuffer, 1, bytesPerRow, fileP);
+    
+        if (feof(fileP))
+            pm_error("Unexpected EOF reading row of PPM image.");
+        else if (ferror(fileP))
+            pm_error("Error reading row.  fread() errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            pm_error("Error reading row.  Short read of %u bytes "
+                     "instead of %u", rc, bytesPerRow);
+    
+        bufferCursor = 0;  /* start at beginning of rowBuffer[] */
+        
+        if (bytesPerSample == 1) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                pixval const r = rowBuffer[bufferCursor++];
+                pixval const g = rowBuffer[bufferCursor++];
+                pixval const b = rowBuffer[bufferCursor++];
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            }
+        } else  {
+            /* two byte samples */
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                pixval r, g, b;
+
+                r = rowBuffer[bufferCursor++] << 8;
+                r |= rowBuffer[bufferCursor++];
+
+                g = rowBuffer[bufferCursor++] << 8;
+                g |= rowBuffer[bufferCursor++];
+
+                b = rowBuffer[bufferCursor++] << 8;
+                b |= rowBuffer[bufferCursor++];
+                
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            }
+        }
+        free(rowBuffer);
+    }
+    break;
+
+    case PGM_FORMAT:
+    case RPGM_FORMAT: {
+        gray * const grayrow = pgm_allocrow(cols);
+        unsigned int col;
+
+        pgm_readpgmrow(fileP, grayrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            pixval const g = grayrow[col];
+            PPM_ASSIGN(pixelrow[col], g, g, g);
+        }
+        pgm_freerow(grayrow);
+    }
+    break;
+
+    case PBM_FORMAT:
+    case RPBM_FORMAT: {
+        bit * const bitrow = pbm_allocrow(cols);
+        unsigned int col;
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col) {
+            pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0;
+            PPM_ASSIGN(pixelrow[col], g, g, g);
+        }
+        pbm_freerow(bitrow);
+    }
+    break;
+
+    default:
+        pm_error("Invalid format code");
+    }
+}
+
+
+
+pixel**
+ppm_readppm(FILE *   const fileP, 
+            int *    const colsP, 
+            int *    const rowsP, 
+            pixval * const maxvalP) {
+    pixel** pixels;
+    int row;
+    int format;
+
+    ppm_readppminit(fileP, colsP, rowsP, maxvalP, &format);
+
+    pixels = ppm_allocarray(*colsP, *rowsP);
+
+    for (row = 0; row < *rowsP; ++row)
+        ppm_readppmrow(fileP, pixels[row], *colsP, *maxvalP, format);
+
+    return pixels;
+}
+
+
+
+void
+ppm_check(FILE *               const fileP, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          pixval               const maxval,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to ppm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to ppm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (PPM_FORMAT_TYPE(format) == PBM_TYPE) {
+        pbm_check(fileP, check_type, format, cols, rows, retval_p);
+    } else if (PPM_FORMAT_TYPE(format) == PGM_TYPE) {
+        pgm_check(fileP, check_type, format, cols, rows, maxval, retval_p);
+    } else if (format != RPPM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = cols * 3 * (maxval > 255 ? 2 : 1);
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+        
+        pm_check(fileP, check_type, need_raster_size, retval_p);
+    }
+}
diff --git a/lib/libppm2.c b/lib/libppm2.c
new file mode 100644
index 00000000..52c4ab16
--- /dev/null
+++ b/lib/libppm2.c
@@ -0,0 +1,208 @@
+/* libppm2.c - ppm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "libppm.h"
+
+void
+ppm_writeppminit(FILE*  const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 pixval const maxval, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    if (maxval > PPM_OVERALLMAXVAL && !plainFormat) 
+        pm_error("too-large maxval passed to ppm_writeppminit(): %d."
+                 "Maximum allowed by the PPM format is %d.",
+                 maxval, PPM_OVERALLMAXVAL);
+
+    fprintf(fileP, "%c%c\n%d %d\n%d\n", 
+            PPM_MAGIC1, 
+            plainFormat || maxval >= 1<<16 ? PPM_MAGIC2 : RPPM_MAGIC2, 
+            cols, rows, maxval );
+#ifdef VMS
+    if (!plainFormat)
+        set_outfile_binary();
+#endif
+}
+
+
+
+static void
+putus(unsigned short const n,
+      FILE *         const fileP) {
+
+    if (n >= 10)
+        putus(n / 10, fileP);
+    putc('0' + n % 10, fileP);
+}
+
+
+
+static void
+format1bpsRow(const pixel *   const pixelrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* single byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        rowBuffer[bufferCursor++] = PPM_GETR(pixelrow[col]);
+        rowBuffer[bufferCursor++] = PPM_GETG(pixelrow[col]);
+        rowBuffer[bufferCursor++] = PPM_GETB(pixelrow[col]);
+    }
+}
+
+
+
+
+static void
+format2bpsRow(const pixel *   const pixelrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+    
+    /* two byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        pixval const r = PPM_GETR(pixelrow[col]);
+        pixval const g = PPM_GETG(pixelrow[col]);
+        pixval const b = PPM_GETB(pixelrow[col]);
+        
+        rowBuffer[bufferCursor++] = r >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)r;
+        rowBuffer[bufferCursor++] = g >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)g;
+        rowBuffer[bufferCursor++] = b >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)b;
+    }
+}
+
+
+
+static void
+ppm_writeppmrowraw(FILE *        const fileP,
+                   const pixel * const pixelrow,
+                   unsigned int  const cols,
+                   pixval        const maxval ) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
+
+    unsigned char * rowBuffer;
+    ssize_t rc;
+
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+
+    if (rowBuffer == NULL)
+        pm_error("Unable to allocate memory for row buffer "
+                 "for %u columns", cols);
+
+    if (maxval < 256)
+        format1bpsRow(pixelrow, cols, rowBuffer);
+    else
+        format2bpsRow(pixelrow, cols, rowBuffer);
+
+    rc = fwrite(rowBuffer, 1, bytesPerRow, fileP);
+
+    if (rc < 0)
+        pm_error("Error writing row.  fwrite() errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc != bytesPerRow)
+        pm_error("Error writing row.  Short write of %u bytes "
+                 "instead of %u", rc, bytesPerRow);
+
+    free(rowBuffer);
+}
+
+
+
+
+static void
+ppm_writeppmrowplain(FILE *       const fileP,
+                     pixel *      const pixelrow,
+                     unsigned int const cols,
+                     pixval       const maxval) {
+
+    unsigned int col;
+    unsigned int charcount;
+
+    charcount = 0;
+
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 65) {
+            putc('\n', fileP);
+            charcount = 0;
+        } else if (charcount > 0) {
+            putc(' ', fileP);
+            putc(' ', fileP);
+            charcount += 2;
+        }
+        putus(PPM_GETR(pixelrow[col]), fileP);
+        putc(' ', fileP);
+        putus(PPM_GETG(pixelrow[col]), fileP);
+        putc(' ', fileP);
+        putus(PPM_GETB(pixelrow[col]), fileP);
+        charcount += 11;
+    }
+    if (charcount > 0)
+        putc('\n', fileP);
+}
+
+
+
+void
+ppm_writeppmrow(FILE *  const fileP, 
+                pixel * const pixelrow, 
+                int     const cols, 
+                pixval  const maxval, 
+                int     const forceplain) {
+
+    if (forceplain || pm_plain_output || maxval >= 1<<16) 
+        ppm_writeppmrowplain(fileP, pixelrow, cols, maxval);
+    else 
+        ppm_writeppmrowraw(fileP, pixelrow, cols, maxval);
+}
+
+
+
+void
+ppm_writeppm(FILE *  const file, 
+             pixel** const pixels, 
+             int     const cols, 
+             int     const rows, 
+             pixval  const maxval, 
+             int     const forceplain)  {
+    int row;
+    
+    ppm_writeppminit(file, cols, rows, maxval, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        ppm_writeppmrow(file, pixels[row], cols, maxval, forceplain);
+}
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
new file mode 100644
index 00000000..a9efccbc
--- /dev/null
+++ b/lib/libppmcmap.c
@@ -0,0 +1,650 @@
+/* libppm3.c - ppm utility library part 3
+**
+** Colormap routines.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "libppm.h"
+#include "mallocvar.h"
+#include "ppmcmap.h"
+
+#define HASH_SIZE 20023
+
+#define ppm_hashpixel(p) ( ( ( (long) PPM_GETR(p) * 33023 + \
+                               (long) PPM_GETG(p) * 30013 + \
+                               (long) PPM_GETB(p) * 27011 ) \
+                             & 0x7fffffff ) % HASH_SIZE )
+
+colorhist_vector
+ppm_computecolorhist( pixel ** const pixels, 
+                      const int cols, const int rows, const int maxcolors, 
+                      int * const colorsP ) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram for the image described by 'pixels',
+   'cols', and 'rows'.  I.e. a colorhist_vector containing an entry
+   for each color in the image and for each one the number of pixels
+   of that color (i.e. a color histogram).
+
+   If 'maxcolors' is zero, make the output have 5 spare slots at the end
+   for expansion.
+   
+   If 'maxcolors' is nonzero, make the output have 'maxcolors' slots in
+   it, and if there are more colors than that in the image, don't return
+   anything except a NULL pointer.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    colorhist_vector chv;
+
+    cht = ppm_computecolorhash(pixels, cols, rows, maxcolors, colorsP);
+    if (cht == NULL)
+        chv = NULL;
+    else {
+        chv = ppm_colorhashtocolorhist(cht, maxcolors);
+        ppm_freecolorhash(cht);
+    }
+    return chv;
+}
+
+
+
+colorhist_vector
+ppm_computecolorhist2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP ) {
+
+    colorhash_table cht;
+    colorhist_vector chv;
+
+    cht = ppm_computecolorhash2(ifp, cols, rows, maxval, format, 
+                                maxcolors, colorsP);
+    if (cht ==NULL)
+        return NULL;
+    chv = ppm_colorhashtocolorhist(cht, maxcolors);
+    ppm_freecolorhash(cht);
+    return chv;
+}
+
+
+
+void
+ppm_addtocolorhist( colorhist_vector chv, 
+                    int * const colorsP, const int maxcolors, 
+                    const pixel * const colorP, 
+                    const int value, const int position ) {
+    int i, j;
+
+    /* Search colorhist for the color. */
+    for ( i = 0; i < *colorsP; ++i )
+        if ( PPM_EQUAL( chv[i].color, *colorP ) ) {
+            /* Found it - move to new slot. */
+            if ( position > i ) {
+                for ( j = i; j < position; ++j )
+                    chv[j] = chv[j + 1];
+            } else if ( position < i ) {
+                for ( j = i; j > position; --j )
+                    chv[j] = chv[j - 1];
+            }
+            chv[position].color = *colorP;
+            chv[position].value = value;
+            return;
+        }
+    if ( *colorsP < maxcolors ) {
+        /* Didn't find it, but there's room to add it; so do so. */
+        for ( i = *colorsP; i > position; --i )
+            chv[i] = chv[i - 1];
+        chv[position].color = *colorP;
+        chv[position].value = value;
+        ++(*colorsP);
+    }
+}
+
+
+
+colorhash_table
+ppm_alloccolorhash(void)  {
+    colorhash_table cht;
+    int i;
+
+    MALLOCARRAY(cht, HASH_SIZE);
+    if (cht == NULL)
+        pm_error( "out of memory allocating hash table" );
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        cht[i] = NULL;
+
+    return cht;
+}
+
+
+
+static colorhash_table
+computecolorhash(pixel ** const pixels, 
+                 const int cols, const int rows, 
+                 const int maxcolors, int * const colorsP,
+                 FILE * const ifp, pixval const maxval, int const format) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram from an image.  The input is one of two types:
+
+   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
+      is non-NULL and 'ifp' is NULL.
+
+   2) an open file, positioned to the image data.  In this case,
+      'pixels' is NULL and 'ifp' is non-NULL.  ifp is the stream
+      descriptor for the input file, and 'maxval' and 'format' are
+      parameters of the image data in it.
+      
+      We return with the file still open and its position undefined.  
+
+   In either case, the image is 'cols' by 'rows'.
+
+   Return the number of colors found as *colorsP.
+
+   However, if 'maxcolors' is nonzero and the number of colors is
+   greater than 'maxcolors', return a null return value and *colorsP
+   undefined.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    int row;
+    pixel * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+    
+    cht = ppm_alloccolorhash( );
+    *colorsP = 0;   /* initial value */
+
+    rowbuffer = ppm_allocrow(cols);
+
+    /* Go through the entire image, building a hash table of colors. */
+    for (row = 0; row < rows; ++row) {
+        int col;
+        pixel * pixelrow;  /* The row of pixels we are processing */
+
+        if (ifp) {
+            ppm_readppmrow(ifp, rowbuffer, cols, maxval, format);
+            pixelrow = rowbuffer;
+        } else 
+            pixelrow = pixels[row];
+
+        for (col = 0; col < cols; ++col) {
+            const pixel apixel = pixelrow[col];
+            const int hash = ppm_hashpixel(apixel);
+            colorhist_list chl; 
+
+            for (chl = cht[hash]; 
+                 chl != (colorhist_list) 0 && 
+                     !PPM_EQUAL(chl->ch.color, apixel);
+                 chl = chl->next);
+
+            if (chl)
+                chl->ch.value++;
+            else {
+                /* It's not in the hash yet, so add it (if allowed) */
+                ++(*colorsP);
+                if (maxcolors > 0 && *colorsP > maxcolors) {
+                    ppm_freecolorhash(cht);
+                    return NULL;
+                } else {
+                    MALLOCVAR(chl);
+                    if (chl == NULL)
+                        pm_error("out of memory computing hash table");
+                    chl->ch.color = apixel;
+                    chl->ch.value = 1;
+                    chl->next = cht[hash];
+                    cht[hash] = chl;
+                }
+            }
+        }
+    }
+    ppm_freerow(rowbuffer);
+    return cht;
+}
+
+
+
+colorhash_table
+ppm_computecolorhash(pixel ** const pixels, 
+                     const int cols, const int rows, 
+                     const int maxcolors, int * const colorsP) {
+
+    return computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
+                            NULL, 0, 0);
+}
+
+
+
+colorhash_table
+ppm_computecolorhash2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP ) {
+
+    return computecolorhash(NULL, cols, rows, maxcolors, colorsP,
+                            ifp, maxval, format);
+}
+
+
+
+int
+ppm_addtocolorhash(colorhash_table const cht, 
+                   const pixel *   const colorP, 
+                   int             const value) {
+/*----------------------------------------------------------------------------
+   Add color *colorP to the color hash 'cht' with associated value 'value'.
+
+   Assume the color is not already in the hash.
+-----------------------------------------------------------------------------*/
+    int retval;
+    colorhist_list chl;
+
+    MALLOCVAR(chl);
+    if (chl == NULL)
+        retval = -1;
+    else {
+        int const hash = ppm_hashpixel(*colorP);
+
+        chl->ch.color = *colorP;
+        chl->ch.value = value;
+        chl->next = cht[hash];
+        cht[hash] = chl;
+        retval = 0;
+    }
+    return retval;
+}
+
+
+
+void
+ppm_delfromcolorhash(colorhash_table const cht, 
+                     const pixel *   const colorP) {
+/*----------------------------------------------------------------------------
+   Delete the color *colorP from the colorhahs 'cht', if it's there.
+-----------------------------------------------------------------------------*/
+    int hash;
+    colorhist_list * chlP;
+
+    hash = ppm_hashpixel(*colorP);
+    for (chlP = &cht[hash]; *chlP; chlP = &(*chlP)->next) {
+        if (PPM_EQUAL((*chlP)->ch.color, *colorP)) {
+            /* chlP points to a pointer to the hash chain element we want
+               to remove.
+            */
+            colorhist_list const chl = *chlP;
+            *chlP = chl->next;
+            free(chl);
+            return;
+        }
+    }
+}
+
+
+
+static unsigned int
+colorHashSize(colorhash_table const cht) {
+/*----------------------------------------------------------------------------
+   Return the number of colors in the colorhash table 'cht'
+-----------------------------------------------------------------------------*/
+    unsigned int nColors;
+        /* Number of colors found so far */
+    int i;
+    /* Loop through the hash table. */
+    nColors = 0;
+    for (i = 0; i < HASH_SIZE; ++i) {
+        colorhist_list chl;
+        for (chl = cht[i]; chl; chl = chl->next) 
+            ++nColors;
+    }
+    return nColors;
+}
+
+
+
+colorhist_vector
+ppm_colorhashtocolorhist(colorhash_table const cht, int const maxcolors) {
+
+    colorhist_vector chv;
+    colorhist_list chl;
+    unsigned int chvSize;
+
+    if (maxcolors == 0)
+        /* We leave space for 5 more colors so caller can add in special
+           colors like background color and transparent color.
+        */
+        chvSize = colorHashSize(cht) + 5;
+    else
+        /* Caller is responsible for making sure there are no more
+           than 'maxcolors' colors in the colorhash table.  NOTE:
+           Before March 2002, the maxcolors == 0 invocation didn't
+           exist.  
+        */
+        chvSize = maxcolors;
+
+    /* Collate the hash table into a simple colorhist array. */
+    MALLOCARRAY(chv, chvSize);
+    if (chv == NULL)
+        pm_error("out of memory generating histogram");
+
+    {
+        int i, j;
+        /* Loop through the hash table. */
+        j = 0;
+        for (i = 0; i < HASH_SIZE; ++i)
+            for (chl = cht[i]; chl; chl = chl->next) {
+                /* Add the new entry. */
+                chv[j] = chl->ch;
+                ++j;
+            }
+    }
+    return chv;
+}
+
+
+
+colorhash_table
+ppm_colorhisttocolorhash(colorhist_vector const chv, 
+                         int              const colors) {
+    colorhash_table cht;
+    int i, hash;
+    pixel color;
+    colorhist_list chl;
+
+    cht = ppm_alloccolorhash( );  /* Initializes to NULLs */
+
+    for (i = 0; i < colors; ++i) {
+        color = chv[i].color;
+        hash = ppm_hashpixel(color);
+        for (chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next)
+            if (PPM_EQUAL(chl->ch.color, color))
+                pm_error(
+                    "same color found twice - %d %d %d", PPM_GETR(color),
+                    PPM_GETG(color), PPM_GETB(color) );
+        MALLOCVAR(chl);
+        if (chl == NULL)
+            pm_error("out of memory");
+        chl->ch.color = color;
+        chl->ch.value = i;
+        chl->next = cht[hash];
+        cht[hash] = chl;
+    }
+    return cht;
+}
+
+
+
+int
+ppm_lookupcolor(colorhash_table const cht, 
+                const pixel *   const colorP) {
+    int hash;
+    colorhist_list chl;
+
+    hash = ppm_hashpixel(*colorP);
+    for (chl = cht[hash]; chl; chl = chl->next)
+        if (PPM_EQUAL(chl->ch.color, *colorP))
+            return chl->ch.value;
+
+    return -1;
+}
+
+
+
+void
+ppm_freecolorhist(colorhist_vector const chv) {
+    free(chv);
+}
+
+
+
+void
+ppm_freecolorhash(colorhash_table const cht) {
+
+    int i;
+    colorhist_list chl, chlnext;
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        for (chl = cht[i]; chl != (colorhist_list) 0; chl = chlnext) {
+            chlnext = chl->next;
+            free(chl);
+        }
+    free(cht);
+}
+
+
+/*****************************************************************************
+  The following "color row" routines are taken from Ingo Wilken's ilbm
+  package, dated December 1994.  Since they're only used by ppmtoilbm
+  and ilbmtoppm today, they aren't documented or well maintained, but
+  they seem pretty useful and ought to be used in other programs.
+
+  -Bryan 2001.03.10
+****************************************************************************/
+
+/* libcmap2.c - support routines for color rows
+**
+** Copyright (C) 1994 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+colorhash_table
+ppm_colorrowtocolorhash(colorrow, ncolors)
+    pixel *colorrow;
+    int ncolors;
+{
+    colorhash_table cht;
+    int i;
+
+    cht = ppm_alloccolorhash();
+    for( i = 0; i < ncolors; i++ ) {
+        if( ppm_lookupcolor(cht, &colorrow[i]) < 0 ) {
+            if( ppm_addtocolorhash(cht, &colorrow[i], i) < 0 )
+                pm_error("out of memory adding to hash table");
+        }
+    }
+    return cht;
+}
+
+
+pixel *
+ppm_computecolorrow(pixels, cols, rows, maxcolors, ncolorsP)
+    pixel **pixels;
+    int cols, rows, maxcolors;
+    int *ncolorsP;
+{
+    int ncolors, row, col;
+    colorhash_table cht;
+    pixel *pixrow;
+
+    pixrow = ppm_allocrow(maxcolors);
+    cht = ppm_alloccolorhash(); ncolors = 0;
+    for( row = 0; row < rows; row++ ) {
+        for( col = 0; col < cols; col++ ) {
+            if( ppm_lookupcolor(cht, &pixels[row][col]) < 0 ) {
+                if( ncolors >= maxcolors ) {
+                    ppm_freerow(pixrow);
+                    pixrow = (pixel *)0;
+                    ncolors = -1;
+                    goto fail;
+                }
+                if( ppm_addtocolorhash(cht, &pixels[row][col], ncolors) < 0 )
+                    pm_error("out of memory adding to hash table");
+                pixrow[ncolors] = pixels[row][col];
+                ++ncolors;
+            }
+        }
+    }
+fail:
+    ppm_freecolorhash(cht);
+
+    *ncolorsP = ncolors;
+    return pixrow;
+}
+
+
+pixel *
+ppm_mapfiletocolorrow(file, maxcolors, ncolorsP, maxvalP)
+    FILE *file;
+    int maxcolors;
+    int *ncolorsP;
+    pixval *maxvalP;
+{
+    int cols, rows, format, row, col, ncolors;
+    pixel *pixrow, *temprow;
+    colorhash_table cht;
+
+    pixrow = ppm_allocrow(maxcolors);
+
+    ppm_readppminit(file, &cols, &rows, maxvalP, &format);
+    temprow = ppm_allocrow(cols);
+    cht = ppm_alloccolorhash(); ncolors = 0;
+    for( row = 0; row < rows; row++ ) {
+        ppm_readppmrow(file, temprow, cols, *maxvalP, format);
+        for( col = 0; col < cols; col++ ) {
+            if( ppm_lookupcolor(cht, &temprow[col]) < 0 ) {
+                if( ncolors >= maxcolors ) {
+                    ppm_freerow(pixrow);
+                    pixrow = (pixel *)0;
+                    ncolors = -1;
+                    goto fail;
+                }
+                if( ppm_addtocolorhash(cht, &temprow[col], ncolors) < 0 )
+                    pm_error("out of memory adding to hash table");
+                pixrow[ncolors] = temprow[col];
+                ++ncolors;
+            }
+        }
+    }
+fail:
+    ppm_freecolorhash(cht);
+    ppm_freerow(temprow);
+
+    *ncolorsP = ncolors;
+    return pixrow;
+}
+
+
+static int
+pixel_cmp(const void * const a, const void * const b) {
+    const pixel *p1 = (const pixel *)a, *p2 = (const pixel *)b;
+    int diff;
+
+    diff = PPM_GETR(*p1) - PPM_GETR(*p2);
+    if( diff == 0 ) {
+        diff = PPM_GETG(*p1) - PPM_GETG(*p2);
+        if( diff == 0 )
+            diff = PPM_GETB(*p1) - PPM_GETB(*p2);
+    }
+    return diff;
+}
+
+static int (*custom_cmp)(pixel *, pixel *);
+
+static int
+custom_stub(const void * const a, const void * const b) {
+    return (*custom_cmp)((pixel *)a, (pixel *)b);
+}
+
+
+void
+ppm_sortcolorrow(pixel * const colorrow, const int ncolors, 
+                 int (*cmpfunc)(pixel *, pixel *)) {
+
+    if( cmpfunc ) {
+        custom_cmp = cmpfunc;
+        qsort((void *)colorrow, ncolors, sizeof(pixel), custom_stub);
+    } else
+        qsort((void *)colorrow, ncolors, sizeof(pixel), pixel_cmp);
+}
+
+
+
+int
+ppm_addtocolorrow(colorrow, ncolorsP, maxcolors, pixelP)
+    pixel *colorrow;
+    int *ncolorsP;
+    int maxcolors;
+    pixel *pixelP;
+{
+    int i;
+    pixval r, g, b;
+
+    r = PPM_GETR(*pixelP);
+    g = PPM_GETG(*pixelP);
+    b = PPM_GETB(*pixelP);
+
+    for( i = 0; i < *ncolorsP; i++ ) {
+        if( PPM_GETR(colorrow[i]) == r &&
+            PPM_GETG(colorrow[i]) == g &&
+            PPM_GETB(colorrow[i]) == b )
+                return i;
+    }
+
+    i = *ncolorsP;
+    if( i >= maxcolors )
+        return -1;
+    colorrow[i] = *pixelP;
+    ++*ncolorsP;
+    return i;
+}
+
+
+int
+ppm_findclosestcolor(const pixel * const colormap, 
+                     int           const ncolors, 
+                     const pixel * const pP) {
+    
+    /* Search colormap for closest match.       */
+
+    int i;
+    int ind;
+    unsigned int bestDist;
+
+    bestDist = UINT_MAX;
+    ind = -1;
+
+    for(i = 0; i < ncolors && bestDist > 0; ++i) {
+        unsigned int const dist = PPM_DISTANCE(*pP, colormap[i]);
+        
+        if (dist < bestDist ) {
+            ind = i;
+            bestDist = dist;
+        }
+    }
+    return ind;
+}
+
+
+void
+#if __STDC__
+ppm_colorrowtomapfile(FILE *ofp, pixel *colormap, int ncolors, pixval maxval)
+#else
+ppm_colorrowtomapfile(ofp, colormap, ncolors, maxval)
+    FILE *ofp;
+    pixel *colormap;
+    int ncolors;
+    pixval maxval;
+#endif
+{
+    int i;
+
+    ppm_writeppminit(ofp, ncolors, 1, maxval, 1);
+    for( i = 0; i < ncolors; i++ )
+        ppm_writeppmrow(ofp, &colormap[i], 1, maxval, 1);
+}
+
+
+
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
new file mode 100644
index 00000000..ff1a1e67
--- /dev/null
+++ b/lib/libppmcolor.c
@@ -0,0 +1,710 @@
+/*
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "colorname.h"
+
+
+static void
+computeHexTable(int hexit[]) {
+    int i;
+    for ( i = 0; i < 256; ++i )
+        hexit[i] = 1234567890;
+    hexit['0'] = 0;
+    hexit['1'] = 1;
+    hexit['2'] = 2;
+    hexit['3'] = 3;
+    hexit['4'] = 4;
+    hexit['5'] = 5;
+    hexit['6'] = 6;
+    hexit['7'] = 7;
+    hexit['8'] = 8;
+    hexit['9'] = 9;
+    hexit['a'] = hexit['A'] = 10;
+    hexit['b'] = hexit['B'] = 11;
+    hexit['c'] = hexit['C'] = 12;
+    hexit['d'] = hexit['D'] = 13;
+    hexit['e'] = hexit['E'] = 14;
+    hexit['f'] = hexit['F'] = 15;
+}
+
+
+
+static long
+invRgbnorm(pixval       const rgb,
+           pixval       const maxval,
+           unsigned int const hexDigits) {
+/*----------------------------------------------------------------------------
+  This is the inverse of 'rgbnorm', below.
+-----------------------------------------------------------------------------*/
+    long retval;
+
+    switch (hexDigits) {
+    case 1:
+        retval = (long)((double) rgb * 15 / maxval + 0.5);
+        break;
+    case 2:
+        retval = (long) ((double) rgb * 255 / maxval + 0.5);
+        break;
+    case 3:
+        retval = (long) ((double) rgb * 4095 / maxval + 0.5);
+        break;
+    case 4:
+        retval = (long) ((double) rgb * 65535UL / maxval + 0.5);
+        break;
+    default:
+        pm_message("Internal error in invRgbnorm()");
+        abort();
+    }
+    return retval;
+}
+
+
+
+static pixval
+rgbnorm(long         const rgb, 
+        pixval       const maxval, 
+        unsigned int const hexDigitCount, 
+        bool         const closeOk,
+        const char * const colorname) {
+/*----------------------------------------------------------------------------
+   Normalize the color (r, g, or b) value 'rgb', which was specified
+   with 'hexDigitCount' digits, to a maxval of 'maxval'.  If the
+   number of digits isn't valid, issue an error message and identify
+   the complete color color specification in error as 'colorname'.
+
+   For example, if the user says 0ff/000/000 and the maxval is 100,
+   then rgb is 0xff, n is 3, and our result is 
+   0xff / (16**3-1) * 100 = 6.
+
+-----------------------------------------------------------------------------*/
+    pixval retval;
+
+    assert(hexDigitCount > 0);
+
+    switch (hexDigitCount) {
+    case 1:
+        retval = (pixval)((double) rgb * maxval / 15 + 0.5);
+        break;
+    case 2:
+        retval = (pixval) ((double) rgb * maxval / 255 + 0.5);
+        break;
+    case 3:
+        retval = (pixval) ((double) rgb * maxval / 4095 + 0.5);
+        break;
+    case 4:
+        retval = (pixval) ((double) rgb * maxval / 65535L + 0.5);
+        break;
+    default:
+        pm_error("color specifier '%s' has too many digits", colorname);
+    }
+
+    if (!closeOk) {
+        long const newrgb = invRgbnorm(retval, maxval, hexDigitCount);
+        if (newrgb != rgb)
+            pm_message("WARNING: Component 0x%lx of color '%s' "
+                       "cannot be represented precisely with maxval %u.  "
+                       "Approximating as %u.",
+                       rgb, colorname, maxval, retval);
+    }
+    return retval;
+}
+
+
+
+static void
+parseNewHexX11(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    int hexit[256];
+
+    const char* cp;
+    pixval r,g,b;
+    pixval rNorm, gNorm, bNorm;
+
+    int i;
+
+    computeHexTable(hexit);
+
+    cp = colorname + 4;
+
+    for (i = 0, r = 0; *cp != '/'; ++i, ++cp)
+        r = r * 16 + hexit[(int)*cp];
+    rNorm = rgbnorm(r, maxval, i, closeOk, colorname);
+
+    for (i = 0, g = 0,++cp; *cp != '/'; ++i, ++cp )
+        g = g * 16 + hexit[(int)*cp];
+    gNorm = rgbnorm(g, maxval, i, closeOk, colorname);
+
+    for (i = 0, b = 0, ++cp; *cp != '\0'; ++i, ++cp )
+        b = b * 16 + hexit[(int)*cp];
+    bNorm = rgbnorm(b, maxval, i, closeOk, colorname);
+
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseNewDecX11(char       const colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf( colorname, "rgbi:%f/%f/%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((double)rNorm/maxval - fr) > epsilon ||
+            fabs((double)gNorm/maxval - fg) > epsilon ||
+            fabs((double)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseOldX11(const char       colorname[], 
+            pixval     const maxval,
+            bool       const closeOk,
+            pixel *    const colorP) {
+
+    int hexit[256];
+    long r,g,b;
+    pixval rNorm, gNorm, bNorm;
+    
+    computeHexTable(hexit);
+
+    switch (strlen(colorname) - 1 /* (Number of hex digits) */) {
+    case 3:
+        r = hexit[(int)colorname[1]];
+        g = hexit[(int)colorname[2]];
+        b = hexit[(int)colorname[3]];
+        rNorm = rgbnorm(r, maxval, 1, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 1, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 1, closeOk, colorname);
+        break;
+
+    case 6:
+        r = (hexit[(int)colorname[1]] << 4 ) + hexit[(int)colorname[2]];
+        g = (hexit[(int)colorname[3]] << 4 ) + hexit[(int)colorname[4]];
+        b = (hexit[(int)colorname[5]] << 4 ) + hexit[(int)colorname[6]];
+        rNorm = rgbnorm(r, maxval, 2, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 2, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 2, closeOk, colorname);
+        break;
+
+    case 9:
+        r = (hexit[(int)colorname[1]] << 8) +
+            (hexit[(int)colorname[2]] << 4) +
+            (hexit[(int)colorname[3]] << 0);
+        g = (hexit[(int)colorname[4]] << 8) + 
+            (hexit[(int)colorname[5]] << 4) +
+            (hexit[(int)colorname[6]] << 0);
+        b = (hexit[(int)colorname[7]] << 8) + 
+            (hexit[(int)colorname[8]] << 4) +
+            (hexit[(int)colorname[9]] << 0);
+        rNorm = rgbnorm(r, maxval, 3, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 3, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 3, closeOk, colorname);
+        break;
+
+    case 12:
+        r = (hexit[(int)colorname[1]] << 12) + 
+            (hexit[(int)colorname[2]] <<  8) +
+            (hexit[(int)colorname[3]] <<  4) + hexit[(int)colorname[4]];
+        g = (hexit[(int)colorname[5]] << 12) + 
+            (hexit[(int)colorname[6]] <<  8) +
+            (hexit[(int)colorname[7]] <<  4) + hexit[(int)colorname[8]];
+        b = (hexit[(int)colorname[9]] << 12) + 
+            (hexit[(int)colorname[10]] << 8) +
+            (hexit[(int)colorname[11]] << 4) + hexit[(int)colorname[12]];
+        rNorm = rgbnorm(r, maxval, 4, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 4, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 4, closeOk, colorname);
+        break;
+
+    default:
+        pm_error("invalid color specifier '%s'", colorname);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+
+static void
+parseOldX11Dec(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf(colorname, "%f,%f,%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((float)rNorm/maxval - fr) > epsilon ||
+            fabs((float)gNorm/maxval - fg) > epsilon ||
+            fabs((float)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+pixel
+ppm_parsecolor2(const char * const colorname,
+                pixval       const maxval,
+                int          const closeOk) {
+
+    pixel color;
+    
+    if (strncmp(colorname, "rgb:", 4) == 0)
+        /* It's a new-X11-style hexadecimal rgb specifier. */
+        parseNewHexX11(colorname, maxval, closeOk, &color);
+    else if (strncmp(colorname, "rgbi:", 5) == 0)
+        /* It's a new-X11-style decimal/float rgb specifier. */
+        parseNewDecX11(colorname, maxval, closeOk, &color);
+    else if (colorname[0] == '#')
+        /* It's an old-X11-style hexadecimal rgb specifier. */
+        parseOldX11(colorname, maxval, closeOk, &color);
+    else if ((colorname[0] >= '0' && colorname[0] <= '9') ||
+             colorname[0] == '.')
+        /* It's an old-style decimal/float rgb specifier. */
+        parseOldX11Dec(colorname, maxval, closeOk, &color);
+    else 
+        /* Must be a name from the X-style rgb file. */
+        pm_parse_dictionary_name(colorname, maxval, closeOk, &color);
+    
+    return color;
+}
+
+
+
+pixel
+ppm_parsecolor(const char * const colorname, 
+               pixval       const maxval) {
+
+    return ppm_parsecolor2(colorname, maxval, TRUE);
+}
+
+
+
+char*
+ppm_colorname(const pixel* const colorP, 
+              pixval       const maxval, 
+              int          const hexok)   {
+
+    int r, g, b;
+    FILE* f;
+    static char colorname[200];
+
+    if (maxval == 255) {
+        r = PPM_GETR(*colorP);
+        g = PPM_GETG(*colorP);
+        b = PPM_GETB(*colorP);
+    } else {
+        r = (int) PPM_GETR(*colorP) * 255 / (int) maxval;
+        g = (int) PPM_GETG(*colorP) * 255 / (int) maxval;
+        b = (int) PPM_GETB(*colorP) * 255 / (int) maxval;
+    }
+
+    f = pm_openColornameFile(NULL, !hexok);
+    if (f != NULL) {
+        int best_diff, this_diff;
+        bool eof;
+
+        best_diff = 32767;
+        eof = FALSE;
+        while (!eof && best_diff > 0 ) {
+            struct colorfile_entry const ce = pm_colorget(f);
+            if (ce.colorname)  {
+                this_diff = abs(r - ce.r) + abs(g - ce.g) + abs(b - ce.b);
+                if (this_diff < best_diff) {
+                    best_diff = this_diff;
+                    strcpy(colorname, ce.colorname);
+                }
+            } else
+                eof = TRUE;
+        }
+        fclose(f);
+        if (best_diff != 32767 && (best_diff == 0 || ! hexok))
+            return colorname;
+    }
+
+    /* Color lookup failed, but caller is willing to take an X11-style
+       hex specifier, so return that.
+    */
+    sprintf(colorname, "#%02x%02x%02x", r, g, b);
+    return colorname;
+}
+
+
+
+#define MAXCOLORNAMES 1000u
+
+static void
+processColorfileEntry(struct colorfile_entry const ce,
+                      colorhash_table        const cht,
+                      const char **          const colornames,
+                      pixel *                const colors,
+                      unsigned int *         const colornameIndexP) {
+
+    if (*colornameIndexP >= MAXCOLORNAMES)
+        pm_error("Too many colors in colorname dictionary.  "
+                 "Max allowed is %u", MAXCOLORNAMES);
+    else {
+        pixel color;
+
+        PPM_ASSIGN(color, ce.r, ce.g, ce.b);
+
+        if (ppm_lookupcolor(cht, &color) >= 0) {
+            /* The color is already in the hash, which means we saw it
+               earlier in the file.  We prefer the first name that the
+               file gives for each color, so we just ignore the
+               current entry.  
+            */
+        } else {
+            ppm_addtocolorhash(cht, &color, *colornameIndexP);
+            colornames[*colornameIndexP] = strdup(ce.colorname);
+            colors[*colornameIndexP] = color;
+            if (colornames[*colornameIndexP] == NULL)
+                pm_error("Unable to allocate space for color name");
+            ++(*colornameIndexP);
+        }
+    }
+}
+
+
+
+static void
+readcolordict(const char *    const fileName,
+              bool            const mustOpen,
+              unsigned int *  const nColorsP,
+              const char **   const colornames,
+              pixel * const   colors,
+              colorhash_table const cht) {
+
+    FILE * colorFile;
+
+    colorFile = pm_openColornameFile(fileName, mustOpen);
+
+    if (colorFile != NULL) {
+        unsigned int colornameIndex;
+        bool done;
+
+        colornameIndex = 0;  /* initial value */
+        done = FALSE;
+        while (!done) {
+            struct colorfile_entry const ce = pm_colorget(colorFile);
+
+            if (!ce.colorname)  
+                done = TRUE;
+            else 
+                processColorfileEntry(ce, cht, colornames, colors,
+                                      &colornameIndex);
+        }
+
+        *nColorsP = colornameIndex;
+
+        while (colornameIndex < MAXCOLORNAMES)
+            colornames[colornameIndex++] = NULL;
+
+        fclose(colorFile);
+    }
+}
+
+
+
+void
+ppm_readcolordict(const char *      const fileName,
+                  int               const mustOpen,
+                  unsigned int *    const nColorsP,
+                  const char ***    const colornamesP,
+                  pixel **          const colorsP,
+                  colorhash_table * const chtP) {
+
+    colorhash_table cht;
+    const char ** colornames;
+    pixel * colors;
+    unsigned int nColors;
+
+    cht = ppm_alloccolorhash();
+
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
+
+    colors = ppm_allocrow(MAXCOLORNAMES);
+
+    if (colornames == NULL)
+        pm_error("Unable to allocate space for colorname table.");
+
+    readcolordict(fileName, mustOpen, &nColors, colornames, colors, cht);
+
+    if (chtP)
+        *chtP = cht;
+    else
+        ppm_freecolorhash(cht);
+    if (colornamesP)
+        *colornamesP = colornames;
+    else
+        ppm_freecolornames(colornames);
+    if (colorsP)
+        *colorsP = colors;
+    else
+        ppm_freerow(colors);
+    if (nColorsP)
+        *nColorsP = nColors;
+}
+
+
+
+void
+ppm_readcolornamefile(const char *      const fileName, 
+                      int               const mustOpen,
+                      colorhash_table * const chtP, 
+                      const char ***    const colornamesP) {
+
+    ppm_readcolordict(fileName, mustOpen, NULL, colornamesP, NULL, chtP);
+}
+
+
+
+void
+ppm_freecolornames(const char ** const colornames) {
+
+    unsigned int i;
+
+    for (i = 0; i < MAXCOLORNAMES; ++i)
+        if (colornames[i])
+            free((char *)colornames[i]);
+
+    free(colornames);
+}
+
+
+
+static unsigned int 
+nonnegative(unsigned int const arg) {
+
+    if ((int)(arg) < 0)
+        return 0;
+    else
+        return arg;
+}
+
+
+
+pixel
+ppm_color_from_ycbcr(unsigned int const y, 
+                     int          const cb, 
+                     int          const cr) {
+/*----------------------------------------------------------------------------
+   Return the color that has luminance 'y', blue chrominance 'cb', and
+   red chrominance 'cr'.
+
+   The 3 input values can be on any scale (as long as it's the same
+   scale for all 3) and the maxval of the returned pixel value is the
+   same as that for the input values.
+
+   Rounding may cause an output value to be greater than the maxval.
+-----------------------------------------------------------------------------*/
+    pixel retval;
+
+    PPM_ASSIGN(retval, 
+               y + 1.4022 * cr,
+               nonnegative(y - 0.7145 * cr - 0.3456 * cb),
+               y + 1.7710 * cb
+        );
+    
+    return retval;
+}
+
+
+
+pixel
+ppm_color_from_hsv(struct hsv const hsv,
+                   pixval     const maxval) {
+
+    pixel retval;
+    double R, G, B;
+
+    if (hsv.s == 0) {
+        R = hsv.v;
+        G = hsv.v;
+        B = hsv.v;
+    } else {
+        unsigned int const sectorSize = 60;
+            /* Color wheel is divided into six 60 degree sectors. */
+        unsigned int const sector = (hsv.h/sectorSize);
+            /* The sector in which our color resides.  Value is in 0..5 */
+        double const f = (hsv.h - sector*sectorSize)/60;
+            /* The fraction of the way the color is from one side of
+               our sector to the other side, going clockwise.  Value is
+               in [0, 1).
+            */
+        double const m = (hsv.v * (1 - hsv.s));
+        double const n = (hsv.v * (1 - (hsv.s * f)));
+        double const k = (hsv.v * (1 - (hsv.s * (1 - f))));
+
+        switch (sector) {
+        case 0:
+            R = hsv.v;
+            G = k;
+            B = m;
+            break;
+        case 1:
+            R = n;
+            G = hsv.v;
+            B = m;
+            break;
+        case 2:
+            R = m;
+            G = hsv.v;
+            B = k;
+            break;
+        case 3:
+            R = m;
+            G = n;
+            B = hsv.v;
+            break;
+        case 4:
+            R = k;
+            G = m;
+            B = hsv.v;
+            break;
+        case 5:
+            R = hsv.v;
+            G = m;
+            B = n;
+            break;
+        default:
+            pm_error("Invalid H value passed to color_from_HSV: %f", hsv.h);
+        }
+    }
+    PPM_ASSIGN(retval, 
+               ROUNDU(R * maxval),
+               ROUNDU(G * maxval),
+               ROUNDU(B * maxval));
+
+    return retval;
+}
+
+
+
+struct hsv
+ppm_hsv_from_color(pixel  const color,
+                   pixval const maxval) {
+
+    double const epsilon = 1e-5;
+
+    double const R = (double)PPM_GETR(color) / maxval;
+    double const G = (double)PPM_GETG(color) / maxval;
+    double const B = (double)PPM_GETB(color) / maxval;
+
+    enum hueSector {SECTOR_RED, SECTOR_GRN, SECTOR_BLU};
+    enum hueSector hueSector;
+
+    struct hsv retval;
+    double range;
+
+    if (R >= G) {
+        if (R >= B) {
+            hueSector = SECTOR_RED;
+            retval.v = R;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    } else {
+        if (G >= B) {
+            hueSector = SECTOR_GRN;
+            retval.v = G;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    }
+
+    range = retval.v - MIN(R, MIN(G, B));
+
+    if (retval.v < epsilon)
+        retval.s = 0.0;
+    else
+        retval.s = range/retval.v;
+
+    if (range < epsilon)
+        /* It's gray, so hue really has no meaning.  We arbitrarily pick 0 */
+        retval.h = 0.0;
+    else {
+        double const cr = (retval.v - R) / range;
+        double const cg = (retval.v - G) / range;
+        double const cb = (retval.v - B) / range;
+
+        double angle;  /* hue angle, in range -30 - +330 */
+
+        switch(hueSector) {
+        case SECTOR_RED: angle =   0.0 + 60.0 * (cb - cg); break;
+        case SECTOR_GRN: angle = 120.0 + 60.0 * (cr - cb); break;
+        case SECTOR_BLU: angle = 240.0 + 60.0 * (cg - cr); break;
+        }
+        retval.h = angle >= 0.0 ? angle : 360 + angle;
+    }
+
+    return retval;
+}
+
+
diff --git a/lib/libppmd.c b/lib/libppmd.c
new file mode 100644
index 00000000..6c764ca1
--- /dev/null
+++ b/lib/libppmd.c
@@ -0,0 +1,1177 @@
+/* 
+**
+** This library module contains the ppmdraw routines.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+** 
+** The character drawing routines are by John Walker
+** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "ppmdfont.h"
+#include "ppmdraw.h"
+
+typedef int qsort_compare(const void *, const void *);
+    /* A compare function to pass to stdlib.h's qsort() */
+
+
+#define DDA_SCALE 8192
+
+struct penpos {
+    int x;
+    int y;
+};
+
+
+
+
+static void
+drawPoint(ppmd_drawproc       drawproc,
+          const void *  const clientdata,
+          pixel **      const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x, 
+          int           const y) {
+/*----------------------------------------------------------------------------
+   Draw a single point, assuming that it is within the bounds of the
+   image.
+-----------------------------------------------------------------------------*/
+    if (drawproc == PPMD_NULLDRAWPROC) {
+        const pixel * const pixelP = clientdata;
+        
+        assert(x >= 0); assert(x < cols);
+        assert(y >= 0); assert(y < rows);
+
+        pixels[y][x] = *pixelP;
+    } else
+        drawproc(pixels, cols, rows, maxval, x, y, clientdata);
+}
+
+
+
+void
+ppmd_point_drawproc(pixel**     const pixels, 
+                    int         const cols, 
+                    int         const rows, 
+                    pixval      const maxval, 
+                    int         const x, 
+                    int         const y, 
+                    const void* const clientdata) {
+
+    if (x >= 0 && x < cols && y >= 0 && y < rows)
+        pixels[y][x] = *((pixel*)clientdata);
+}
+
+
+/* Simple fill routine. */
+
+void
+ppmd_filledrectangle(pixel **      const pixels, 
+                     int           const cols, 
+                     int           const rows, 
+                     pixval        const maxval, 
+                     int           const x, 
+                     int           const y, 
+                     int           const width, 
+                     int           const height, 
+                     ppmd_drawproc      drawProc,
+                     const void *  const clientdata) {
+
+    int cx, cy, cwidth, cheight, row;
+
+    /* Clip. */
+    cx = x;
+    cy = y;
+    cwidth = width;
+    cheight = height;
+
+    if (cx < 0) {
+        cx = 0;
+        cwidth += x;
+    }
+    if (cy < 0) {
+        cy = 0;
+        cheight += y;
+    }
+    if (cx + cwidth > cols)
+        cwidth = cols - cx;
+
+    if (cy + cheight > rows)
+        cheight = rows - cy;
+
+    /* Draw. */
+    for (row = cy; row < cy + cheight; ++row) {
+        unsigned int col;
+        for (col = cx; col < cx + cwidth; ++col)
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, col, row);
+    }
+}
+
+
+/* Outline drawing stuff. */
+
+static int linetype = PPMD_LINETYPE_NORMAL;
+
+int
+ppmd_setlinetype(int const type) {
+
+    int old;
+
+    old = linetype;
+    linetype = type;
+    return old;
+}
+
+
+
+static bool lineclip = TRUE;
+
+
+
+int
+ppmd_setlineclip(int const newSetting) {
+
+    bool previousSetting;
+
+    previousSetting = lineclip;
+
+    lineclip = newSetting;
+
+    return previousSetting;
+}
+
+
+
+static void
+clipEnd0(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx0P,
+         int *  const cy0P,
+         bool * const noLineP) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from (x0, y0) to (x1, y1), where any of
+   these coordinates may be anywhere in space -- not just in the frame,
+   clip the (x0, y0) end to bring it into the frame.  
+   Return the clipped-to location as (*cx0P, *cy0P).
+
+   Iff this is not possible because the entire line described is
+   outside the frame, return *nolineP == true.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+
+    int cx0, cy0;
+    bool noLine;
+
+    cx0 = x0;        /* initial value */
+    cy0 = y0;        /* initial value */
+    noLine = FALSE;  /* initial value */
+
+    /* Clip End 0 of the line horizontally */
+    if (cx0 < 0) {
+        if (x1 < 0)
+            noLine = TRUE;
+        else {
+            cy0 = cy0 + (y1 - cy0) * (-cx0) / (x1 - cx0);
+            cx0 = 0;
+        }
+    } else if (cx0 >= cols) {
+        if (x1 >= cols)
+            noLine = TRUE;
+        else {
+            cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0);
+            cx0 = cols - 1;
+        }
+    }
+
+    /* Clip End 0 of the line vertically */
+    if (cy0 < 0) {
+        if (y1 < 0)
+            noLine = TRUE;
+        else {
+            cx0 = cx0 + (x1 - cx0) * (-cy0) / (y1 - cy0);
+            cy0 = 0;
+        }
+    } else if (cy0 >= rows) {
+        if (y1 >= rows)
+            noLine = TRUE;
+        else {
+            cx0 = cx0 + (x1 - cx0) * (rows - 1 - cy0) / (y1 - cy0);
+            cy0 = rows - 1;
+        }
+    }
+
+    *cx0P = cx0;
+    *cy0P = cy0;
+    *noLineP = noLine;
+}
+
+
+
+static void
+clipEnd1(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx1P,
+         int *  const cy1P) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from (x0, y0) to (x1, y1), where (x0, y0) is
+   within the frame, but (x1, y1) can be anywhere in space, clip the
+   (x1, y1) end to bring it into the frame.  Return the clipped-to
+   location as (*cx1P, *cy1P).
+
+   This is guaranteed to be possible, since we already know at least one
+   point (i.e. (x0, y0)) is in the frame.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+    int cx1, cy1;
+
+    assert(x0 >= 0 && y0 < cols);
+    assert(y0 >= 0 && y0 < rows);
+    
+    /* Clip End 1 of the line horizontally */
+    cx1 = x1;  /* initial value */
+    cy1 = y1;  /* initial value */
+    
+    if (cx1 < 0) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is left of frame.
+        */
+        cy1 = cy1 + (y0 - cy1) * (-cx1) / (x0 - cx1);
+        cx1 = 0;
+    } else if (cx1 >= cols) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is right of frame.
+        */
+        cy1 = cy1 + (y0 - cy1) * (cols - 1 - cx1) / (x0 - cx1);
+        cx1 = cols - 1;
+    }
+    
+    /* Clip End 1 of the line vertically */
+    if (cy1 < 0) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is above frame.
+        */
+        cx1 = cx1 + (x0 - cx1) * (-cy1) / (y0 - cy1);
+        cy1 = 0;
+    } else if (cy1 >= rows) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is below frame.
+        */
+        cx1 = cx1 + (x0 - cx1) * (rows - 1 - cy1) / (y0 - cy1);
+        cy1 = rows - 1;
+    }
+
+    *cx1P = cx1;
+    *cy1P = cy1;
+}
+
+
+
+static void
+clipLine(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx0P,
+         int *  const cy0P,
+         int *  const cx1P,
+         int *  const cy1P,
+         bool * const noLineP) {
+/*----------------------------------------------------------------------------
+   Clip the line that goes from (x0, y0) to (x1, y1) so that none of it
+   is outside the boundaries of the raster with width 'cols' and height
+   'rows'
+
+   The clipped line goes from (*cx0P, *cy0P) to (*cx1P, *cy1P).
+
+   But if the entire line is outside the boundaries (i.e. we clip the
+   entire line), return *noLineP true and the other values undefined.
+-----------------------------------------------------------------------------*/
+    int cx0, cy0, cx1, cy1;
+        /* The line we successively modify.  Starts out as the input
+           line and ends up as the output line.
+        */
+    bool noLine;
+
+    clipEnd0(x0, y0, x1, y1, cols, rows, &cx0, &cy0, &noLine);
+
+    if (!noLine) {
+        /* (cx0, cy0) is in the frame: */
+        assert(cx0 >= 0 && cy0 < cols);
+        assert(cy0 >= 0 && cy0 < rows);
+
+        clipEnd1(cx0, cy0, x1, y1, cols, rows, &cx1, &cy1);
+    }
+
+    *cx0P = cx0;
+    *cy0P = cy0;
+    *cx1P = cx1;
+    *cy1P = cy1;
+    *noLineP = noLine;
+}
+
+
+
+static void
+drawShallowLine(ppmd_drawproc       drawProc,
+                const void *  const clientdata,
+                pixel **      const pixels, 
+                int           const cols, 
+                int           const rows, 
+                pixval        const maxval, 
+                int           const x0, 
+                int           const y0,
+                int           const x1,
+                int           const y1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more horizontal than vertical.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+
+    /* Loop over X domain. */
+    long dy, srow;
+    int dx, col, row, prevrow;
+
+    if (x1 > x0)
+        dx = 1;
+    else
+        dx = -1;
+    dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0);
+    prevrow = row = y0;
+    srow = row * DDA_SCALE + DDA_SCALE / 2;
+    col = x0;
+    for ( ; ; ) {
+        if (linetype == PPMD_LINETYPE_NODIAGS && row != prevrow) {
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, col, prevrow);
+            prevrow = row;
+        }
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, col, row);
+        if (col == x1)
+            break;
+        srow += dy;
+        row = srow / DDA_SCALE;
+        col += dx;
+    }
+}
+
+
+
+static void
+drawSteepLine(ppmd_drawproc       drawProc,
+              const void *  const clientdata,
+              pixel **      const pixels, 
+              int           const cols, 
+              int           const rows, 
+              pixval        const maxval, 
+              int           const x0, 
+              int           const y0,
+              int           const x1,
+              int           const y1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more vertical than horizontal.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+    /* Loop over Y domain. */
+
+    long dx, scol;
+    int dy, col, row, prevcol;
+
+    if (y1 > y0)
+        dy = 1;
+    else
+        dy = -1;
+    dx = (x1 - x0) * DDA_SCALE / abs(y1 - y0);
+    row = y0;
+    prevcol = col = x0;
+    scol = col * DDA_SCALE + DDA_SCALE / 2;
+    for ( ; ; ) {
+        if (linetype == PPMD_LINETYPE_NODIAGS && col != prevcol) {
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, prevcol, row);
+            prevcol = col;
+        }
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, col, row);
+        if (row == y1)
+            break;
+        row += dy;
+        scol += dx;
+        col = scol / DDA_SCALE;
+    }
+}
+
+
+
+void
+ppmd_line(pixel **      const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x0, 
+          int           const y0, 
+          int           const x1, 
+          int           const y1, 
+          ppmd_drawproc       drawProc,
+          const void *  const clientdata) {
+
+    int cx0, cy0, cx1, cy1;
+    bool noLine;  /* There's no line left after clipping */
+
+    if (lineclip) {
+        clipLine(x0, y0, x1, y1, cols, rows, &cx0, &cy0, &cx1, &cy1, &noLine);
+    } else {
+        cx0 = x0;
+        cy0 = y0;
+        cx1 = x1;
+        cy1 = y1;
+        noLine = FALSE;
+    }
+
+    if (noLine) {
+        /* Nothing to draw */
+    } else if (cx0 == cx1 && cy0 == cy1) {
+        /* This line is just a point.  Because there aren't two
+           distinct endpoints, we have a special case.
+        */
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, cx0, cy0);
+    } else {
+        /* Draw, using a simple DDA. */
+        if (abs(cx1 - cx0) > abs(cy1 - cy0))
+            drawShallowLine(drawProc, clientdata, pixels, cols, rows, maxval,
+                            cx0, cy0, cx1, cy1);
+        else
+            drawSteepLine(drawProc, clientdata, pixels, cols, rows, maxval,
+                          cx0, cy0, cx1, cy1);
+    }
+}
+
+
+
+#define SPLINE_THRESH 3
+void
+ppmd_spline3(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             ppmd_drawproc       drawProc,
+             const void *  const clientdata) {
+
+    register int xa, ya, xb, yb, xc, yc, xp, yp;
+
+    xa = ( x0 + x1 ) / 2;
+    ya = ( y0 + y1 ) / 2;
+    xc = ( x1 + x2 ) / 2;
+    yc = ( y1 + y2 ) / 2;
+    xb = ( xa + xc ) / 2;
+    yb = ( ya + yc ) / 2;
+
+    xp = ( x0 + xb ) / 2;
+    yp = ( y0 + yb ) / 2;
+    if ( abs( xa - xp ) + abs( ya - yp ) > SPLINE_THRESH )
+        ppmd_spline3(
+            pixels, cols, rows, maxval, x0, y0, xa, ya, xb, yb, drawProc,
+            clientdata );
+    else
+        ppmd_line(
+            pixels, cols, rows, maxval, x0, y0, xb, yb, drawProc, clientdata);
+
+    xp = ( x2 + xb ) / 2;
+    yp = ( y2 + yb ) / 2;
+    if ( abs( xc - xp ) + abs( yc - yp ) > SPLINE_THRESH )
+        ppmd_spline3(
+            pixels, cols, rows, maxval, xb, yb, xc, yc, x2, y2, drawProc,
+            clientdata );
+    else
+        ppmd_line(
+            pixels, cols, rows, maxval, xb, yb, x2, y2,
+            drawProc, clientdata );
+}
+
+
+
+void
+ppmd_polyspline(pixel **      const pixels, 
+                int           const cols, 
+                int           const rows, 
+                pixval        const maxval, 
+                int           const x0, 
+                int           const y0, 
+                int           const nc, 
+                int *         const xc, 
+                int *         const yc, 
+                int           const x1, 
+                int           const y1, 
+                ppmd_drawproc       drawProc,
+                const void *  const clientdata) {
+
+    register int i, x, y, xn, yn;
+
+    x = x0;
+    y = y0;
+    for ( i = 0; i < nc - 1; ++i )
+    {
+        xn = ( xc[i] + xc[i + 1] ) / 2;
+        yn = ( yc[i] + yc[i + 1] ) / 2;
+        ppmd_spline3(
+            pixels, cols, rows, maxval, x, y, xc[i], yc[i], xn, yn, drawProc,
+            clientdata );
+        x = xn;
+        y = yn;
+    }
+    ppmd_spline3(
+        pixels, cols, rows, maxval, x, y, xc[nc - 1], yc[nc - 1], x1, y1,
+        drawProc, clientdata );
+}
+
+
+
+void
+ppmd_spline4(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             int           const x3, 
+             int           const y3, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata) {
+
+    pm_error("ppmd_spline4() has not been written yet!");
+
+}
+
+
+
+void
+ppmd_circle(pixel **      const pixels, 
+            int           const cols, 
+            int           const rows, 
+            pixval        const maxval, 
+            int           const cx, 
+            int           const cy, 
+            int           const radius, 
+            ppmd_drawproc       drawProc,
+            const void *  const clientdata) {
+
+    int x0, y0, x, y, prevx, prevy, nopointsyet;
+    long sx, sy, e;
+
+    x0 = x = radius;
+    y0 = y = 0;
+    sx = x * DDA_SCALE + DDA_SCALE / 2;
+    sy = y * DDA_SCALE + DDA_SCALE / 2;
+    e = DDA_SCALE / radius;
+    drawPoint(drawProc, clientdata,
+              pixels, cols, rows, maxval, x + cx, y + cy);
+    nopointsyet = 1;
+
+    do {
+        prevx = x;
+        prevy = y;
+        sx += e * sy / DDA_SCALE;
+        sy -= e * sx / DDA_SCALE;
+        x = sx / DDA_SCALE;
+        y = sy / DDA_SCALE;
+        if (x != prevx || y != prevy) {
+            nopointsyet = 0;
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, x + cx, y + cy);
+        }
+    }
+    while (nopointsyet || x != x0 || y != y0);
+}
+
+
+
+/* Arbitrary fill stuff. */
+
+typedef struct
+{
+    short x;
+    short y;
+    short edge;
+} coord;
+
+typedef struct fillobj {
+    int n;
+    int size;
+    int curedge;
+    int segstart;
+    int ydir;
+    int startydir;
+    coord * coords;
+} fillobj;
+
+#define SOME 1000
+
+static int oldclip;
+
+struct fillobj *
+ppmd_fill_create(void) {
+
+    fillobj * fillObjP;
+
+    MALLOCVAR(fillObjP);
+    if (fillObjP == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    fillObjP->n = 0;
+    fillObjP->size = SOME;
+    MALLOCARRAY(fillObjP->coords, fillObjP->size);
+    if (fillObjP->coords == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    fillObjP->curedge = 0;
+    
+    /* Turn off line clipping. */
+    /* UGGH! We must eliminate this global variable */
+    oldclip = ppmd_setlineclip(0);
+    
+    return fillObjP;
+}
+
+
+
+void
+ppmd_fill_destroy(struct fillobj * fillObjP) {
+
+    free(fillObjP->coords);
+    free(fillObjP);
+}
+
+
+void
+ppmd_fill_drawproc(pixel**      const pixels, 
+                   int          const cols, 
+                   int          const rows, 
+                   pixval       const maxval, 
+                   int          const x, 
+                   int          const y, 
+                   const void * const clientdata) {
+
+    fillobj * fh;
+    coord * cp;
+    coord * ocp;
+
+    fh = (fillobj*) clientdata;
+
+    if (fh->n > 0) {
+        /* If these are the same coords we saved last time, don't bother. */
+        ocp = &(fh->coords[fh->n - 1]);
+        if ( x == ocp->x && y == ocp->y )
+            return;
+    }
+
+    /* Ok, these are new; check if there's room for two more coords. */
+    if (fh->n + 1 >= fh->size) {
+        fh->size += SOME;
+        REALLOCARRAY(fh->coords, fh->size);
+        if (fh->coords == NULL)
+            pm_error( "out of memory enlarging a fillhandle" );
+    }
+
+    /* Check for extremum and set the edge number. */
+    if (fh->n == 0) {
+        /* Start first segment. */
+        fh->segstart = fh->n;
+        fh->ydir = 0;
+        fh->startydir = 0;
+    } else {
+        int dx, dy;
+
+        dx = x - ocp->x;
+        dy = y - ocp->y;
+        if (dx < -1 || dx > 1 || dy < -1 || dy > 1) {
+            /* Segment break.  Close off old one. */
+            if (fh->startydir != 0 && fh->ydir != 0)
+                if (fh->startydir == fh->ydir) {
+                    /* Oops, first edge and last edge are the same.
+                       Renumber the first edge in the old segment.
+                    */
+                    coord * fcp;
+                    int oldedge;
+
+                    fcp = &(fh->coords[fh->segstart]);
+                    oldedge = fcp->edge;
+                    for ( ; fcp->edge == oldedge; ++fcp )
+                        fcp->edge = ocp->edge;
+                }
+            /* And start new segment. */
+            ++fh->curedge;
+            fh->segstart = fh->n;
+            fh->ydir = 0;
+            fh->startydir = 0;
+        } else {
+            /* Segment continues. */
+            if (dy != 0) {
+                if (fh->ydir != 0 && fh->ydir != dy) {
+                    /* Direction changed.  Insert a fake coord, old
+                       position but new edge number.
+                    */
+                    ++fh->curedge;
+                    cp = &fh->coords[fh->n];
+                    cp->x = ocp->x;
+                    cp->y = ocp->y;
+                    cp->edge = fh->curedge;
+                    ++fh->n;
+                }
+                fh->ydir = dy;
+                if (fh->startydir == 0)
+                    fh->startydir = dy;
+            }
+        }
+    }
+
+    /* Save this coord. */
+    cp = &fh->coords[fh->n];
+    cp->x = x;
+    cp->y = y;
+    cp->edge = fh->curedge;
+    ++fh->n;
+}
+
+
+
+
+static qsort_compare yx_compare;
+
+static int
+yx_compare(const void * const c1Arg,
+           const void * const c2Arg) {
+
+    const coord * const c1P = c1Arg;
+    const coord * const c2P = c2Arg;
+
+    int retval;
+    
+    if (c1P->y > c2P->y)
+        retval = 1;
+    else if (c1P->y < c2P->y)
+        retval = -1;
+    else if (c1P->x > c2P->x)
+        retval = 1;
+    else if (c1P->x < c2P->x)
+        retval = -1;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+void
+ppmd_fill(pixel **         const pixels, 
+          int              const cols, 
+          int              const rows, 
+          pixval           const maxval, 
+          struct fillobj * const fh,
+          ppmd_drawproc          drawProc,
+          const void *     const clientdata) {
+
+    int pedge;
+    int i, edge, lx, rx, py;
+    coord * cp;
+    bool eq;
+    bool leftside;
+
+    /* Close off final segment. */
+    if (fh->n > 0 && fh->startydir != 0 && fh->ydir != 0) {
+        if (fh->startydir == fh->ydir) {
+            /* Oops, first edge and last edge are the same. */
+            coord * fcp;
+            int lastedge, oldedge;
+
+            lastedge = fh->coords[fh->n - 1].edge;
+            fcp = &(fh->coords[fh->segstart]);
+            oldedge = fcp->edge;
+            for ( ; fcp->edge == oldedge; ++fcp )
+                fcp->edge = lastedge;
+        }
+    }
+    /* Restore clipping now. */
+    ppmd_setlineclip(oldclip);
+
+    /* Sort the coords by Y, secondarily by X. */
+    qsort((char*) fh->coords, fh->n, sizeof(coord), yx_compare);
+
+    /* Find equal coords with different edge numbers, and swap if necessary. */
+    edge = -1;
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i > 1 && eq && cp->edge != edge && cp->edge == pedge) {
+            /* Swap .-1 and .-2. */
+            coord t;
+
+            t = fh->coords[i-1];
+            fh->coords[i-1] = fh->coords[i-2];
+            fh->coords[i-2] = t;
+        }
+        if (i > 0) {
+            if (cp->x == lx && cp->y == py) {
+                eq = TRUE;
+                if (cp->edge != edge && cp->edge == pedge) {
+                    /* Swap . and .-1. */
+                    coord t;
+
+                    t = *cp;
+                    *cp = fh->coords[i-1];
+                    fh->coords[i-1] = t;
+                }
+            } else
+                eq = FALSE;
+        }
+        lx    = cp->x;
+        py    = cp->y;
+        pedge = edge;
+        edge  = cp->edge;
+    }
+
+    /* Ok, now run through the coords filling spans. */
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i == 0) {
+            lx       = rx = cp->x;
+            py       = cp->y;
+            edge     = cp->edge;
+            leftside = TRUE;
+        } else {
+            if (cp->y != py) {
+                /* Row changed.  Emit old span and start a new one. */
+                ppmd_filledrectangle(
+                    pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1,
+                    drawProc, clientdata);
+                lx       = rx = cp->x;
+                py       = cp->y;
+                edge     = cp->edge;
+                leftside = TRUE;
+            } else {
+                if (cp->edge == edge) {
+                    /* Continuation of side. */
+                    rx = cp->x;
+                } else {
+                    /* Edge changed.  Is it a span? */
+                    if (leftside) {
+                        rx       = cp->x;
+                        leftside = FALSE;
+                    } else {
+                        /* Got a span to fill. */
+                        ppmd_filledrectangle(
+                            pixels, cols, rows, maxval, lx, py, rx - lx + 1,
+                            1, drawProc, clientdata);
+                        lx       = rx = cp->x;
+                        leftside = TRUE;
+                    }
+                    edge = cp->edge;
+                }
+            }
+        }
+    }
+}
+
+
+
+/* Table used to look up sine of angles from 0 through 90 degrees.
+   The value returned is the sine * 65536.  Symmetry is used to
+   obtain sine and cosine for arbitrary angles using this table. */
+
+static long sintab[] = {
+    0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380,
+    12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336,
+    22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767,
+    31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440,
+    40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142,
+    47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683,
+    54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903,
+    59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672,
+    62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898,
+    65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536
+};
+
+static int extleft, exttop, extright, extbottom;  /* To accumulate extents */
+
+/* LINTLIBRARY */
+
+/*  ISIN  --  Return sine of an angle in integral degrees.  The
+          value returned is 65536 times the sine.  */
+
+#if __STDC__
+static long isin(int deg)
+#else
+    static long isin(deg)
+    int deg;
+#endif
+{
+    /* Domain reduce to 0 to 360 degrees. */
+
+    if (deg < 0) {
+        deg = (360 - ((- deg) % 360)) % 360;
+    } else if (deg >= 360) {
+        deg = deg % 360;
+    }
+
+    /* Now look up from table according to quadrant. */
+
+    if (deg <= 90) {
+        return sintab[deg];
+    } else if (deg <= 180) {
+        return sintab[180 - deg];
+    } else if (deg <= 270) {
+        return -sintab[deg - 180];
+    }
+    return -sintab[360 - deg];
+}
+
+/*  ICOS  --  Return cosine of an angle in integral degrees.  The
+          value returned is 65536 times the cosine.  */
+
+#if __STDC__
+static long icos(int deg)
+#else
+    static long icos(deg)
+    int deg;
+#endif
+{
+    return isin(deg + 90);
+}  
+
+#define SCHAR(x) (u = (x), (((u) & 0x80) ? ((u) | (-1 ^ 0xFF)) : (u)))
+
+#define Scalef 21       /* Font design size */
+#define Descend 9       /* Descender offset */
+
+
+
+static void
+drawGlyph(const struct ppmd_glyph * const glyphP,
+          int *                     const xP,
+          int                       const y,
+          pixel **                  const pixels,
+          unsigned int              const cols,
+          unsigned int              const rows,
+          pixval                    const maxval,
+          int                       const height,
+          int                       const xpos,
+          int                       const ypos,
+          long                      const rotcos,
+          long                      const rotsin,
+          ppmd_drawproc                   drawProc,
+          const void *              const clientdata
+          ) {
+/*----------------------------------------------------------------------------
+   *xP is the column number of the left side of the glyph in the
+   output upon entry, and we update it to the left side of the next
+   glyph.
+
+   'y' is the row number of either the top or the bottom of the glyph
+   (I can't tell which right now) in the output.
+-----------------------------------------------------------------------------*/
+    struct penpos penPos;
+    unsigned int commandNum;
+    int x;
+    int u;  /* Used by the SCHAR macro */
+
+    x = *xP;  /* initial value */
+
+    x -= SCHAR(glyphP->header.skipBefore);
+
+    penPos.x = x;
+    penPos.y = y;
+
+    for (commandNum = 0;
+         commandNum < glyphP->header.commandCount;
+         ++commandNum) {
+
+        const struct ppmd_glyphCommand * const commandP =
+            &glyphP->commandList[commandNum];
+
+        switch (commandP->verb) {
+        case CMD_NOOP:
+            break;
+        case CMD_DRAWLINE:
+        {
+            int const nx = x + SCHAR(commandP->x);
+            int const ny = y + SCHAR(commandP->y);
+
+            int mx1, my1, mx2, my2;
+            int tx1, ty1, tx2, ty2;
+
+            /* Note that up until this  moment  we've  been
+               working  in  an  arbitrary model co-ordinate
+               system with  fixed  size  and  no  rotation.
+               Before  drawing  the  stroke,  transform  to
+               viewing co-ordinates to  honour  the  height
+               and angle specifications.
+            */
+
+            mx1 = (penPos.x * height) / Scalef;
+            my1 = ((penPos.y - Descend) * height) / Scalef;
+            mx2 = (nx * height) / Scalef;
+            my2 = ((ny - Descend) * height) / Scalef;
+            tx1 = xpos + (mx1 * rotcos - my1 * rotsin) / 65536;
+            ty1 = ypos + (mx1 * rotsin + my1 * rotcos) / 65536;
+            tx2 = xpos + (mx2 * rotcos - my2 * rotsin) / 65536;
+            ty2 = ypos + (mx2 * rotsin + my2 * rotcos) / 65536;
+            
+            ppmd_line(pixels, cols, rows, maxval, tx1, ty1, tx2, ty2,
+                      drawProc, clientdata);
+
+            penPos.x = nx;
+            penPos.y = ny;
+        }
+            break;
+        case CMD_MOVEPEN:
+            penPos.x = x + SCHAR(commandP->x);
+            penPos.y = y + SCHAR(commandP->y);
+            break;
+        }
+    }
+    x += glyphP->header.skipAfter; 
+
+    *xP = x;
+}
+
+
+/* PPMD_TEXT  --  Draw the zero-terminated  string  s,  with  its  baseline
+          starting  at  point  (x, y), inclined by angle degrees to
+          the X axis, with letters height pixels  high  (descenders
+          will  extend below the baseline).  The supplied drawproc
+          and cliendata are passed to ppmd_line which performs  the
+          actual drawing. */
+
+void
+ppmd_text(pixel**       const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const xpos, 
+          int           const ypos, 
+          int           const height, 
+          int           const angle, 
+          const char *  const sArg, 
+          ppmd_drawproc       drawProc,
+          const void *  const clientdata) {
+
+    const struct ppmd_font * const fontP = ppmd_get_font();
+    long rotsin, rotcos;
+    int x, y;
+    const char * s;
+
+    x = y = 0;
+    rotsin = isin(-angle);
+    rotcos = icos(-angle);
+
+    s = sArg;
+    while (*s) {
+        unsigned char const ch = *s++;
+
+        if (ch >= fontP->header.firstCodePoint &&
+            ch < fontP->header.firstCodePoint + fontP->header.characterCount) {
+
+            const struct ppmd_glyph * const glyphP =
+                &fontP->glyphTable[ch - fontP->header.firstCodePoint];
+
+            drawGlyph(glyphP, &x, y, pixels, cols, rows, maxval,
+                      height, xpos, ypos, rotcos, rotsin,
+                      drawProc, clientdata);
+        } else if (ch == '\n') {
+            /* Move to the left edge of the next line down */
+            y += Scalef + Descend;
+            x = 0;
+        }
+    }
+}
+
+/* EXTENTS_DRAWPROC  --  Drawproc which just accumulates the extents
+             rectangle bounding the text. */
+
+static void 
+extents_drawproc (pixel**      const pixels, 
+                  int          const cols, 
+                  int          const rows,
+                  pixval       const maxval, 
+                  int          const x, 
+                  int          const y, 
+                  const void * const clientdata)
+{
+    extleft = MIN(extleft, x);
+    exttop = MIN(exttop, y);
+    extright = MAX(extright, x);
+    extbottom = MAX(extbottom, y);
+}
+
+
+/* PPMD_TEXT_BOX  --  Calculate  extents  rectangle for a given piece of
+   text.  For most  applications  where  extents  are
+   needed,   angle  should  be  zero  to  obtain  the
+   unrotated extents.  If you need  the  extents  box
+   for post-rotation text, however, you can set angle
+   nonzero and it will be calculated correctly.
+*/
+
+void
+ppmd_text_box(int const height, 
+              int const angle, 
+              const char * const s, 
+              int * const left, 
+              int * const top, 
+              int * const right, 
+              int * const bottom)
+{
+    extleft = 32767;
+    exttop = 32767;
+    extright = -32767;
+    extbottom = -32767;
+    ppmd_text(NULL, 32767, 32767, 255, 1000, 1000, height, angle, s, 
+              extents_drawproc, NULL);
+    *left = extleft - 1000; 
+    *top = exttop - 1000;
+    *right = extright - 1000;
+    *bottom = extbottom - 1000;
+}
diff --git a/lib/libppmfloyd.c b/lib/libppmfloyd.c
new file mode 100644
index 00000000..071c3c36
--- /dev/null
+++ b/lib/libppmfloyd.c
@@ -0,0 +1,270 @@
+/* 
+These functions were taken from Ingo Wilken's ilbm package by Bryan
+Henderson on 01.03.10.  Because ppmtoilbm and ilbmtoppm are the only
+programs that will use these in the foreseeable future, they remain
+lightly documented and tested. 
+
+But they look like they would be useful in other Netpbm programs that
+do Floyd-Steinberg.
+*/
+
+
+
+/* libfloyd.c - generic Floyd-Steinberg error distribution routines for PBMPlus
+**
+** Copyright (C) 1994 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "ppmfloyd.h"
+#include "mallocvar.h"
+
+
+
+static void
+fs_adjust(ppm_fs_info * const fi, 
+          int           const col) {
+
+    int     const errcol = col+1;
+    pixel * const pP     = &(fi->pixrow[col]);
+    pixval  const maxval = fi->maxval;
+
+    long r, g, b;
+
+    /* Use Floyd-Steinberg errors to adjust actual color. */
+    r = fi->thisrederr  [errcol]; if( r < 0 ) r -= 8; else r += 8; r /= 16;
+    g = fi->thisgreenerr[errcol]; if( g < 0 ) g -= 8; else g += 8; g /= 16;
+    b = fi->thisblueerr [errcol]; if( b < 0 ) b -= 8; else b += 8; b /= 16;
+
+    r += PPM_GETR(*pP); if ( r < 0 ) r = 0; else if ( r > maxval ) r = maxval;
+    g += PPM_GETG(*pP); if ( g < 0 ) g = 0; else if ( g > maxval ) g = maxval;
+    b += PPM_GETB(*pP); if ( b < 0 ) b = 0; else if ( b > maxval ) b = maxval;
+
+    PPM_ASSIGN(*pP, r, g, b);
+    fi->red = r; fi->green = g; fi->blue = b;
+}
+
+
+
+static ppm_fs_info *
+allocateFi(int const cols) {
+
+    ppm_fs_info * fi;
+
+    MALLOCVAR(fi);
+    
+    if (fi != NULL) {
+        MALLOCARRAY(fi->thisrederr  , cols + 2);
+        MALLOCARRAY(fi->thisgreenerr, cols + 2);
+        MALLOCARRAY(fi->thisblueerr , cols + 2);
+        MALLOCARRAY(fi->nextrederr  , cols + 2);
+        MALLOCARRAY(fi->nextgreenerr, cols + 2);
+        MALLOCARRAY(fi->nextblueerr , cols + 2);
+        
+        if (fi->thisrederr   == NULL || 
+            fi->thisgreenerr == NULL || 
+            fi->thisblueerr  == NULL ||
+            fi->nextrederr   == NULL || 
+            fi->nextgreenerr == NULL || 
+            fi->nextblueerr  == NULL)
+            pm_error("out of memory allocating "
+                     "Floyd-Steinberg control structure");
+    } else
+        pm_error("out of memory allocating Floyd-Steinberg control structure");
+
+    return(fi);
+}
+
+
+
+ppm_fs_info *
+ppm_fs_init(int cols, pixval maxval, int flags) {
+
+    ppm_fs_info *fi;
+    
+    fi = allocateFi(cols);
+
+    fi->lefttoright = 1;
+    fi->cols = cols;
+    fi->maxval = maxval;
+    fi->flags = flags;
+    
+    if( flags & FS_RANDOMINIT ) {
+        unsigned int i;
+        srand((int)(time(0) ^ getpid()));
+        for( i = 0; i < cols +2; i++ ) {
+            /* random errors in [-1..+1] */
+            fi->thisrederr[i]   = rand() % 32 - 16;
+            fi->thisgreenerr[i] = rand() % 32 - 16;
+            fi->thisblueerr[i]  = rand() % 32 - 16;
+        }
+    }
+    else {
+        unsigned int i;
+
+        for( i = 0; i < cols + 2; i++ )
+            fi->thisrederr[i] = fi->thisgreenerr[i] = 
+                fi->thisblueerr[i] = 0;
+    }
+    return fi;
+}
+
+
+
+void
+ppm_fs_free(fi)
+    ppm_fs_info *fi;
+{
+    if( fi ) {
+        free(fi->thisrederr); free(fi->thisgreenerr); free(fi->thisblueerr);
+        free(fi->nextrederr); free(fi->nextgreenerr); free(fi->nextblueerr);
+        free(fi);
+    }
+}
+
+
+int
+ppm_fs_startrow(fi, pixrow)
+    ppm_fs_info *fi;
+    pixel *pixrow;
+{
+    register int col;
+
+    if( !fi )
+        return 0;
+
+    fi->pixrow = pixrow;
+
+    for( col = 0; col < fi->cols + 2; col++ )
+        fi->nextrederr[col] = fi->nextgreenerr[col] = fi->nextblueerr[col] = 0;
+
+    if( fi->lefttoright ) {
+        fi->col_end = fi->cols;
+        col = 0;
+    }
+    else {
+        fi->col_end = -1;
+        col = fi->cols - 1;
+    }
+    fs_adjust(fi, col);
+    return col;
+}
+
+
+int
+ppm_fs_next(fi, col)
+    ppm_fs_info *fi;
+    int col;
+{
+    if( !fi )
+        ++col;
+    else {
+        if( fi->lefttoright )
+            ++col;
+        else
+            --col;
+        if( col == fi->col_end )
+            col = fi->cols;
+        else
+            fs_adjust(fi, col);
+    }
+    return col;
+}
+
+
+void
+ppm_fs_endrow(fi)
+    ppm_fs_info *fi;
+{
+    long *tmp;
+
+    if( fi ) {
+        tmp = fi->thisrederr;   fi->thisrederr   = fi->nextrederr;   fi->nextrederr   = tmp;
+        tmp = fi->thisgreenerr; fi->thisgreenerr = fi->nextgreenerr; fi->nextgreenerr = tmp;
+        tmp = fi->thisblueerr;  fi->thisblueerr  = fi->nextblueerr;  fi->nextblueerr  = tmp;
+        if( fi->flags & FS_ALTERNATE )
+            fi->lefttoright = !(fi->lefttoright);
+    }
+}
+
+
+void
+ppm_fs_update(fi, col, pP)
+    ppm_fs_info *fi;
+    int col;
+    pixel *pP;
+{
+    if( fi )
+        ppm_fs_update3(fi, col, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+}
+
+
+void
+ppm_fs_update3(ppm_fs_info * const fi, 
+               int           const col, 
+               pixval        const r, 
+               pixval        const g, 
+               pixval        const b) {
+
+    int const errcol = col + 1;
+    long err;
+
+    if (fi) {
+        long const rerr = (long)(fi->red)   - (long)r;
+        long const gerr = (long)(fi->green) - (long)g;
+        long const berr = (long)(fi->blue)  - (long)b;
+    
+        if ( fi->lefttoright ) {
+            long two_err;
+
+            two_err = 2*rerr;
+            err = rerr;     fi->nextrederr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextrederr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisrederr[errcol+1] += err;    /* 7/16 */
+
+            two_err = 2*gerr;
+            err = gerr;     fi->nextgreenerr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextgreenerr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisgreenerr[errcol+1] += err;    /* 7/16 */
+
+            two_err = 2*berr;
+            err = berr;     fi->nextblueerr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextblueerr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisblueerr[errcol+1] += err;    /* 7/16 */
+        }
+        else {
+            long two_err;
+
+            two_err = 2*rerr;
+            err = rerr;     fi->nextrederr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextrederr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisrederr[errcol-1] += err;    /* 7/16 */
+
+            two_err = 2*gerr;
+            err = gerr;     fi->nextgreenerr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextgreenerr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisgreenerr[errcol-1] += err;    /* 7/16 */
+
+            two_err = 2*berr;
+            err = berr;     fi->nextblueerr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextblueerr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisblueerr[errcol-1] += err;    /* 7/16 */
+        }
+    }
+}
+
+
+
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
new file mode 100644
index 00000000..6127d5d5
--- /dev/null
+++ b/lib/libppmfuzzy.c
@@ -0,0 +1,434 @@
+/*=============================================================================
+  This file contains fuzzy color matching code.
+
+  It is all based on algorithms and methods by Kenan Kalajdzic
+  <kenan@unix.ba> in 2006.
+=============================================================================*/
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "ppm.h"
+
+typedef double fzLog;
+    /* fuzzy logic truth value */
+
+/*----------------------------------------------------------------------------
+   Member functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+memberS(fzLog const x1,
+        fzLog const x2,
+        fzLog const x) {
+
+    fzLog retval;
+
+    if (x <= x1)
+        retval = 0;
+    else if (x > x1 && x <= x2)
+        retval = (x - x1) / (x2 - x1);
+    else
+        retval = 1;
+
+    return retval;
+}
+
+
+
+static fzLog
+memberZ(fzLog const x1,
+        fzLog const x2,
+        fzLog const x) {
+
+    fzLog retval;
+
+    if (x <= x1)
+        retval = 1;
+    else if (x > x1 && x <= x2)
+        retval = (x2 - x) / (x2 - x1);
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static fzLog
+memberTrapez(fzLog const x1,
+             fzLog const x2,
+             fzLog const x3,
+             fzLog const x4,
+             fzLog const x) {
+
+    fzLog retval;
+    
+    if (x <= x1 || x > x4)
+        retval = 0;
+    else if (x > x1 && x <= x2)
+        retval = (x - x1) / (x2 - x1);
+    else if (x > x2 && x <= x3)
+        retval = 1;
+    else
+        retval = (x4 - x) / (x4 - x3);
+
+    return retval;
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Hue membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+hueIsAround000(double const hue) {
+
+    return memberZ(10, 30, hue);
+}
+
+
+
+static fzLog
+hueIsAround015(double const hue) {
+
+    return memberZ(30, 40, hue);
+}
+
+
+
+static fzLog
+hueIsAround030(double const hue) {
+
+    return memberTrapez(10, 30, 40, 60, hue);
+}
+
+
+
+static fzLog
+hueIsAround060(double const hue) {
+
+    return memberTrapez(40, 60, 60, 80, hue);
+}
+
+
+
+static fzLog
+hueIsAround120(double const hue) {
+
+    return memberTrapez(60, 80, 150, 180, hue);
+}
+
+
+
+static fzLog
+hueIsAround180(double const hue) {
+
+    return memberTrapez(150, 180, 240, 260, hue);
+}
+
+
+
+static fzLog
+hueIsAround270(double const hue) {
+
+    return memberTrapez(240, 260, 290, 310, hue);
+}
+
+
+
+static fzLog
+hueIsAround320(double const hue) {
+
+    return memberTrapez(290, 310, 320, 350, hue);
+}
+
+
+
+static fzLog
+hueIsAround360(double const hue) {
+
+    return memberS(320, 350, hue);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Saturation membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+satIsVeryLow(double const sat) {
+
+    return memberZ(0.02, 0.1, sat);
+}
+
+
+
+static fzLog
+satIsLow(double const sat) {
+    return memberTrapez(0.02, 0.1, 0.2, 0.3, sat);
+}
+
+
+
+static fzLog
+satIsMedium(double const sat) {
+
+    return memberTrapez(0.2, 0.3, 0.6, 0.7, sat);
+}
+
+
+
+static fzLog
+satIsHigh(double const sat) {
+
+    return memberS(0.6, 0.7, sat);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Value membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+valIsVeryLow(double const val) {
+
+    return memberZ(0.1, 0.2, val);
+}
+
+
+
+static fzLog
+valIsLow(double const val) {
+
+    return memberTrapez(0.1, 0.2, 0.3, 0.6, val);
+}
+
+
+
+static fzLog
+valIsMedium(double const val) {
+
+    return memberTrapez(0.3, 0.6, 0.7, 0.8, val);
+}
+
+
+
+static fzLog
+valIsHigh(double const val) {
+
+    return memberS(0.7, 0.8, val);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Fuzzy logic functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+fzAnd(fzLog const opLeft,
+      fzLog const opRight) {
+
+    return MIN(opLeft, opRight);
+}
+
+
+
+static fzLog
+fzOr(fzLog const opLeft,
+     fzLog const opRight) {
+
+    return MAX(opLeft, opRight);
+}
+
+
+
+static fzLog
+fzNot(fzLog const op) {
+    return 1.0 - op;
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Fuzzy color matching
+-----------------------------------------------------------------------------*/
+
+static void
+matchBk(pixel     const color,
+        pixval    const maxval,
+        fzLog (* const bkMatchP)[BKCOLOR_COUNT]) {
+
+    struct hsv const hsv = ppm_hsv_from_color(color, maxval);
+
+    fzLog const satVeryLow = satIsVeryLow(hsv.s);
+    fzLog const satLow     = satIsLow(hsv.s);
+    fzLog const satMedium  = satIsMedium(hsv.s);
+    fzLog const satHigh    = satIsHigh(hsv.s);
+
+    fzLog const valVeryLow = valIsVeryLow(hsv.v);
+    fzLog const valLow     = valIsLow(hsv.v);
+    fzLog const valMedium  = valIsMedium(hsv.v);
+    fzLog const valHigh    = valIsHigh(hsv.v);
+
+    fzLog const hueAround000 = hueIsAround000(hsv.h);
+    fzLog const hueAround015 = hueIsAround015(hsv.h);
+    fzLog const hueAround030 = hueIsAround030(hsv.h);
+    fzLog const hueAround060 = hueIsAround060(hsv.h);
+    fzLog const hueAround120 = hueIsAround120(hsv.h);
+    fzLog const hueAround180 = hueIsAround180(hsv.h);
+    fzLog const hueAround270 = hueIsAround270(hsv.h);
+    fzLog const hueAround320 = hueIsAround320(hsv.h);
+    fzLog const hueAround360 = hueIsAround360(hsv.h);
+
+    (*bkMatchP)[BKCOLOR_BLACK]  =
+        fzAnd(fzOr(satVeryLow, satLow), valVeryLow);
+
+    (*bkMatchP)[BKCOLOR_GRAY]   =
+        fzAnd(fzOr(satVeryLow, satLow), fzOr(valLow, valMedium));
+
+    (*bkMatchP)[BKCOLOR_WHITE]  =
+        fzAnd(fzOr(satVeryLow, satLow), valHigh);
+    
+    (*bkMatchP)[BKCOLOR_RED]    =
+        fzAnd(fzAnd(fzOr(hueAround000, hueAround360), fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_ORANGE] =
+        fzAnd(fzAnd(hueAround030, fzOr(satMedium, satHigh)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_YELLOW] =
+        fzAnd(fzAnd(hueAround060, fzOr(satMedium, satHigh)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_GREEN]  =
+        fzAnd(fzAnd(hueAround120, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_BLUE]   =
+        fzAnd(fzAnd(hueAround180, fzAnd(fzNot(satVeryLow), fzNot(satLow))),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_VIOLET] =
+        fzAnd(fzAnd(hueAround270, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_PURPLE] =
+        fzAnd(fzAnd(hueAround320, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_BROWN]  =
+        fzAnd(fzOr(hueAround015, hueAround360),
+              fzAnd(fzNot(satVeryLow), fzNot(valHigh))
+             );
+}
+
+
+
+bk_color
+ppm_bk_color_from_color(pixel  const color,
+                        pixval const maxval) {
+
+    fzLog bkmatch[BKCOLOR_COUNT];
+    bk_color i;
+    bk_color bestSoFar;
+    fzLog bestMatch;
+
+    matchBk(color, maxval, &bkmatch);
+
+    for (i = 0, bestSoFar = 0, bestMatch = 0.0; i < BKCOLOR_COUNT; ++i) {
+        if (bkmatch[i] > bestMatch) {
+            bestSoFar = i;
+            bestMatch = bkmatch[i];
+        }
+    }
+    return bestSoFar;
+}
+
+
+
+static pixel const bkColorMap[BKCOLOR_COUNT] = {
+    {  0,   0,   0}, /* BKCOLOR_BLACK  */
+    {174, 174, 174}, /* BKCOLOR_GRAY   */
+    {255, 255, 255}, /* BKCOLOR_WHITE  */
+    {255,   0,   0}, /* BKCOLOR_RED    */
+    {255, 128,   0}, /* BKCOLOR_ORANGE */
+    {255, 255,   0}, /* BKCOLOR_YELLOW */
+    {  0, 255,   0}, /* BKCOLOR_GREEN  */
+    {  0,   0, 255}, /* BKCOLOR_BLUE   */
+    {143,  94, 153}, /* BKCOLOR_VIOLET */
+    {160,  32, 240}, /* BKCOLOR_PURPLE */
+    {128,  42,  42}  /* BKCOLOR_BROWN  */
+};
+
+
+
+pixel
+ppm_color_from_bk_color(bk_color const bkColor,
+                        pixval   const maxval) {
+
+    pixel const color255 = bkColorMap[bkColor];
+
+    pixel retval;
+
+    if (maxval != 255) {
+        PPM_DEPTH(retval, color255, 255, maxval);
+    } else
+        retval = color255;
+
+    return retval;
+}
+
+
+
+static const char * const bkColorNameMap[BKCOLOR_COUNT] = {
+    "black",
+    "gray",
+    "white",
+    "red",
+    "orange",
+    "yellow",
+    "green",
+    "blue",
+    "violet",
+    "purple",
+    "brown"
+};
+
+
+
+bk_color
+ppm_bk_color_from_name(const char * const name) {
+
+    bk_color i;
+
+    for (i = 0; i < BKCOLOR_COUNT; ++i) {
+        if (STREQ(name, bkColorNameMap[i]))
+            return i;
+    }
+    pm_error("Invalid Berlin-Kay color name: '%s'", name);
+    return 0;  /* quiet compiler warning */
+}
+
+
+
+const char *
+ppm_name_from_bk_color(bk_color const bkColor) {
+
+    if (bkColor >= BKCOLOR_COUNT)
+        pm_error("Invalid color passed to name_from_bk_color(): %u",
+                 bkColor);
+
+    return bkColorNameMap[bkColor];
+}
diff --git a/lib/libsystem.c b/lib/libsystem.c
new file mode 100644
index 00000000..e0d62178
--- /dev/null
+++ b/lib/libsystem.c
@@ -0,0 +1,319 @@
+/*=============================================================================
+                                 pm_system
+===============================================================================
+   This is the library subroutine pm_system().  It is just like Standard C
+   Library system(), except that you can supply routines for it to run to
+   generate the Standard Input for the executed shell command and to accept
+   the Standard Output from it.  system(), by contrast, always sets up the
+   current Standard Input and Standard Output as the Standard Input and
+   Standard Output of the shell command.
+
+   By Bryan Henderson, San Jose CA  2002.12.14.
+
+   Contributed to the public domain.
+=============================================================================*/
+#define _XOPEN_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "pm.h"
+#include "pm_system.h"
+
+#define STDIN 0
+#define STDOUT 1
+
+
+static void
+execProgram(const char * const shellCommand,
+            int          const inputPipeFd,
+            int          const outputPipeFd) {
+/*----------------------------------------------------------------------------
+   Run the shell command 'shellCommand', supplying to the shell
+   'inputPipeFd' as its Standard Input and 'outputPipeFd' as its 
+   Standard Output.
+
+   But leave Standard Input and Standard Output as we found them.
+-----------------------------------------------------------------------------*/
+    int stdinSaveFd, stdoutSaveFd;
+    int rc;
+
+    /* Make inputPipeFd Standard Input.
+       Make outputPipeFd Standard Output.
+    */
+    stdinSaveFd = dup(STDIN);
+    stdoutSaveFd = dup(STDOUT);
+    
+    close(STDIN);
+    close(STDOUT);
+
+    dup2(inputPipeFd, STDIN);
+    dup2(outputPipeFd, STDOUT);
+
+    rc = execl("/bin/sh", "sh", "-c", shellCommand, NULL);
+
+    close(STDIN);
+    close(STDOUT);
+    dup2(stdinSaveFd, STDIN);
+    dup2(stdoutSaveFd, STDOUT);
+    close(stdinSaveFd);
+    close(stdoutSaveFd);
+
+    if (rc < 0)
+        pm_error("Unable to exec the shell.  Errno=%d (%s)",
+                 errno, strerror(errno));
+    else
+        pm_error("INTERNAL ERROR.  execl() returns, but does not fail.");
+}
+
+
+
+static void
+createPipeFeeder(void          pipeFeederRtn(int, void *), 
+                 void *  const feederParm, 
+                 int *   const fdP,
+                 pid_t * const pidP) {
+/*----------------------------------------------------------------------------
+   Create a process and a pipe.  Have the process run program
+   'pipeFeederRtn' to fill the pipe and return the file descriptor of the
+   other end of the pipe as *fdP.
+-----------------------------------------------------------------------------*/
+    int pipeToFeed[2];
+    pid_t feederPid;
+
+    pipe(pipeToFeed);
+    feederPid = fork();
+    if (feederPid < 0) {
+        pm_error("fork() of stdin feeder failed.  errno=%d (%s)", 
+                 errno, strerror(errno));
+    } else if (feederPid == 0) {
+        /* This is the child -- the stdin feeder process */
+        close(pipeToFeed[0]);
+        (*pipeFeederRtn)(pipeToFeed[1], feederParm);
+        exit(0);
+    }
+    else {
+        /* This is the parent */
+        close(pipeToFeed[1]);
+        *fdP = pipeToFeed[0];
+        *pidP = feederPid;
+    }
+}
+
+
+
+static void
+spawnProcessor(const char * const shellCommand, 
+               int          const stdinFd,
+               int *        const stdoutFdP,
+               pid_t *      const pidP) {
+/*----------------------------------------------------------------------------
+   Create a process to run a shell that runs command 'shellCommand'.
+   Pass file descriptor 'stdinFd' to the shell as Standard Input.
+   Set up a pipe and pass it to the shell as Standard Output.  Return
+   as *stdoutFdP the file descriptor of the other end of that pipe,
+   from which Caller can suck the shell's Standard Output.
+-----------------------------------------------------------------------------*/
+    int stdoutpipe[2];
+    pid_t processorpid;
+        
+    pipe(stdoutpipe);
+
+    processorpid = fork();
+    if (processorpid < 0) {
+        pm_error("fork() of processor process failed.  errno=%d (%s)\n", 
+                 errno, strerror(errno));
+    } else if (processorpid == 0) {
+        /* The second child */
+        close(stdoutpipe[0]);
+
+        execProgram(shellCommand, stdinFd, stdoutpipe[1]);
+
+        close(stdinFd);
+        close(stdoutpipe[1]);
+        pm_error("INTERNAL ERROR: execProgram() returns.");
+    } else {
+        /* The parent */
+        close(stdoutpipe[1]);
+        *stdoutFdP = stdoutpipe[0];
+        *pidP = processorpid;
+    }
+}
+
+
+static void
+cleanupProcessorProcess(pid_t const processorPid) {
+
+    int status;
+    waitpid(processorPid, &status, 0);
+    if (status != 0) 
+        pm_message("Shell process ended abnormally.  "
+                   "completion code = %d", status);
+}
+
+
+
+static void
+cleanupFeederProcess(pid_t const feederPid) {
+    int status;
+
+    waitpid(feederPid, &status, 0);
+
+    if (WIFSIGNALED(status)) {
+        if (WTERMSIG(status) == SIGPIPE)
+            pm_message("WARNING: "
+                       "Standard Input feeder process was terminated by a "
+                       "SIGPIPE signal because the shell command closed its "
+                       "Standard Input before the Standard Input feeder was "
+                       "through feeding it.");
+        else
+            pm_message("WARNING: "
+                       "Standard Input feeder was terminated by a Signal %d.",
+                       WTERMSIG(status));
+    }
+    else if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0)
+            pm_message("WARNING: "
+                       "Standard Input feeder process ended abnormally.  "
+                       "exit status = %d", WEXITSTATUS(status));
+    } else
+        pm_message("WARNING: "
+                   "Unrecognized process completion status from "
+                   "Standard Input feeder: %d", status);
+}
+
+
+
+void
+pm_system(void stdinFeeder(int, void *),
+          void *          const feederParm,
+          void stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand) {
+/*----------------------------------------------------------------------------
+   Run a shell and have it run command 'shellCommand'.  Feed its
+   Standard Input with a pipe, which is fed by the routine
+   'stdinFeeder' with parameter 'feederParm'.  Process its Standard
+   Output with the routine 'stdoutAccepter' with parameter 'accepterParm'.
+
+   But if 'stdinFeeder' is NULL, just feed the shell our own Standard
+   Input.  And if 'stdoutFeeder' is NULL, just send its Standard Output
+   to our own Standard Output.
+-----------------------------------------------------------------------------*/
+
+    /* If 'stdinFeeder' is non-NULL, we create a child process to run
+       'stdinFeeder' and create a pipe between from that process as the
+       shell's Standard Input.
+
+       If 'stdoutFeeder' is non-NULL, we create a child process to run
+       the shell and create a pipe between the shell's Standard Output
+       and this process, and then this process runs 'stdoutAccepter'
+       to read the data from that pipe.
+       
+       But if 'stdoutFeeder' is NULL, we just run the shell in this
+       process.
+
+       So there can be 1, 2, or 3 processes involved depending on 
+       parameters.
+    */
+    
+    int shellStdinFd;
+    pid_t feederPid;
+
+    if (stdinFeeder) 
+        createPipeFeeder(stdinFeeder, feederParm, &shellStdinFd, &feederPid);
+    else {
+        shellStdinFd = STDIN;
+        feederPid = 0;
+    }
+
+    if (stdoutAccepter) {
+        int shellStdoutFd;
+        pid_t processorPid;
+
+        /* Make a child process to run the shell and pipe back to us its
+           Standard Output 
+        */
+        spawnProcessor(shellCommand, shellStdinFd, 
+                       &shellStdoutFd, &processorPid);
+
+        /* Dispose of the stdout from that shell */
+        (*stdoutAccepter)(shellStdoutFd, accepterParm);
+        close(shellStdoutFd);
+
+        cleanupProcessorProcess(processorPid);
+    } else {
+        /* Run a child process for the shell that sends its Standard Output
+           to our Standard Output
+        */
+        int const stdinSaveFd = dup(STDIN);
+        int rc;
+
+        dup2(shellStdinFd, STDIN);
+        
+        rc = system(shellCommand);
+
+        close(STDIN);
+        dup2(stdinSaveFd, STDIN);
+        
+        if (rc < 0)
+            pm_error("Unable to invoke the shell.  Errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != 0)
+            pm_message("WARNING: Shell process completion code = %d", rc);
+    }
+
+    if (feederPid) 
+        cleanupFeederProcess(feederPid);
+}
+
+
+
+
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm) {
+
+    struct bufferDesc * const inputBufferP = feederParm;
+    
+    FILE * const outfile = fdopen(pipeToFeedFd, "w");
+    
+    int bytesTransferred;
+
+    /* The following signals (and normally kills) the process with
+       SIGPIPE if the pipe does not take all 'size' bytes.
+    */
+    bytesTransferred = 
+        fwrite(inputBufferP->buffer, 1, inputBufferP->size, outfile);
+
+    if (inputBufferP->bytesTransferredP)
+        *(inputBufferP->bytesTransferredP) = bytesTransferred;
+
+    fclose(outfile);
+}
+
+
+
+void
+pm_accept_to_memory(int             const pipetosuckFd,
+                    void *          const accepterParm ) {
+
+    struct bufferDesc * const outputBufferP = accepterParm;
+    
+    FILE * const infile = fdopen(pipetosuckFd, "r");
+
+    int bytesTransferred;
+
+    bytesTransferred =
+        fread(outputBufferP->buffer, 1, outputBufferP->size, infile);
+
+    fclose(infile);
+
+    if (outputBufferP->bytesTransferredP)
+        *(outputBufferP->bytesTransferredP) = bytesTransferred;
+}
diff --git a/lib/libsystem_dummy.c b/lib/libsystem_dummy.c
new file mode 100644
index 00000000..afe20dde
--- /dev/null
+++ b/lib/libsystem_dummy.c
@@ -0,0 +1,47 @@
+/*=============================================================================
+                             libsystem_dummy.c
+===============================================================================
+  This is a dummy version of libsystem.c, for use on systems that don't
+  have the kind of process control that libsystem.c needs.
+
+  With this module, program will build cleanly, but if a program actually
+  calls pm_system(), it will die with an error message saying that
+  the facility is not available.
+=============================================================================*/
+
+#include <assert.h>
+
+#include "pm.h"
+#include "pm_system.h"
+
+void
+pm_system(void                  stdinFeeder(int, void *),
+          void *          const feederParm,
+          void                  stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand) {
+
+    pm_error("This program wants to run another program using pm_system() in "
+             "the libnetpbm library, but libnetpbm was built without "
+             "the pm_system() facility -- probably because this system "
+             "doesn't have the process management facilities pm_system() "
+             "requires.");
+}
+
+
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm) {
+
+    assert(FALSE);  /* Can't ever run, since pm_system() is a dummy */
+}
+
+
+
+void
+pm_accept_to_memory(int    const pipetosuckFd,
+                    void * const accepterParm) {
+
+    assert(FALSE);  /* Can't ever run, since pm_system() is a dummy */
+}
+
diff --git a/lib/lum.h b/lib/lum.h
new file mode 100644
index 00000000..34d3866e
--- /dev/null
+++ b/lib/lum.h
@@ -0,0 +1,123 @@
+    /* Lookup tables for fast RGB -> luminance calculation. */
+    static int times77[256] = {
+	    0,    77,   154,   231,   308,   385,   462,   539,
+	  616,   693,   770,   847,   924,  1001,  1078,  1155,
+	 1232,  1309,  1386,  1463,  1540,  1617,  1694,  1771,
+	 1848,  1925,  2002,  2079,  2156,  2233,  2310,  2387,
+	 2464,  2541,  2618,  2695,  2772,  2849,  2926,  3003,
+	 3080,  3157,  3234,  3311,  3388,  3465,  3542,  3619,
+	 3696,  3773,  3850,  3927,  4004,  4081,  4158,  4235,
+	 4312,  4389,  4466,  4543,  4620,  4697,  4774,  4851,
+	 4928,  5005,  5082,  5159,  5236,  5313,  5390,  5467,
+	 5544,  5621,  5698,  5775,  5852,  5929,  6006,  6083,
+	 6160,  6237,  6314,  6391,  6468,  6545,  6622,  6699,
+	 6776,  6853,  6930,  7007,  7084,  7161,  7238,  7315,
+	 7392,  7469,  7546,  7623,  7700,  7777,  7854,  7931,
+	 8008,  8085,  8162,  8239,  8316,  8393,  8470,  8547,
+	 8624,  8701,  8778,  8855,  8932,  9009,  9086,  9163,
+	 9240,  9317,  9394,  9471,  9548,  9625,  9702,  9779,
+	 9856,  9933, 10010, 10087, 10164, 10241, 10318, 10395,
+	10472, 10549, 10626, 10703, 10780, 10857, 10934, 11011,
+	11088, 11165, 11242, 11319, 11396, 11473, 11550, 11627,
+	11704, 11781, 11858, 11935, 12012, 12089, 12166, 12243,
+	12320, 12397, 12474, 12551, 12628, 12705, 12782, 12859,
+	12936, 13013, 13090, 13167, 13244, 13321, 13398, 13475,
+	13552, 13629, 13706, 13783, 13860, 13937, 14014, 14091,
+	14168, 14245, 14322, 14399, 14476, 14553, 14630, 14707,
+	14784, 14861, 14938, 15015, 15092, 15169, 15246, 15323,
+	15400, 15477, 15554, 15631, 15708, 15785, 15862, 15939,
+	16016, 16093, 16170, 16247, 16324, 16401, 16478, 16555,
+	16632, 16709, 16786, 16863, 16940, 17017, 17094, 17171,
+	17248, 17325, 17402, 17479, 17556, 17633, 17710, 17787,
+	17864, 17941, 18018, 18095, 18172, 18249, 18326, 18403,
+	18480, 18557, 18634, 18711, 18788, 18865, 18942, 19019,
+	19096, 19173, 19250, 19327, 19404, 19481, 19558, 19635 };
+    static int times150[256] = {
+	    0,   150,   300,   450,   600,   750,   900,  1050,
+	 1200,  1350,  1500,  1650,  1800,  1950,  2100,  2250,
+	 2400,  2550,  2700,  2850,  3000,  3150,  3300,  3450,
+	 3600,  3750,  3900,  4050,  4200,  4350,  4500,  4650,
+	 4800,  4950,  5100,  5250,  5400,  5550,  5700,  5850,
+	 6000,  6150,  6300,  6450,  6600,  6750,  6900,  7050,
+	 7200,  7350,  7500,  7650,  7800,  7950,  8100,  8250,
+	 8400,  8550,  8700,  8850,  9000,  9150,  9300,  9450,
+	 9600,  9750,  9900, 10050, 10200, 10350, 10500, 10650,
+	10800, 10950, 11100, 11250, 11400, 11550, 11700, 11850,
+	12000, 12150, 12300, 12450, 12600, 12750, 12900, 13050,
+	13200, 13350, 13500, 13650, 13800, 13950, 14100, 14250,
+	14400, 14550, 14700, 14850, 15000, 15150, 15300, 15450,
+	15600, 15750, 15900, 16050, 16200, 16350, 16500, 16650,
+	16800, 16950, 17100, 17250, 17400, 17550, 17700, 17850,
+	18000, 18150, 18300, 18450, 18600, 18750, 18900, 19050,
+	19200, 19350, 19500, 19650, 19800, 19950, 20100, 20250,
+	20400, 20550, 20700, 20850, 21000, 21150, 21300, 21450,
+	21600, 21750, 21900, 22050, 22200, 22350, 22500, 22650,
+	22800, 22950, 23100, 23250, 23400, 23550, 23700, 23850,
+	24000, 24150, 24300, 24450, 24600, 24750, 24900, 25050,
+	25200, 25350, 25500, 25650, 25800, 25950, 26100, 26250,
+	26400, 26550, 26700, 26850, 27000, 27150, 27300, 27450,
+	27600, 27750, 27900, 28050, 28200, 28350, 28500, 28650,
+	28800, 28950, 29100, 29250, 29400, 29550, 29700, 29850,
+	30000, 30150, 30300, 30450, 30600, 30750, 30900, 31050,
+	31200, 31350, 31500, 31650, 31800, 31950, 32100, 32250,
+	32400, 32550, 32700, 32850, 33000, 33150, 33300, 33450,
+	33600, 33750, 33900, 34050, 34200, 34350, 34500, 34650,
+	34800, 34950, 35100, 35250, 35400, 35550, 35700, 35850,
+	36000, 36150, 36300, 36450, 36600, 36750, 36900, 37050,
+	37200, 37350, 37500, 37650, 37800, 37950, 38100, 38250 };
+    static int times29[256] = {
+	    0,    29,    58,    87,   116,   145,   174,   203,
+	  232,   261,   290,   319,   348,   377,   406,   435,
+	  464,   493,   522,   551,   580,   609,   638,   667,
+	  696,   725,   754,   783,   812,   841,   870,   899,
+	  928,   957,   986,  1015,  1044,  1073,  1102,  1131,
+	 1160,  1189,  1218,  1247,  1276,  1305,  1334,  1363,
+	 1392,  1421,  1450,  1479,  1508,  1537,  1566,  1595,
+	 1624,  1653,  1682,  1711,  1740,  1769,  1798,  1827,
+	 1856,  1885,  1914,  1943,  1972,  2001,  2030,  2059,
+	 2088,  2117,  2146,  2175,  2204,  2233,  2262,  2291,
+	 2320,  2349,  2378,  2407,  2436,  2465,  2494,  2523,
+	 2552,  2581,  2610,  2639,  2668,  2697,  2726,  2755,
+	 2784,  2813,  2842,  2871,  2900,  2929,  2958,  2987,
+	 3016,  3045,  3074,  3103,  3132,  3161,  3190,  3219,
+	 3248,  3277,  3306,  3335,  3364,  3393,  3422,  3451,
+	 3480,  3509,  3538,  3567,  3596,  3625,  3654,  3683,
+	 3712,  3741,  3770,  3799,  3828,  3857,  3886,  3915,
+	 3944,  3973,  4002,  4031,  4060,  4089,  4118,  4147,
+	 4176,  4205,  4234,  4263,  4292,  4321,  4350,  4379,
+	 4408,  4437,  4466,  4495,  4524,  4553,  4582,  4611,
+	 4640,  4669,  4698,  4727,  4756,  4785,  4814,  4843,
+	 4872,  4901,  4930,  4959,  4988,  5017,  5046,  5075,
+	 5104,  5133,  5162,  5191,  5220,  5249,  5278,  5307,
+	 5336,  5365,  5394,  5423,  5452,  5481,  5510,  5539,
+	 5568,  5597,  5626,  5655,  5684,  5713,  5742,  5771,
+	 5800,  5829,  5858,  5887,  5916,  5945,  5974,  6003,
+	 6032,  6061,  6090,  6119,  6148,  6177,  6206,  6235,
+	 6264,  6293,  6322,  6351,  6380,  6409,  6438,  6467,
+	 6496,  6525,  6554,  6583,  6612,  6641,  6670,  6699,
+	 6728,  6757,  6786,  6815,  6844,  6873,  6902,  6931,
+	 6960,  6989,  7018,  7047,  7076,  7105,  7134,  7163,
+	 7192,  7221,  7250,  7279,  7308,  7337,  7366,  7395 };
+
+/* The ppm_fastlumin() macro is a way to compute luminosity without
+   floating point arithmetic.  On modern computers, floating point often isn't
+   any slower, so you may prefer ppm_lumin() in ppm.h.
+
+   ppm_fastlumin() works only with maxval <= 255, because the multiplication
+   tables go up only that high.
+
+   In the following arithmetic, note that shifting right 8 bits is the same
+   as dividing by (77+150+29), but some compilers may generate faster code
+   for >>8 than for /(77+150+29).
+*/
+static __inline__ pixval
+ppm_fastlumin(pixel const p) {
+
+    return
+        (times77[PPM_GETR(p)] +
+         times150[PPM_GETG(p)] +
+         times29[PPM_GETB(p)] +
+         128)  /* round off */
+             >> 8;
+}
+
diff --git a/lib/mkstemp.c b/lib/mkstemp.c
new file mode 100644
index 00000000..cd9140c7
--- /dev/null
+++ b/lib/mkstemp.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "pm_c_util.h"
+
+
+
diff --git a/lib/pam.h b/lib/pam.h
new file mode 100644
index 00000000..97d5b3cb
--- /dev/null
+++ b/lib/pam.h
@@ -0,0 +1,490 @@
+/*----------------------------------------------------------------------------
+   These are declarations for use with the Portable Arbitrary Map (PAM)
+   format and the Netpbm library functions specific to them.
+-----------------------------------------------------------------------------*/
+
+#ifndef PAM_H
+#define PAM_H
+
+#include "pm.h"
+#include "pnm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef unsigned long sample;
+    /* Regardless of the capacity of "unsigned long", a sample is always
+       less than 1 << 16.  This is essential for some code to avoid
+       arithmetic overflows.
+    */
+
+struct pam {
+/* This structure describes an open PAM image file.  It consists
+   entirely of information that belongs in the header of a PAM image
+   and filesystem information.  It does not contain any state
+   information about the processing of that image.  
+
+   This is not considered to be an opaque object.  The user of Netbpm
+   libraries is free to access and set any of these fields whenever
+   appropriate.  The structure exists to make coding of function calls
+   easy.
+*/
+
+    /* 'size' and 'len' are necessary in order to provide forward and
+       backward compatibility between library functions and calling programs
+       as this structure grows.
+       */
+    unsigned int size;   
+        /* The storage size of this entire structure, in bytes */
+    unsigned int len;    
+        /* The length, in bytes, of the information in this structure.
+           The information starts in the first byte and is contiguous.  
+           This cannot be greater than 'size'
+           */
+    FILE * file;
+    int format;
+        /* The format code of the raw image.  This is PAM_FORMAT
+           unless the PAM image is really a view of a PBM, PGM, or PPM
+           image.  Then it's PBM_FORMAT, RPBM_FORMAT, etc.
+           */
+    unsigned int plainformat;
+        /* Logical: On output, use the plain version of the format type
+           indicated by 'format'.  Otherwise, use the raw version.
+           (i.e., on output, the plainness information in 'format' is
+           irrelevant).  Input functions set this to FALSE, for the
+           convenience of programs that copy an input pam structure for
+           use with output.
+
+           Before Netpbm 10.32, this was rather different.  It simply
+           described for convenience the plainness of the format indicated
+           by 'format'.
+        */
+    int height;  /* Height of image in rows */
+    int width;   
+        /* Width of image in number of columns (tuples per row) */
+    unsigned int depth;   
+        /* Depth of image (number of samples in each tuple). */
+    sample maxval;  /* Maximum defined value for a sample */
+    unsigned int bytes_per_sample;  
+        /* Number of bytes used to represent each sample in the image file.
+           Note that this is strictly a function of 'maxval'.  It is in a
+           a separate member for computational speed.
+        */
+    char tuple_type[256];
+        /* The tuple type string from the image header.  If the PAM image
+           is really a view of a PBM, PGM, or PPM image, the value is
+           PAM_PBM_TUPLETYPE, PAM_PGM_TUPLETYPE, or PAM_PPM_TUPLETYPE,
+           respectively.
+        */
+    unsigned int allocation_depth;
+        /* The number of samples for which memory is allocated for any
+           'tuple' type associated with this PAM structure.  This must
+           be at least as great as 'depth'.  Only the first 'depth' of
+           the samples of a tuple are meaningful.
+
+           The purpose of this is to make it possible for a program to
+           change the type of a tuple to one with more or fewer
+           planes.  
+
+           0 means the allocation depth is the same as the image depth.
+        */
+    const char ** comment_p;
+        /* Pointer to a pointer to a NUL-terminated ASCII string of
+           comments.  When reading an image, this contains the
+           comments from the image's PAM header; when writing, the
+           image gets these as comments, right after the magic number
+           line.  The individual comments are delimited by newlines
+           and are in the same order as in the PAM header.
+
+           On output, NULL means no comments.
+
+           On input, libnetpbm mallocs storage for the comments and placed
+           the pointer at *comment_p.  Caller must free it.  NULL means
+           libnetpbm does not return comments and does not allocate any
+           storage.
+        */
+};
+
+#define PAM_HAVE_ALLOCATION_DEPTH 1
+#define PAM_HAVE_COMMENT_P 1
+
+/* PAM_STRUCT_SIZE(x) tells you how big a struct pam is up through the 
+   member named x.  This is useful in conjunction with the 'len' value
+   to determine which fields are present in the structure.
+*/
+#define PAM_MEMBER_OFFSET(mbrname) \
+  ((unsigned int)(char*)&((struct pam *)0)->mbrname)
+#define PAM_MEMBER_SIZE(mbrname) \
+  sizeof(((struct pam *)0)->mbrname)
+#define PAM_STRUCT_SIZE(mbrname) \
+  (PAM_MEMBER_OFFSET(mbrname) + PAM_MEMBER_SIZE(mbrname))
+
+#define PAM_BLACK 0
+#define PAM_BW_WHITE 1
+
+#define PAM_PBM_TUPLETYPE "BLACKANDWHITE"
+#define PAM_PGM_TUPLETYPE "GRAYSCALE"
+#define PAM_PPM_TUPLETYPE "RGB"
+
+#define PAM_PBM_BLACK PAM_BLACK
+#define PAM_PBM_WHITE PAM_BW_WHITE
+    /* These are values of samples in a PAM image that represents a black
+       and white bitmap image.  They are the values of black and white,
+       respectively.  For example, if you use pnm_readpamrow() to read a
+       row from a PBM file, the black pixels get returned as 
+       PAM_PBM_BLACK.
+    */
+
+#define PAM_RED_PLANE 0
+#define PAM_GRN_PLANE 1
+#define PAM_BLU_PLANE 2
+    /* These are plane numbers for the 3 planes of a PAM image that
+       represents an RGB image (tuple type is "RGB").  So
+       if 'pixel' is a tuple returned by pnmreadpamrow(), then
+       pixel[PAM_GRN_PLANE] is the value of the green sample in that
+       pixel.
+       */
+#define PAM_TRN_PLANE 3
+    /* A PAM with "RGB_ALPHA" tuple type has this 4th plane
+       for transparency.  0 = transparent, maxval = opaque.
+    */
+#define PAM_GRAY_TRN_PLANE 1
+    /* For a "GRAYSCALE" tuple type, this is the transparency plane */
+
+typedef sample *tuple;  
+    /* A tuple in a PAM.  This is an array such that tuple[i-1] is the
+       ith sample (element) in the tuple.  It's dimension is the depth
+       of the image (see pam.depth above).
+    */
+
+#define PAM_OVERALL_MAXVAL 65535
+
+/* Note: xv uses the same "P7" signature for its thumbnail images (it
+   started using it years before PAM and unbeknownst to the designer
+   of PAM).  But these images are still easily distinguishable from
+   PAMs 
+*/
+#define PAM_MAGIC1 'P'
+#define PAM_MAGIC2 '7'
+#define PAM_FORMAT (PAM_MAGIC1 * 256 + PAM_MAGIC2)
+#define PAM_TYPE PAM_FORMAT
+
+/* Macro for turning a format number into a type number. */
+
+#define PAM_FORMAT_TYPE(f) ((f) == PAM_FORMAT ? PAM_TYPE : PPM_FORMAT_TYPE(f))
+
+struct pamtuples {
+    struct pam * pamP;
+    tuple ***    tuplesP;
+};
+
+
+typedef float * pnm_transformMap;
+    /* This is an array of transform maps.  transform[N] is the 
+       array that is the map for Plane N.
+   
+       Transform maps define a transformation between PAM sample value
+       to normalized libnetpbm "samplen" value, i.e. what you get back
+       from pnm_readpamrown() or pass to pnm_writepamrown().
+       Typically, it's a gamma transfer function generated by
+       pnm_creategammatransform() or pnm_createungammatransform().
+
+       NULL for any transform means just plain normalization -- divide
+       the PAM sample value by the maxval to get the samplen, multiply
+       samplen by the maxval and round to get PAM sample value.
+
+       NULL for map table, or 'transform' member not present (pam
+       structure is too small to contain it) means ALL transforms
+       are plain normalization.
+
+       Each transform map is an array indexed by a PAM sample
+       value, containing 'float' values.  So it must have 'maxval'
+       entries.  The sample -> samplen tranformation is just the
+       obvious table lookup.  The samplen -> sample transformation is
+       more complicated -- if the samplen value is between map[N]
+       and map[N+1], then the sample value is N.  And only transforms
+       where map[N+1] > map[N] are allowed.  
+    */
+
+/* Declarations of library functions. */
+
+/* We don't have a specific PAM function for init and nextimage, because
+   one can simply use pnm_init() and pnm_nextimage() from pnm.h.
+*/
+
+unsigned int
+pnm_bytespersample(sample const maxval);
+
+int
+pnm_tupleequal(const struct pam * const pamP, 
+               tuple              const comparand, 
+               tuple              const comparator);
+
+void
+pnm_assigntuple(const struct pam * const pamP,
+                tuple              const dest,
+                tuple              const source);
+
+static __inline__ sample
+pnm_scalesample(sample const source, 
+                sample const oldmaxval, 
+                sample const newmaxval) {
+
+    if (oldmaxval == newmaxval)
+        /* Fast path for common case */
+        return source;
+    else 
+        return (source * newmaxval + (oldmaxval/2)) / oldmaxval;
+}
+
+
+
+void
+pnm_scaletuple(const struct pam * const pamP,
+               tuple              const dest,
+               tuple              const source, 
+               sample             const newmaxval);
+
+void 
+pnm_scaletuplerow(const struct pam * const pamP,
+                  tuple *            const destRow,
+                  tuple *            const sourceRow,
+                  sample             const newMaxval);
+
+void 
+pnm_maketuplergb(const struct pam * const pamP,
+                 tuple              const tuple);
+
+void 
+pnm_makerowrgb(const struct pam * const pamP,
+               tuple *            const tuplerow);
+
+void 
+pnm_makearrayrgb(const struct pam * const pamP,
+                 tuple **           const tuples);
+
+void
+pnm_getopacity(const struct pam * const pamP,
+               int *              const haveOpacityP,
+               unsigned int *     const opacityPlaneP);
+
+void
+pnm_createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
+
+void
+createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
+
+
+tuple
+pnm_allocpamtuple(const struct pam * const pamP);
+
+#define pnm_freepamtuple(tuple) pm_freerow((char*) tuple)
+
+tuple *
+pnm_allocpamrow(const struct pam * const pamP);
+
+#define pnm_freepamrow(tuplerow) pm_freerow((char*) tuplerow)
+
+tuple **
+pnm_allocpamarray(const struct pam * const pamP);
+
+void
+pnm_freepamarray(tuple ** const tuplearray, const struct pam * const pamP);
+
+void 
+pnm_setminallocationdepth(struct pam * const pamP,
+                          unsigned int const allocationDepth);
+
+void
+pnm_setpamrow(const struct pam * const pam, 
+              tuple *            const tuplerow, 
+              sample             const value);
+
+unsigned char *
+pnm_allocrowimage(const struct pam * const pamP);
+
+void
+pnm_freerowimage(unsigned char * const rowimage);
+
+void 
+pnm_readpaminit(FILE *       const file, 
+                struct pam * const pamP, 
+                int          const size);
+
+void 
+pnm_readpamrow(const struct pam * const pamP, tuple* const tuplerow);
+
+tuple ** 
+pnm_readpam(FILE *       const file, 
+            struct pam * const pamP, 
+            int          const size);
+
+void 
+pnm_writepaminit(struct pam * const pamP);
+
+void
+pnm_formatpamrow(const struct pam * const pamP,
+                 const tuple *      const tuplerow,
+                 unsigned char *    const outbuf,
+                 unsigned int *     const rowSizeP);
+
+void 
+pnm_writepamrow(const struct pam * const pamP, const tuple * const tuplerow);
+
+void
+pnm_writepamrowmult(const struct pam * const pamP, 
+                    const tuple *      const tuplerow,
+                    unsigned int       const rptcnt);
+
+void 
+pnm_writepam(struct pam * const pamP, tuple ** const tuplearray);
+
+void
+pnm_checkpam(const struct pam *   const pamP, 
+             enum pm_check_type   const checkType,
+             enum pm_check_code * const retvalP);
+
+/*----------------------------------------------------------------------------
+   Facilities for working with maxval-normalized samples.  Such samples
+   are floating point quantities in the range 0..1.
+
+   This is just a working format; there is no Netpbm image format that
+   has normalized samples.
+-----------------------------------------------------------------------------*/
+typedef float samplen;
+
+typedef samplen *tuplen;
+    /* Same as 'tuple', except using normalized samples. */
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP);
+
+#define pnm_freepamrown(tuplenrow) pm_freerow((char*) tuplenrow)
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP);
+
+void 
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow);
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow);
+
+tuplen **
+pnm_allocpamarrayn(const struct pam * const pamP);
+
+void
+pnm_freepamarrayn(tuplen **          const tuplenarray, 
+                  const struct pam * const pamP);
+
+tuplen** 
+pnm_readpamn(FILE *       const file, 
+             struct pam * const pamP, 
+             int          const size);
+
+void 
+pnm_writepamn(struct pam * const pamP, 
+              tuplen **    const tuplenarray);
+
+
+void
+pnm_normalizetuple(struct pam * const pamP,
+                   tuple        const tuple,
+                   tuplen       const tuplen);
+
+void
+pnm_unnormalizetuple(struct pam * const pamP,
+                     tuplen       const tuplen,
+                     tuple        const tuple);
+
+void
+pnm_normalizeRow(struct pam *             const pamP,
+                 const tuple *            const tuplerow,
+                 const pnm_transformMap * const transform,
+                 tuplen *                 const tuplenrow);
+
+void
+pnm_unnormalizeRow(struct pam *             const pamP,
+                   const tuplen *           const tuplenrow,
+                   const pnm_transformMap * const transform,
+                   tuple *                  const tuplerow);
+
+/*----------------------------------------------------------------------------
+   Facilities for working with visual images in particular
+-----------------------------------------------------------------------------*/
+
+
+void
+pnm_gammarown(struct pam * const pamP,
+              tuplen *     const row);
+
+void
+pnm_ungammarown(struct pam * const pamP,
+                tuplen *     const row);
+
+void
+pnm_applyopacityrown(struct pam * const pamP,
+                     tuplen *     const tuplenrow);
+
+void
+pnm_unapplyopacityrown(struct pam * const pamP,
+                       tuplen *     const tuplenrow);
+
+pnm_transformMap *
+pnm_creategammatransform(const struct pam * const pamP);
+
+void
+pnm_freegammatransform(const pnm_transformMap * const transform,
+                       const struct pam *       const pamP);
+
+pnm_transformMap *
+pnm_createungammatransform(const struct pam * const pamP);
+
+#define pnm_freeungammatransform pnm_freegammatransform;
+
+tuple
+pnm_parsecolor(const char * const colorname,
+               sample       const maxval);
+
+extern double 
+pnm_lumin_factor[3];
+
+void
+pnm_YCbCrtuple(const tuple tuple, 
+               double * const YP, double * const CbP, double * const CrP);
+
+void 
+pnm_YCbCr_to_rgbtuple(const struct pam * const pamP,
+                      tuple              const tuple,
+                      double             const Y,
+                      double             const Cb, 
+                      double             const Cr,
+                      int *              const overflowP);
+
+#define pnm_rgbtupleisgray(tuple) \
+    ((tuple)[PAM_RED_PLANE] == (tuple)[PAM_GRN_PLANE] && \
+     (tuple)[PAM_RED_PLANE] == (tuple)[PAM_BLU_PLANE])
+
+/*----------------------------------------------------------------------------
+   These are meant for passing to pm_system() as Standard Input feeder
+   and Standard Output accepter.
+-----------------------------------------------------------------------------*/
+
+void
+pm_feed_from_pamtuples(int    const pipeToFeedFd,
+                       void * const feederParm);
+
+void
+pm_accept_to_pamtuples(int    const pipeToSuckFd,
+                       void * const accepterParm);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/pammap.h b/lib/pammap.h
new file mode 100644
index 00000000..fa054deb
--- /dev/null
+++ b/lib/pammap.h
@@ -0,0 +1,124 @@
+/******************************************************************************
+                                pammap.h
+*******************************************************************************
+
+  Interface header file for hash table and lookup table pam functions
+  in libpnm.
+
+******************************************************************************/
+
+#ifndef PAMMAP_H
+#define PAMMAP_H
+#include "colorname.h"
+#include "pam.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct tupleint {
+    /* An ordered pair of a tuple value and an integer, such as you 
+       would find in a tuple table or tuple hash.
+
+       Note that this is a variable length structure.
+    */
+    int value;
+    sample tuple[1];  
+        /* This is actually a variable size array -- its size is the 
+           depth of the tuple in question.  Some compilers do not let us
+           declare a variable length array.
+        */
+};
+typedef struct tupleint ** tupletable;
+
+typedef struct {
+    unsigned int size;
+    tupletable table;
+} tupletable2;
+
+struct tupleint_list_item {
+    struct tupleint_list_item * next;
+    struct tupleint tupleint;
+};
+typedef struct tupleint_list_item * tupleint_list;
+
+typedef tupleint_list * tuplehash;
+
+unsigned int
+pnm_hashtuple(struct pam * const pamP, tuple const tuple);
+
+void
+pnm_addtotuplehash(struct pam *   const pamP,
+                   tuplehash      const tuplehash, 
+                   tuple          const tuple,
+                   int            const value,
+                   int *          const fitsP);
+
+void
+pnm_addtuplefreqoccurrence(struct pam *   const pamP,
+                           tuple          const value, 
+                           tuplehash      const tuplefreqhash,
+                           int *          const firstOccurrenceP);
+
+void
+pnm_lookuptuple(struct pam * const pamP, const tuplehash tuplehash, 
+                const tuple searchval, 
+                int * const foundP, int * const retvalP);
+
+tupletable
+pnm_alloctupletable(const struct pam * const pamP, unsigned int const size);
+
+void
+pnm_freetupletable(struct pam * const pamP, tupletable const tupletable);
+
+void
+pnm_freetupletable2(struct pam * const pamP, tupletable2 const tupletable);
+
+tuplehash
+pnm_createtuplehash(void);
+
+void
+pnm_destroytuplehash(tuplehash const tuplehash);
+
+tupletable
+pnm_computetuplefreqtable(struct pam *   const pamP,
+                          tuple **       const tupleArray,
+                          unsigned int   const maxsize,
+                          unsigned int * const sizeP);
+
+tupletable
+pnm_computetuplefreqtable2(struct pam *   const pamP,
+                           tuple **       const tupleArray,
+                           unsigned int   const maxsize,
+                           sample         const newMaxval,
+                           unsigned int * const sizeP);
+
+tuplehash
+pnm_computetuplefreqhash(struct pam *   const pamP,
+                         tuple **       const tupleArray,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP);
+
+tuplehash
+pnm_computetupletablehash(struct pam * const pamP, 
+                          tupletable   const tupletable,
+                          unsigned int const tupletableSize);
+
+tupletable
+pnm_tuplehashtotable(const struct pam * const pamP,
+                     tuplehash          const tuplehash,
+                     unsigned int       const maxsize);
+
+char*
+pam_colorname(struct pam *         const pamP, 
+              tuple                const color, 
+              enum colornameFormat const format);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 00000000..79985109
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,468 @@
+/* This library module contains "ppmdraw" routines for drawing paths.
+
+   By Bryan Henderson San Jose CA 06.05.24.
+   Contributed to the public domain.
+
+   I actually wrote this before I knew ppmd_fill() already existed.
+   ppmd_fill() is more general.  But path.c is probably faster and is
+   better code, so I'm keeping it for now.
+*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "ppmdfont.h"
+#include "ppmdraw.h"
+
+
+
+/* This is the algorithm we use to fill a path.
+
+   A path is a set of points that form a closed figure.  The last point
+   and first point are identical.  But the path may cross itself other
+   places too.  Our job is to color the interior of that figure.
+
+   We do it with horizontal lines.  We follow the path around from
+   start to finish, visiting every point in it.  We remember in a
+   stack the points we've been to.  As long as we keep going in the
+   same vertical direction, that stack grows.  When we turn around and
+   visit a row that we've been to before, we drop a horizontal line of
+   fill color from where we are now to where we were the last time we
+   visited that row, and then remove that entry from the stack.  Note
+   that because we go one point at a time, the entry on the stack for
+   the row we're at now will always be on the top of stack.
+   
+   Note that the points on the stack always have consecutive row
+   numbers, monotonically increasing or decreasing, whichever is the
+   direction we started out in.
+
+   This goes on, with the stack alternately growing and shrinking as
+   the path turns to head up and down, until we get back to the row
+   where we started.  At this point, the stack becomes empty following
+   the algorithm in the previous paragraph.  If the path crosses over
+   and keeps going, we just start filling the stack again, with the
+   entries now going in the opposite direction from before -- e.g.
+   if the path started out heading downward, the points in the stack
+   mononotically increased in row number; after crossing back over,
+   the points in the stack monotonically decrease in row number.
+
+
+   It's probably more efficient to use vertical lines than horizontal
+   ones when the image is tall and narrow.  But we're not that
+   sophisticated.
+*/
+
+
+/* NOTE NOTE NOTE
+
+   In all the path logic below, we call the direction of increasing row
+   number "up" because we think of the raster as standard Cartesian
+   plane.  So visualize the image as upside down in the first quadrant
+   of the Cartesian plane -- the real top left corner of the image is at
+   the origin.
+*/
+
+
+
+static bool
+pointEqual(ppmd_point const comparator,
+           ppmd_point const comparand) {
+
+    return comparator.x == comparand.x && comparator.y == comparand.y;
+}
+
+
+
+static int
+vertDisp(ppmd_point const begPoint,
+         ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the vertical displacement of 'endPoint' with respect to
+   'begPoint' -- How much higher 'endPoint' is than 'begPoint'.
+-----------------------------------------------------------------------------*/
+    return endPoint.y - begPoint.y;
+}
+
+
+
+static int
+horzDisp(ppmd_point const begPoint,
+         ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the horizontal displacement of 'endPoint' with respect to
+   'begPoint' -- How much further right 'endPoint' is than 'begPoint'.
+-----------------------------------------------------------------------------*/
+    return endPoint.x - begPoint.x;
+}
+
+
+
+static bool
+isOnLineSeg(ppmd_point const here,
+            ppmd_point const begPoint,
+            ppmd_point const endPoint) {
+
+    return
+        here.y >= MIN(begPoint.y, endPoint.y) &&
+        here.y <= MAX(begPoint.y, endPoint.y) &&
+        here.x >= MIN(begPoint.x, endPoint.x) &&
+        here.x <= MAX(begPoint.x, endPoint.x);
+}    
+
+
+
+static double
+lineSlope(ppmd_point const begPoint,
+          ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the slope of the line that begins at 'begPoint' and ends at
+   'endPoint'.
+
+   Positive slope means as row number increases, column number increases.
+-----------------------------------------------------------------------------*/
+    return (double)vertDisp(begPoint, endPoint) / horzDisp(begPoint, endPoint);
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A complicated structure for tracking the state of a the filling
+   algorithm.  See description of algorithm above.
+-----------------------------------------------------------------------------*/
+    ppmd_point * stack;
+        /* An array which holds the fundamental stack.  The bottom of the
+           stack is element 0.  It grows consecutively up.
+        */
+    unsigned int topOfStack;
+        /* Index in stack[] of the top of the entry which will hold next
+           _next_ element pushed onto the stack.
+        */
+    unsigned int stackSize;
+        /* Number of elements in stack[] -- i.e. maximum height of stack */
+    int step;
+        /* -1 or 1.  -1 means each new point pushed on the stack is one
+           unit below the previous one; 1 means each new point pushed on
+           the stack is one unit above the previous one.
+        */
+} fillStack;
+
+
+
+static void
+createStack(fillStack ** const stackPP) {
+/*----------------------------------------------------------------------------
+   Create an empty fill stack.
+-----------------------------------------------------------------------------*/
+    fillStack * stackP;
+
+    MALLOCVAR_NOFAIL(stackP);
+
+    stackP->stackSize = 1024;
+
+    MALLOCARRAY(stackP->stack, stackP->stackSize);
+
+    if (stackP->stack == NULL)
+        pm_error("Could not allocate memory for a fill stack of %u points",
+                 stackP->stackSize);
+
+    stackP->topOfStack = 0;
+
+    stackP->step = +1;  /* arbitrary choice */
+
+    *stackPP = stackP;
+}
+
+
+
+static void
+destroyStack(fillStack * const stackP) {
+
+    free(stackP->stack);
+
+    free(stackP);
+}
+
+
+
+static ppmd_point
+topOfStack(fillStack * const stackP) {
+
+    assert(stackP->topOfStack > 0);
+
+    return stackP->stack[stackP->topOfStack-1];
+}
+
+
+
+static bool
+stackIsEmpty(fillStack * const stackP) {
+
+    return stackP->topOfStack == 0;
+}
+
+
+
+static bool
+inStackDirection(fillStack * const stackP,
+                 ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is is a step in the current stack
+   direction from the point on the top of the stack.  I.e. we could push
+   this point onto the stack without violating the monotonic property.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return true;
+    else
+        return topOfStack(stackP).y + stackP->step == point.y;
+}
+
+
+
+static bool
+againstStackDirection(fillStack * const stackP,
+                      ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is a step in the other direction
+   form the current stack direction.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return false;
+    else
+        return topOfStack(stackP).y - stackP->step == point.y;
+}
+
+
+
+static bool
+isLateralFromTopOfStack(fillStack * const stackP,
+                        ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is laterally across from the point
+   on the top of the stack.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return false;
+    else
+        return point.y == topOfStack(stackP).y;
+}
+
+
+
+static void
+pushStack(fillStack * const stackP,
+          ppmd_point  const newPoint) {
+
+    assert(inStackDirection(stackP, newPoint));
+
+    if (stackP->topOfStack >= stackP->stackSize) {
+        stackP->stackSize *= 2;
+
+        REALLOCARRAY(stackP->stack, stackP->stackSize);
+
+        if (stackP->stack == NULL)
+            pm_error("Could not allocate memory for a fill stack of %u points",
+                     stackP->stackSize);
+    }
+    assert(stackP->topOfStack < stackP->stackSize);
+
+    stackP->stack[stackP->topOfStack++] = newPoint;
+pm_message("pushed (%u, %u) at %u", newPoint.x, newPoint.y, stackP->topOfStack-1);
+}
+
+
+
+static ppmd_point
+popStack(fillStack * const stackP) {
+
+    ppmd_point retval;
+
+    assert(stackP->topOfStack < stackP->stackSize);
+
+    retval = stackP->stack[--stackP->topOfStack];
+pm_message("popped (%u, %u) at %u", retval.x, retval.y, stackP->topOfStack);
+    return retval;
+}
+
+
+
+static void
+replaceTopOfStack(fillStack * const stackP,
+                  ppmd_point  const point) {
+
+    assert(stackP->topOfStack > 0);
+
+    stackP->stack[stackP->topOfStack-1] = point;
+}
+
+
+
+static void
+reverseStackDirection(fillStack * const stackP) {
+
+    stackP->step *= -1;
+}
+
+
+
+static void
+drawFillLine(ppmd_point const begPoint,
+             ppmd_point const endPoint,
+             pixel **   const pixels,
+             pixel      const color) {
+
+    unsigned int leftCol, rghtCol;
+    unsigned int col;
+    unsigned int row;
+
+    /* Fill lines are always horizontal */
+
+    assert(begPoint.y == endPoint.y);
+
+pm_message("filling from (%u, %u) to (%u, %u)", begPoint.x, begPoint.y, endPoint.x, endPoint.y);
+    row = begPoint.y;
+
+    if (begPoint.x <= endPoint.x) {
+        leftCol = begPoint.x;
+        rghtCol = endPoint.x;
+    } else {
+        leftCol = endPoint.x;
+        rghtCol = begPoint.x;
+    }
+
+    for (col = leftCol; col <= rghtCol; ++col)
+        pixels[row][col] = color;
+}
+
+
+
+static void
+fillPoint(fillStack * const stackP,
+          ppmd_point  const point,
+          pixel **    const pixels,
+          pixel       const color) {
+/*----------------------------------------------------------------------------
+   Follow the outline of the figure to the point 'point', which is adjacent
+   to the point most recently added.
+
+   Fill the image in 'pixels' with color 'color' and update *stackP as
+   required.
+-----------------------------------------------------------------------------*/
+pm_message("filling point (%u, %u)", point.x, point.y);
+    if (inStackDirection(stackP, point)) {
+        pushStack(stackP, point);
+        pixels[point.y][point.x] = color;
+    } else {
+        if (againstStackDirection(stackP, point))
+            popStack(stackP);
+        
+        if (stackIsEmpty(stackP)) {
+            reverseStackDirection(stackP);
+            pushStack(stackP, point);
+        } else {
+            assert(isLateralFromTopOfStack(stackP, point));
+            
+            drawFillLine(topOfStack(stackP), point, pixels, color);
+            replaceTopOfStack(stackP, point);
+        }
+    }
+}
+
+
+
+static void
+fillLeg(ppmd_point  const begPoint,
+        ppmd_point  const endPoint,
+        fillStack * const stackP,
+        pixel **    const pixels,
+        pixel       const color) {
+/*----------------------------------------------------------------------------
+   Follow the leg which is a straight line segment from 'begPoint'
+   through 'endPoint', filling the raster 'pixels' with color 'color',
+   according to *stackP as we go.
+
+   We update *stackP accordingly.
+
+   A leg starts where the leg before it ends, so we skip the first point
+   in the line segment.
+-----------------------------------------------------------------------------*/
+    assert(!stackIsEmpty(stackP));
+
+    if (endPoint.y == begPoint.y)
+        /* Line is horizontal; We need just the end point. */
+        fillPoint(stackP, endPoint, pixels, color);
+    else {
+        double const invSlope = 1/lineSlope(begPoint, endPoint);
+        int const step = endPoint.y > begPoint.y ? +1 : -1;
+
+        ppmd_point here;
+
+        here = begPoint;
+    
+        while (here.y != endPoint.y) {
+            here.y += step;
+            here.x = ROUNDU(begPoint.x + vertDisp(begPoint, here) * invSlope);
+
+            assert(isOnLineSeg(here, begPoint, endPoint));
+
+            fillPoint(stackP, here, pixels, color);
+        }
+    }
+}
+
+
+
+void
+ppmd_fill_path(pixel **      const pixels, 
+               int           const cols, 
+               int           const rows, 
+               pixval        const maxval,
+               ppmd_path *   const pathP,
+               pixel         const color) {
+/*----------------------------------------------------------------------------
+   Draw a path which defines a closed figure (or multiple closed figures)
+   and fill it in.
+
+   *pathP describes the path.  'color' is the color with which to fill.
+
+   'pixels' is the canvas on which to draw the figure; its dimensions are
+   'cols' x 'rows'.
+
+   'maxval' is the maxval for 'color' and for 'pixels'.
+
+   Fail (abort the program) if the path does not end on the same point at
+   which it began.
+-----------------------------------------------------------------------------*/
+    ppmd_point prevVertex;
+    fillStack * stackP;
+    unsigned int legNumber;
+
+    createStack(&stackP);
+
+    prevVertex = pathP->begPoint;
+    pushStack(stackP, pathP->begPoint);
+
+    for (legNumber = 0; legNumber < pathP->legCount; ++legNumber) {
+        ppmd_pathleg * const legP = &pathP->legs[legNumber];
+        ppmd_point const nextVertex = legP->u.linelegparms.end;
+
+        if (prevVertex.y >= rows || nextVertex.y >= rows)
+            pm_error("Path extends below the image.");
+        if (prevVertex.x >= cols || nextVertex.x >= cols)
+            pm_error("Path extends off the image to the right.");
+
+        fillLeg(prevVertex, nextVertex, stackP, pixels, color);
+
+        prevVertex = nextVertex;
+    }
+    if (!pointEqual(prevVertex, pathP->begPoint))
+        pm_error("Failed to fill a path -- the path is not closed "
+                 "(i.e. it doesn't end up at the same point where it began)");
+
+    assert(pointEqual(popStack(stackP), pathP->begPoint));
+    assert(stackIsEmpty(stackP));
+
+    destroyStack(stackP);
+}
diff --git a/lib/pbm.h b/lib/pbm.h
new file mode 100644
index 00000000..1591c77f
--- /dev/null
+++ b/lib/pbm.h
@@ -0,0 +1,97 @@
+#ifndef PBM_H_INCLUDED
+#define PBM_H_INCLUDED
+
+#include "pm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef unsigned char bit;
+#define PBM_WHITE 0
+#define PBM_BLACK 1
+
+
+/* Magic constants. */
+
+#define PBM_MAGIC1 'P'
+#define PBM_MAGIC2 '1'
+#define RPBM_MAGIC2 '4'
+#define PBM_FORMAT (PBM_MAGIC1 * 256 + PBM_MAGIC2)
+#define RPBM_FORMAT (PBM_MAGIC1 * 256 + RPBM_MAGIC2)
+#define PBM_TYPE PBM_FORMAT
+
+
+/* Macro for turning a format number into a type number. */
+
+#define PBM_FORMAT_TYPE(f) \
+  ((f) == PBM_FORMAT || (f) == RPBM_FORMAT ? PBM_TYPE : -1)
+
+
+/* Declarations of routines. */
+
+void pbm_init ARGS(( int* argcP, char* argv[] ));
+void
+pbm_nextimage(FILE *file, int * const eofP);
+
+#define pbm_allocarray(cols, rows) \
+  ((bit**) pm_allocarray(cols, rows, sizeof(bit)))
+#define pbm_allocrow(cols) ((bit*) pm_allocrow(cols, sizeof(bit)))
+#define pbm_freearray(bits, rows) pm_freearray((char**) bits, rows)
+#define pbm_freerow(bitrow) pm_freerow((char*) bitrow)
+#define pbm_packed_bytes(cols) (((cols)+7)/8)
+#define pbm_allocrow_packed(cols) \
+    ((unsigned char *) pm_allocrow(pbm_packed_bytes(cols), \
+                                   sizeof(unsigned char)))
+#define pbm_freerow_packed(packed_bits) \
+    pm_freerow((char *) packed_bits)
+#define pbm_allocarray_packed(cols, rows) ((unsigned char **) \
+    pm_allocarray(pbm_packed_bytes(cols), rows, sizeof(unsigned char)))
+#define pbm_freearray_packed(packed_bits, rows) \
+    pm_freearray((char **) packed_bits, rows)
+
+bit** pbm_readpbm(FILE* file, int* colsP, int* rowsP);
+void pbm_readpbminit(FILE* file, int* colsP, int* rowsP, int* formatP);
+void pbm_readpbmrow(FILE* file, bit* bitrow, int cols, int format);
+void pbm_readpbmrow_packed(
+    FILE* const file, unsigned char * const packed_bits, 
+    const int cols, const int format);
+
+void
+pbm_writepbminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 int    const forceplain);
+
+void
+pbm_writepbm(FILE * const fileP, 
+             bit ** const bits, 
+             int    const cols, 
+             int    const rows, 
+             int    const forceplain);
+
+void
+pbm_writepbmrow(FILE * const fileP, 
+                bit *  const bitrow, 
+                int    const cols, 
+                int    const forceplain);
+
+void
+pbm_writepbmrow_packed(FILE *                const fileP, 
+                       const unsigned char * const packed_bits,
+                       int                   const cols, 
+                       int                   const forceplain);
+
+void
+pbm_check(FILE * file, const enum pm_check_type check_type, 
+          const int format, const int cols, const int rows,
+          enum pm_check_code * const retval_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
new file mode 100644
index 00000000..432aea3c
--- /dev/null
+++ b/lib/pbmfont.h
@@ -0,0 +1,75 @@
+/* pbmfont.h - header file for font routines in libpbm
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct glyph {
+    /* A glyph consists of white borders and the "central glyph" which
+       can be anything, but normally does not have white borders because
+       it's more efficient to represent white borders explicitly.
+    */
+	int width, height;
+        /* The dimensions of the central glyph, i.e. the 'bmap' array */
+	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;
+        /* Height in pixels of the white bottom border of this glyph */
+	int xadd;
+        /* Width of glyph -- white left border plus central glyph
+           plus white right border
+        */
+	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
+           'height' is 40 pixels and it's a rectangle that is black on
+           the top half and white on the bottom, this is an array of
+           800 bytes, with the first 400 having value 0x01 and the
+           last 400 having value 0x00.
+        */
+};
+
+struct font {
+    /* This describes a combination of font and character set.  Given
+       an code point in the range 0..255, this structure describes the
+       glyph for that character.
+    */
+	int maxwidth, maxheight;
+	int x;
+        /* ?? Not used by Pbmtext */
+    int y;
+        /* Amount of white space that should be added between lines of
+           this font.
+        */
+	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;
+};
+
+struct font* pbm_defaultfont(const char* const which);
+struct font*
+pbm_dissectfont(bit ** const font,
+                int    const frows,
+                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
+}
+#endif
diff --git a/lib/pgm.h b/lib/pgm.h
new file mode 100644
index 00000000..86935307
--- /dev/null
+++ b/lib/pgm.h
@@ -0,0 +1,136 @@
+/* pgm.h - header file for libpgm portable graymap library
+*/
+
+#ifndef _PGM_H_
+#define _PGM_H_
+
+#include "pbm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* The following definition has nothing to do with the format of a PGM file */
+typedef unsigned int gray;
+
+/* Since April 2000, we are capable of reading and generating raw
+   (binary) PGM files with maxvals up to 65535.  However, before that
+   the maximum (as usually implemented) was 255, and people still want
+   to generate files with a maxval of no more than 255 in most cases
+   (because then old Netpbm programs can process them, and they're
+   only half as big).
+
+   So we keep PGM_MAXMAXVAL = 255, even though it's kind of a misnomer.  
+
+   Note that one could always write a file with maxval > PGM_MAXMAXVAL and
+   it would just go into plain (text) format instead of raw (binary) format.
+   Along with the expansion to 16 bit raw files, we took away that ability.
+   Unless you specify 'forceplain' on the pgm_writepgminit() call, it will
+   fail if you specify a maxval > PGM_OVERALLMAXVAL.  I made this design
+   decision because I don't think anyone really wants to get a plain format
+   file with samples larger than 65535 in it.  However, it should be possible
+   just to increase PGM_OVERALLMAXVAL and get that old function back for
+   maxvals that won't fit in 16 bits.  I think the only thing really
+   constraining PGM_OVERALLMAXVAL is the size of the 'gray' data structure,
+   which is generally 32 bits.
+*/
+
+#define PGM_OVERALLMAXVAL 65535
+#define PGM_MAXMAXVAL 255
+
+
+/* Magic constants. */
+
+#define PGM_MAGIC1 'P'
+#define PGM_MAGIC2 '2'
+#define RPGM_MAGIC2 '5'
+#define PGM_FORMAT (PGM_MAGIC1 * 256 + PGM_MAGIC2)
+#define RPGM_FORMAT (PGM_MAGIC1 * 256 + RPGM_MAGIC2)
+#define PGM_TYPE PGM_FORMAT
+
+/* For the alpha-mask variant of PGM: */
+#define PGM_TRANSPARENT 0
+
+/* Macro for turning a format number into a type number. */
+
+#define PGM_FORMAT_TYPE(f) ((f) == PGM_FORMAT || (f) == RPGM_FORMAT ? PGM_TYPE : PBM_FORMAT_TYPE(f))
+
+
+/* Declarations of routines. */
+
+void 
+pgm_init(int *   const argcP,
+         char ** const argv);
+
+gray *
+pgm_allocrow(unsigned int const cols);
+
+#define pgm_freerow(grayrow) free(grayrow)
+
+#define pgm_allocarray( cols, rows ) \
+  ((gray**) pm_allocarray( cols, rows, sizeof(gray) ))
+#define pgm_freearray( grays, rows ) pm_freearray( (char**) grays, rows )
+
+gray **
+pgm_readpgm(FILE * const file,
+            int *  const colsP,
+            int *  const rowsP, 
+            gray * const maxvalP);
+
+void
+pgm_readpgminit(FILE * const file,
+                int *  const colsP, 
+                int *  const rowsP,
+                gray * const maxvalP,
+                int *  const formatP);
+
+void
+pgm_readpgmrow(FILE * const file,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format);
+
+void
+pgm_writepgminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 gray   const maxval, 
+                 int    const forceplain);
+
+void
+pgm_writepgmrow(FILE *       const fileP, 
+                const gray * const grayrow, 
+                int          const cols, 
+                gray         const maxval, 
+                int          const forceplain);
+
+void
+pgm_writepgm(FILE *  const fileP,
+             gray ** const grays,
+             int     const cols,
+             int     const rows,
+             gray    const maxval,
+             int     const forceplain);
+
+void
+pgm_nextimage(FILE * const file, int * const eofP);
+
+void
+pgm_check(FILE *               const file, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          gray                 const maxval,
+          enum pm_check_code * const retval_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /*_PGM_H_*/
diff --git a/lib/pm.h b/lib/pm.h
new file mode 100644
index 00000000..040a6a4b
--- /dev/null
+++ b/lib/pm.h
@@ -0,0 +1,346 @@
+/* pm.h - interface to format-independent part of libpbm.
+**
+** Copyright (C) 1988, 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#ifndef PM_H_INCLUDED
+#define PM_H_INCLUDED
+
+#include "pm_config.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+
+#ifdef VMS
+#include <perror.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+/* Definitions to make Netpbm programs work with either ANSI C or C
+   Classic.
+
+   This is obsolete, as all compilers recognize the ANSI syntax now.
+
+   We are slowly removing all the ARGS invocations from the programs
+   (and replacing them with explicit ANSI syntax), but we have a lot
+   of programs where we have removed ARGS from the definition but not
+   the prototype, and we have discovered that the Sun compiler
+   considers the resulting mismatch between definition and prototype
+   to be an error.  So we make ARGS create the ANSI syntax
+   unconditionally to avoid having to fix all those mismatches.  */
+
+#if 0
+#if __STDC__
+#define ARGS(alist) alist
+#else /*__STDC__*/
+#define ARGS(alist) ()
+#define const
+#endif /*__STDC__*/
+#endif
+#define ARGS(alist) alist
+
+
+/* PM_GNU_PRINTF_ATTR lets the GNU compiler check pm_message() and pm_error()
+   calls to be sure the arguments match the format string, thus preventing
+   runtime segmentation faults and incorrect messages.
+*/
+#ifdef __GNUC__
+#define PM_GNU_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define PM_GNU_PRINTF_ATTR
+#endif
+
+
+/* PURE_FN_ATTR is the attribute you add to a function declaration
+   that indicates it's a true function -- has no side effects and return
+   value is not influenced by anything except its arguments.
+*/
+#ifdef __GNUC__
+#define PURE_FN_ATTR __attribute__ ((const))
+#else
+#define PURE_FN_ATTR
+#endif
+
+typedef struct {
+    /* Coordinates of a pixel within an image.  Row 0 is the top row.
+       Column 0 is the left column.
+    */
+    unsigned int row;
+    unsigned int col;
+} pm_pixelcoord;
+
+extern int pm_plain_output;
+    /* Output functions are to produce plain (as opposed to raw) format
+       regardless of their 'plainformat' arguments.
+    */
+
+void 
+pm_init(const char * const progname, unsigned int const flags);
+
+void 
+pm_proginit(int* const argcP, char* argv[]);
+
+void
+pm_setMessage(int const newState, int * const oldStateP);
+
+FILE * 
+pm_tmpfile(void);
+
+void
+pm_make_tmpfile(FILE **       const filePP,
+                const char ** const filenameP);
+
+void
+pm_nextimage(FILE * const file, int * const eofP);
+
+/* Variable-sized arrays definitions. */
+
+char** 
+pm_allocarray (int const cols, int const rows, int const size );
+
+char * 
+pm_allocrow(unsigned int const cols,
+            unsigned int const size);
+
+void 
+pm_freearray (char** const its, int const rows);
+
+void 
+pm_freerow(char* const itrow);
+
+
+/* Obsolete -- use shhopt instead */
+int 
+pm_keymatch (char* const str, const char* const keyword, int const minchars);
+
+
+int PURE_FN_ATTR
+pm_maxvaltobits(int const maxval);
+
+int PURE_FN_ATTR
+pm_bitstomaxval(int const bits);
+
+unsigned int PURE_FN_ATTR
+pm_lcm (unsigned int const x, 
+        unsigned int const y,
+        unsigned int const z,
+        unsigned int const limit);
+
+void
+pm_setjmpbuf(jmp_buf * const jmpbufP);
+
+void
+pm_setjmpbufsave(jmp_buf *  const jmpbufP,
+                 jmp_buf ** const oldJmpbufPP);
+
+void
+pm_longjmp(void);
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_message (const char format[], ...);     
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error (const char reason[], ...);       
+
+/* Obsolete - use helpful error message instead */
+void
+pm_perror (const char reason[]);           
+
+/* Obsolete - use shhopt and user's manual instead */
+void 
+pm_usage (const char usage[]);             
+
+FILE* 
+pm_openr (const char* const name);
+         
+FILE*    
+pm_openw (const char* const name);
+         
+FILE *
+pm_openr_seekable(const char name[]);
+
+void     
+pm_close (FILE* const f);
+
+void 
+pm_closer (FILE* const f);
+          
+void      
+pm_closew (FILE* const f);
+
+
+
+void
+pm_readchar(FILE * const ifP,
+            char * const cP);
+
+static __inline__ void
+pm_readcharu(FILE *          const ifP,
+             unsigned char * const cP) {
+    pm_readchar(ifP, (char *) cP);
+}
+
+void
+pm_writechar(FILE * const ofP,
+             char   const c);
+
+static __inline__ void
+pm_writecharu(FILE *        const ofP,
+              unsigned char const c) {
+    pm_writechar(ofP, (char) c);
+}
+
+int
+pm_readbigshort(FILE *  const ifP, 
+                short * const sP);
+
+static __inline__ int
+pm_readbigshortu(FILE*            const ifP, 
+                 unsigned short * const sP) {
+    return pm_readbigshort(ifP, (short *) sP);
+}
+
+int
+pm_writebigshort(FILE * const ofP,
+                 short  const s);
+
+static __inline__ int
+pm_writebigshortu(FILE *          const ofP,
+                  unsigned short  const s) {
+    return pm_writebigshort(ofP, (short) s);
+}
+
+int
+pm_readbiglong(FILE * const ifP, 
+               long * const lP);
+
+static __inline__ int
+pm_readbiglongu(FILE *          const ifP,
+                unsigned long * const lP) {
+    return pm_readbiglong(ifP, (long *) lP);
+}
+
+int
+pm_writebiglong(FILE * const ofP,
+                long   const l);
+
+static __inline__ int
+pm_writebiglongu(FILE *        const ofP,
+                 unsigned long const l) {
+    return pm_writebiglong(ofP, (long) l);
+}
+
+int
+pm_readlittleshort(FILE  * const ifP,
+                   short * const sP);
+
+static __inline__ int
+pm_readlittleshortu(FILE  *          const ifP,
+                    unsigned short * const sP) {
+    return pm_readlittleshort(ifP, (short *) sP);
+}
+
+int
+pm_writelittleshort(FILE * const ofP,
+                    short  const s);
+
+static __inline__ int
+pm_writelittleshortu(FILE *          const ofP,
+                     unsigned short  const s) {
+    return pm_writelittleshort(ofP, (short) s);
+}
+
+int
+pm_readlittlelong(FILE * const ifP,
+                  long * const lP);
+
+static __inline__ int
+pm_readlittlelongu(FILE *          const ifP,
+                   unsigned long * const lP) {
+    return pm_readlittlelong(ifP, (long *) lP);
+}
+
+int
+pm_writelittlelong(FILE * const ofP,
+                   long   const l);
+
+static __inline__ int
+pm_writelittlelongu(FILE *        const ofP,
+                    unsigned long const l) {
+    return pm_writelittlelong(ofP, (long) l);
+}
+
+int 
+pm_readmagicnumber(FILE * const ifP);
+
+char* 
+pm_read_unknown_size(FILE * const ifP, 
+                     long * const buf);
+
+short
+pm_bs_short(short const s);
+
+long
+pm_bs_long(long const l);
+
+unsigned int
+pm_tell(FILE * const fileP);
+
+void
+pm_tell2(FILE *       const fileP, 
+         void *       const fileposP,
+         unsigned int const fileposSize);
+
+void
+pm_seek2(FILE *             const fileP, 
+         const pm_filepos * const fileposP,
+         unsigned int       const fileposSize);
+
+void
+pm_seek(FILE * const fileP, unsigned long filepos);
+
+enum pm_check_code {
+    PM_CHECK_OK,
+    PM_CHECK_UNKNOWN_TYPE,
+    PM_CHECK_TOO_LONG,
+    PM_CHECK_UNCHECKABLE,
+    PM_CHECK_TOO_SHORT
+};
+
+enum pm_check_type {
+    PM_CHECK_BASIC
+};
+
+void
+pm_check(FILE *               const file, 
+         enum pm_check_type   const check_type, 
+         pm_filepos           const need_raster_size,
+         enum pm_check_code * const retval_p);
+
+char *
+pm_arg0toprogname(const char arg0[]);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/lib/pm_gamma.h b/lib/pm_gamma.h
new file mode 100644
index 00000000..92b34145
--- /dev/null
+++ b/lib/pm_gamma.h
@@ -0,0 +1,68 @@
+#ifndef _PM_GAMMA_H_
+#define _PM_GAMMA_H_
+
+#include "pm_config.h"
+
+#include <math.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+static __inline__ float
+pm_gamma709(float const intensity) {
+
+    /* Here are parameters of the gamma transfer function
+       for the Netpbm formats.  This is CIE Rec 709.
+       
+       This transfer function is linear for sample values 0 .. .018 
+       and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    float const gamma = 2.2;
+    float const oneOverGamma = 1.0 / gamma;
+    float const linearCutoff = 0.018;
+    float const linearExpansion = 
+        (1.099 * pow(linearCutoff, oneOverGamma) - 0.099) / linearCutoff;
+
+    float brightness;
+
+    if (intensity < linearCutoff)
+        brightness = intensity * linearExpansion;
+    else
+        brightness = 1.099 * pow(intensity, oneOverGamma) - 0.099;
+
+    return brightness;
+}
+
+
+
+static __inline__ float
+pm_ungamma709(float const brightness) {
+
+    /* These are the same parameters as in pm_gamma, above */
+
+    float const gamma = 2.2;
+    float const oneOverGamma = 1.0 / gamma;
+    float const linearCutoff = 0.018;
+    float const linearExpansion = 
+        (1.099 * pow(linearCutoff, oneOverGamma) - 0.099) / linearCutoff;
+    
+    float intensity;
+
+    if (brightness < linearCutoff * linearExpansion)
+        intensity = brightness / linearExpansion;
+    else
+        intensity = pow((brightness + 0.099) / 1.099, gamma);
+
+    return intensity;
+}
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/pm_system.h b/lib/pm_system.h
new file mode 100644
index 00000000..0605f888
--- /dev/null
+++ b/lib/pm_system.h
@@ -0,0 +1,43 @@
+#ifndef PM_SYSTEM_H_INCLUDED
+#define PM_SYSTEM_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+void
+pm_system(void                  stdinFeeder(int, void *),
+          void *          const feederParm,
+          void                  stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand);
+
+
+struct bufferDesc {
+    /* This is just a parameter for the routines below */
+    unsigned int    size;
+    unsigned char * buffer;
+    unsigned int *  bytesTransferredP;
+};
+
+
+/* The following are a Standard Input feeder and a Standard Output accepter
+   for pm_system().  
+*/
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm);
+
+void
+pm_accept_to_memory(int    const pipetosuckFd,
+                    void * const accepterParm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/pnm.h b/lib/pnm.h
new file mode 100644
index 00000000..d3b6f84f
--- /dev/null
+++ b/lib/pnm.h
@@ -0,0 +1,134 @@
+/* pnm.h - header file for libpnm portable anymap library
+*/
+
+#ifndef _PNM_H_
+#define _PNM_H_
+
+#include "ppm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+typedef pixel xel;
+typedef pixval xelval;
+#define PNM_OVERALLMAXVAL PPM_OVERALLMAXVAL
+#define PNM_MAXMAXVAL PPM_MAXMAXVAL
+#define PNM_GET1(x) PPM_GETB(x)
+#define PNM_ASSIGN1(x,v) PPM_ASSIGN(x,0,0,v)
+#define PNM_ASSIGN(x,r,g,b) PPM_ASSIGN(x,r,g,b)
+#define PNM_EQUAL(x,y) PPM_EQUAL(x,y)
+#define PNM_FORMAT_TYPE(f) PPM_FORMAT_TYPE(f)
+#define PNM_DEPTH(newp,p,oldmaxval,newmaxval) \
+    PPM_DEPTH(newp,p,oldmaxval,newmaxval)
+
+/* Declarations of routines. */
+
+void pnm_init ARGS(( int* argcP, char* argv[] ));
+
+void
+pnm_nextimage(FILE *file, int * const eofP);
+
+xel *
+pnm_allocrow(unsigned int const cols);
+
+#define pnm_freerow(xelrow) free(xelrow)
+
+#define pnm_allocarray( cols, rows ) \
+  ((xel**) pm_allocarray( cols, rows, sizeof(xel) ))
+#define pnm_freearray( xels, rows ) pm_freearray( (char**) xels, rows )
+
+
+void
+pnm_readpnminit(FILE *   const fileP,
+                int *    const colsP,
+                int *    const rowsP,
+                xelval * const maxvalP,
+                int *    const formatP);
+
+void
+pnm_readpnmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format);
+
+xel **
+pnm_readpnm(FILE *   const fileP,
+            int *    const colsP,
+            int *    const rowsP,
+            xelval * const maxvalP,
+            int *    const formatP);
+
+void
+pnm_check(FILE *               const fileP,
+          enum pm_check_type   const check_type, 
+          int                  const format,
+          int                  const cols,
+          int                  const rows,
+          int                  const maxval,
+          enum pm_check_code * const retvalP);
+
+
+void
+pnm_writepnminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 xelval const maxval, 
+                 int    const format, 
+                 int    const forceplain);
+
+void
+pnm_writepnmrow(FILE * const fileP, 
+                xel *  const xelrow, 
+                int    const cols, 
+                xelval const maxval, 
+                int    const format, 
+                int    const forceplain);
+
+void
+pnm_writepnm(FILE * const fileP,
+             xel ** const xels,
+             int    const cols,
+             int    const rows,
+             xelval const maxval,
+             int    const format,
+             int    const forceplain);
+
+xel 
+pnm_backgroundxel (xel** xels, int cols, int rows, xelval maxval, int format);
+
+xel 
+pnm_backgroundxelrow (xel* xelrow, int cols, xelval maxval, int format);
+
+xel 
+pnm_whitexel (xelval maxval, int format);
+
+xel 
+pnm_blackxel(xelval maxval, int format);
+
+void 
+pnm_invertxel(xel *  const x,
+              xelval const maxval,
+              int    const format);
+
+void 
+pnm_promoteformat(xel** xels, int cols, int rows, xelval maxval, int format, 
+                  xelval newmaxval, int newformat);
+void 
+pnm_promoteformatrow(xel* xelrow, int cols, xelval maxval, int format, 
+                     xelval newmaxval, int newformat);
+
+pixel
+xeltopixel(xel const inputxel);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /*_PNM_H_*/
diff --git a/lib/ppm.h b/lib/ppm.h
new file mode 100644
index 00000000..033330b9
--- /dev/null
+++ b/lib/ppm.h
@@ -0,0 +1,300 @@
+/* Interface header file for PPM-related functions in libnetpbm */
+
+#ifndef _PPM_H_
+#define _PPM_H_
+
+#include "pgm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef gray pixval;
+
+/* These are just for use in this header file */
+#define PPM_MAX(a,b) ((a) > (b) ? (a) : (b))
+#define PPM_MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+#define PPM_OVERALLMAXVAL PGM_OVERALLMAXVAL
+#define PPM_MAXMAXVAL PGM_MAXMAXVAL
+typedef struct {
+    pixval r, g, b;
+} pixel;
+#define PPM_GETR(p) ((p).r)
+#define PPM_GETG(p) ((p).g)
+#define PPM_GETB(p) ((p).b)
+
+/************* added definitions *****************/
+#define PPM_PUTR(p,red) ((p).r = (red))
+#define PPM_PUTG(p,grn) ((p).g = (grn))
+#define PPM_PUTB(p,blu) ((p).b = (blu))
+/**************************************************/
+
+#define PPM_ASSIGN(p,red,grn,blu) \
+  do { (p).r = (red); (p).g = (grn); (p).b = (blu); } while (0)
+#define PPM_EQUAL(p,q) \
+  ( (p).r == (q).r && (p).g == (q).g && (p).b == (q).b )
+
+
+/* Magic constants. */
+
+#define PPM_MAGIC1 'P'
+#define PPM_MAGIC2 '3'
+#define RPPM_MAGIC2 '6'
+#define PPM_FORMAT (PPM_MAGIC1 * 256 + PPM_MAGIC2)
+#define RPPM_FORMAT (PPM_MAGIC1 * 256 + RPPM_MAGIC2)
+#define PPM_TYPE PPM_FORMAT
+
+
+#include "ppmcmap.h"
+
+/* Macro for turning a format number into a type number. */
+
+#define PPM_FORMAT_TYPE(f) \
+  ((f) == PPM_FORMAT || (f) == RPPM_FORMAT ? PPM_TYPE : PGM_FORMAT_TYPE(f))
+
+
+/* Declarations of routines. */
+
+void ppm_init(int * argcP, char* argv[]);
+
+#define ppm_allocarray(cols, rows) \
+  ((pixel**) pm_allocarray(cols, rows, sizeof(pixel)))
+
+pixel *
+ppm_allocrow(unsigned int const cols);
+
+#define ppm_freearray(pixels, rows) pm_freearray((char**) pixels, rows)
+
+#define ppm_freerow(pixelrow) free(pixelrow);
+
+pixel**
+ppm_readppm(FILE *   const fileP, 
+            int *    const colsP, 
+            int *    const rowsP, 
+            pixval * const maxvalP);
+
+void
+ppm_readppminit(FILE *   const fileP, 
+                int *    const colsP, 
+                int *    const rowsP, 
+                pixval * const maxvalP, 
+                int *    const formatP);
+
+void
+ppm_readppmrow(FILE*  const fileP, 
+               pixel* const pixelrow, 
+               int    const cols, 
+               pixval const maxval, 
+               int    const format);
+
+void
+ppm_writeppm(FILE *  const fileP, 
+             pixel** const pixels, 
+             int     const cols, 
+             int     const rows, 
+             pixval  const maxval, 
+             int     const forceplain);
+
+void
+ppm_writeppminit(FILE*  const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 pixval const maxval, 
+                 int    const forceplain);
+
+void
+ppm_writeppmrow(FILE *  const fileP, 
+                pixel * const pixelrow, 
+                int     const cols, 
+                pixval  const maxval, 
+                int     const forceplain);
+
+void
+ppm_check(FILE *               const fileP, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          pixval               const maxval,
+          enum pm_check_code * const retval_p);
+
+void
+ppm_nextimage(FILE * const fileP, 
+              int *  const eofP);
+
+pixel 
+ppm_parsecolor(const char * const colorname,
+               pixval       const maxval);
+
+pixel
+ppm_parsecolor2(const char * const colorname,
+                pixval       const maxval,
+                int          const closeOk);
+
+char*
+ppm_colorname(const pixel* const colorP, 
+              pixval       const maxval, 
+              int          const hexok);
+
+void
+ppm_readcolordict(const char *      const fileName,
+                  int               const mustOpen,
+                  unsigned int *    const nColorsP,
+                  const char ***    const colornamesP,
+                  pixel **          const colorsP,
+                  colorhash_table * const chtP);
+
+void
+ppm_readcolornamefile(const char *      const fileName, 
+                      int               const mustOpen,
+                      colorhash_table * const chtP, 
+                      const char ***    const colornamesP);
+
+void
+ppm_freecolornames(const char ** const colornames);
+
+#define PPM_ISGRAY(pixel) \
+    (PPM_GETR(pixel) == PPM_GETG(pixel) && PPM_GETR(pixel) == PPM_GETB(pixel))
+
+/* Color scaling macro -- to make writing ppmtowhatever easier. */
+
+#define PPM_DEPTH(newp,p,oldmaxval,newmaxval) \
+    PPM_ASSIGN( (newp), \
+	( (int) PPM_GETR(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \
+	( (int) PPM_GETG(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \
+	( (int) PPM_GETB(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval) )
+
+#define PPM_SQR(x) (x)*(x)
+
+static __inline__ unsigned int
+PPM_DISTANCE(pixel const p1,
+             pixel const p2) {
+    return (
+        PPM_SQR(PPM_GETR(p1)-PPM_GETR(p2)) +
+        PPM_SQR(PPM_GETG(p1)-PPM_GETG(p2)) +
+        PPM_SQR(PPM_GETB(p1)-PPM_GETB(p2)) );
+}
+#undef PPM_SQR
+
+/* Note that because a sample can be at most 1 << 16 - 1, PPM_DISTANCE
+   is less than UINT_MAX.  That means you can use UINT_MAX as an infinite
+   distance in some applications.
+*/
+
+/* Luminance, Chrominance macros. */
+
+/* The following are weights of the red, green, and blue components
+   respectively in the luminance of a color.  Actually, it's "luma,"
+   not luminance, the difference being that luminance would be a linear
+   combination of intensities, whereas luma is a linear combination of
+   gamma-adjusted intensities, as you would find in a Netpbm image.
+
+   These are from ITU-R BT.601.5.  That means they probably aren't technically
+   right for use with PPM images, because the PPM spec says ITU-R BT.709.
+   The two are similar, though.
+*/
+#define PPM_LUMINR (0.2989)
+#define PPM_LUMING (0.5866)
+#define PPM_LUMINB (0.1145)
+
+#define PPM_LUMIN(p) ( PPM_LUMINR * PPM_GETR(p) \
+                       + PPM_LUMING * PPM_GETG(p) \
+                       + PPM_LUMINB * PPM_GETB(p) )
+#define PPM_CHROM_B(p) ( -0.16874 * PPM_GETR(p) \
+                         - 0.33126 * PPM_GETG(p) \
+                         + 0.5 * PPM_GETB(p) )
+#define PPM_CHROM_R(p) ( 0.5 * PPM_GETR(p) \
+                         - 0.41869 * PPM_GETG(p) \
+                         - 0.08131 * PPM_GETB(p) )
+
+pixel
+ppm_color_from_ycbcr(unsigned int const y, 
+                     int          const cb, 
+                     int          const cr);
+
+/* Hue/Saturation/Value calculations */
+
+struct hsv {
+    double h;  /* hue (degrees)  0..360 */
+    double s;  /* saturation (0-1) */
+    double v;  /* value (0-1) */
+};
+
+pixel
+ppm_color_from_hsv(struct hsv const hsv,
+                   pixval     const maxval);
+
+struct hsv
+ppm_hsv_from_color(pixel  const color,
+                   pixval const maxval);
+
+static __inline pixval
+ppm_colorvalue(pixel const p) {
+/*----------------------------------------------------------------------------
+  The color value (V is HSV) as a pixval
+-----------------------------------------------------------------------------*/
+    return PPM_MAX(PPM_GETR(p), PPM_MAX(PPM_GETG(p), PPM_GETB(p)));
+}
+
+static __inline pixval
+ppm_saturation(pixel const p,
+               pixval const maxval) {
+/*----------------------------------------------------------------------------
+  The saturation, as a pixval (i.e. if saturation is 50% and maxval
+  is 100, this is 50).
+-----------------------------------------------------------------------------*/
+    pixval const maxIntensity =
+        PPM_MAX(PPM_GETR(p), PPM_MAX(PPM_GETG(p), PPM_GETB(p)));
+    pixval const minIntensity =
+        PPM_MIN(PPM_GETR(p), PPM_MIN(PPM_GETG(p), PPM_GETB(p)));
+    pixval const range = maxIntensity - minIntensity;
+
+    return (pixval)((unsigned long)range * maxval / maxIntensity);
+}
+
+typedef enum {
+    /* A color from the set of universally understood colors developed
+       by Brent Berlin and Paul Kay
+    */
+    BKCOLOR_BLACK = 0,
+    BKCOLOR_GRAY,
+    BKCOLOR_WHITE,
+    BKCOLOR_RED,
+    BKCOLOR_ORANGE,
+    BKCOLOR_YELLOW,
+    BKCOLOR_GREEN,
+    BKCOLOR_BLUE,
+    BKCOLOR_VIOLET,
+    BKCOLOR_PURPLE,
+    BKCOLOR_BROWN
+} bk_color;
+
+#define BKCOLOR_COUNT (BKCOLOR_BROWN+1)
+
+bk_color
+ppm_bk_color_from_color(pixel  const color,
+                        pixval const maxval);
+
+pixel
+ppm_color_from_bk_color(bk_color const bkColor,
+                        pixval   const maxval);
+
+bk_color
+ppm_bk_color_from_name(const char * const name);
+
+const char *
+ppm_name_from_bk_color(bk_color const bkColor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#undef PPM_MIN
+#undef PPM_MAX
+
+#endif /*_PPM_H_*/
diff --git a/lib/ppmcmap.h b/lib/ppmcmap.h
new file mode 100644
index 00000000..b44dcbea
--- /dev/null
+++ b/lib/ppmcmap.h
@@ -0,0 +1,111 @@
+#ifndef PPMCMAP_INCLUDED
+#define PPMCMAP_INCLUDED
+/* ppmcmap.h - header file for colormap routines in libppm
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+/* Color histogram stuff. */
+
+typedef struct colorhist_item* colorhist_vector;
+struct colorhist_item {
+    pixel color;
+    int value;
+};
+
+typedef struct colorhist_list_item* colorhist_list;
+struct colorhist_list_item {
+    struct colorhist_item ch;
+    colorhist_list next;
+};
+
+colorhist_vector
+ppm_computecolorhist( pixel ** const pixels, 
+                      const int cols, const int rows, const int maxcolors, 
+                      int * const colorsP );
+colorhist_vector
+ppm_computecolorhist2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP );
+
+void
+ppm_addtocolorhist( colorhist_vector chv, 
+                    int * const colorsP, const int maxcolors, 
+                    const pixel * const colorP, 
+                    const int value, const int position );
+
+void
+ppm_freecolorhist(colorhist_vector const chv);
+
+
+/* Color hash table stuff. */
+
+typedef colorhist_list* colorhash_table;
+
+colorhash_table
+ppm_computecolorhash( pixel ** const pixels, 
+                      const int cols, const int rows, 
+                      const int maxcolors, int * const colorsP );
+
+colorhash_table
+ppm_computecolorhash2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP);
+
+int
+ppm_lookupcolor(colorhash_table const cht, 
+                const pixel *   const colorP );
+
+colorhist_vector
+ppm_colorhashtocolorhist(colorhash_table const cht, 
+                         int             const maxcolors);
+
+colorhash_table
+ppm_colorhisttocolorhash(colorhist_vector const chv, 
+                         int              const colors);
+
+int
+ppm_addtocolorhash(colorhash_table const cht, 
+                   const pixel *   const colorP, 
+                   int             const value);
+
+void
+ppm_delfromcolorhash(colorhash_table const cht, 
+                     const pixel *   const colorP);
+
+
+colorhash_table
+ppm_alloccolorhash(void);
+
+void
+ppm_freecolorhash(colorhash_table const cht);
+
+
+colorhash_table ppm_colorrowtocolorhash ARGS((pixel *colorrow, int ncolors));
+pixel * ppm_computecolorrow ARGS((pixel **pixels, int cols, int rows, int maxcolors, int *ncolorsP));
+pixel * ppm_mapfiletocolorrow ARGS((FILE *file, int maxcolors, int *ncolorsP, pixval *maxvalP));
+void    ppm_colorrowtomapfile ARGS((FILE *ofp, pixel *colormap, int ncolors, pixval maxval));
+void    ppm_sortcolorrow (pixel * const colorrow, const int ncolors, 
+                          int (*cmpfunc)(pixel *, pixel *) );
+int     ppm_addtocolorrow ARGS((pixel *colorrow, int *ncolorsP, int maxcolors, pixel *pixelP));
+
+int
+ppm_findclosestcolor(const pixel * const colormap, 
+                     int           const ncolors, 
+                     const pixel * const pP);
+
+/* standard sort function for ppm_sortcolorrow() */
+#define PPM_STDSORT     (int (*)(pixel *, pixel *))0
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/ppmdfont.c b/lib/ppmdfont.c
new file mode 100644
index 00000000..a378f79c
--- /dev/null
+++ b/lib/ppmdfont.c
@@ -0,0 +1,133 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pm.h"
+#include "mallocvar.h"
+#include "ppmdfont.h"
+
+
+extern struct ppmd_font ppmd_standardfont;
+
+static const struct ppmd_font * currentFontP = &ppmd_standardfont;
+
+static void
+readGlyphHeader(FILE *                    const ifP,
+                struct ppmd_glyphHeader * const glyphHeaderP) {
+
+    glyphHeaderP->commandCount = fgetc(ifP);
+    glyphHeaderP->skipBefore   = fgetc(ifP);
+    glyphHeaderP->skipAfter    = fgetc(ifP);
+}
+
+
+
+static void
+readGlyphCommand(FILE *                     const ifP,
+                 struct ppmd_glyphCommand * const glyphCommandP) {
+
+    glyphCommandP->verb = fgetc(ifP);
+    glyphCommandP->x    = fgetc(ifP);
+    glyphCommandP->y    = fgetc(ifP);
+}
+
+
+
+static void
+readCharacter(FILE *              const ifP,
+              struct ppmd_glyph * const glyphP) {
+
+    unsigned int commandNum;
+    struct ppmd_glyphCommand * commandList;
+
+    readGlyphHeader(ifP, &glyphP->header);
+
+    MALLOCARRAY(commandList, glyphP->header.commandCount);
+
+    if (commandList == NULL)
+        pm_error("Insufficient memory to create a %u-command "
+                 "command list.", glyphP->header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyphP->header.commandCount;
+         ++commandNum) {
+
+        readGlyphCommand(ifP, &commandList[commandNum]);
+    }
+    glyphP->commandList = commandList;
+}
+
+
+
+static void
+readFontHeader(FILE *                   const ifP,
+               struct ppmd_fontHeader * const fontHeaderP) {
+    
+    fread(&fontHeaderP->signature, 1, sizeof(fontHeaderP->signature), ifP);
+    fontHeaderP->format         = fgetc(ifP);
+    fontHeaderP->characterCount = fgetc(ifP);
+    fontHeaderP->firstCodePoint = fgetc(ifP);
+}
+
+
+
+void
+ppmd_set_font(const struct ppmd_font * const newFontP) {
+
+    currentFontP = newFontP;
+}
+
+
+
+const struct ppmd_font *
+ppmd_get_font(void) {
+
+    return currentFontP;
+}
+
+
+
+void
+ppmd_read_font(FILE *                        const ifP,
+               const struct ppmd_font **     const fontPP) {
+
+    unsigned int relativeCodePoint;
+    struct ppmd_glyph * glyphTable;
+    struct ppmd_font * fontP;
+
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
+        pm_error("Insufficient memory for font header");
+
+    readFontHeader(ifP, &fontP->header);
+
+    MALLOCARRAY(glyphTable, fontP->header.characterCount);
+    if (glyphTable == NULL)
+        pm_error("Insufficient memory to store %u characters",
+                 fontP->header.characterCount);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        readCharacter(ifP, &glyphTable[relativeCodePoint]);
+    }
+    fontP->glyphTable = glyphTable;
+    *fontPP = fontP;
+}
+
+
+
+void
+ppmd_free_font(const struct ppmd_font * const fontP) {
+
+    unsigned int relativeCodePoint;
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+        
+        free((void*)fontP->glyphTable[relativeCodePoint].commandList);
+    }
+    free((void*)fontP->glyphTable);
+    free((void*)fontP);
+}
diff --git a/lib/ppmdfont.h b/lib/ppmdfont.h
new file mode 100644
index 00000000..329386ff
--- /dev/null
+++ b/lib/ppmdfont.h
@@ -0,0 +1,74 @@
+#ifndef PPMDFONT_INCLUDED
+#define PPMDFONT_INCLUDED
+
+#include <stdio.h>
+
+/* A font file has the following format, with proper packing:
+
+    struct ppmd_fontHeader fontHeader;
+    struct {
+        struct ppmd_glyphHeader glyphHeader;
+        struct ppmd_glyphCommand glyphCommand[N];
+    } glyph[M]
+    
+    Where:
+        M is fontHeader.characterCount
+        N is glyphHeader.commandCount   
+
+    glyph[i] is the glyph for code point Q,
+      where i = Q - fontHeader.firstCodePoint
+
+*/
+
+struct ppmd_fontHeader {
+    char signature[8];             /* "ppmdfont" */
+    unsigned char format;          /* 0x01 */
+    unsigned char characterCount;
+        /* Number of characters in this font */
+    unsigned char firstCodePoint;
+        /* lowest code point in the font */
+};
+
+struct ppmd_glyphHeader {
+    unsigned char commandCount;
+        /* Number of struct glyphCommand that follow */
+    unsigned char skipBefore;
+    unsigned char skipAfter;
+};
+
+enum ppmd_glyphCommandVerb {CMD_NOOP     = 0,
+                            CMD_DRAWLINE = 1,
+                            CMD_MOVEPEN  = 2
+};
+
+struct ppmd_glyphCommand {
+    enum ppmd_glyphCommandVerb verb;
+    unsigned char x;
+    unsigned char y;
+};
+
+struct ppmd_glyph {
+    struct ppmd_glyphHeader header;
+    const struct ppmd_glyphCommand * commandList;
+};
+
+struct ppmd_font {
+    struct ppmd_fontHeader header;
+    const struct ppmd_glyph * glyphTable;
+};
+
+void
+ppmd_set_font(const struct ppmd_font * const newFontP);
+
+const struct ppmd_font *
+ppmd_get_font(void);
+
+void
+ppmd_read_font(FILE *                    const ifP,
+               const struct ppmd_font ** const fontPP);
+
+void
+ppmd_free_font(const struct ppmd_font * const fontP);
+
+
+#endif
diff --git a/lib/ppmdraw.h b/lib/ppmdraw.h
new file mode 100644
index 00000000..9efe51b9
--- /dev/null
+++ b/lib/ppmdraw.h
@@ -0,0 +1,303 @@
+/* ppmdraw.h - header file for simple drawing routines in libppm
+**
+** Simple, yes, and also fairly slow if the truth be told; but also very
+** flexible and powerful.
+**
+** The two basic concepts are the drawproc and clientdata.  All the drawing
+** routines take a drawproc that does the actual drawing.  A drawproc draws
+** a single point, and it looks like this:
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+typedef enum {
+    PPMD_PATHLEG_LINE
+} ppmd_pathlegtype;
+
+typedef struct {
+    unsigned int x;
+    unsigned int y;
+} ppmd_point;
+
+struct ppmd_linelegparms {
+    ppmd_point end;
+};
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A leg of a ppmd_path.
+-----------------------------------------------------------------------------*/
+    ppmd_pathlegtype type;
+    union {
+        struct ppmd_linelegparms linelegparms;
+    } u;
+} ppmd_pathleg;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A closed path
+-----------------------------------------------------------------------------*/
+    unsigned int version;
+        /* Must be zero.  For future expansion. */
+    ppmd_point   begPoint;  
+    unsigned int legCount;
+        /* Number of legs in the path; i.e. size of 'legs' array */
+    size_t       legSize;
+        /* Size of storage occupied by one ppmd_pathleg.  I.e.
+           sizeof(ppmd_pathleg).  Used for
+           binary backward compatibility between callers and libppmd
+           as the definition of ppmd_pathleg changes.
+        */
+    ppmd_pathleg * legs;
+} ppmd_path;
+
+
+
+typedef void ppmd_drawproc(pixel **, int, int, pixval, int, int, const void *);
+
+ppmd_drawproc ppmd_point_drawproc;
+
+/*
+** So, you call a drawing routine, e.g. ppmd_line(), and pass it a drawproc;
+** it calls the drawproc for each point it wants to draw.  Why so complicated?
+** Because you can define your own drawprocs to do more interesting things than
+** simply draw the point.  For example, you could make one that calls back into
+** another drawing routine, say ppmd_circle() to draw a circle at each point
+** of a line.
+**
+** Slow?  Well sure, we're talking results here, not realtime.  You can do
+** tricks with this arrangement that you couldn't even think of before.
+** Still, to speed things up for the 90% case you can use this:
+*/
+#define PPMD_NULLDRAWPROC NULL
+/*
+** Just like ppmd_point_drawproc() it simply draws the point, but it's done
+** inline, and clipping is assumed to be handled at a higher level.
+**
+** Now, what about clientdata.  Well, it's an arbitrary pointer, and can
+** mean something different to each different drawproc.  For the above two
+** drawprocs, clientdata should be a pointer to a pixel holding the color
+** to be drawn.  Other drawprocs can use it to point to something else,
+** e.g. some structure to be modified, or they can ignore it.
+*/
+
+
+/* Outline drawing routines.  Lines, splines, circles, etc. */
+
+int 
+ppmd_setlinetype(int const type);
+
+#define PPMD_LINETYPE_NORMAL 0
+#define PPMD_LINETYPE_NODIAGS 1
+/* If you set NODIAGS, all pixels drawn by ppmd_line() will be 4-connected
+** instead of 8-connected; in other words, no diagonals.  This is useful
+** for some applications, for example when you draw many parallel lines
+** and you want them to fit together without gaps.
+*/
+
+int
+ppmd_setlineclip(int const clip);
+
+#define ppmd_setlineclipping(x)     ppmd_setlineclip(x)
+/* Normally, ppmd_line() clips to the edges of the pixmap.  You can use this
+** routine to disable the clipping, for example if you are using a drawproc
+** that wants to do its own clipping.
+*/
+
+void
+ppmd_line(pixel**       const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x0, 
+          int           const y0, 
+          int           const x1, 
+          int           const y1, 
+          ppmd_drawproc       drawproc,
+          const void *  const clientdata);
+/* Draws a line from (x0, y0) to (x1, y1).
+*/
+
+void
+ppmd_spline3(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata);
+    /* Draws a three-point spline from (x0, y0) to (x2, y2), with (x1,
+       y1) as the control point.  All drawing is done via ppmd_line(),
+       so the routines that control it control ppmd_spline3() as well. 
+    */
+
+void
+ppmd_polyspline(pixel **     const pixels, 
+                int          const cols, 
+                int          const rows, 
+                pixval       const maxval, 
+                int          const x0, 
+                int          const y0, 
+                int          const nc, 
+                int *        const xc, 
+                int *        const yc, 
+                int          const x1, 
+                int          const y1, 
+                ppmd_drawproc      drawProc,
+                const void * const clientdata);
+    /* Draws a bunch of splines end to end.  (x0, y0) and (x1, y1) are
+       the initial and final points, and the xc and yc are the
+       intermediate control points.  nc is the number of these control
+       points.
+    */
+
+void
+ppmd_spline4(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             int           const x3, 
+             int           const y3, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata);
+    /* Draws a four-point spline from (x0, y0) to (x3, y3), with (x1,
+       y1) and (x2, y2) as the control points.  All drawing is done
+       via ppmd_line(), so the routines that control it control this
+       as well.
+    */
+
+void
+ppmd_circle(pixel **     const pixels, 
+            int          const cols, 
+            int          const rows, 
+            pixval       const maxval, 
+            int          const cx, 
+            int          const cy, 
+            int          const radius, 
+            ppmd_drawproc      drawProc,
+            const void * const clientdata);
+    /* Draws a circle centered at (cx, cy) with the specified radius. */
+
+
+/* Simple filling routines.  */
+
+void
+ppmd_filledrectangle(pixel **      const pixels, 
+                     int           const cols, 
+                     int           const rows, 
+                     pixval        const maxval, 
+                     int           const x, 
+                     int           const y, 
+                     int           const width, 
+                     int           const height, 
+                     ppmd_drawproc       drawproc,
+                     const void *  const clientdata );
+    /* Fills in the rectangle [x, y, width, height]. */
+
+
+void
+ppmd_fill_path(pixel **      const pixels, 
+               int           const cols, 
+               int           const rows, 
+               pixval        const maxval,
+               ppmd_path *   const pathP,
+               pixel         const color);
+    /* Fills in a closed path.  Not much different from ppmd_fill(),
+       but with a different interface.
+    */
+
+
+/* Arbitrary filling routines.  With these you can fill any outline that
+** you can draw with the outline routines.
+*/
+
+struct fillobj;
+
+struct fillobj *
+ppmd_fill_create(void);
+    /* Returns a blank fillhandle. */
+
+void
+ppmd_fill_destroy(struct fillobj * fillObjP);
+
+void
+ppmd_fill_drawproc(pixel ** const pixels, 
+                   int      const cols, 
+                   int      const rows, 
+                   pixval   const maxval, 
+                   int      const x, 
+                   int      const y, 
+                   const void * const clientdata);
+    /* Use this drawproc to trace the outline you want filled.  Use
+       the fillhandle as the clientdata.
+    */
+void
+ppmd_fill(pixel **         const pixels, 
+          int              const cols, 
+          int              const rows, 
+          pixval           const maxval, 
+          struct fillobj * const fh,
+          ppmd_drawproc          drawProc,
+          const void *     const clientdata);
+
+/* Once you've traced the outline, give the fillhandle to this routine to
+** do the actual drawing.  As usual, it takes a drawproc and clientdata;
+** you could define drawprocs to do stipple fills and such.
+*/
+
+/* Text drawing routines. */
+
+void
+ppmd_text(pixel**      const pixels, 
+          int          const cols, 
+          int          const rows, 
+          pixval       const maxval, 
+          int          const xpos, 
+          int          const ypos, 
+          int          const height, 
+          int          const angle, 
+          const char * const sArg, 
+          void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), 
+    const void*  const clientdata);
+/* Draws the null-terminated string 's' left justified at the point
+   ('x', 'y').  The text will be 'height' pixels high and will be aligned on a
+   baseline inclined 'angle' degrees with the X axis.  The supplied
+   drawproc and clientdata are passed to ppmd_line() which performs the
+   actual drawing.
+*/
+
+void
+ppmd_text_box(int          const height, 
+              int          const angle, 
+              const char * const s, 
+              int *        const leftP, 
+              int *        const topP, 
+              int *        const rightP, 
+              int *        const bottomP);
+/* Calculates the extents box for text drawn by ppm_text with the given
+   string, size, and orientation.  Most extent box calculations should use
+   an angle specification of zero to calculate the unrotated box enclosing
+   the text.  If you need the extents of rotated text, however, you can
+   call ppmd_text_box with a nonzero angle.
+*/
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/ppmfloyd.h b/lib/ppmfloyd.h
new file mode 100644
index 00000000..e16ad651
--- /dev/null
+++ b/lib/ppmfloyd.h
@@ -0,0 +1,67 @@
+/* These declarations were supposed to be in the libfloyd.h file in the ilbm
+   package, but that file was missing, so I made them up myself.  
+   - Bryan 01.03.10.
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct ppm_fs_info {
+    /* thisXerr and nextXerr are dynamically allocated arrays each of whose
+       dimension is the width of the image plus 2
+       */
+    long *thisrederr;
+    long *thisgreenerr;
+    long *thisblueerr;
+    long *nextrederr;
+    long *nextgreenerr;
+    long *nextblueerr;
+    int lefttoright;
+    int cols;
+    pixval maxval;
+    int flags;
+    pixel *pixrow;
+    int col_end;
+    pixval red, green, blue;
+};
+
+typedef struct ppm_fs_info ppm_fs_info;
+
+/* Bitmasks for ppm_fs_info.flags */
+#define FS_RANDOMINIT 0x01
+#define FS_ALTERNATE  0x02
+
+ppm_fs_info *
+ppm_fs_init(int cols, pixval maxval, int flags);
+
+void
+ppm_fs_free(ppm_fs_info *fi);
+
+int
+ppm_fs_startrow(ppm_fs_info *fi, pixel *pixrow);
+
+int
+ppm_fs_next(ppm_fs_info *fi, int col);
+
+void
+ppm_fs_endrow(ppm_fs_info *fi);
+
+void
+ppm_fs_update(    ppm_fs_info *fi, int col, pixel *pP);
+
+
+void
+ppm_fs_update3(ppm_fs_info * const fi, 
+               int           const col, 
+               pixval        const r, 
+               pixval        const g, 
+               pixval        const b);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/rgb.txt b/lib/rgb.txt
new file mode 100644
index 00000000..ad4d9d87
--- /dev/null
+++ b/lib/rgb.txt
@@ -0,0 +1,881 @@
+# The colors in this color dictionary are from the following sources, in
+# order.  Some color names are defined multiple times, and sometimes
+# with different colors.  Many colors have multiple names.
+
+# - Netpbm originals
+# - Crayola crayons, as determined by John Thomas at Tektronix
+# - Hollasch at Microsoft Research
+# - HTML 4.0
+# - Some HTML extension that Internet Explorer understands
+# - XFree86 rgb.txt ca. 2001, derived from MIT X11
+
+# More details on the sources are above each group.
+
+# The preferred form of names is The X Window System standard, all one
+# word with the initial letter of each word capitalized.  But programs that
+# look up in this dictionary should convert the target color name and each
+# name in this dictionary to all lower case and remove spaces before
+# comparing.
+#
+# Numbered colors (e.g. blue4) are deprecated, but many are here for
+# compatibility with X.
+
+# These are the 72 colors from Crayola crayons, as determined by
+# John Thomas at Tektronix
+# (taken from http://www.swiss.ai.mit.edu/~jaffer/Color/thomas.txt on
+# 2002.09.03, with all the grayN removed).
+
+  0   0   0 Black
+255 255 255 White
+255   0   0 Red
+  0 255   0 Green
+  0   0 255 Blue
+  0 255 255 Cyan
+255   0 211 Magenta
+255 255   0 Yellow
+255 138   0 Orange
+159 211   0 GreenYellow
+  0 255 159 Spring Green
+  0 138 255 SkyBlue
+148   0 211 Violet
+255   0 148 VioletRed
+105 105 105 DimGray
+174 174 174 Gray
+174 174 174 Grey
+211 211 211 LightGrey
+211 211 211 LightGray
+105 105 105 DimGrey
+199  21 133 MediumVioletRed
+114  33 188 BlueViolet
+218 107 212 Orchid
+172  77 166 MediumOrchid
+106  37 102 DarkOrchid
+103   7  72 Maroon
+ 76  46  87 Plum
+146  62 112 Thistle
+171 197 255 LightBlue
+ 61  98 208 MediumBlue
+100 149 237 CornflowerBlue
+  0   0 142 NavyBlue
+  0   0 142 Navy
+ 12  62  99 MidnightBlue
+ 72 209 204 Turquoise
+ 62 172 181 MediumTurquoise
+ 29 111 117 DarkTurquoise
+ 52 152 202 LightSteelBlue
+ 55 121 153 SteelBlue
+126 125 160 CadetBlue
+117 134 190 SlateBlue
+ 95 109 154 MediumSlateBlue
+ 51  62  99 DarkSlateBlue
+ 60  64  74 DarkSlateGrey
+ 60  64  74 DarkSlateGray
+  0  83   0 DarkGreen
+ 79  79  47 DarkOliveGreen
+ 85 192  52 ForestGreen
+107 142  35 MediumForestGreen
+ 46 155  28 LimeGreen
+ 60 141  35 MediumSpringGreen
+152 255 152 PaleGreen
+ 43 167 112 SeaGreen
+ 27 134  86 MediumSeaGreen
+ 41 171 151 Aquamarine
+ 21 135 118 MediumAquamarine
+ 75 211   0 YellowGreen
+254 197  68 Gold
+184 134  11 MediumGoldenrod
+218 165  32 Goldenrod
+229 199 117 Wheat
+189 167 107 Khaki
+176 155 125 Tan
+178 143  86 SandyBrown
+142 107  35 Sienna
+103  67   0 Brown
+101  46  46 IndianRed
+255 174 185 Pink
+248 137 117 Coral
+248 109 104 Salmon
+226  65  42 OrangeRed
+136  18  13 Firebrick
+
+# The following 192 colors are from
+# http://www.research.microsoft.com/~hollasch/cgindex/color/colors.txt
+# on 2002.09.02.  See examples of them at
+# http://www.swiss.ai.mit.edu/~jaffer/Color/hollasch.pdf
+
+250 235 215 AntiqueWhite
+240 255 255 Azure
+255 228 196 Bisque
+255 235 205 BlanchedAlmond
+255 248 220 Cornsilk
+252 230 201 Eggshell
+255 250 240 FloralWhite
+220 220 220 Gainsboro
+248 248 255 GhostWhite
+240 255 240 Honeydew
+255 255 240 Ivory
+230 230 250 Lavender
+255 240 245 LavenderBlush
+255 250 205 LemonChiffon
+250 240 230 Linen
+245 255 250 MintCream
+255 228 225 MistyRose
+255 228 181 Moccasin
+255 222 173 NavajoWhite
+253 245 230 OldLace
+255 239 213 PapayaWhip
+255 218 185 PeachPuff
+255 245 238 Seashell
+255 250 250 Snow
+216 191 216 Thistle
+252 255 240 TitaniumWhite
+245 222 179 Wheat
+255 255 255 White
+245 245 245 WhiteSmoke
+253 248 255 ZincWhite
+128 138 135 ColdGrey
+105 105 105 DimGrey
+192 192 192 Grey
+211 211 211 LightGrey
+112 128 144 SlateGrey
+ 47  79  79 SlateGreyDark
+119 136 153 SlateGreyLight
+128 128 105 WarmGrey
+  0   0   0 Black
+ 41  36  33 IvoryBlack
+ 46  71  59 Lamp Black
+227  38  54 AlizarinCrimson
+156 102  31 Brick
+227  23  13 CadmiumRedDeep
+255 127  80 Coral
+240 128 128 CoralLight
+255  20 147 DeepPink
+212  61  26 EnglishRed
+178  34  34 Firebrick
+227  18  48 GeraniumLake
+255 105 180 HotPink
+176  23  31 IndianRed
+255 160 122 LightSalmon
+227  46  48 MadderLakeDeep
+176  48  96 Maroon
+255 192 203 Pink
+255 182 193 PinkLight
+135  38  87 Raspberry
+255   0   0 Red
+227  54  56 RoseMadder
+250 128 114 Salmon
+255  99  71 Tomato
+212  26  31 VenetianRed
+163 148 128 Beige
+128  42  42 Brown
+219  41  41 BrownMadder
+135  66  31 BrownOchre
+222 184 135 Burlywood
+138  54  15 BurntSienna
+138  51  36 BurntUmber
+210 105  30 Chocolate
+115  61  26 DeepOchre
+255 125  64 Flesh
+255  87  33 FleshOchre
+199 120  38 GoldOchre
+255  61  13 GreenishUmber
+240 230 140 Khaki
+189 183 107 KhakiDark
+245 245 220 LightBeige
+205 133  63 Peru
+188 143 143 RosyBrown
+199  97  20 RawSienna
+115  74  18 RawUmber
+ 94  38  18 Sepia
+160  82  45 Sienna
+139  69  19 SaddleBrown
+244 164  96 SandyBrown
+210 180 140 Tan
+ 94  38   5 VanDykeBrown
+255  97   3 CadmiumOrange
+255   3  13 CadmiumRedLight
+237 145  33 Carrot
+255 140   0 DarkOrange
+150  69  20 MarsOrange
+227 112  26 MarsYellow
+255 128   0 Orange
+255  69   0 OrangeRed
+227 130  23 YellowOchre
+255 168  36 AureolineYellow
+227 207  87 Banana
+255 227   3 CadmiumLemon
+255 153  18 CadmiumYellow
+255 176  15 CadmiumYellowLight
+255 215   0 Gold
+218 165  32 Goldenrod
+184 134  11 GoldenrodDark
+250 250 210 GoldenrodLight
+238 232 170 GoldenrodPale
+238 221 130 LightGoldenrod
+227 168 105 Melon
+255 168  18 NaplesYellowDeep
+255 255   0 Yellow
+255 255 224 YellowLight
+127 255   0 Chartreuse
+102 128  20 ChromeOxideGreen
+ 97 179  41 CinnabarGreen
+ 61 145  64 CobaltGreen
+  0 201  87 EmeraldGreen
+ 34 139  34 ForestGreen
+  0 255   0 Green
+  0 100   0 GreenDark
+152 251 152 GreenPale
+173 255  47 GreenYellow
+124 252   0 LawnGreen
+ 50 205  50 LimeGreen
+189 252 201 Mint
+ 59  94  43 Olive
+107 142  35 OliveDrab
+ 85 107  47 OliveGreenDark
+ 10 201  43 PermanentGreen
+ 48 128  20 SapGreen
+ 46 139  87 SeaGreen
+143 188 143 SeaGreenDark
+ 60 179 113 SeaGreenMedium
+ 32 178 170 SeaGreenLight
+  0 255 127 SpringGreen
+  0 250 154 SpringGreenMedium
+ 56  94  15 TerreVerte
+110 255 112 ViridianLight
+154 205  50 YellowGreen
+127 255 212 Aquamarine
+102 205 170 AquamarineMedium
+  0 255 255 Cyan
+224 255 255 CyanWhite
+ 64 224 208 Turquoise
+  0 206 209 TurquoiseDark
+ 72 209 204 TurquoiseMedium
+175 238 238 TurquoisePale
+240 248 255 AliceBlue
+  0   0 255 Blue
+173 216 230 BlueLight
+  0   0 205 BlueMedium
+ 95 158 160 Cadet
+ 61  89 171 Cobalt
+100 149 237 Cornflower
+  5 184 204 Cerulean
+ 30 144 255 DodgerBlue
+ 46   8  84 Indigo
+  3 168 158 ManganeseBlue
+ 25  25 112 MidnightBlue
+  0   0 128 Navy
+ 51 161 201 Peacock
+176 224 230 PowderBlue
+ 65 105 225 RoyalBlue
+106  90 205 SlateBlue
+ 72  61 139 SlateBlueDark
+132 112 255 SlateBlueLight
+123 104 238 SlateBlueMedium
+135 206 235 SkyBlue
+  0 191 255 SkyBlueDeep
+135 206 250 SkyBlueLight
+ 70 130 180 SteelBlue
+176 196 222 SteelBlueLight
+  0 199 140 TurquoiseBlue
+ 18  10 143 Ultramarine
+138  43 226 BlueViolet
+145  33 158 CobaltVioletDeep
+255   0 255 Magenta
+218 112 214 Orchid
+153  50 204 OrchidDark
+186  85 211 OrchidMedium
+219  38  69 PermanentRedViolet
+221 160 221 Plum
+160  32 240 Purple
+147 112 219 PurpleMedium
+ 92  36 110 UltramarineViolet
+143  94 153 Violet
+148   0 211 VioletDark
+208  32 144 VioletRed
+199  21 133 VioletRedMedium
+219 112 147 VioletRedPale
+#
+# From HTML 4.0 specification.  These are colors you can specify by name
+# in HTML tags.
+#
+# Actually, these are the colors as if HTML's sRGB values were actually
+# Rec. 709.  E.g. HTML 4.0 says silver is r=192, g=192, b=192, in the
+# SRGB system.
+# This file, which by definition contains Rec. 709 values, shows the same
+# three numbers (192,192,192).  So this file defines a slightly different
+# color than HTML does.
+#
+# There's some extension to HTML, apparently not blessed by W3C, that adds
+# all the MIT colors (see above).  Internet Explorer can render all of them.
+
+000 000 000 Black
+192 192 192 Silver
+128 128 128 Gray
+255 255 255 White
+128 000 000 Maroon
+255 000 000 Red
+128 000 128 Purple
+255 000 255 Fuchsia
+000 128 000 Green
+000 255 000 Lime
+128 128 000 Olive
+255 255 000 Yellow
+000 000 128 Navy
+000 000 255 Blue
+000 128 128 Teal
+000 255 255 Aqua
+
+# These are from XFree86 rgb.txt ca. 2001.  We have extracted the color names
+# that aren't already included above and also converted all the names to the
+# standard form and eliminated duplicates.
+#
+# The original file started with this line:
+#
+#! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $
+112 128 144 SlateGray
+119 136 153 LightSlateGray
+119 136 153 LightSlateGrey
+211 211 211 light grey
+100 149 237 CornflowerBlue
+132 112 255 LightSlateBlue
+  0 191 255 DeepSkyBlue
+135 206 250 LightSkyBlue
+175 238 238 PaleTurquoise
+224 255 255 LightCyan
+143 188 143 DarkSeaGreen
+ 32 178 170 LightSeaGreen
+189 183 107 DarkKhaki
+238 232 170 PaleGoldenrod
+250 250 210 LightGoldenrodYellow
+255 255 224 LightYellow
+233 150 122 DarkSalmon
+240 128 128 LightCoral
+255 182 193 LightPink
+219 112 147 PaleVioletRed
+148   0 211 DarkViolet
+147 112 219 MediumPurple
+255 250 250 Snow1
+238 233 233 Snow2
+205 201 201 Snow3
+139 137 137 Snow4
+255 245 238 Seashell1
+238 229 222 Seashell2
+205 197 191 Seashell3
+139 134 130 Seashell4
+255 239 219 AntiqueWhite1
+238 223 204 AntiqueWhite2
+205 192 176 AntiqueWhite3
+139 131 120 AntiqueWhite4
+255 228 196 Bisque1
+238 213 183 Bisque2
+205 183 158 Bisque3
+139 125 107 Bisque4
+255 218 185 PeachPuff1
+238 203 173 PeachPuff2
+205 175 149 PeachPuff3
+139 119 101 PeachPuff4
+255 222 173 NavajoWhite1
+238 207 161 NavajoWhite2
+205 179 139 NavajoWhite3
+139 121	 94 NavajoWhite4
+255 250 205 LemonChiffon1
+238 233 191 LemonChiffon2
+205 201 165 LemonChiffon3
+139 137 112 LemonChiffon4
+255 248 220 Cornsilk1
+238 232 205 Cornsilk2
+205 200 177 Cornsilk3
+139 136 120 Cornsilk4
+255 255 240 Ivory1
+238 238 224 Ivory2
+205 205 193 Ivory3
+139 139 131 Ivory4
+240 255 240 Honeydew1
+224 238 224 Honeydew2
+193 205 193 Honeydew3
+131 139 131 Honeydew4
+255 240 245 LavenderBlush1
+238 224 229 LavenderBlush2
+205 193 197 LavenderBlush3
+139 131 134 LavenderBlush4
+255 228 225 MistyRose1
+238 213 210 MistyRose2
+205 183 181 MistyRose3
+139 125 123 MistyRose4
+240 255 255 Azure1
+224 238 238 Azure2
+193 205 205 Azure3
+131 139 139 Azure4
+131 111 255 SlateBlue1
+122 103 238 SlateBlue2
+105  89 205 SlateBlue3
+ 71  60 139 SlateBlue4
+ 72 118 255 RoyalBlue1
+ 67 110 238 RoyalBlue2
+ 58  95 205 RoyalBlue3
+ 39  64 139 RoyalBlue4
+  0   0 255 Blue1
+  0   0 238 Blue2
+  0   0 205 Blue3
+  0   0 139 Blue4
+ 30 144 255 DodgerBlue1
+ 28 134 238 DodgerBlue2
+ 24 116 205 DodgerBlue3
+ 16  78 139 DodgerBlue4
+ 99 184 255 SteelBlue1
+ 92 172 238 SteelBlue2
+ 79 148 205 SteelBlue3
+ 54 100 139 SteelBlue4
+  0 191 255 DeepSkyBlue1
+  0 178 238 DeepSkyBlue2
+  0 154 205 DeepSkyBlue3
+  0 104 139 DeepSkyBlue4
+135 206 255 SkyBlue1
+126 192 238 SkyBlue2
+108 166 205 SkyBlue3
+ 74 112 139 SkyBlue4
+176 226 255 LightSkyBlue1
+164 211 238 LightSkyBlue2
+141 182 205 LightSkyBlue3
+ 96 123 139 LightSkyBlue4
+198 226 255 SlateGray1
+185 211 238 SlateGray2
+159 182 205 SlateGray3
+108 123 139 SlateGray4
+202 225 255 LightSteelBlue1
+188 210 238 LightSteelBlue2
+162 181 205 LightSteelBlue3
+110 123 139 LightSteelBlue4
+191 239 255 LightBlue1
+178 223 238 LightBlue2
+154 192 205 LightBlue3
+104 131 139 LightBlue4
+224 255 255 LightCyan1
+209 238 238 LightCyan2
+180 205 205 LightCyan3
+122 139 139 LightCyan4
+187 255 255 PaleTurquoise1
+174 238 238 PaleTurquoise2
+150 205 205 PaleTurquoise3
+102 139 139 PaleTurquoise4
+152 245 255 CadetBlue1
+142 229 238 CadetBlue2
+122 197 205 CadetBlue3
+ 83 134 139 CadetBlue4
+  0 245 255 Turquoise1
+  0 229 238 Turquoise2
+  0 197 205 Turquoise3
+  0 134 139 Turquoise4
+  0 255 255 Cyan1
+  0 238 238 Cyan2
+  0 205 205 Cyan3
+  0 139 139 Cyan4
+151 255 255 DarkSlateGray1
+141 238 238 DarkSlateGray2
+121 205 205 DarkSlateGray3
+ 82 139 139 DarkSlateGray4
+127 255 212 Aquamarine1
+118 238 198 Aquamarine2
+102 205 170 Aquamarine3
+ 69 139 116 Aquamarine4
+193 255 193 DarkSeaGreen1
+180 238 180 DarkSeaGreen2
+155 205 155 DarkSeaGreen3
+105 139 105 DarkSeaGreen4
+ 84 255 159 SeaGreen1
+ 78 238 148 SeaGreen2
+ 67 205 128 SeaGreen3
+ 46 139	 87 SeaGreen4
+154 255 154 PaleGreen1
+144 238 144 PaleGreen2
+124 205 124 PaleGreen3
+ 84 139	 84 PaleGreen4
+  0 255 127 SpringGreen1
+  0 238 118 SpringGreen2
+  0 205 102 SpringGreen3
+  0 139	 69 SpringGreen4
+  0 255	  0 Green1
+  0 238	  0 Green2
+  0 205	  0 Green3
+  0 139	  0 Green4
+127 255	  0 Chartreuse1
+118 238	  0 Chartreuse2
+102 205	  0 Chartreuse3
+ 69 139	  0 Chartreuse4
+192 255	 62 OliveDrab1
+179 238	 58 OliveDrab2
+154 205	 50 OliveDrab3
+105 139	 34 OliveDrab4
+202 255 112 DarkOliveGreen1
+188 238 104 DarkOliveGreen2
+162 205	 90 DarkOliveGreen3
+110 139	 61 DarkOliveGreen4
+255 246 143 Khaki1
+238 230 133 Khaki2
+205 198 115 Khaki3
+139 134	 78 Khaki4
+255 236 139 LightGoldenrod1
+238 220 130 LightGoldenrod2
+205 190 112 LightGoldenrod3
+139 129	 76 LightGoldenrod4
+255 255 224 LightYellow1
+238 238 209 LightYellow2
+205 205 180 LightYellow3
+139 139 122 LightYellow4
+255 255	  0 Yellow1
+238 238	  0 Yellow2
+205 205	  0 Yellow3
+139 139	  0 Yellow4
+255 215	  0 Gold1
+238 201	  0 Gold2
+205 173	  0 Gold3
+139 117	  0 Gold4
+255 193	 37 Goldenrod1
+238 180	 34 Goldenrod2
+205 155	 29 Goldenrod3
+139 105	 20 Goldenrod4
+255 185	 15 DarkGoldenrod1
+238 173	 14 DarkGoldenrod2
+205 149	 12 DarkGoldenrod3
+139 101	  8 DarkGoldenrod4
+255 193 193 RosyBrown1
+238 180 180 RosyBrown2
+205 155 155 RosyBrown3
+139 105 105 RosyBrown4
+255 106 106 IndianRed1
+238  99	 99 IndianRed2
+205  85	 85 IndianRed3
+139  58	 58 IndianRed4
+255 130	 71 Sienna1
+238 121	 66 Sienna2
+205 104	 57 Sienna3
+139  71	 38 Sienna4
+255 211 155 Burlywood1
+238 197 145 Burlywood2
+205 170 125 Burlywood3
+139 115	 85 Burlywood4
+255 231 186 Wheat1
+238 216 174 Wheat2
+205 186 150 Wheat3
+139 126 102 Wheat4
+255 165	 79 Tan1
+238 154	 73 Tan2
+205 133	 63 Tan3
+139  90	 43 Tan4
+255 127	 36 Chocolate1
+238 118	 33 Chocolate2
+205 102	 29 Chocolate3
+139  69	 19 Chocolate4
+255  48	 48 Firebrick1
+238  44	 44 Firebrick2
+205  38	 38 Firebrick3
+139  26	 26 Firebrick4
+255  64	 64 Brown1
+238  59	 59 Brown2
+205  51	 51 Brown3
+139  35	 35 Brown4
+255 140 105 Salmon1
+238 130	 98 Salmon2
+205 112	 84 Salmon3
+139  76	 57 Salmon4
+255 160 122 LightSalmon1
+238 149 114 LightSalmon2
+205 129	 98 LightSalmon3
+139  87	 66 LightSalmon4
+255 165	  0 Orange1
+238 154	  0 Orange2
+205 133	  0 Orange3
+139  90	  0 Orange4
+255 127	  0 DarkOrange1
+238 118	  0 DarkOrange2
+205 102	  0 DarkOrange3
+139  69	  0 DarkOrange4
+255 114	 86 Coral1
+238 106	 80 Coral2
+205  91	 69 Coral3
+139  62	 47 Coral4
+255  99	 71 Tomato1
+238  92	 66 Tomato2
+205  79	 57 Tomato3
+139  54	 38 Tomato4
+255  69	  0 OrangeRed1
+238  64	  0 OrangeRed2
+205  55	  0 OrangeRed3
+139  37	  0 OrangeRed4
+255   0	  0 Red1
+238   0	  0 Red2
+205   0	  0 Red3
+139   0	  0 Red4
+255  20 147 DeepPink1
+238  18 137 DeepPink2
+205  16 118 DeepPink3
+139  10	 80 DeepPink4
+255 110 180 HotPink1
+238 106 167 HotPink2
+205  96 144 HotPink3
+139  58  98 HotPink4
+255 181 197 Pink1
+238 169 184 Pink2
+205 145 158 Pink3
+139  99 108 Pink4
+255 174 185 LightPink1
+238 162 173 LightPink2
+205 140 149 LightPink3
+139  95 101 LightPink4
+255 130 171 PaleVioletRed1
+238 121 159 PaleVioletRed2
+205 104 137 PaleVioletRed3
+139  71	 93 PaleVioletRed4
+255  52 179 Maroon1
+238  48 167 Maroon2
+205  41 144 Maroon3
+139  28	 98 Maroon4
+255  62 150 VioletRed1
+238  58 140 VioletRed2
+205  50 120 VioletRed3
+139  34	 82 VioletRed4
+255   0 255 Magenta1
+238   0 238 Magenta2
+205   0 205 Magenta3
+139   0 139 Magenta4
+255 131 250 Orchid1
+238 122 233 Orchid2
+205 105 201 Orchid3
+139  71 137 Orchid4
+255 187 255 Plum1
+238 174 238 Plum2
+205 150 205 Plum3
+139 102 139 Plum4
+224 102 255 MediumOrchid1
+209  95 238 MediumOrchid2
+180  82 205 MediumOrchid3
+122  55 139 MediumOrchid4
+191  62 255 DarkOrchid1
+178  58 238 DarkOrchid2
+154  50 205 DarkOrchid3
+104  34 139 DarkOrchid4
+155  48 255 Purple1
+145  44 238 Purple2
+125  38 205 Purple3
+ 85  26 139 Purple4
+171 130 255 MediumPurple1
+159 121 238 MediumPurple2
+137 104 205 MediumPurple3
+ 93  71 139 MediumPurple4
+255 225 255 Thistle1
+238 210 238 Thistle2
+205 181 205 Thistle3
+139 123 139 Thistle4
+  0   0   0 Gray0
+  0   0   0 Grey0
+  3   3   3 Gray1
+  3   3   3 Grey1
+  5   5   5 Gray2
+  5   5   5 Grey2
+  8   8   8 Gray3
+  8   8   8 Grey3
+ 10  10  10 Gray4
+ 10  10  10 Grey4
+ 13  13  13 Gray5
+ 13  13  13 Grey5
+ 15  15  15 Gray6
+ 15  15  15 Grey6
+ 18  18  18 Gray7
+ 18  18  18 Grey7
+ 20  20  20 Gray8
+ 20  20  20 Grey8
+ 23  23  23 Gray9
+ 23  23  23 Grey9
+ 26  26  26 Gray10
+ 26  26  26 Grey10
+ 28  28  28 Gray11
+ 28  28  28 Grey11
+ 31  31  31 Gray12
+ 31  31  31 Grey12
+ 33  33  33 Gray13
+ 33  33  33 Grey13
+ 36  36  36 Gray14
+ 36  36  36 Grey14
+ 38  38  38 Gray15
+ 38  38  38 Grey15
+ 41  41  41 Gray16
+ 41  41  41 Grey16
+ 43  43  43 Gray17
+ 43  43  43 Grey17
+ 46  46  46 Gray18
+ 46  46  46 Grey18
+ 48  48  48 Gray19
+ 48  48  48 Grey19
+ 51  51  51 Gray20
+ 51  51  51 Grey20
+ 54  54  54 Gray21
+ 54  54  54 Grey21
+ 56  56  56 Gray22
+ 56  56  56 Grey22
+ 59  59  59 Gray23
+ 59  59  59 Grey23
+ 61  61  61 Gray24
+ 61  61  61 Grey24
+ 64  64  64 Gray25
+ 64  64  64 Grey25
+ 66  66  66 Gray26
+ 66  66  66 Grey26
+ 69  69  69 Gray27
+ 69  69  69 Grey27
+ 71  71  71 Gray28
+ 71  71  71 Grey28
+ 74  74  74 Gray29
+ 74  74  74 Grey29
+ 77  77  77 Gray30
+ 77  77  77 Grey30
+ 79  79  79 Gray31
+ 79  79  79 Grey31
+ 82  82  82 Gray32
+ 82  82  82 Grey32
+ 84  84  84 Gray33
+ 84  84  84 Grey33
+ 87  87  87 Gray34
+ 87  87  87 Grey34
+ 89  89  89 Gray35
+ 89  89  89 Grey35
+ 92  92  92 Gray36
+ 92  92  92 Grey36
+ 94  94  94 Gray37
+ 94  94  94 Grey37
+ 97  97  97 Gray38
+ 97  97  97 Grey38
+ 99  99  99 Gray39
+ 99  99  99 Grey39
+102 102 102 Gray40
+102 102 102 Grey40
+105 105 105 Gray41
+105 105 105 Grey41
+107 107 107 Gray42
+107 107 107 Grey42
+110 110 110 Gray43
+110 110 110 Grey43
+112 112 112 Gray44
+112 112 112 Grey44
+115 115 115 Gray45
+115 115 115 Grey45
+117 117 117 Gray46
+117 117 117 Grey46
+120 120 120 Gray47
+120 120 120 Grey47
+122 122 122 Gray48
+122 122 122 Grey48
+125 125 125 Gray49
+125 125 125 Grey49
+127 127 127 Gray50
+127 127 127 Grey50
+130 130 130 Gray51
+130 130 130 Grey51
+133 133 133 Gray52
+133 133 133 Grey52
+135 135 135 Gray53
+135 135 135 Grey53
+138 138 138 Gray54
+138 138 138 Grey54
+140 140 140 Gray55
+140 140 140 Grey55
+143 143 143 Gray56
+143 143 143 Grey56
+145 145 145 Gray57
+145 145 145 Grey57
+148 148 148 Gray58
+148 148 148 Grey58
+150 150 150 Gray59
+150 150 150 Grey59
+153 153 153 Gray60
+153 153 153 Grey60
+156 156 156 Gray61
+156 156 156 Grey61
+158 158 158 Gray62
+158 158 158 Grey62
+161 161 161 Gray63
+161 161 161 Grey63
+163 163 163 Gray64
+163 163 163 Grey64
+166 166 166 Gray65
+166 166 166 Grey65
+168 168 168 Gray66
+168 168 168 Grey66
+171 171 171 Gray67
+171 171 171 Grey67
+173 173 173 Gray68
+173 173 173 Grey68
+176 176 176 Gray69
+176 176 176 Grey69
+179 179 179 Gray70
+179 179 179 Grey70
+181 181 181 Gray71
+181 181 181 Grey71
+184 184 184 Gray72
+184 184 184 Grey72
+186 186 186 Gray73
+186 186 186 Grey73
+189 189 189 Gray74
+189 189 189 Grey74
+191 191 191 Gray75
+191 191 191 Grey75
+194 194 194 Gray76
+194 194 194 Grey76
+196 196 196 Gray77
+196 196 196 Grey77
+199 199 199 Gray78
+199 199 199 Grey78
+201 201 201 Gray79
+201 201 201 Grey79
+204 204 204 Gray80
+204 204 204 Grey80
+207 207 207 Gray81
+207 207 207 Grey81
+209 209 209 Gray82
+209 209 209 Grey82
+212 212 212 Gray83
+212 212 212 Grey83
+214 214 214 Gray84
+214 214 214 Grey84
+217 217 217 Gray85
+217 217 217 Grey85
+219 219 219 Gray86
+219 219 219 Grey86
+222 222 222 Gray87
+222 222 222 Grey87
+224 224 224 Gray88
+224 224 224 Grey88
+227 227 227 Gray89
+227 227 227 Grey89
+229 229 229 Gray90
+229 229 229 Grey90
+232 232 232 Gray91
+232 232 232 Grey91
+235 235 235 Gray92
+235 235 235 Grey92
+237 237 237 Gray93
+237 237 237 Grey93
+240 240 240 Gray94
+240 240 240 Grey94
+242 242 242 Gray95
+242 242 242 Grey95
+245 245 245 Gray96
+245 245 245 Grey96
+247 247 247 Gray97
+247 247 247 Grey97
+250 250 250 Gray98
+250 250 250 Grey98
+252 252 252 Gray99
+252 252 252 Grey99
+255 255 255 Gray100
+255 255 255 Grey100
+169 169 169 DarkGrey
+169 169 169 DarkGray
+0     0 139 DarkBlue
+0   139 139 DarkCyan
+139   0 139 DarkMagenta
+139   0   0 DarkRed
+144 238 144 LightGreen
+
+
+# These were more or less invented for use with Netpbm:
+255 255 255  D65
diff --git a/lib/standard.ppmdfont b/lib/standard.ppmdfont
new file mode 100644
index 00000000..124e1d73
--- /dev/null
+++ b/lib/standard.ppmdfont
Binary files differdiff --git a/lib/standardppmdfont.c b/lib/standardppmdfont.c
new file mode 100644
index 00000000..aa4707e2
--- /dev/null
+++ b/lib/standardppmdfont.c
@@ -0,0 +1,3177 @@
+/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmfont file. */
+
+#include "ppmdfont.h"
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_33[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_34[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 249 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_35[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 253 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 3 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_36[26] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 13 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 13 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_37[31] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_38[34] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_39[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 250 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_40[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 4, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_41[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_42[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 6 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_43[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 0 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 0 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_44[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_45[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 0 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_46[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_47[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_48[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_49[4] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_50[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_51[15] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_52[6] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_53[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_54[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_55[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_56[29] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_57[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_58[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_59[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_60[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_61[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 253 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 3 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_62[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_63[20] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_64[55] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 1 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 6 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_65[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_66[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_67[18] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_68[15] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_69[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_70[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_71[22] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_72[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_73[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_74[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 4, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_75[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_76[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_77[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_78[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_79[21] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_80[13] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 255 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_81[24] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 5 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 11 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_82[16] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_83[20] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_84[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_85[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_86[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_87[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 246, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_88[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_89[6] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_90[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_91[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 240 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 16 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_92[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 240 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_93[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 240 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 16 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_94[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_95[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 20, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_96[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 248 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_97[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_98[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_99[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_100[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_101[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_102[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_103[22] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_104[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_105[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_106[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_107[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 5 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_108[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_109[18] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 245, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 245, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 245, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_110[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_111[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_112[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_113[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_114[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_115[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_116[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_117[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_118[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_119[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_120[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_121[9] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_122[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_123[39] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 255 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_124[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_125[39] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 255 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_126[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 13, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 15, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 17, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 0 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 13, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 15, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 17, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 252 }
+};
+
+struct ppmd_glyph const ppmd_standardfont_glyphTable[95] = {
+  { /* glyph */
+    { /* header */ 0, 0, 21}
+    ,
+    NULL
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_33
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 253, 15}
+    ,
+    ppmd_standardfont_glyphTable_cmd_34
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_35
+  }
+  ,
+  { /* glyph */
+    { /* header */ 26, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_36
+  }
+  ,
+  { /* glyph */
+    { /* header */ 31, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_37
+  }
+  ,
+  { /* glyph */
+    { /* header */ 34, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_38
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_39
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_40
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_41
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_42
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_43
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_44
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_45
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_46
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_47
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_48
+  }
+  ,
+  { /* glyph */
+    { /* header */ 4, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_49
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_50
+  }
+  ,
+  { /* glyph */
+    { /* header */ 15, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_51
+  }
+  ,
+  { /* glyph */
+    { /* header */ 6, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_52
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_53
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_54
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_55
+  }
+  ,
+  { /* glyph */
+    { /* header */ 29, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_56
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_57
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_58
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_59
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_60
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_61
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_62
+  }
+  ,
+  { /* glyph */
+    { /* header */ 20, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_63
+  }
+  ,
+  { /* glyph */
+    { /* header */ 55, 243, 14}
+    ,
+    ppmd_standardfont_glyphTable_cmd_64
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_65
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_66
+  }
+  ,
+  { /* glyph */
+    { /* header */ 18, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_67
+  }
+  ,
+  { /* glyph */
+    { /* header */ 15, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_68
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_69
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 246, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_70
+  }
+  ,
+  { /* glyph */
+    { /* header */ 22, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_71
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_72
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_73
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_74
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_75
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 246, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_76
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_77
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_78
+  }
+  ,
+  { /* glyph */
+    { /* header */ 21, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_79
+  }
+  ,
+  { /* glyph */
+    { /* header */ 13, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_80
+  }
+  ,
+  { /* glyph */
+    { /* header */ 24, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_81
+  }
+  ,
+  { /* glyph */
+    { /* header */ 16, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_82
+  }
+  ,
+  { /* glyph */
+    { /* header */ 20, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_83
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_84
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_85
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_86
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_87
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_88
+  }
+  ,
+  { /* glyph */
+    { /* header */ 6, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_89
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_90
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_91
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_92
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_93
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_94
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 253, 22}
+    ,
+    ppmd_standardfont_glyphTable_cmd_95
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_96
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_97
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_98
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_99
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_100
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_101
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_102
+  }
+  ,
+  { /* glyph */
+    { /* header */ 22, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_103
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_104
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_105
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_106
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 247, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_107
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_108
+  }
+  ,
+  { /* glyph */
+    { /* header */ 18, 241, 15}
+    ,
+    ppmd_standardfont_glyphTable_cmd_109
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_110
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_111
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_112
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_113
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 249, 6}
+    ,
+    ppmd_standardfont_glyphTable_cmd_114
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_115
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_116
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_117
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_118
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_119
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_120
+  }
+  ,
+  { /* glyph */
+    { /* header */ 9, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_121
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_122
+  }
+  ,
+  { /* glyph */
+    { /* header */ 39, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_123
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_124
+  }
+  ,
+  { /* glyph */
+    { /* header */ 39, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_125
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 255, 21}
+    ,
+    ppmd_standardfont_glyphTable_cmd_126
+  }
+};
+
+
+struct ppmd_font const ppmd_standardfont = {
+  {/* .header */
+    {'p','p','m','d','f','o','n','t'},
+    0x01,
+    95,
+    32
+  }
+  ,
+  /* .glyphTable: */ ppmd_standardfont_glyphTable
+};
diff --git a/lib/util/LICENSE.txt b/lib/util/LICENSE.txt
new file mode 100644
index 00000000..aeb06a7f
--- /dev/null
+++ b/lib/util/LICENSE.txt
@@ -0,0 +1,121 @@
+The Frontier Artistic License Version 1.0
+Derived from the Artistic License at OpenSource.org.
+Submitted to OpenSource.org for Open Source Initiative certification.
+   
+Preamble
+
+The intent of this document is to state the conditions under which a
+Package may be copied, such that the Copyright Holder maintains some
+semblance of artistic control over the development of the package,
+while giving the users of the package the right to use and distribute
+the Package in a more-or-less customary fashion, plus the right to
+make reasonable modifications.
+   
+Definitions
+
+  "Package" refers to the script, suite, file, or collection of
+  scripts, suites, and/or files distributed by the Copyright Holder,
+  and to derivatives of that Package created through textual modification.
+
+  "Standard Version" refers to such a Package if it has not been
+  modified, or has been modified in accordance with the wishes of
+  the Copyright Holder.
+
+  "Copyright Holder" is whoever is named in the copyright statement
+  or statements for the package.
+
+  "You" is you, if you're thinking about copying or distributing
+  this Package.
+
+  "Reasonable copying fee" is whatever you can justify on the basis
+  of media cost, duplication charges, time of people involved, and
+  so on. (You will not be required to justify it to the Copyright
+  Holder, but only to the computing community at large as a market
+  that must bear the fee.)
+
+  "Freely Available" means that no fee is charged for the item
+  itself, though there may be fees involved in handling the item.
+  It also means that recipients of the item may redistribute it under
+  the same conditions they received it.
+       
+
+Terms
+
+1. You may make and give away verbatim copies of the source form of
+the Standard Version of this Package without restriction, provided
+that you duplicate all of the original copyright notices and
+associated disclaimers.
+   
+2. You may apply bug fixes, portability fixes, and other modifications
+derived from the Public Domain or from the Copyright Holder. A Package
+modified in such a way shall still be considered the Standard Version.
+   
+3. You may otherwise modify your copy of this Package in any way,
+provided that you insert a prominent notice in each changed script,
+suite, or file stating how and when you changed that script, suite,
+or file, and provided that you do at least ONE of the following:
+   
+  a) Use the modified Package only within your corporation or
+  organization, or retain the modified Package solely for personal use.
+     
+  b) Place your modifications in the Public Domain or otherwise make
+  them Freely Available, such as by posting said modifications to Usenet
+  or an equivalent medium, or placing the modifications on a major archive
+  site such as ftp.uu.net, or by allowing the Copyright Holder to include
+  your modifications in the Standard Version of the Package.
+     
+  c) Rename any non-standard executables so the names do not conflict
+  with standard executables, which must also be provided, and provide
+  a separate manual page (or equivalent) for each non-standard executable
+  that clearly documents how it differs from the Standard Version.
+     
+  d) Make other distribution arrangements with the Copyright Holder.
+     
+4. You may distribute the programs of this Package in object code or
+executable form, provided that you do at least ONE of the following:
+   
+  a) Distribute a Standard Version of the executables and library
+  files, together with instructions (in the manual page or
+  equivalent) on where to get the Standard Version.
+     
+  b) Accompany the distribution with the machine-readable source of
+  the Package with your modifications.
+     
+  c) Accompany any non-standard executables with their corresponding
+  Standard Version executables, give the non-standard executables
+  non-standard names, and clearly document the differences in manual
+  pages (or equivalent), together with instructions on where to get
+  the Standard Version.
+     
+  d) Make other distribution arrangements with the Copyright Holder.
+     
+5. You may charge a reasonable copying fee for any distribution of
+this Package. You may charge any fee you choose for support of this
+Package. You may not charge a fee for this Package itself. However,
+you may distribute this Package in aggregate with other (possibly
+commercial) programs as part of a larger (possibly commercial)
+software distribution provided that you do not advertise this Package
+as a product of your own.
+   
+6. The scripts and library files supplied as input to or produced as
+output from the programs of this Package do not automatically fall
+under the copyright of this Package, but belong to whomever generated
+them, and may be sold commercially, and may be aggregated with this
+Package.
+   
+7. Scripts, suites, or programs supplied by you that depend on or
+otherwise make use of this Package shall not be considered part of
+this Package.
+   
+8. The name of the Copyright Holder may not be used to endorse or
+promote products derived from this software without specific prior
+written permission.
+   
+9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+   
+                        The End
+
+
+http://www.spinwardstars.com/frontier/fal.html
diff --git a/lib/util/Makefile b/lib/util/Makefile
new file mode 100644
index 00000000..8f461f28
--- /dev/null
+++ b/lib/util/Makefile
@@ -0,0 +1,29 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = lib/util
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+INCLUDES = -I $(BUILDDIR) -I $(SRCDIR)/$(SUBDIR)/..
+
+# nstring is required for asprintf(), etc.  Also some systems don't have
+# snprintf(), e.g. Solaris 2.5.1.  2002.03.29.
+UTILOBJECTS = shhopt.o nstring.o filename.o
+
+MERGE_OBJECTS =
+
+all: $(UTILOBJECTS)
+
+include $(SRCDIR)/Makefile.common
+
+$(UTILOBJECTS):%.o:%.c importinc
+	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) $(CFLAGS_SHLIB) \
+	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+testnstring: test.c nstring.h nstring.o
+	$(CC) $(CFLAGS) $(CADD) -o $@ nstring.o $<
+
+include Makefile.depend
diff --git a/lib/util/bitreverse.h b/lib/util/bitreverse.h
new file mode 100644
index 00000000..b3f0ea13
--- /dev/null
+++ b/lib/util/bitreverse.h
@@ -0,0 +1,46 @@
+/*
+** bitreverse.h
+**
+** This particular array seems to be useful in a lot of bitmap
+** conversion programs.  It's not used with standard libnetpbm PBM
+** processing because bits are stored one per byte (or per word), for
+** easier manipulation.  But if you wanted to write, for example, a
+** program to directly convert Sun raster format into X bitmaps, you
+** could use this.  It's also useful for the "packed PBM" libnetpbm
+** functions.
+**
+** Of course, you could also use this fairly slick chunk of code:
+**
+**     c = ((c >>  1) & 0x55) | ((c <<  1) & 0xaa);
+**     c = ((c >>  2) & 0x33) | ((c <<  2) & 0xcc);
+**     c = ((c >> 4) & 0x0f) | ((c << 4) & 0xf0); 
+*/
+
+#ifndef _BITR_H_
+#define _BITR_H_
+
+static unsigned char bitreverse[256] = {
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0,
+    0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4,
+    0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc,
+    0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca,
+    0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6,
+    0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1,
+    0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9,
+    0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd,
+    0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3,
+    0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7,
+    0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf,
+    0x3f, 0xbf, 0x7f, 0xff};
+
+#endif /*_BITR_H_*/
diff --git a/lib/util/filename.c b/lib/util/filename.c
new file mode 100644
index 00000000..e3a9a89f
--- /dev/null
+++ b/lib/util/filename.c
@@ -0,0 +1,26 @@
+#include "nstring.h"
+
+#include "filename.h"
+
+const char *
+pm_basename(const char * const fileName) {
+/*----------------------------------------------------------------------------
+   Return the filename portion of a file name, e.g. "foo.ppm" from
+   "/home/bryanh/foo.ppm".
+
+   Return it as a malloc'ed string.
+-----------------------------------------------------------------------------*/
+    unsigned int basenameStart;
+    unsigned int i;
+    const char * retval;
+
+    basenameStart = 0;  /* initial assumption */
+
+    for (i = 0; fileName[i]; ++i) {
+        if (fileName[i] == '/')
+            basenameStart = i+1;
+    }
+    asprintfN(&retval, "%s", &fileName[basenameStart]);
+
+    return retval;
+}
diff --git a/lib/util/filename.h b/lib/util/filename.h
new file mode 100644
index 00000000..f598fec1
--- /dev/null
+++ b/lib/util/filename.h
@@ -0,0 +1,7 @@
+#ifndef FILENAME_H_INCLUDED
+#define FILENAME_H_INCLUDED
+
+const char *
+pm_basename(const char * const fileName);
+
+#endif
diff --git a/lib/util/intcode.h b/lib/util/intcode.h
new file mode 100644
index 00000000..4d9c83aa
--- /dev/null
+++ b/lib/util/intcode.h
@@ -0,0 +1,86 @@
+#ifndef INTCODE_H_INCLUDED
+#define INTCODE_H_INCLUDED
+
+#include "pm_config.h"  /* For uint32_t, BYTE_ORDER */
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is a big-endian representation of a 32 bit integer.  I.e.
+   bytes[0] is the most significant 8 bits; bytes[3] is the least
+   significant 8 bits of the number in pure binary.
+
+   On a big-endian machines, this is bit for bit identical to uint32_t.
+   On a little-endian machine, it isn't.
+
+   This is an important data type because decent file formats use
+   big-endian -- they don't care if some CPU happens to use some other
+   code for its own work.
+-----------------------------------------------------------------------------*/
+    unsigned char bytes[4];
+} bigend32;
+
+
+unsigned int const pm_byteOrder = BYTE_ORDER;
+
+
+static __inline__ uint32_t
+pm_uintFromBigend32(bigend32 const arg) {
+
+    uint32_t retval;
+
+    switch (pm_byteOrder) {
+    case BIG_ENDIAN: {
+        union {
+            bigend32 bigend;
+            uint32_t native;
+        } converter;
+        
+        converter.bigend = arg;
+        
+        retval = converter.native;
+    }; break;
+    case LITTLE_ENDIAN: {
+        retval =
+            (arg.bytes[0] << 24) |
+            (arg.bytes[1] << 16) |
+            (arg.bytes[2] <<  8) |
+            (arg.bytes[3] <<  0);
+    } break;
+    }
+    return retval;
+}
+
+
+
+static __inline__ bigend32
+pm_bigendFromUint32(uint32_t const arg) {
+
+    bigend32 retval;
+
+    switch (pm_byteOrder) {
+    case BIG_ENDIAN: {
+        union {
+            bigend32 bigend;
+            uint32_t native;
+        } converter;
+        
+        converter.native = arg;
+
+        retval = converter.bigend;
+    } break;
+    case LITTLE_ENDIAN: {
+        uint32_t shift;
+        shift = arg;
+        retval.bytes[3] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[2] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[1] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[0] = shift;  /* Takes lower 8 bits */
+    } break;
+    }
+    return retval;
+}
+
+#endif
diff --git a/lib/util/lexheader b/lib/util/lexheader
new file mode 100644
index 00000000..c5691660
--- /dev/null
+++ b/lib/util/lexheader
@@ -0,0 +1,9 @@
+/* This is the file 'lexheader'.  It contains extra stuff needed by
+   parsers generated by lex.
+
+   GNU Flex generates a parser that refers to the non-ansi C library 
+   subroutine fileno().  In order to let it compile without warnings,
+   we define _XOPEN_SOURCE to declare that fact.
+*/
+#define _XOPEN_SOURCE
+/* END OF lexheader */
diff --git a/lib/util/mallocvar.h b/lib/util/mallocvar.h
new file mode 100644
index 00000000..a26d007b
--- /dev/null
+++ b/lib/util/mallocvar.h
@@ -0,0 +1,107 @@
+/* These are some dynamic memory allocation facilities.  They are essentially
+   an extension to C, as they do allocations with a cognizance of C 
+   variables.  You can use them to make C read more like a high level
+   language.
+
+   Before including this, you must define an __inline__ macro if your
+   compiler doesn't recognize it as a keyword.
+*/
+
+#ifndef MALLOCVAR_INCLUDED
+#define MALLOCVAR_INCLUDED
+
+#include "pm_config.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+static __inline__ void
+mallocProduct(void **      const resultP, 
+              unsigned int const factor1,
+              unsigned int const factor2) {
+/*----------------------------------------------------------------------------
+   malloc a space whose size in bytes is the product of 'factor1' and
+   'factor2'.  But if that size cannot be represented as an unsigned int,
+   return NULL without allocating anything.  Also return NULL if the malloc
+   fails.
+
+   If either factor is zero, malloc a single byte.
+
+   Note that malloc() actually takes a size_t size argument, so the
+   proper test would be whether the size can be represented by size_t,
+   not unsigned int.  But there is no reliable indication available to
+   us, like UINT_MAX, of what the limitations of size_t are.  We
+   assume size_t is at least as expressive as unsigned int and that
+   nobody really needs to allocate more than 4GB of memory.
+-----------------------------------------------------------------------------*/
+    if (factor1 == 0 || factor2 == 0)
+        *resultP = malloc(1);
+    else {
+        if (UINT_MAX / factor2 < factor1) 
+            *resultP = NULL;
+        else 
+            *resultP = malloc(factor1 * factor2); 
+    }
+}
+
+
+
+static __inline__ void
+reallocProduct(void **      const blockP,
+               unsigned int const factor1,
+               unsigned int const factor2) {
+    
+    if (UINT_MAX / factor2 < factor1) 
+        *blockP = NULL;
+    else 
+        *blockP = realloc(*blockP, factor1 * factor2); 
+}
+
+
+
+#define MALLOCARRAY(arrayName, nElements) do { \
+    void * array; \
+    mallocProduct(&array, nElements, sizeof(arrayName[0])); \
+    arrayName = array; \
+} while (0)
+
+#define REALLOCARRAY(arrayName, nElements) { \
+    void * array; \
+    array = arrayName; \
+    reallocProduct(&array, nElements, sizeof(arrayName[0])); \
+    arrayName = array; \
+} while (0)
+
+
+#define MALLOCARRAY_NOFAIL(arrayName, nElements) \
+do { \
+    MALLOCARRAY(arrayName, nElements); \
+    if ((arrayName) == NULL) \
+        abort(); \
+} while(0)
+
+#define REALLOCARRAY_NOFAIL(arrayName, nElements) \
+do { \
+    REALLOCARRAY(arrayName, nElements); \
+    if ((arrayName) == NULL) \
+        abort(); \
+} while(0)
+
+
+#define MALLOCVAR(varName) \
+    varName = malloc(sizeof(*varName))
+
+#define MALLOCVAR_NOFAIL(varName) \
+    do {if ((varName = malloc(sizeof(*varName))) == NULL) abort();} while(0)
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
new file mode 100644
index 00000000..702a3c44
--- /dev/null
+++ b/lib/util/nstring.c
@@ -0,0 +1,906 @@
+/*
+ * snprintf.c - a portable implementation of snprintf
+ *
+
+   THIS MODULE WAS ADAPTED FOR NETPBM BY BRYAN HENDERSON ON 2002.03.24.
+   Bryan got the base from 
+   http://www.ijs.si/software/snprintf/snprintf-2.2.tar.gz, but made
+   a lot of changes and additions.
+
+ * AUTHOR
+ *   Mark Martinec <mark.martinec@ijs.si>, April 1999.
+ *
+ *   Copyright 1999, Mark Martinec. All rights reserved.
+ *
+ * TERMS AND CONDITIONS
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the "Frontier Artistic License" which comes
+ *   with this Kit.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty
+ *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *   See the Frontier Artistic License for more details.
+ *
+ *   You should have received a copy of the Frontier Artistic License
+ *   with this Kit in the file named LICENSE.txt .
+ *   If not, I'll be glad to provide one.
+ *
+ * FEATURES
+ * - careful adherence to specs regarding flags, field width and precision;
+ * - good performance for large string handling (large format, large
+ *   argument or large paddings). Performance is similar to system's sprintf
+ *   and in several cases significantly better (make sure you compile with
+ *   optimizations turned on, tell the compiler the code is strict ANSI
+ *   if necessary to give it more freedom for optimizations);
+ * - return value semantics per ISO/IEC 9899:1999 ("ISO C99");
+ * - written in standard ISO/ANSI C - requires an ANSI C compiler.
+ *
+ * IMPLEMENTED CONVERSION SPECIFIERS AND DATA TYPES
+ *
+ * This snprintf implements only the following conversion specifiers:
+ * s, c, d, u, o, x, X, p  (and synonyms: i, D, U, O - see below)
+ * with flags: '-', '+', ' ', '0' and '#'.
+ * An asterisk is acceptable for field width as well as precision.
+ *
+ * Length modifiers 'h' (short int), 'l' (long int),
+ * and 'll' (long long int) are implemented.
+ *
+ * Conversion of numeric data (conversion specifiers d, u, o, x, X, p)
+ * with length modifiers (none or h, l, ll) is left to the system routine
+ * sprintf, but all handling of flags, field width and precision as well as
+ * c and s conversions is done very carefully by this portable routine.
+ * If a string precision (truncation) is specified (e.g. %.8s) it is
+ * guaranteed the string beyond the specified precision will not be referenced.
+ *
+ * Length modifiers h, l and ll are ignored for c and s conversions (you
+ * can't use data types wint_t and wchar_t).
+ *
+ * The following common synonyms for conversion characters are acceptable:
+ *   - i is a synonym for d
+ *   - D is a synonym for ld, explicit length modifiers are ignored
+ *   - U is a synonym for lu, explicit length modifiers are ignored
+ *   - O is a synonym for lo, explicit length modifiers are ignored
+ * The D, O and U conversion characters are nonstandard, they are accepted
+ * for backward compatibility only, and should not be used for new code.
+ *
+ * The following is specifically NOT implemented:
+ *   - flag ' (thousands' grouping character) is recognized but ignored
+ *   - numeric conversion specifiers: f, e, E, g, G and synonym F,
+ *     as well as the new a and A conversion specifiers
+ *   - length modifier 'L' (long double) and 'q' (quad - use 'll' instead)
+ *   - wide character/string conversions: lc, ls, and nonstandard
+ *     synonyms C and S
+ *   - writeback of converted string length: conversion character n
+ *   - the n$ specification for direct reference to n-th argument
+ *   - locales
+ *
+ * It is permitted for str_m to be zero, and it is permitted to specify NULL
+ * pointer for resulting string argument if str_m is zero (as per ISO C99).
+ *
+ * The return value is the number of characters which would be generated
+ * for the given input, excluding the trailing null. If this value
+ * is greater or equal to str_m, not all characters from the result
+ * have been stored in str, output bytes beyond the (str_m-1) -th character
+ * are discarded. If str_m is greater than zero it is guaranteed
+ * the resulting string will be null-terminated.
+ *
+ * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1,
+ * but is different from some older and vendor implementations,
+ * and is also different from XPG, XSH5, SUSv2 specifications.
+ * For historical discussion on changes in the semantics and standards
+ * of snprintf see printf(3) man page in the Linux programmers manual.
+ *
+ * Routines asprintf and vasprintf return a pointer (in the ptr argument)
+ * to a buffer sufficiently large to hold the resulting string. This pointer
+ * should be passed to free(3) to release the allocated storage when it is
+ * no longer needed. If sufficient space cannot be allocated, these functions
+ * will return -1 and set ptr to be a NULL pointer. These two routines are a
+ * GNU C library extensions (glibc).
+ *
+ * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
+ * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
+ * characters into the allocated output string, the last character in the
+ * allocated buffer then gets the terminating null. If the formatted string
+ * length (the return value) is greater than or equal to the str_m argument,
+ * the resulting string was truncated and some of the formatted characters
+ * were discarded. These routines present a handy way to limit the amount
+ * of allocated memory to some sane value.
+ *
+ * AVAILABILITY
+ *   http://www.ijs.si/software/snprintf/
+ *
+ */
+
+
+#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
+#define PORTABLE_SNPRINTF_VERSION_MINOR 2
+
+#include <sys/types.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "pm.h"
+#include "pm_c_util.h"
+
+#include "nstring.h"
+
+#ifdef isdigit
+#undef isdigit
+#endif
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+/* For copying strings longer or equal to 'breakeven_point'
+ * it is more efficient to call memcpy() than to do it inline.
+ * The value depends mostly on the processor architecture,
+ * but also on the compiler and its optimization capabilities.
+ * The value is not critical, some small value greater than zero
+ * will be just fine if you don't care to squeeze every drop
+ * of performance out of the code.
+ *
+ * Small values favor memcpy, large values favor inline code.
+ */
+#if defined(__alpha__) || defined(__alpha)
+#  define breakeven_point   2	/* AXP (DEC Alpha)     - gcc or cc or egcs */
+#endif
+#if defined(__i386__)  || defined(__i386)
+#  define breakeven_point  12	/* Intel Pentium/Linux - gcc 2.96 */
+#endif
+#if defined(__hppa)
+#  define breakeven_point  10	/* HP-PA               - gcc */
+#endif
+#if defined(__sparc__) || defined(__sparc)
+#  define breakeven_point  33	/* Sun Sparc 5         - gcc 2.8.1 */
+#endif
+
+/* some other values of possible interest: */
+/* #define breakeven_point  8 */  /* VAX 4000          - vaxc */
+/* #define breakeven_point 19 */  /* VAX 4000          - gcc 2.7.0 */
+
+#ifndef breakeven_point
+#  define breakeven_point   6	/* some reasonable one-size-fits-all value */
+#endif
+
+#define fast_memcpy(d,s,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memcpy((d), (s), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const char *ss; \
+      for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
+
+#define fast_memset(d,c,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memset((d), (int)(c), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const int cc=(int)(c); \
+      for (dd=(d); nn>0; nn--) *dd++ = cc; } }
+
+/* declarations */
+
+static char credits[] = "\n\
+@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
+@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
+@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
+
+
+
+void
+vsnprintfN(char *       const str,
+           size_t       const str_m,
+           const char * const fmt,
+           va_list            ap,
+           size_t *     const sizeP) {
+
+    size_t str_l = 0;
+    const char *p = fmt;
+
+    /* In contrast with POSIX, the ISO C99 now says that str can be
+       NULL and str_m can be 0.  This is more useful than the old:
+       if (str_m < 1) return -1;
+    */
+
+    if (!p) p = "";
+    while (*p) {
+        if (*p != '%') {
+            /* if (str_l < str_m) str[str_l++] = *p++; -- this would
+               be sufficient but the following code achieves better
+               performance for cases * where format string is long and
+               contains few conversions
+            */
+            const char *q = strchr(p + 1,'%');
+            size_t n = !q ? strlen(p) : (q - p);
+            if (str_l < str_m) {
+                size_t avail = str_m - str_l;
+                fast_memcpy(str + str_l, p, (n > avail ? avail : n));
+            }
+            p += n; str_l += n;
+        } else {
+            const char *starting_p;
+            size_t min_field_width = 0, precision = 0;
+            int zero_padding = 0, precision_specified = 0, justify_left = 0;
+            int alternate_form = 0, force_sign = 0;
+            int space_for_positive = 1;
+                /* If both the ' ' and '+' flags appear,
+                   the ' ' flag should be ignored.
+                */
+            char length_modifier = '\0';  /* allowed values: \0, h, l, L */
+            char tmp[32];
+                /* temporary buffer for simple numeric->string conversion */
+
+            const char *str_arg;
+                /* string address in case of string argument */
+            size_t str_arg_l;
+                /* natural field width of arg without padding and sign */
+            unsigned char uchar_arg;
+                /* unsigned char argument value - only defined for c
+                   conversion.  N.B. standard explicitly states the char
+                   argument for the c conversion is unsigned.
+                */
+
+            size_t number_of_zeros_to_pad = 0;
+                /* number of zeros to be inserted for numeric
+                   conversions as required by the precision or minimal
+                   field width
+                */
+
+            size_t zero_padding_insertion_ind = 0;
+                /* index into tmp where zero padding is to be inserted */
+
+            char fmt_spec = '\0';
+                /* current conversion specifier character */
+
+            str_arg = credits;
+                /* just to make compiler happy (defined but not used) */
+            str_arg = NULL;
+            starting_p = p;
+            ++p;  /* skip '%' */
+
+            /* parse flags */
+            while (*p == '0' || *p == '-' || *p == '+' ||
+                   *p == ' ' || *p == '#' || *p == '\'') {
+                switch (*p) {
+                case '0': zero_padding = 1; break;
+                case '-': justify_left = 1; break;
+                case '+': force_sign = 1; space_for_positive = 0; break;
+                case ' ': force_sign = 1; break;
+                    /* If both the ' ' and '+' flags appear, the ' '
+                       flag should be ignored
+                    */
+                case '#': alternate_form = 1; break;
+                case '\'': break;
+                }
+                ++p;
+            }
+            /* If the '0' and '-' flags both appear, the '0' flag
+               should be ignored.
+            */
+
+            /* parse field width */
+            if (*p == '*') {
+                int j;
+                p++; j = va_arg(ap, int);
+                if (j >= 0) min_field_width = j;
+                else { min_field_width = -j; justify_left = 1; }
+            } else if (isdigit((int)(*p))) {
+                /* size_t could be wider than unsigned int; make sure
+                   we treat argument like common implementations do
+                */
+                unsigned int uj = *p++ - '0';
+                while (isdigit((int)(*p)))
+                    uj = 10*uj + (unsigned int)(*p++ - '0');
+                min_field_width = uj;
+            }
+            /* parse precision */
+            if (*p == '.') {
+                p++; precision_specified = 1;
+                if (*p == '*') {
+                    int j = va_arg(ap, int);
+                    p++;
+                    if (j >= 0) precision = j;
+                    else {
+                        precision_specified = 0; precision = 0;
+                        /* NOTE: Solaris 2.6 man page claims that in
+                           this case the precision should be set to 0.
+                           Digital Unix 4.0, HPUX 10 and BSD man page
+                           claim that this case should be treated as
+                           unspecified precision, which is what we do
+                           here.
+                        */
+                    }
+                } else if (isdigit((int)(*p))) {
+                    /* size_t could be wider than unsigned int; make
+                       sure we treat argument like common
+                       implementations do
+                    */
+                    unsigned int uj = *p++ - '0';
+                    while (isdigit((int)(*p)))
+                        uj = 10*uj + (unsigned int)(*p++ - '0');
+                    precision = uj;
+                }
+            }
+            /* parse 'h', 'l' and 'll' length modifiers */
+            if (*p == 'h' || *p == 'l') {
+                length_modifier = *p; p++;
+                if (length_modifier == 'l' && *p == 'l') {
+                    /* double l = long long */
+                    length_modifier = 'l';  /* treat it as a single 'l' */
+                    p++;
+                }
+            }
+            fmt_spec = *p;
+
+            /* common synonyms: */
+            switch (fmt_spec) {
+            case 'i': fmt_spec = 'd'; break;
+            case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+            case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+            case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+            default: break;
+            }
+            /* get parameter value, do initial processing */
+            switch (fmt_spec) {
+            case '%':
+                /* % behaves similar to 's' regarding flags and field widths */
+            case 'c':
+                /* c behaves similar to 's' regarding flags and field widths */
+            case 's':
+                /* wint_t and wchar_t not handled */
+                length_modifier = '\0';
+                /* the result of zero padding flag with non-numeric
+                    conversion specifier is undefined. Solaris and
+                    HPUX 10 does zero padding in this case, Digital
+                    Unix and Linux does not.
+                */
+
+                zero_padding = 0;
+                    /* turn zero padding off for string conversions */
+                str_arg_l = 1;
+                switch (fmt_spec) {
+                case '%':
+                    str_arg = p; break;
+                case 'c': {
+                    int j = va_arg(ap, int);
+                    uchar_arg = (unsigned char) j;
+                        /* standard demands unsigned char */
+                    str_arg = (const char *) &uchar_arg;
+                    break;
+                }
+                case 's':
+                    str_arg = va_arg(ap, const char *);
+                    if (!str_arg)
+                        /* make sure not to address string beyond the
+                           specified precision !!!
+                        */
+                        str_arg_l = 0;
+                    else if (!precision_specified)
+                        /* truncate string if necessary as requested by
+                           precision 
+                        */
+                        str_arg_l = strlen(str_arg);
+                    else if (precision == 0)
+                        str_arg_l = 0;
+                    else {
+                        /* memchr on HP does not like n > 2^31  !!! */
+                        const char * q =
+                            memchr(str_arg, '\0',
+                                   precision <= 0x7fffffff ?
+                                   precision : 0x7fffffff);
+                        str_arg_l = !q ? precision : (q-str_arg);
+                    }
+                    break;
+                default: break;
+                }
+                break;
+            case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
+                /* NOTE: the u, o, x, X and p conversion specifiers imply
+                   the value is unsigned;  d implies a signed value
+                */
+                int arg_sign = 0;
+                /* 0  if numeric argument is zero (or if pointer is NULL
+                      for 'p'),
+                   +1 if greater than zero (or nonzero for unsigned arguments),
+                   -1 if negative (unsigned argument is never negative)
+                */
+
+                int int_arg = 0;
+                unsigned int uint_arg = 0;
+                   /* defined only for length modifier h, or for no
+                      length modifiers
+                   */
+
+                long int long_arg = 0;  unsigned long int ulong_arg = 0;
+                /* only defined for length modifier l */
+
+                void *ptr_arg = NULL;
+                /* pointer argument value -only defined for p conversion */
+
+                if (fmt_spec == 'p') {
+                    /* HPUX 10: An l, h, ll or L before any other
+                        conversion character (other than d, i, u, o,
+                        x, or X) is ignored.
+
+                      Digital Unix: not specified, but seems to behave
+                      as HPUX does.
+
+                      Solaris: If an h, l, or L appears before any
+                      other conversion specifier (other than d, i, u,
+                      o, x, or X), the behavior is
+                      undefined. (Actually %hp converts only 16-bits
+                      of address and %llp treats address as 64-bit
+                      data which is incompatible with (void *)
+                      argument on a 32-bit system). 
+                    */
+
+                    length_modifier = '\0';
+                    ptr_arg = va_arg(ap, void *);
+                    if (ptr_arg != NULL) arg_sign = 1;
+                } else if (fmt_spec == 'd') {  /* signed */
+                    switch (length_modifier) {
+                    case '\0':
+                    case 'h':
+                        /* It is non-portable to specify a second
+                           argument of char or short to va_arg,
+                           because arguments seen by the called
+                           function are not char or short.  C converts
+                           char and short arguments to int before
+                           passing them to a function.
+                        */
+                        int_arg = va_arg(ap, int);
+                        if      (int_arg > 0) arg_sign =  1;
+                        else if (int_arg < 0) arg_sign = -1;
+                        break;
+                    case 'l':
+                        long_arg = va_arg(ap, long int);
+                        if      (long_arg > 0) arg_sign =  1;
+                        else if (long_arg < 0) arg_sign = -1;
+                        break;
+                    }
+                } else {  /* unsigned */
+                    switch (length_modifier) {
+                    case '\0':
+                    case 'h':
+                        uint_arg = va_arg(ap, unsigned int);
+                        if (uint_arg)
+                            arg_sign = 1;
+                        break;
+                    case 'l':
+                        ulong_arg = va_arg(ap, unsigned long int);
+                        if (ulong_arg)
+                            arg_sign = 1;
+                        break;
+                    }
+                }
+                str_arg = tmp; str_arg_l = 0;
+                /* NOTE: For d, i, u, o, x, and X conversions, if
+                   precision is specified, the '0' flag should be
+                   ignored. This is so with Solaris 2.6, Digital UNIX
+                   4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with
+                   Perl.
+                */
+                if (precision_specified)
+                    zero_padding = 0;
+                if (fmt_spec == 'd') {
+                    if (force_sign && arg_sign >= 0)
+                        tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+                    /* leave negative numbers for sprintf to handle,
+                       to avoid handling tricky cases like (short
+                       int)(-32768)
+                    */
+                } else if (alternate_form) {
+                    if (arg_sign != 0 && (fmt_spec == 'x' ||
+                                          fmt_spec == 'X')) {
+                        tmp[str_arg_l++] = '0';
+                        tmp[str_arg_l++] = fmt_spec;
+                    }
+                    /* alternate form should have no effect for p
+                       conversion, but ...
+                    */
+                }
+                zero_padding_insertion_ind = str_arg_l;
+                if (!precision_specified)
+                    precision = 1;   /* default precision is 1 */
+                if (precision == 0 && arg_sign == 0) {
+                    /* converted to null string */
+                    /* When zero value is formatted with an explicit
+                       precision 0, the resulting formatted string is
+                       empty (d, i, u, o, x, X, p).
+                    */
+                } else {
+                    char f[5]; int f_l = 0;
+                    f[f_l++] = '%';
+                        /* construct a simple format string for sprintf */
+                    if (!length_modifier) { }
+                    else if (length_modifier=='2') {
+                        f[f_l++] = 'l'; f[f_l++] = 'l';
+                    }
+                    else
+                        f[f_l++] = length_modifier;
+                    f[f_l++] = fmt_spec; f[f_l++] = '\0';
+                    if (fmt_spec == 'p')
+                        str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
+                    else if (fmt_spec == 'd') {  /* signed */
+                        switch (length_modifier) {
+                        case '\0':
+                        case 'h':
+                            str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg);
+                            break;
+                        case 'l':
+                            str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg);
+                            break;
+                        }
+                    } else {  /* unsigned */
+                        switch (length_modifier) {
+                        case '\0':
+                        case 'h':
+                            str_arg_l += sprintf(tmp+str_arg_l, f, uint_arg);
+                            break;
+                        case 'l':
+                            str_arg_l += sprintf(tmp+str_arg_l, f, ulong_arg);
+                            break;
+                        }
+                    }
+                    /* include the optional minus sign and possible "0x"
+                       in the region before the zero padding insertion point
+                    */
+                    if (zero_padding_insertion_ind < str_arg_l &&
+                        tmp[zero_padding_insertion_ind] == '-') {
+                        zero_padding_insertion_ind++;
+                    }
+                    if (zero_padding_insertion_ind+1 < str_arg_l &&
+                        tmp[zero_padding_insertion_ind]   == '0' &&
+                        (tmp[zero_padding_insertion_ind+1] == 'x' ||
+                         tmp[zero_padding_insertion_ind+1] == 'X') ) {
+                        zero_padding_insertion_ind += 2;
+                    }
+                }
+                {
+                    size_t num_of_digits =
+                        str_arg_l - zero_padding_insertion_ind;
+                    if (alternate_form && fmt_spec == 'o'
+                        /* unless zero is already the first character */
+                        && !(zero_padding_insertion_ind < str_arg_l
+                             && tmp[zero_padding_insertion_ind] == '0')) {
+                        /* assure leading zero for alternate-form
+                           octal numbers 
+                        */
+                        if (!precision_specified ||
+                            precision < num_of_digits+1) {
+                            /* precision is increased to force the
+                               first character to be zero, except if a
+                               zero value is formatted with an
+                               explicit precision of zero
+                            */
+                            precision = num_of_digits+1;
+                            precision_specified = 1;
+                        }
+                    }
+                    /* zero padding to specified precision? */
+                    if (num_of_digits < precision) 
+                        number_of_zeros_to_pad = precision - num_of_digits;
+                }
+                /* zero padding to specified minimal field width? */
+                if (!justify_left && zero_padding) {
+                    int n =
+                        min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                    if (n > 0) number_of_zeros_to_pad += n;
+                }
+            } break;
+            default:
+                /* unrecognized conversion specifier, keep format
+                   string as-is
+                */
+                zero_padding = 0;
+                    /* turn zero padding off for non-numeric convers. */
+                /* reset flags */
+                justify_left = 1;
+                min_field_width = 0;
+                /* discard the unrecognized conversion, just keep the
+                   unrecognized conversion character
+                */
+                str_arg = p;
+                str_arg_l = 0;
+                if (*p)
+                    /* include invalid conversion specifier unchanged
+                       if not at end-of-string
+                    */
+                    ++str_arg_l;
+                break;
+            }
+            if (*p)
+                p++;  /* step over the just processed conversion specifier */
+            /* insert padding to the left as requested by
+               min_field_width; this does not include the zero padding
+               in case of numerical conversions
+            */
+
+            if (!justify_left) {
+                /* left padding with blank or zero */
+                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memset(str+str_l, (zero_padding ? '0' : ' '),
+                                    (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* zero padding as requested by the precision or by the
+               minimal field width for numeric conversions required?
+            */
+            if (number_of_zeros_to_pad <= 0) {
+                /* will not copy first part of numeric right now,
+                   force it to be copied later in its entirety
+                */
+                zero_padding_insertion_ind = 0;
+            } else {
+                /* insert first part of numerics (sign or '0x') before
+                   zero padding
+                */
+                int n = zero_padding_insertion_ind;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
+                    }
+                    str_l += n;
+                }
+                /* insert zero padding as requested by the precision
+                   or min field width
+                */
+                n = number_of_zeros_to_pad;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m - str_l;
+                        fast_memset(str + str_l, '0', (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* insert formatted string (or as-is conversion specifier
+               for unknown conversions)
+            */
+            {
+                int n = str_arg_l - zero_padding_insertion_ind;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memcpy(str + str_l,
+                                    str_arg + zero_padding_insertion_ind,
+                                    (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* insert right padding */
+            if (justify_left) {
+                /* right blank padding to the field width */
+                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memset(str+str_l, ' ', (n>avail?avail:n));
+                    }
+                    str_l += n;
+                }
+            }
+        }
+    }
+    if (str_m > 0) {
+        /* make sure the string is null-terminated even at the expense
+           of overwriting the last character (shouldn't happen, but
+           just in case)
+        */
+        str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+    }
+    *sizeP = str_l;
+}
+
+
+
+int
+snprintfN(char *       const dest,
+          size_t       const str_m,
+          const char * const fmt,
+          ...) {
+
+    size_t size;
+    va_list ap;
+
+    va_start(ap, fmt);
+    
+    vsnprintfN(dest, str_m, fmt, ap, &size);
+
+    va_end(ap);
+
+    assert(size <= INT_MAX);
+
+    return size;
+}
+
+
+
+/* When a function that is supposed to return a malloc'ed string cannot
+   get the memory for it, it should return 'strsol'.  That has a much
+   better effect on the caller, if the caller doesn't explicitly allow for
+   the out of memory case, than returning NULL.  Note that it is very
+   rare for the system not to have enough memory to return a small string,
+   so it's OK to have somewhat nonsensical behavior when it happens.  We
+   just don't want catastrophic behavior.
+
+   'strsol' is an external symbol, so if Caller wants to detect the
+   out-of-memory failure, he certainly can.
+*/
+const char * const strsol = "NO MEMORY TO CREATE STRING!";
+
+
+
+/* We would like to have vasprintfN(), but it is difficult because you
+   can't run through a va_list twice, which we would want to do: once
+   to measure the length; once actually to build the string.  On some
+   machines, you can simply make two copies of the va_list variable in
+   normal C fashion, but on others you need va_copy, which is a
+   relatively recent invention.  In particular, the simple va_list copy
+   failed on an AMD64 Gcc Linux system in March 2006.
+*/
+
+void PM_GNU_PRINTF_ATTR(2,3)
+asprintfN(const char ** const resultP,
+          const char *  const fmt, 
+          ...) {
+
+    va_list varargs;
+    
+    size_t dryRunLen;
+    
+    va_start(varargs, fmt);
+    
+    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+
+    va_end(varargs);
+
+    if (dryRunLen + 1 < dryRunLen)
+        /* arithmetic overflow */
+        *resultP = strsol;
+    else {
+        size_t const allocSize = dryRunLen + 1;
+        char * result;
+        result = malloc(allocSize);
+        if (result == NULL)
+            *resultP = strsol;
+        else {
+            va_list varargs;
+            size_t realLen;
+
+            va_start(varargs, fmt);
+
+            vsnprintfN(result, allocSize, fmt, varargs, &realLen);
+                
+            assert(realLen == dryRunLen);
+            va_end(varargs);
+
+            *resultP = result;
+        }
+    }
+}
+
+
+
+void
+strfree(const char * const string) {
+
+    if (string != strsol)
+        free((void *) string);
+}
+
+
+
+const char *
+strsepN(char ** const stringP, const char * const delim) {
+    const char * retval;   
+
+    if (stringP == NULL || *stringP == NULL)
+        retval = NULL;
+    else {
+        char * p;
+
+        retval = *stringP;
+
+        for (p = *stringP; *p && strchr(delim, *p) == NULL; ++p);
+ 
+        if (*p) {
+            /* We hit a delimiter, not end-of-string.  So null out the 
+               delimiter and advance user's pointer to the next token
+            */
+            *p++ = '\0';
+            *stringP = p;
+        } else {
+            /* We ran out of string.  So the end-of-string delimiter is 
+               already there, and we set the user's pointer to NULL to 
+               indicate there are no more tokens.
+            */
+            *stringP = NULL;
+        }
+    }
+    return retval;
+}
+
+
+
+int
+stripeq(const char * const comparand,
+        const char * const comparator) {
+/*----------------------------------------------------------------------------
+  Compare two strings, ignoring leading and trailing white space.
+
+  Return 1 (true) if the strings are identical; 0 (false) otherwise.
+-----------------------------------------------------------------------------*/
+    char *p, *q, *px, *qx;
+    char equal;
+  
+    /* Make p and q point to the first non-blank character in each string.
+     If there are no non-blank characters, make them point to the terminating
+     NULL.
+     */
+
+    p = (char *) comparand;
+    while (ISSPACE(*p)) p++;
+    q = (char *) comparator;
+    while (ISSPACE(*q)) q++;
+
+    /* Make px and qx point to the last non-blank character in each string.
+       If there are no nonblank characters (which implies the string is
+       null), make them point to the terminating NULL.
+    */
+
+    if (*p == '\0') px = p;
+    else {
+        px = p + strlen(p) - 1;
+        while (ISSPACE(*px)) px--;
+    }
+
+    if (*q == '\0') qx = q;
+    else {
+        qx = q + strlen(q) - 1;
+        while (ISSPACE(*qx)) qx--;
+    }
+
+    equal = 1;   /* initial assumption */
+  
+    /* If the stripped strings aren't the same length, 
+       we know they aren't equal 
+     */
+    if (px - p != qx - q) equal = 0;
+
+
+    while (p <= px) {
+        if (*p != *q) equal = 0;
+        p++; q++;
+    }
+    return equal;
+}
+
+
+
+const char *
+memmemN(const char * const haystack,
+        size_t       const haystacklen,
+        const char * const needle,
+        size_t       const needlelen) {
+
+    /* This does the same as the function of the same name in the GNU
+       C library
+    */
+    const char * p;
+
+    for (p = haystack; p <= haystack + haystacklen - needlelen; ++p)
+        if (MEMEQ(p, needle, needlelen))
+            return p;
+
+    return NULL;
+}
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
new file mode 100644
index 00000000..9ed20051
--- /dev/null
+++ b/lib/util/nstring.h
@@ -0,0 +1,157 @@
+#ifndef _NSTRING_H
+#define _NSTRING_H
+
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "pm.h"  /* For PM_GNU_PRINTF_ATTR, __inline__ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* Here is are string functions that respect the size of the array
+   into which you are copying -- E.g. STRSCPY truncates the source string as
+   required so that it fits, with the terminating null, in the destination
+   array.
+*/
+#define STRSCPY(A,B) \
+	(strncpy((A), (B), sizeof(A)), *((A)+sizeof(A)-1) = '\0')
+#define STRSCMP(A,B) \
+	(strncmp((A), (B), sizeof(A)))
+#define STRSCAT(A,B) \
+    (strncpy(A+strlen(A), B, sizeof(A)-strlen(A)), *((A)+sizeof(A)-1) = '\0')
+
+#define STREQ(A, B) \
+    (strcmp((A), (B)) == 0)
+#define STRNEQ(A, B, C) \
+    (strncmp((A), (B), (C)) == 0)
+#define STRCASEEQ(A, B) \
+    (strcasecmp((A), (B)) == 0)
+#define STRNCASEEQ(A, B, C) \
+    (strncasecmp((A), (B), (C)) == 0)
+#define STRSEQ(A, B) \
+	(strncmp((A), (B), sizeof(A)) == 0)
+
+#define MEMEQ(A, B, C) \
+    (memcmp((A), (B), (C)) == 0)
+#define MEMSZERO(A) \
+    bzero((A), sizeof(A))
+
+
+static __inline__ int
+streq(const char * const comparand,
+      const char * const comparator) {
+
+    return strcmp(comparand, comparator) == 0;
+}
+
+
+
+/* The standard C library routines isdigit(), for some weird 
+   historical reason, does not take a character (type 'char') as its
+   argument.  Instead it takes an integer.  When the integer is a whole
+   number, it represents a character in the obvious way using the local
+   character set encoding.  When the integer is negative, the results
+   are undefined.
+
+   Passing a character to isdigit(), which expects an integer, results in
+   isdigit() sometimes getting a negative number.
+
+   On some systems, when the integer is negative, it represents exactly
+   the character you want it to anyway (e.g. -1 is the character that is
+   encoded 0xFF).  But on others, it does not.
+
+   (The same is true of other routines like isdigit()).
+
+   Therefore, we have the substitutes for isdigit() etc. that take an
+   actual character (type 'char') as an argument.
+*/
+
+#define ISALNUM(C) (isalnum((unsigned char)(C)))
+#define ISALPHA(C) (isalpha((unsigned char)(C)))
+#define ISCNTRL(C) (iscntrl((unsigned char)(C)))
+#define ISDIGIT(C) (isdigit((unsigned char)(C)))
+#define ISGRAPH(C) (isgraph((unsigned char)(C)))
+#define ISLOWER(C) (islower((unsigned char)(C)))
+#define ISPRINT(C) (isprint((unsigned char)(C)))
+#define ISPUNCT(C) (ispunct((unsigned char)(C)))
+#define ISSPACE(C) (isspace((unsigned char)(C)))
+#define ISUPPER(C) (isupper((unsigned char)(C)))
+#define ISXDIGIT(C) (isxdigit((unsigned char)(C)))
+#define TOUPPER(C) ((char)toupper((unsigned char)(C)))
+
+
+/* These are all private versions of commonly available standard C
+   library subroutines whose names are the same except with the N at
+   the end.  Because not all standard C libraries have them all,
+   Netpbm must include them in its own libraries, and because some
+   standard C libraries have some of them, Netpbm must use different
+   names for them.
+   
+   The GNU C library has all of them.  All but the oldest standard C libraries
+   have snprintf().
+
+   There are slight differences between the asprintf() family and that
+   found in other libraries:
+
+     - There is no return value.
+
+     - The returned string is a const char * instead of a char *.  The
+       const is more correct.
+
+     - If the function can't get the memory, it returns 'strsol',
+       which is a string that is in static memory that contains text
+       indicating an out of memory failure has occurred, intead of
+       NULL.  This makes it much easier for programs to ignore this
+       possibility.
+
+   strfree() is strictly a Netpbm invention, to allow proper type checking
+   when freeing storage allocated by the Netpbm asprintfN().
+*/
+
+extern const char * const strsol;
+
+int
+snprintfN(char *       const dest,
+          size_t       const str_m,
+          const char * const fmt,
+          ...) PM_GNU_PRINTF_ATTR(3,4);
+
+void
+vsnprintfN(char *       const str,
+           size_t       const str_m,
+           const char * const fmt,
+           va_list            ap,
+           size_t *     const sizeP);
+
+void
+asprintfN(const char ** const resultP,
+          const char *  const fmt,
+          ...) PM_GNU_PRINTF_ATTR(2,3);
+
+void 
+strfree(const char * const string);
+
+const char *
+strsepN(char ** const stringP, const char * const delim);
+
+int
+stripeq(const char * const comparand,
+        const char * const comparator);
+
+const char *
+memmemN(const char * const haystack,
+        size_t       const haystacklen,
+        const char * const needle,
+        size_t       const needlelen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h
new file mode 100644
index 00000000..f21a2f82
--- /dev/null
+++ b/lib/util/pm_c_util.h
@@ -0,0 +1,62 @@
+#ifndef PM_C_UTIL_INCLUDED
+#define PM_C_UTIL_INCLUDED
+
+#undef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#undef ABS
+#define ABS(a) ((a) >= 0 ? (a) : -(a))
+#undef SGN
+#define SGN(a)		(((a)<0) ? -1 : 1)
+#undef ODD
+#define ODD(n) ((n) & 1)
+#undef ROUND
+#define ROUND(X) (((X) >= 0) ? (int)((X)+0.5) : (int)((X)-0.5))
+#undef ROUNDU
+#define ROUNDU(X) ((unsigned int)((X)+0.5))
+#undef SQR
+#define SQR(a) ((a)*(a))
+
+/* NOTE: do not use "bool" as a type in an external interface.  It could
+   have different definitions on either side of the interface.  Even if both
+   sides include this interface header file, the conditional compilation
+   here means one side may use the typedef below and the other side may
+   use some other definition.  For an external interface, be safe and just
+   use "int".
+*/
+
+/* We used to assume that if TRUE was defined, then bool was too.
+   However, we had a report on 2001.09.21 of a Tru64 system that had
+   TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was
+   likewise.  So now we define bool all the time, unless the macro
+   HAVE_BOOL is defined.  If someone is using the Netpbm libraries and
+   also another library that defines bool, he can either make the
+   other library define/respect HAVE_BOOL or just define HAVE_BOOL in
+   the file that includes pm_config.h or with a compiler option.  Note
+   that C++ always has bool.  
+
+   A preferred way of getting booleans is <stdbool.h>.  But it's not
+   available on all platforms, and it's easy to reproduce what it does
+   here.
+*/
+#ifndef TRUE
+  #define TRUE 1
+  #endif
+#ifndef FALSE
+  #define FALSE 0
+  #endif
+/* C++ has a bool type and false and true constants built in. */
+#ifndef __cplusplus
+  #ifndef HAVE_BOOL
+    #define HAVE_BOOL 1
+    typedef int bool;
+    #endif
+  #ifndef true
+    enum boolvalue {false=0, true=1};
+    #endif
+  #endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+#endif
diff --git a/lib/util/shhopt-1.1.6.lsm b/lib/util/shhopt-1.1.6.lsm
new file mode 100644
index 00000000..7fba316e
--- /dev/null
+++ b/lib/util/shhopt-1.1.6.lsm
@@ -0,0 +1,16 @@
+Begin3
+Title:		shhopt - library for parsing command line options.
+Version:	1.1.6
+Entered-date:	10MAR00
+Description:	C-functions for parsing command line options, both
+                traditional one-character options, and GNU'ish
+                --long-options.
+Keywords:	programming, library, lib, commandline, options
+Author: 	sverrehu@online.no (Sverre H. Huseby)
+Primary-site:	http://home.sol.no/~sverrehu/pub-unix/
+Alternate-site:	sunsite.unc.edu /pub/Linux/libs
+		shhopt-1.1.6.tar.gz
+Platforms:	Requires ANSI C-compiler.
+Copying-policy:	Artistic License
+		http://www.opensource.org/licenses/artistic-license.html
+End
diff --git a/lib/util/shhopt.README b/lib/util/shhopt.README
new file mode 100644
index 00000000..2d241edf
--- /dev/null
+++ b/lib/util/shhopt.README
@@ -0,0 +1,200 @@
+Shhopt was originally written by Sverre H. Huseby, and extended by
+Bryan Henderson starting in March 2000 for use in Netpbm.  
+
+Below is the README file from Huseby's package.
+
+The file LICENSE.TXT in this directory contains the license (the
+Artistic License) under which Bryan took and redistributed Shhopt and
+the license under which Bryan offers the modified Shhopt to others.
+
+Bryan made the following changes to shhopt for Netpbm.  It is fully
+backward compatible with the original.
+
+- OPT_FLOAT (floating point number) data type added
+
+- optParseOptions2() added.  Advantages over optParseOptions(): You
+  can have a syntax where there is no such thing as a short option
+  (e.g. -a.  Maybe stacked like -tanp).  Then the long options can
+  have either 1 or 2 dashes (e.g. -width or --width).  Of course, -w
+  could be an abbreviation of -width; that's not the same thing as a
+  short option.
+
+- optParseOptions3() added.  Advantages over optParseOptions2(): 
+  Tells you whether (how many times, actually) an option was
+  specified - no need to play games with defaults.  Also, no need
+  to initialize an option value variable.
+
+- optStruct longName changed from char * to const char * to avoid
+  compiler warnings (with -Wwrite-strings) when you assign a string
+  literal to it (which is the normal case).
+
+- OPTENTRY/OPTENT3 macros added for declaring the option definition
+  array.
+
+- replace isdigit() with ISDIGIT() from Netpbm nstring.h so weird 
+  8-bit characters don't cause incorrect results.
+
+------------------------------------------------------------------------------
+
+
+shhopt - library for parsing command line options.
+==================================================
+
+This is a set of functions for parsing command line options. Both
+traditional one-character options, and GNU-style --long-options are
+supported.
+
+
+What separates this from traditional getopt?
+--------------------------------------------
+
+This library does more of the parsing for you. You set up a special
+structure describing the names and types of the options you want your
+program to support. In the structure you also give addresses of
+variables to update or functions to call for the various
+options. By calling optParseOptions, all options in argv are parsed
+and removed from argv. What is left, are the non-optional arguments to
+your program.
+
+The down-side of this, is that you won't be able to make a program
+where the position of the options between the non-options are
+significant.
+
+shhopt is distributed under the "Artistic license" (aka. the Perl
+license), which IMHO gives more freedom than GPL or LGPL. For a copy
+of the Artistic license, see
+
+    http://www.opensource.org/licenses/artistic-license.html
+
+For more information on Open Source licenses, go to
+
+    http://www.opensource.org/licenses/
+
+
+Usage
+-----
+
+To see how to use this library, take a look at the sample program
+example.c.
+
+A brief explanation:
+
+To parse your command line, you need to create and initialize an array
+of optStruct's. Each element in the array describes a long and short
+version of an option and specifies what type of option it is and how
+to handle it.
+
+The structure fields (see also shhopt.h):
+
+  `shortName' is the short option name without the leading '-'.
+
+  `longName' is the long option name without the leading "--".
+
+  `type' specifies what type of option this is. (Does it expect an
+      argument? Is it a flag? If it takes an argument, what type
+      should it be?)
+
+  `arg' is either a function to be called with the argument from
+      the commandline, or a pointer to a location in which to store
+      the value.
+
+  `flags' indicates whether `arg' points to a function or a storage
+      location.
+
+The different argument types:
+
+  `OPT_END' flags this as the last element in the options array.
+
+  `OPT_FLAG' indicates an option that takes no arguments. If `arg' is
+      not a function pointer, the value of `arg' will be set to 1 if
+      this flag is found on the command line.
+
+  `OPT_STRING' expects a string argument.
+
+  `OPT_INT' expects an int argument.
+
+  `OPT_UINT' expects an unsigned int argument.
+
+  `OPT_LONG' expects a long argument.
+
+  `OPT_ULONG' expects an unsigned long argument.
+
+The different flag types:
+
+  `OPT_CALLFUNC' indicates that `arg' is a function pointer. If this
+      is not given, `arg' is taken as a pointer to a variable.
+
+
+Notes
+-----
+
+* A dash (`-') by itself is not taken as any kind of an option, as
+  several programs use this to indicate the special files stdin and
+  stdout. It is thus left as a normal argument to the program.
+
+* Two dashes (`--') as an argument, is taken to mean that the rest of
+  the arguments should not be scanned for options. This simplifies
+  giving names of files that start with a dash.
+
+* Short (one-character) options accept parameters in two ways, either
+  directly following the option in the same argv-entry, or in the next
+  argv-entry:
+
+	-sPARAMETER
+	-s PARAMETER
+
+* Long options accept parameters in two ways:
+
+	--long-option=PARAMETER
+	--long-option PARAMETER
+
+  To follow the GNU-tradition, your program documentation should use
+  the first form.
+
+* Several one-character options may be combined after a single
+  dash. If any of the options requires a parameter, the rest of the
+  string is taken as this parameter. If there is no "rest of the
+  string", the next argument is taken as the parameter.
+
+* There is no support for floating point (double) arguments to
+  options. This is to avoid unnecessary linking with the math
+  library. See example.c for how to get around this by writing a
+  function converting a string argument to a double (functions
+  strToDouble and doubleFunc).
+
+
+Portability
+-----------
+
+If your libc lacks strtoul, you will need to link with GNU's -liberty,
+that may be found by anonymous ftp to ftp://ftp.gnu.org/pub/gnu/
+
+The library has (more or less recently) been compiled and `tested' on
+the following systems:
+
+	IRIX Release 5.3 IP22
+	GNU/Linux 2.2.11
+	SunOS Release 4.1.3_U1 (-liberty needed)
+	ULTRIX V4.4 (Rev. 69)
+
+All compilations were done using GNU's gcc, and GNU's make.
+
+
+Author
+------
+
+The program is written by
+
+        Sverre H. Huseby        sverrehu@online.no
+        Lofthusvn. 11 B         http://home.sol.no/~sverrehu/
+        N-0587 Oslo
+        Norway
+
+You can use and copy this for _free_, but I would be very happy if you
+send me an E-mail and tell me that you use it. If you insist on paying
+something, please donate some money to an organization that strives to
+make the world a better place for everyone.
+
+I don't like bugs, so please help me removing them by reporting
+whatever you find!
+
diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c
new file mode 100644
index 00000000..7722b5d5
--- /dev/null
+++ b/lib/util/shhopt.c
@@ -0,0 +1,939 @@
+/*------------------------------------------------------------------------
+ |  FILE            shhopt.c
+ |
+ |  DESCRIPTION     Functions for parsing command line arguments. Values
+ |                  of miscellaneous types may be stored in variables,
+ |                  or passed to functions as specified.
+ |
+ |  REQUIREMENTS    Some systems lack the ANSI C -function strtoul. If your
+ |                  system is one of those, you'll ned to write one yourself,
+ |                  or get the GNU liberty-library (from prep.ai.mit.edu).
+ |
+ |  WRITTEN BY      Sverre H. Huseby <sverrehu@online.no>
+ +----------------------------------------------------------------------*/
+
+/*************************************************************************
+  This is based on work by Sverre H. Huseby <sverrehu@online.no>.
+  These functions are backward compatible with the 'shhopt'
+  distributed by Huseby.
+
+  See the file README.shhopt for copy licensing information.
+*************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+/*-----------------------------------------------------------------------+
+|  PRIVATE DATA                                                          |
++-----------------------------------------------------------------------*/
+
+static void optFatalFunc(const char *, ...);
+static void (*optFatal)(const char *format, ...) = optFatalFunc;
+
+/*-----------------------------------------------------------------------+
+|  PRIVATE FUNCTIONS                                                     |
++-----------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------
+ |  NAME          optFatalFunc
+ |
+ |  FUNCTION      Show given message and abort the program.
+ |
+ |  INPUT         format, ...
+ |                        Arguments used as with printf().
+ |
+ |  RETURNS       Never returns. The program is aborted.
+ */
+static void
+optFatalFunc(const char *format, ...)
+{
+    va_list ap;
+
+    fflush(stdout);
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    exit(99);
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optStructCount
+ |
+ |  FUNCTION      Get number of options in a optStruct.
+ |
+ |  INPUT         opt     array of possible options.
+ |
+ |  RETURNS       Number of options in the given array.
+ |
+ |  DESCRIPTION   Count elements in an optStruct-array. The strcture must
+ |                be ended using an element of type OPT_END.
+ */
+static int
+optStructCount(const optEntry opt[])
+{
+    int ret = 0;
+
+    while (opt[ret].type != OPT_END && ret < 500)
+        ++ret;
+    return ret;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optMatch
+ |
+ |  FUNCTION      Find a matching option.
+ |
+ |  INPUT         opt     array of possible options.
+ |                s       string to match, without `-' or `--'.
+ |                lng     match long option, otherwise short.
+ |
+ |  RETURNS       Index to the option if found, -1 if not found.
+ |
+ |  DESCRIPTION   Short options are matched from the first character in
+ |                the given string.
+ */
+static int
+optMatch(const optEntry opt[], const char *s, int lng)
+{
+    int        nopt, q, matchlen = 0;
+    const char *p;
+
+    nopt = optStructCount(opt);
+    if (lng) {
+        if ((p = strchr(s, '=')) != NULL)
+            matchlen = p - s;
+        else
+            matchlen = strlen(s);
+    }
+    for (q = 0; q < nopt; q++) {
+        if (lng) {
+            if (!opt[q].longName)
+                continue;
+            if (strncmp(s, opt[q].longName, matchlen) == 0)
+                return q;
+        } else {
+            if (!opt[q].shortName)
+                continue;
+            if (*s == opt[q].shortName)
+                return q;
+        }
+    }
+    return -1;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optString
+ |
+ |  FUNCTION      Return a (static) string with the option name.
+ |
+ |  INPUT         opt     the option to stringify.
+ |                lng     is it a long option?
+ |
+ |  RETURNS       Pointer to static string.
+ */
+static char *
+optString(const optEntry opte, int lng)
+{
+    static char ret[31];
+
+    if (lng) {
+        strcpy(ret, "--");
+        strncpy(ret + 2, opte.longName, 28);
+    } else {
+        ret[0] = '-';
+        ret[1] = opte.shortName;
+        ret[2] = '\0';
+    }
+    return ret;
+}
+
+
+    
+static optEntry
+optStructToEntry(const optStruct opt) {
+/*----------------------------------------------------------------------------
+   Return the information in 'opt' (an optStruct type) as an optEntry type.
+   optEntry is newer and has an additional field.
+-----------------------------------------------------------------------------*/
+    optEntry opte;
+
+    opte.shortName = opt.shortName;
+    opte.longName  = opt.longName;
+    opte.type      = opt.type;
+    opte.arg       = opt.arg;
+    opte.specified = NULL;
+    opte.flags     = opt.flags;
+
+    return(opte);
+}
+
+
+
+static optEntry *
+optStructTblToEntryTbl(const optStruct optStructTable[]) {
+/*----------------------------------------------------------------------------
+   Return a table of optEntry types containing the information in the
+   input table of optStruct types.
+
+   Return it in newly malloc'ed storage.
+-----------------------------------------------------------------------------*/
+    int count;
+        /* Number of entries in input table, including OPT_END marker */
+    int i;
+
+    optEntry *optEntryTable;  /* malloc'ed array */
+    
+    /* Count the entries in optStructTable[] */
+    for (i = 0; optStructTable[i].type != OPT_END && i < 500; i++);
+    count = i+1;
+
+    optEntryTable = (optEntry *) malloc(count * sizeof(optEntry));
+    if (optEntryTable) {
+        int i;
+        for (i = 0; i < count; i++) 
+            optEntryTable[i] = optStructToEntry(optStructTable[i]);
+    }
+    return(optEntryTable);
+}
+
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optNeedsArgument
+ |
+ |  FUNCTION      Check if an option requires an argument.
+ |
+ |  INPUT         opt     the option to check.
+ |
+ |  RETURNS       Boolean value.
+ */
+static int
+optNeedsArgument(const optEntry opt)
+{
+    return opt.type == OPT_STRING
+	|| opt.type == OPT_INT
+	|| opt.type == OPT_UINT
+	|| opt.type == OPT_LONG
+	|| opt.type == OPT_ULONG
+    || opt.type == OPT_FLOAT
+    || opt.type == OPT_NAMELIST
+        ;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          argvRemove
+ |
+ |  FUNCTION      Remove an entry from an argv-array.
+ |
+ |  INPUT         argc    pointer to number of options.
+ |                argv    array of option-/argument-strings.
+ |                i       index of option to remove.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with given argument removed.
+ */
+static void
+argvRemove(int *argc, char *argv[], int i)
+{
+    if (i >= *argc)
+        return;
+    while (i++ < *argc)
+        argv[i - 1] = argv[i];
+    --*argc;
+}
+
+
+
+static void
+getToken(const char *  const tokenStart,
+         char          const delimiter,
+         const char ** const tokenP,
+         const char ** const nextP) {
+/*----------------------------------------------------------------------------
+   Find the token starting at 'tokenStart' up to but not including
+   the first 'delimiter' character or end of string.  Return it in newly
+   malloced memory as *tokenP, NUL-terminated.
+
+   Make *nextP point just past the token, i.e. to the delimiter or
+   end of string NUL character.
+
+   Note that if the string is empty, or starts with the delimiter,
+   we return an empty string and *nextP == tokenStart, i.e. *nextP
+   doesn't necessarily advance.
+-----------------------------------------------------------------------------*/
+    char * token;
+    const char * cursor;
+    unsigned int charCount;
+
+    /* Run through the token, counting characters */
+
+    charCount = 0;
+    cursor = tokenStart;
+
+    while (*cursor != delimiter && *cursor != '\0') {
+        if (*cursor == '\\') {
+            ++cursor;
+            if (*cursor == '\0')
+                optFatal("string ends with an escape character (\\)");
+        }
+        ++cursor;
+        ++charCount;
+    }
+    
+    token = malloc(charCount + 1);
+    if (token == NULL)
+        optFatal("Could not allocate %u bytes of memory to parse a string",
+                 charCount + 1);
+
+    /* Go back and do it again, this time copying the characters */
+    charCount = 0;
+    cursor = tokenStart;
+
+    while (*cursor != delimiter && *cursor != '\0') {
+        if (*cursor == '\\')
+            ++cursor;
+
+        assert(*cursor != '\0');
+
+        token[charCount++] = *cursor++;
+    }
+    token[charCount] = '\0';
+
+    *tokenP = token;
+    *nextP = cursor;
+}
+
+
+
+static void
+parseNameList(const char *           const listText,
+              struct optNameValue ** const listP) {
+
+    unsigned int const maxOptionCount = 100;
+
+    const char * cursor;
+    unsigned int optionCount;
+    struct optNameValue * list;
+
+    MALLOCARRAY_NOFAIL(list, maxOptionCount+1);
+
+    cursor = &listText[0];  /* initial value */
+
+    optionCount = 0;  /* initial value */
+
+    while (optionCount < maxOptionCount && *cursor != '\0') {
+        const char * next;
+        struct optNameValue pair;
+
+        getToken(cursor, '=', &pair.name, &next);
+
+        cursor = next;
+
+        if (*cursor == '\0')
+            optFatal("name=value option value ends prematurely.  An equal "
+                     "sign was expected following name '%s'", pair.name);
+
+        assert(*cursor == '=');
+        ++cursor;
+
+        getToken(cursor, ',', &pair.value, &next);
+
+        cursor = next;
+
+        list[optionCount++] = pair;
+
+        if (*cursor != '\0') {
+            assert(*cursor == ',');
+            ++cursor;
+        }
+    }
+    list[optionCount].name  = NULL;
+    list[optionCount].value = NULL;
+
+    *listP = list;
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optExecute
+ |
+ |  FUNCTION      Perform the action of an option.
+ |
+ |  INPUT         opt     element in array of defined options that 
+ |                        applies to this option
+ |                arg     argument to option, if it applies.
+ |                lng     was the option given as a long option?
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ */
+static void
+optExecute(optEntry  const opt, char *arg, int lng)
+{
+    if (opt.specified)
+        (*(opt.specified))++;
+
+    switch (opt.type) {
+    case OPT_FLAG:
+        if (opt.arg)
+            *((int *) opt.arg) = 1;
+        break;
+
+    case OPT_STRING:
+        if (opt.arg)
+            *((char **) opt.arg) = arg;
+        break;
+
+    case OPT_INT:
+    case OPT_LONG: {
+        long tmp;
+        char *e;
+	  
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtol(arg, &e, 10);
+        if (*e)
+            optFatal("invalid number `%s'", arg);
+        if (errno == ERANGE
+            || (opt.type == OPT_INT && (tmp > INT_MAX || tmp < INT_MIN)))
+            optFatal("number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.type == OPT_INT) {
+            *((int *) opt.arg) = (int) tmp;
+        } else /* OPT_LONG */ {
+            if (opt.arg)
+                *((long *) opt.arg) = tmp;
+        }
+    } break;
+	
+    case OPT_UINT:
+    case OPT_ULONG: {
+        unsigned long tmp;
+        char *e;
+        
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtoul(arg, &e, 10);
+        if (*e)
+            optFatal("invalid number `%s'", arg);
+        if (errno == ERANGE
+            || (opt.type == OPT_UINT && tmp > UINT_MAX))
+            optFatal("number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.type == OPT_UINT) {
+           if (opt.arg)
+               *((unsigned *) opt.arg) = (unsigned) tmp;
+        } else /* OPT_ULONG */ {
+            if (opt.arg)
+                *((unsigned long *) opt.arg) = tmp;
+        }
+    } break;
+    case OPT_FLOAT: {
+        float tmp;
+        char *e;
+	  
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtod(arg, &e);
+        if (*e)
+            optFatal("invalid floating point number `%s'", arg);
+        if (errno == ERANGE)
+            optFatal("floating point number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.arg)
+            *((float *) opt.arg) = tmp;
+    } break;
+    case OPT_NAMELIST: {
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+
+        if (opt.arg)
+            parseNameList(arg, (struct optNameValue **)opt.arg);
+
+    } break;
+    default:
+        break;
+    }
+}
+
+
+
+/*-----------------------------------------------------------------------+
+|  PUBLIC FUNCTIONS                                                      |
++-----------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------
+ |  NAME          optSetFatalFunc
+ |
+ |  FUNCTION      Set function used to display error message and exit.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optSetFatalFunc(void (*f)(const char *, ...));
+ |
+ |  INPUT         f       function accepting printf()'like parameters,
+ |                        that _must_ abort the program.
+ */
+void
+optSetFatalFunc(void (*f)(const char *, ...))
+{
+    optFatal = f;
+}
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optParseOptions(int *argc, char *argv[],
+ |                                     optStruct opt[], int allowNegNum);
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Array of possible options.
+ |                allowNegNum
+ |                        a negative number is not to be taken as
+ |                        an option.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
+{
+    int  ai,        /* argv index. */
+         optarg,    /* argv index of option argument, or -1 if none. */
+         mi,        /* Match index in opt. */
+         done;
+    char *arg,      /* Pointer to argument to an option. */
+         *o,        /* pointer to an option character */
+         *p;
+
+    optEntry *opt_table;  /* malloc'ed array */
+
+    opt_table = optStructTblToEntryTbl(opt);
+    if (opt_table == NULL)
+        optFatal("Memory allocation failed (trying to allocate space for "
+                 "new-format option table)");
+
+    /*
+     *  Loop through all arguments.
+     */
+    for (ai = 0; ai < *argc; ) {
+        /*
+         *  "--" indicates that the rest of the argv-array does not
+         *  contain options.
+         */
+        if (strcmp(argv[ai], "--") == 0) {
+            argvRemove(argc, argv, ai);
+            break;
+        }
+
+        if (allowNegNum && argv[ai][0] == '-' && ISDIGIT(argv[ai][1])) {
+            ++ai;
+            continue;
+        } else if (strncmp(argv[ai], "--", 2) == 0) {
+            /* long option */
+            /* find matching option */
+            if ((mi = optMatch(opt_table, argv[ai] + 2, 1)) < 0)
+                optFatal("unrecognized option `%s'", argv[ai]);
+
+            /* possibly locate the argument to this option. */
+            arg = NULL;
+            if ((p = strchr(argv[ai], '=')) != NULL)
+                arg = p + 1;
+	    
+            /* does this option take an argument? */
+            optarg = -1;
+            if (optNeedsArgument(opt_table[mi])) {
+                /* option needs an argument. find it. */
+                if (!arg) {
+                    if ((optarg = ai + 1) == *argc)
+                        optFatal("option `%s' requires an argument",
+                                 optString(opt_table[mi], 1));
+                    arg = argv[optarg];
+                }
+            } else {
+                if (arg)
+                    optFatal("option `%s' doesn't allow an argument",
+                             optString(opt_table[mi], 1));
+            }
+            optExecute(opt_table[mi], arg, 1);
+            /* remove option and any argument from the argv-array. */
+            if (optarg >= 0)
+                argvRemove(argc, argv, ai);
+            argvRemove(argc, argv, ai);
+        } else if (*argv[ai] == '-') {
+            /* A dash by itself is not considered an option. */
+            if (argv[ai][1] == '\0') {
+                ++ai;
+                continue;
+            }
+            /* Short option(s) following */
+            o = argv[ai] + 1;
+            done = 0;
+            optarg = -1;
+            while (*o && !done) {
+                /* find matching option */
+                if ((mi = optMatch(opt_table, o, 0)) < 0)
+                    optFatal("unrecognized option `-%c'", *o);
+
+                /* does this option take an argument? */
+                optarg = -1;
+                arg = NULL;
+                if (optNeedsArgument(opt_table[mi])) {
+                    /* option needs an argument. find it. */
+                    arg = o + 1;
+                    if (!*arg) {
+                        if ((optarg = ai + 1) == *argc)
+                            optFatal("option `%s' requires an argument",
+                                     optString(opt_table[mi], 0));
+                        arg = argv[optarg];
+                    }
+                    done = 1;
+                }
+                /* perform the action of this option. */
+                optExecute(opt_table[mi], arg, 0);
+                ++o;
+            }
+            /* remove option and any argument from the argv-array. */
+            if (optarg >= 0)
+                argvRemove(argc, argv, ai);
+            argvRemove(argc, argv, ai);
+        } else {
+            /* a non-option argument */
+            ++ai;
+        }
+    }
+    free(opt_table);
+}
+
+
+static void
+parse_short_option_token(char *argv[], const int argc, const int ai,
+                         const optEntry opt_table[], 
+                         int * const tokens_consumed_p) {
+
+    char *o;  /* A short option character */
+    char *arg;
+    int mi;   /* index into option table */
+    unsigned char processed_arg;  /* boolean */
+        /* We processed an argument to one of the one-character options. 
+           This necessarily means there are no more options in this token
+           to process.
+           */
+
+    *tokens_consumed_p = 1;  /* initial assumption */
+
+    o = argv[ai] + 1;
+    processed_arg = 0;  /* initial value */
+    while (*o && !processed_arg) {
+		/* find matching option */
+		if ((mi = optMatch(opt_table, o, 0)) < 0)
+		    optFatal("unrecognized option `-%c'", *o);
+
+		/* does this option take an argument? */
+		if (optNeedsArgument(opt_table[mi])) {
+		    /* option needs an argument. find it. */
+		    arg = o + 1;
+		    if (!*arg) {
+                if (ai + 1 >= argc)
+			    optFatal("option `%s' requires an argument",
+				     optString(opt_table[mi], 0));
+			arg = argv[ai+1];
+            (*tokens_consumed_p)++;
+		    }
+		    processed_arg = 1;
+		} else 
+            arg = NULL;
+		/* perform the action of this option. */
+		optExecute(opt_table[mi], arg, 0);
+		++o;
+    }
+}
+
+
+
+static void
+parse_long_option(char *argv[], const int argc, const int ai,
+                  const int namepos,
+                  const optEntry opt_table[], 
+                  int * const tokens_consumed_p) {
+
+    char *equals_arg;
+      /* The argument of an option, included in the same token, after a
+         "=".  NULL if no "=" in the token.
+         */
+    char *arg;     /* The argument of an option; NULL if none */
+    int mi;    /* index into option table */
+
+    /* The current token is an option, and its name starts at
+       Index 'namepos' in the argument.
+    */
+    *tokens_consumed_p = 1;  /* initial assumption */
+    /* find matching option */
+    if ((mi = optMatch(opt_table, &argv[ai][namepos], 1)) < 0)
+        optFatal("unrecognized option `%s'", argv[ai]);
+            
+    /* possibly locate the argument to this option. */
+    { 
+        char *p;
+        if ((p = strchr(argv[ai], '=')) != NULL)
+            equals_arg = p + 1;
+        else 
+            equals_arg = NULL;
+    }
+    /* does this option take an argument? */
+    if (optNeedsArgument(opt_table[mi])) {
+        /* option needs an argument. find it. */
+        if (equals_arg)
+            arg = equals_arg;
+        else {
+            if (ai + 1 == argc)
+                optFatal("option `%s' requires an argument",
+                         optString(opt_table[mi], 1));
+            arg = argv[ai+1];
+            (*tokens_consumed_p)++;
+        }
+    } else {
+        if (equals_arg)
+            optFatal("option `%s' doesn't allow an argument",
+                     optString(opt_table[mi], 1));
+        else 
+            arg = NULL;
+    }
+    /* perform the action of this option. */
+    optExecute(opt_table[mi], arg, 1);
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions2
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optParseOptions2(int *argc, char *argv[],
+ |                                      optStruct2 opt, unsigned long flags);
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Structure describing option syntax.
+ |                flags   Result is undefined if not zero.
+ |                        For future expansion.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                This differs from optParseOptions in that it accepts
+ |                long options with just one hyphen and doesn't accept
+ |                any short options.  It also has accomodations for 
+ |                future expansion.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+                 const unsigned long flags)
+/*----------------------------------------------------------------------------
+   This does the same thing as optParseOptions3(), except that there is no
+   "specified" return value.  
+
+   This function exists for backward compatibility.
+-----------------------------------------------------------------------------*/
+
+{
+    optStruct3 opt3;
+
+    opt3.short_allowed = opt.short_allowed;
+    opt3.allowNegNum   = opt.allowNegNum;
+    opt3.opt_table     = optStructTblToEntryTbl(opt.opt_table);
+
+    if (opt3.opt_table == NULL)
+        optFatal("Memory allocation failed (trying to allocate space for "
+                 "new-format option table)");
+    
+    optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags);
+
+    free(opt3.opt_table);
+}
+
+
+
+
+static void
+zero_specified(optEntry opt_table[]) {
+/*----------------------------------------------------------------------------
+   Set all the "number of times specified" return values identified in the
+   option table opt_table[] to zero.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; opt_table[i].type != OPT_END; i++) {
+        if (opt_table[i].specified)
+            *(opt_table[i].specified) = 0;
+    }
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions3
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Structure describing option syntax.
+ |                optStructSize
+ |                        Size of "opt" (since the caller may be older
+ |                        than this function, it may be using a structure
+ |                        with fewer fields than exist today.  We use this
+ |                        parameter to handle those older callers). 
+ |                flags   Result is undefined if not zero.
+ |                        For future expansion.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |                
+ |                Areas pointed to by pointers in 'opt' get updated with
+ |                option values and counts.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                This differs from optParseOptions in that it accepts
+ |                long options with just one hyphen and doesn't accept
+ |                any short options.  It also has accomodations for 
+ |                future expansion.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+                 const unsigned int optStructSize, const unsigned long flags)
+{
+    int  ai;        /* argv index. */
+    int tokens_consumed;
+    unsigned char no_more_options;  /* boolean */
+        /* We've encountered the "no more options" token */
+
+    zero_specified(opt.opt_table);
+
+    /*
+     *  Loop through all arguments.
+     */
+    no_more_options = 0;  /* initial value */
+    for (ai = 0; ai < *argc_p; ) {
+        if (no_more_options) 
+            /* Can't be an option -- there aren't any more */
+            ai++;
+        else if (argv[ai][0] != '-') 
+            /* Can't be an option -- doesn't start with a dash */
+            ai++;
+        else {
+            /* It starts with a dash -- could be an option */
+            if (argv[ai][1] == '\0') {
+                /* A dash by itself is not considered an option. */
+                ++ai;
+                tokens_consumed = 0;
+            } else if (opt.allowNegNum && ISDIGIT(argv[ai][1])) {
+                /* It's a negative number parameter, not an option */
+                ++ai;
+                tokens_consumed = 0;
+            } else if (argv[ai][1] == '-') {
+                /* It starts with -- */
+                if (argv[ai][2] == '\0') {
+                    /* The entire thing is "--".  That means no more options */
+                    tokens_consumed = 1;
+                    no_more_options = 1;
+                } else 
+                    /* It's an option that starts with "--" */
+                    parse_long_option(argv, *argc_p, ai, 2,
+                                      opt.opt_table, &tokens_consumed);
+            } else {
+                if (opt.short_allowed) {
+                    /* It's a cluster of (one or more) short options */
+                    parse_short_option_token(argv, *argc_p, ai,
+                                             opt.opt_table, &tokens_consumed);
+                } else {
+                    /* It's a long option that starts with "-" */
+                    parse_long_option(argv, *argc_p, ai, 1,
+                                      opt.opt_table, &tokens_consumed);
+                }
+            
+            }
+            /* remove option and any argument from the argv-array. */
+            {
+                int i;
+                for (i = 0; i < tokens_consumed; i++)
+                    argvRemove(argc_p, argv, ai);
+            }
+        }
+    }
+}
+
+
+
+void
+optDestroyNameValueList(struct optNameValue * const list) {
+
+    unsigned int i;
+
+    for (i = 0; list[i].name; ++i) {
+        strfree(list[i].name);
+        strfree(list[i].value);
+    }
+
+    free(list);
+}
diff --git a/lib/util/shhopt.h b/lib/util/shhopt.h
new file mode 100644
index 00000000..fd15b53c
--- /dev/null
+++ b/lib/util/shhopt.h
@@ -0,0 +1,240 @@
+/*==============================================================================
+HERE IS AN EXAMPLE OF THE USE OF SHHOPT:
+
+
+#include <shhopt.h>
+int 
+main ( int argc, char **argv ) {
+
+    int help_flag = 7;
+    unsigned int help_spec =7;
+    unsigned int height_spec =7;
+    unsigned int name_spec= 7;
+    char *name= "initial";
+    int height=7;
+    int verbose_flag=7;
+    int debug_flag=7;
+    struct optNameValue * optlist;
+    
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def = malloc(100*sizeof(option_def[0]));
+
+    OPTENT3(0,   "help",     OPT_FLAG,     &help_flag,    &help_spec,   0);
+    OPTENT3(0,   "height",   OPT_INT,      &height,       &height_spec, 0);
+    OPTENT3('n', "name",     OPT_STRING,   &name,         &name_spec,   0);
+    OPTENT3('v', "verbose",  OPT_FLAG,     &verbose_flag, NULL,         0);
+    OPTENT3('g', "debug",    OPT_FLAG,     &debug_flag,   NULL,         0);
+    OPTENT3(0,   "options",  OPT_NAMELIST, &optlist,      NULL,         0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 1;
+    opt.allowNegNum = 1;
+
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    
+
+    printf("argc=%d\n", argc);
+    printf("help_flag=%d\n", help_flag);
+    printf("help_spec=%d\n", help_spec);
+    printf("height=%d\n", height);
+    printf("height_spec=%d\n", height_spec);
+    printf("name='%s'\n", name);
+    printf("name_spec=%d\n", name_spec);
+    printf("verbose_flag=%d\n", verbose_flag);
+    printf("debug_flag=%d\n", verbose_flag);
+
+    if (optlist) {
+        unsigned int i;
+        while (optlist[i].name) {
+            printf("option '%s' = '%s'\n", optlist[i].name, optlist[i].value);
+            ++i;
+        }
+        optDestroyNameValueList(optlist);
+    } else
+        printf("No -options\n");
+}
+
+Now run this program with something like
+
+  myprog -vg --name=Bryan --hei 4 "My first argument" --help
+
+  or do it with opt.short_allowed=0 and
+
+  myprog -v /etc/passwd -name=Bryan --hei=4
+
+
+========================================================================*/
+
+
+#ifndef SHHOPT_H
+#define SHHOPT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* constants for recognized option types. */
+typedef enum {
+    OPT_END,               /* nothing. used as ending element. */
+    OPT_FLAG,              /* no argument following. sets variable to 1. */
+    OPT_STRING,            /* string argument. */
+    OPT_INT,               /* signed integer argument. */
+    OPT_UINT,              /* unsigned integer argument. */
+    OPT_LONG,              /* signed long integer argument. */
+    OPT_ULONG,             /* unsigned long integer argument. */
+    OPT_FLOAT,             /* floating point argument. */
+    OPT_NAMELIST           /* list like "key1=val1,key2=val2" */
+} optArgType;
+
+
+typedef struct {
+    /* This structure describes a single program option in a form for
+     use by the optParseOptions() or optParseOptions2() function.
+    */
+    char       shortName;  /* short option name. */
+    const char *longName;  /* long option name, not including '--'. */
+    optArgType type;       /* option type. */
+    void       *arg;       /* pointer to variable to fill with argument,
+                            * or pointer to function if type == OPT_FUNC. */
+    int        flags;      /* modifier flags. */
+} optStruct;
+    
+typedef struct {
+    /* This structure describes a single program option in a form for
+     use by the optParseOptions3() function.
+    */
+    char       shortName;  /* short option name. */
+    const char *longName;  /* long option name, not including '--' or '-' */
+    optArgType type;       /* option type. */
+    void       *arg;       
+        /* pointer to variable in which to return option's argument (or TRUE
+           if it's a flag option), or pointer to function if 
+           type == OPT_FUNC.  If the option is specified multiple times, only 
+           the rightmost one affects this return value.
+        */
+    unsigned int *specified;
+        /* pointer to variable in which to return the number of times that
+           the option was specified.  If NULL, don't return anything.
+        */
+    int        flags;      /* modifier flags. */
+} optEntry;
+    
+
+typedef struct {
+    /* This structure describes the options of a program in a form for
+       use with the optParseOptions2() function.
+       */
+    unsigned char short_allowed;  /* boolean */
+        /* The syntax may include short (i.e. one-character) options.
+           These options may be stacked within a single token (e.g.
+           -abc = -a -b -c).  If this value is not true, the short option
+           member of the option table entry is meaningless and long 
+           options may have either one or two dashes.
+           */
+    unsigned char allowNegNum;  /* boolean */
+        /* Anything that starts with - and then a digit is a numeric
+           parameter, not an option 
+           */
+    optStruct *opt_table;
+} optStruct2;
+
+typedef struct {
+    /* Same as optStruct2, but for optParseOptions3() */
+    unsigned char short_allowed;  
+    unsigned char allowNegNum;    
+    optEntry *opt_table;
+} optStruct3;
+
+
+/* You can use OPTENTRY to assign a value to a dynamically or automatically
+   allocated optStruct structure with minimal typing and easy readability.
+
+   Here is an example:
+
+       unsigned int option_def_index = 0;
+       optStruct *option_def = malloc(100*sizeof(optStruct));
+       OPTENTRY('h', "help",     OPT_FLAG, &help_flag, 0);
+       OPTENTRY(0,   "alphaout", OPT_STRING, &alpha_filename, 0);
+*/   
+
+/* If you name your variables option_def and option_def_index like in the 
+   example above, everything's easy.  If you want to use OPTENTRY with other
+   variables, define macros OPTION_DEF and OPTION_DEF_INDEX before calling
+   OPTENTRY.
+*/
+
+#ifndef OPTION_DEF
+#define OPTION_DEF option_def
+#endif
+#ifndef OPTION_DEF_INDEX
+#define OPTION_DEF_INDEX option_def_index
+#endif
+
+#define OPTENTRY(shortvalue,longvalue,typevalue,outputvalue,flagvalue) {\
+    OPTION_DEF[OPTION_DEF_INDEX].shortName = (shortvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].longName = (longvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].type = (typevalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].arg = (outputvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].flags = (flagvalue); \
+    OPTION_DEF[++OPTION_DEF_INDEX].type = OPT_END; \
+    }
+
+/* OPTENT3 is the same as OPTENTRY except that it also sets the "specified"
+   element of the table entry (so it assumes OPTION_DEF is a table of
+   optEntry instead of optStruct).
+
+   Here is an example:
+
+       unsigned int option_def_index = 0;
+       optEntry *option_def = malloc(100*sizeof(optEntry));
+       OPTENT3('h', "help",     OPT_FLAG, &help_flag, 0);
+       OPTENT3(0,   "alphaout", OPT_STRING, &alpha_filename, 0);
+*/
+
+#define OPTENT3(shortvalue,longvalue,typevalue,outputvalue,specifiedvalue, \
+                flagvalue) {\
+    OPTION_DEF[OPTION_DEF_INDEX].specified = (specifiedvalue); \
+    OPTENTRY(shortvalue, longvalue, typevalue, outputvalue, flagvalue) \
+    }
+
+
+struct optNameValue {
+    const char * name;
+    const char * value;
+};
+
+
+        
+void optSetFatalFunc(void (*f)(const char *, ...));
+void optParseOptions(int *argc, char *argv[],
+		     optStruct opt[], int allowNegNum);
+void
+optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+                 const unsigned long flags);
+void
+optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+                 const unsigned int optStructSize, const unsigned long flags);
+
+void
+optDestroyNameValueList(struct optNameValue * const list);
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Here's a hack to help us introduce pm_c_util.h.  That should be
+   #included in nearly every Netpbm program, but we're too lazy to insert
+   it into hundreds of files right now.  But shhopt.h is included into
+   most of those files, so we #include it here!
+
+   Before 2005.12.03, the stuff that is now in pm_c_util.h was in pm.h,
+   but that's a bad place for it because pm.h is an external header file.
+*/
+#include "pm_c_util.h"
+
+#endif
diff --git a/lib/util/testnstring.c b/lib/util/testnstring.c
new file mode 100644
index 00000000..87e6139e
--- /dev/null
+++ b/lib/util/testnstring.c
@@ -0,0 +1,30 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "nstring.h"
+
+#define true (1)
+#define false (0)
+
+int
+main(int argc, char **argv) {
+
+    char snprintfNResult[80];
+    const char * asprintfNResult; 
+
+    printf("Hello world.\n");
+
+    snprintfN(snprintfNResult, 19, "snprintfN test truncated");
+
+    printf("snprintfN result='%s'\n", snprintfNResult);
+
+    asprintfN(&asprintfNResult, "asprintf'ed string %d %u %s", 
+              5, 89, "substring");
+
+    printf("asprintfN result='%s'\n", asprintfNResult);
+
+    strfree(asprintfNResult);
+    return 0;
+}
diff --git a/lib/util/wordaccess.h b/lib/util/wordaccess.h
new file mode 100644
index 00000000..28963aee
--- /dev/null
+++ b/lib/util/wordaccess.h
@@ -0,0 +1,65 @@
+#ifndef WORDACCESS_H_INCLUDED
+#define WORDACCESS_H_INCLUDED
+
+/* These are facilities for accessing data in C programs in ways that
+   exploit the way the machine defines words in order to squeeze out
+   speed and CPU efficiency.
+
+   In particular, routines in this file exploit the endianness of the
+   machine and use explicit machine instructions to access C
+   variables.
+
+   A word is the amount of data that fits in a register; the amount of
+   data that a single machine instruction can process.  For example,
+   on IA32, a word is 32 bits because a single load or store
+   instruction moves that many bits and a single add instruction
+   operates on that many bits.
+
+
+   These facilities revolve around two data types:  wordInt and
+   wordIntBytes.
+
+   wordint is an unsigned integer with precision (size) of one word.
+   It is just the number -- nothing is implied about how it is
+   represented in memory.
+
+   wordintBytes is an array of bytes that represent a word-sized
+   unsigned integer.  x[0] is the high order 8 digits of the binary
+   coding of the integer, x[1] the next highest 8 digits, etc.
+   Note that it has big-endian form, regardless of what endianness the
+   underlying machine uses.
+
+   The actual size of word differs by machine.  Usually it is 32 or 64
+   bits.  Logically it can be as small as one byte.  Fixed bit sequences
+   in each program impose a lower limit of word width.  For example, the
+   longest bit sequence in pbmtog3 has 13 bits, so an 8-bit word won't
+   work with that.
+
+   We also assume that a char is 8 bits.
+*/
+#if (!defined(WORDACCESS_GENERIC) \
+     && defined(__GNUC__) && defined(__GLIBC__) \
+     && (__GNUC__ * 100 + __GNUC_MINOR__ >= 304) )
+
+    #if BYTE_ORDER==BIG_ENDIAN    /* defined by GCC */
+
+        #include "wordaccess_gcc3_be.h"
+
+    #elif defined(__ia64__) || defined(__amd64__) || defined(__x86_64__)
+         /* all these macros are defined by GCC */
+
+        #include "wordaccess_64_le.h"
+
+    #else
+
+        #include "wordaccess_gcc3_le.h"
+
+    #endif
+
+#else
+
+    #include "wordaccess_generic.h"
+
+#endif
+
+#endif
diff --git a/lib/util/wordaccess_64_le.h b/lib/util/wordaccess_64_le.h
new file mode 100644
index 00000000..2343b6d4
--- /dev/null
+++ b/lib/util/wordaccess_64_le.h
@@ -0,0 +1,52 @@
+/*=============================================================================
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * 64 bit Little-Endian machines (IA64, X86-64, AMD64)
+=============================================================================*/
+   
+/*  
+    64 bit hton and ntoh do not exist.  Here we use bswap_64.
+    
+    While bswap_64 works on 64 bit data, __builtin_clzl works on "long" which
+    may or may not be 64 bits.  Code provided to find the right data type and
+    file off any extra when necessary.
+*/
+
+#include <byteswap.h>  /* See note above on bswap_64 */
+ 
+typedef uint64_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+static __inline__ wordint
+bytesToWordint(wordintBytes bytes) {
+    return ((wordint) bswap_64(*(wordint *)bytes));
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+    *(wordint *)bytesP = bswap_64(wordInt);
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x){
+
+    unsigned int s;
+
+    if (x == 0)
+        return sizeof(wordint) * 8;
+
+    /* Find the data type closest to 64 bits, and file off any extra. */
+    else if ((s=sizeof(long int)) >= 8)
+        return (__builtin_clzl((int)x << (s - 8) * 8));
+    else if ((s=sizeof(long long int)) >= 8)
+        return (__builtin_clzll((long long int)x << (s - 8) * 8));
+    else
+        pm_error("Long long int is less than 64 bits on this machine"); 
+}
diff --git a/lib/util/wordaccess_gcc3_be.h b/lib/util/wordaccess_gcc3_be.h
new file mode 100644
index 00000000..6f5d86fc
--- /dev/null
+++ b/lib/util/wordaccess_gcc3_be.h
@@ -0,0 +1,40 @@
+/*=============================================================================
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * Big-Endian machines
+  
+  __builtin_clz is available on GCC 3.4 and above
+     
+  Note that the clz scheme does not work and requires adjustment
+  if long type does not make use of all bits for data storage.
+  
+  This is unlikely.  According to GNU MP (http://www.swox.com/gmp/),
+  in rare cases such as Cray, there are smaller data types that take up
+  the same space as long, but leave the higher bits silent.  Currently,
+  there are no known such cases for data type long.
+*===========================================================================*/
+
+typedef unsigned long int wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+static __inline__ wordint
+bytesToWordint(wordintBytes bytes) {
+    return *((wordint *)bytes);
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+    *(wordint *)bytesP = wordInt;
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x) {
+    return (x==0 ? sizeof(wordint)*8 : __builtin_clzl(x));
+}
diff --git a/lib/util/wordaccess_gcc3_le.h b/lib/util/wordaccess_gcc3_le.h
new file mode 100644
index 00000000..7db218db
--- /dev/null
+++ b/lib/util/wordaccess_gcc3_le.h
@@ -0,0 +1,54 @@
+/*=============================================================================
+
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * 32 bit Little-Endian machines (intel MPUs 80386, Pentium, etc.)
+  * Other non-Big-Endian machines (very rare)
+  
+=============================================================================*/
+
+typedef uint32_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
+/*
+  Here we use the more widely used functions htonl and ntohl instead of
+  bswap_32.  This makes possible the handling of weird byte ordering
+  (neither Big-Endian nor Little-Endian) schemes, if any.
+*/
+
+static __inline__ wordint
+bytesToWordint(wordintBytes const bytes) {
+    return (wordint) ntohl(*(wordint *)bytes);
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+
+    *(wordint *)bytesP = htonl(wordInt);
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x) {
+
+    /* Find the data type closest to 32 bits, and file off any extra.  */
+
+    if (x == 0)
+        return sizeof(wordint) * 8;
+    else if (sizeof(int) >= 4)
+        return __builtin_clz((int)x << (sizeof(int) - 4) * 8);
+    else if (sizeof(long int) >= 4)
+        return __builtin_clzl((long int)x << (sizeof(long int) - 4) * 8);
+    else
+        pm_error("Long int is less than 32 bits on this machine"); 
+}
+
diff --git a/lib/util/wordaccess_generic.h b/lib/util/wordaccess_generic.h
new file mode 100644
index 00000000..7f27ef74
--- /dev/null
+++ b/lib/util/wordaccess_generic.h
@@ -0,0 +1,89 @@
+/*=============================================================================
+
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * Compilers other than GCC
+  * GCC before version 3.4
+  * c libraries other than Glibc
+  * Specified by the user with WORDACCESS_GENERIC
+=============================================================================*/
+
+typedef uint32_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+    
+static __inline__ wordint
+bytesToWordint(wordintBytes const bytes) {
+    wordint retval;
+    unsigned int i;
+
+    /* Note that 'bytes' is a pointer, due to C array degeneration.
+       That means sizeof(bytes) isn't what you think it is.
+    */
+    
+    for (i = 1, retval = bytes[0]; i < sizeof(wordint); ++i) {
+        retval = (retval << 8) + bytes[i];
+    }
+    return retval;
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+
+    wordint buffer;
+    int i;
+
+    for (i = sizeof(*bytesP)-1, buffer = wordInt; i >= 0; --i) {
+        (*bytesP)[i] = buffer & 0xFF;
+        buffer >>= 8;
+    }
+}
+    
+static unsigned char const clz8[256]= {
+    8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
+};
+
+
+
+static __inline__ unsigned int
+clz16(wordint const x) {
+    if (x >> 8  != 0)
+        return clz8[x >> 8];
+    else
+        return clz8[x] + 8;
+}
+
+
+
+static __inline__  unsigned int
+clz32(wordint const x) {
+    if (x >> 16  != 0)
+        return clz16(x >> 16);
+    else
+        return clz16(x) +16;
+}
+
+
+
+static __inline__  unsigned int
+wordintClz(wordint const x) {
+    return clz32(x);
+}