about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-03-25 00:45:40 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-03-25 00:45:40 +0000
commit59588cf8aa12fd79214e937e6a9597630443c4a8 (patch)
treed73b31a0c2f7c7be4a69f8a8e84e00dd39c432b5
parentdaf778ac40755a73086f64c20730e24d237b6759 (diff)
downloadnetpbm-mirror-59588cf8aa12fd79214e937e6a9597630443c4a8.tar.gz
netpbm-mirror-59588cf8aa12fd79214e937e6a9597630443c4a8.tar.xz
netpbm-mirror-59588cf8aa12fd79214e937e6a9597630443c4a8.zip
promote Development to Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4539 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--converter/other/jpegtopnm.c604
-rw-r--r--converter/other/pngx.c2
-rw-r--r--converter/ppm/hpcdtoppm/Makefile22
-rw-r--r--converter/ppm/hpcdtoppm/README37
-rwxr-xr-xconverter/ppm/hpcdtoppm/hpcdtoppm16
-rwxr-xr-xconverter/ppm/hpcdtoppm/pcdovtoppm2
-rw-r--r--doc/HISTORY17
-rw-r--r--generator/pbmtextps.c436
-rw-r--r--lib/util/shhopt.c111
-rw-r--r--other/pnmcolormap.c153
-rw-r--r--test/Test-Order1
-rwxr-xr-xtest/ilbm-roundtrip.test20
-rw-r--r--test/pbmtextps-dump.ok25
-rwxr-xr-xtest/pbmtextps-dump.test64
-rw-r--r--test/pbmtextps.ok4
-rwxr-xr-xtest/pbmtextps.test10
-rw-r--r--test/pnmcolormap.ok24
-rwxr-xr-xtest/pnmcolormap.test31
-rw-r--r--test/pnmcolormap2.ok9
-rwxr-xr-xtest/pnmcolormap2.test55
-rw-r--r--version.mk4
21 files changed, 1163 insertions, 484 deletions
diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c
index 6357e859..7d1750a7 100644
--- a/converter/other/jpegtopnm.c
+++ b/converter/other/jpegtopnm.c
@@ -4,13 +4,13 @@
   This program is part of the Netpbm package.
 
   This program converts from the JFIF format, which is based on JPEG, to
-  the fundamental ppm or pgm format (depending on whether the JFIF 
+  the fundamental ppm or pgm format (depending on whether the JFIF
   image is gray scale or color).
 
   This program is by Bryan Henderson on 2000.03.20, but is derived
   with permission from the program djpeg, which is in the Independent
   Jpeg Group's JPEG library package.  Under the terms of that permission,
-  redistribution of this software is restricted as described in the 
+  redistribution of this software is restricted as described in the
   file README.JPEG.
 
   Copyright (C) 1991-1998, Thomas G. Lane.
@@ -45,13 +45,14 @@
     (http://topo.math.u-psud.fr/~bousch/exifdump.py) and Jhead
     (http://www.sentex.net/~mwandel/jhead).
 
-    
+
 *****************************************************************************/
 
 #define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
+#include <stdbool.h>
 #include <ctype.h>		/* to declare isprint() */
 #include <string.h>
 #include <stdlib.h>
@@ -73,76 +74,78 @@
 
 #define EXIT_WARNING 2  /* Goes with EXIT_FAILURE, EXIT_SUCCESS in stdlib.h */
 
-enum inklevel {NORMAL, ADOBE, GUESS};
+enum Inklevel {NORMAL, ADOBE, GUESS};
    /* This describes image samples that represent ink levels.  NORMAL
       means 0 is no ink; ADOBE means 0 is maximum ink.  GUESS means we
-      don't know what 0 means, so we have to guess from information in 
+      don't know what 0 means, so we have to guess from information in
       the image.
       */
 
-enum colorspace {
+enum Colorspace {
     /* These are the color spaces in which we can get pixels from the
        JPEG decompressor.  We include only those that are possible
        given our particular inputs to the decompressor.  The
        decompressor is theoretically capable of other, e.g. YCCK.
        Unlike the JPEG library, this type distinguishes between the
-       Adobe and non-Adobe style of CMYK samples.  
+       Adobe and non-Adobe style of CMYK samples.
     */
     GRAYSCALE_COLORSPACE,
-    RGB_COLORSPACE, 
-    CMYK_NORMAL_COLORSPACE, 
+    RGB_COLORSPACE,
+    CMYK_NORMAL_COLORSPACE,
     CMYK_ADOBE_COLORSPACE
     };
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    char *input_filespec;
-    char *exif_filespec;
-        /* Filespec in which to save EXIF information.  NULL means don't
+    char * inputFileName;
+    char * exifFileName;
+        /* Name of file in which to save EXIF information.  NULL means don't
            save.  "-" means standard output
         */
-    unsigned int verbose;
-    unsigned int nosmooth;
-    J_DCT_METHOD dct_method;
-    long int max_memory_to_use;
-    unsigned int trace_level;
-    enum inklevel inklevel;
-    unsigned int comments;
-    unsigned int dumpexif;
-    unsigned int multiple;
-    unsigned int repair;
+    unsigned int  verbose;
+    unsigned int  nosmooth;
+    J_DCT_METHOD  dctMethod;
+    long int      maxMemoryToUse;
+    unsigned int  traceLevel;
+    enum Inklevel inklevel;
+    unsigned int  comments;
+    unsigned int  dumpexif;
+    unsigned int  traceexif;
+    unsigned int  multiple;
+    unsigned int  repair;
 };
 
 
 static bool displayComments;
     /* User wants comments from the JPEG to be displayed */
 
-static void 
-interpret_maxmemory(bool         const maxmemorySpec,
-                    const char * const maxmemory, 
-                    long int *   const max_memory_to_use_p) { 
+static void
+interpretMaxmemory(bool         const maxmemorySpec,
+                   const char * const maxmemory,
+                   long int *   const maxMemoryToUseP) {
 /*----------------------------------------------------------------------------
    Interpret the "maxmemory" command line option.
 -----------------------------------------------------------------------------*/
     long int lval;
     char ch;
-    
+
     if (!maxmemorySpec) {
-        *max_memory_to_use_p = -1;  /* unspecified */
+        *maxMemoryToUseP = -1;  /* unspecified */
     } else if (sscanf(maxmemory, "%ld%c", &lval, &ch) < 1) {
         pm_error("Invalid value for --maxmemory option: '%s'.", maxmemory);
     } else {
         if (ch == 'm' || ch == 'M') lval *= 1000L;
-        *max_memory_to_use_p = lval * 1000L;
+        *maxMemoryToUseP = lval * 1000L;
     }
 }
 
 
 static void
-interpret_adobe(const int adobe, const int notadobe, 
-                enum inklevel * const inklevel_p) {
+interpretAdobe(int             const adobe,
+               int             const notadobe,
+               enum Inklevel * const inklevelP) {
 /*----------------------------------------------------------------------------
    Interpret the adobe/notadobe command line options
 -----------------------------------------------------------------------------*/
@@ -150,11 +153,11 @@ interpret_adobe(const int adobe, const int notadobe,
         pm_error("You cannot specify both -adobe and -notadobe options.");
     else {
         if (adobe)
-            *inklevel_p = ADOBE;
+            *inklevelP = ADOBE;
         else if (notadobe)
-            *inklevel_p = NORMAL;
-        else 
-            *inklevel_p = GUESS;
+            *inklevelP = NORMAL;
+        else
+            *inklevelP = GUESS;
     }
 }
 
@@ -163,7 +166,7 @@ interpret_adobe(const int adobe, const int notadobe,
 static void
 parseCommandLine(int                  const argc,
                  char **              const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that many of the strings that this function returns in the
    *cmdlineP structure are actually in the supplied argv array.  And
@@ -173,7 +176,7 @@ parseCommandLine(int                  const argc,
    On the other hand, unlike other option processing functions, we do
    not change argv at all.
 -----------------------------------------------------------------------------*/
-    optEntry *option_def;
+    optEntry * option_def;
         /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
@@ -192,7 +195,7 @@ parseCommandLine(int                  const argc,
 
     MALLOCARRAY_NOFAIL(option_def, 100);
     MALLOCARRAY_NOFAIL(argv_parse, argc);
-    
+
     /* argv, except we modify it as we parse */
 
     option_def_index = 0;   /* incremented by OPTENTRY */
@@ -200,62 +203,63 @@ parseCommandLine(int                  const argc,
     OPTENT3(0, "dct",         OPT_STRING, &dctval,
             &dctvalSpec, 0);
     OPTENT3(0, "maxmemory",   OPT_STRING, &maxmemory,
-            &maxmemorySpec, 0); 
+            &maxmemorySpec, 0);
     OPTENT3(0, "nosmooth",    OPT_FLAG,   NULL, &cmdlineP->nosmooth,      0);
-    OPTENT3(0, "tracelevel",  OPT_UINT,   &cmdlineP->trace_level,   
+    OPTENT3(0, "tracelevel",  OPT_UINT,   &cmdlineP->traceLevel,
             &tracelevelSpec, 0);
     OPTENT3(0, "adobe",       OPT_FLAG,   NULL, &adobe,                   0);
     OPTENT3(0, "notadobe",    OPT_FLAG,   NULL, &notadobe,                0);
     OPTENT3(0, "comments",    OPT_FLAG,   NULL, &cmdlineP->comments,      0);
-    OPTENT3(0, "exif",        OPT_STRING, &cmdlineP->exif_filespec, 
+    OPTENT3(0, "exif",        OPT_STRING, &cmdlineP->exifFileName,
             &exifSpec, 0);
     OPTENT3(0, "dumpexif",    OPT_FLAG,   NULL, &cmdlineP->dumpexif,      0);
     OPTENT3(0, "multiple",    OPT_FLAG,   NULL, &cmdlineP->multiple,      0);
     OPTENT3(0, "repair",      OPT_FLAG,   NULL, &cmdlineP->repair,        0);
+    OPTENT3(0, "traceexif",   OPT_FLAG,   NULL, &cmdlineP->traceexif,     0);
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We may have parms that are negative numbers */
 
     /* Make private copy of arguments for pm_optParseOptions to corrupt */
     argc_parse = argc;
     for (i=0; i < argc; ++i)
         argv_parse[i] = argv[i];
 
-    pm_optParseOptions3( &argc_parse, argv_parse, opt, sizeof(opt), 0);
-        /* Uses and sets argc_parse, argv_parse, 
+    pm_optParseOptions3(&argc_parse, argv_parse, opt, sizeof(opt), 0);
+        /* Uses and sets argc_parse, argv_parse,
            and some of *cmdlineP and others. */
 
     if (!tracelevelSpec)
-        cmdlineP->trace_level = 0;
+        cmdlineP->traceLevel = 0;
 
     if (!exifSpec)
-        cmdlineP->exif_filespec = NULL;
+        cmdlineP->exifFileName = NULL;
 
     if (argc_parse - 1 == 0)
-        cmdlineP->input_filespec = strdup("-");  /* he wants stdin */
+        cmdlineP->inputFileName = strdup("-");  /* he wants stdin */
     else if (argc_parse - 1 == 1)
-        cmdlineP->input_filespec = strdup(argv_parse[1]);
-    else 
+        cmdlineP->inputFileName = strdup(argv_parse[1]);
+    else
         pm_error("Too many arguments.  The only argument accepted "
                  "is the input file specification");
 
     if (!dctvalSpec)
-        cmdlineP->dct_method = JDCT_DEFAULT;
+        cmdlineP->dctMethod = JDCT_DEFAULT;
     else {
         if (streq(dctval, "int"))
-            cmdlineP->dct_method = JDCT_ISLOW;
+            cmdlineP->dctMethod = JDCT_ISLOW;
         else if (streq(dctval, "fast"))
-            cmdlineP->dct_method = JDCT_IFAST;
+            cmdlineP->dctMethod = JDCT_IFAST;
         else if (streq(dctval, "float"))
-            cmdlineP->dct_method = JDCT_FLOAT;
+            cmdlineP->dctMethod = JDCT_FLOAT;
         else pm_error("Invalid value for the --dct option: '%s'.", dctval);
     }
 
-    interpret_maxmemory(maxmemorySpec, maxmemory, 
-                        &cmdlineP->max_memory_to_use);
+    interpretMaxmemory(maxmemorySpec, maxmemory,
+                       &cmdlineP->maxMemoryToUse);
 
-    interpret_adobe(adobe, notadobe, &cmdlineP->inklevel);
+    interpretAdobe(adobe, notadobe, &cmdlineP->inklevel);
 
     free(argv_parse);
 }
@@ -270,70 +274,72 @@ parseCommandLine(int                  const argc,
 
 #if 0
 static unsigned int
-jpeg_getc (j_decompress_ptr cinfo)
+jpegGetc (j_decompress_ptr const cinfoP) {
+
 /* Read next byte */
-{
-  struct jpeg_source_mgr * datasrc = cinfo->src;
-
-  if (datasrc->bytes_in_buffer == 0) {
-      if (! (*datasrc->fill_input_buffer) (cinfo)) 
-          pm_error("Can't suspend here.");
-  }
-  datasrc->bytes_in_buffer--;
-  return GETJOCTET(*datasrc->next_input_byte++);
+    struct jpeg_source_mgr * datasrcP = cinfoP->src;
+
+    if (datasrcP->bytes_in_buffer == 0) {
+        if (! (*datasrcP->fill_input_buffer) (cinfoP))
+            pm_error("Can't suspend here.");
+    }
+    datasrcP->bytes_in_buffer--;
+    return GETJOCTET(*datasrcP->next_input_byte++);
 }
 
 
-static boolean
-print_text_marker (j_decompress_ptr cinfo) {
+static bool
+printTextMarker(j_decompress_ptr const cinfoP) {
 /*----------------------------------------------------------------------------
    This is a routine that you can register with the Jpeg decompressor
    with e.g.
 
-     jpeg_set_marker_processor(cinfoP, JPEG_APP0 + app_type, 
-                               print_text_marker);
+     jpeg_set_marker_processor(cinfoP, JPEG_APP0 + appType,
+                               printTextMarker);
 
   The decompressor then calls it when it encounters a miscellaneous marker
   of the specified type (e.g. APP1).  At that time, the jpeg input stream
   is positioned to the marker contents -- first 2 bytes of length information,
   MSB first, where the length includes those two bytes, then the data.
-  
+
   We just get and print the contents of the marker.
 
   This routine is no longer used; it is kept as an example in case we want
   to use it in the future.  Instead, we use jpeg_save_markers() and have
   the Jpeg library store all the markers in memory for our later access.
 -----------------------------------------------------------------------------*/
-    const boolean traceit = (cinfo->err->trace_level >= 1);
-    const boolean display_value = 
-        traceit || (cinfo->unread_marker == JPEG_COM && displayComments);
-    
+    const bool traceit = (cinfoP->err->trace_level >= 1);
+    const bool display_value =
+        traceit || (cinfoP->unread_marker == JPEG_COM && displayComments);
+
     INT32 length;
     unsigned int ch;
-    unsigned int lastch = 0;
-    
-    length = jpeg_getc(cinfo) << 8;
-    length += jpeg_getc(cinfo);
+    unsigned int lastch;
+
+    lastch = 0;  /* initial value */
+
+    length = jpeg_getc(cinfoP) << 8;
+    length += jpeg_getc(cinfoP);
     length -= 2;			/* discount the length word itself */
 
     if (traceit) {
-        if (cinfo->unread_marker == JPEG_COM)
+        if (cinfoP->unread_marker == JPEG_COM)
             fprintf(stderr, "Comment, length %ld:\n", (long) length);
         else			/* assume it is an APPn otherwise */
             fprintf(stderr, "APP%d, length %ld:\n",
-                    cinfo->unread_marker - JPEG_APP0, (long) length);
+                    cinfoP->unread_marker - JPEG_APP0, (long) length);
     }
-    
-    if (cinfo->unread_marker == JPEG_COM && displayComments)
+
+    if (cinfoP->unread_marker == JPEG_COM && displayComments)
         fprintf(stderr, "COMMENT: ");
-    
+
     while (--length >= 0) {
-        ch = jpeg_getc(cinfo);
+        ch = jpeg_getc(cinfoP);
         if (display_value) {
             /* Emit the character in a readable form.
              * Nonprintables are converted to \nnn form,
              * while \ is converted to \\.
-             * Newlines in CR, CR/LF, or LF form will be printed as one 
+             * Newlines in CR, CR/LF, or LF form will be printed as one
              * newline.
              */
             if (ch == '\r') {
@@ -351,18 +357,18 @@ print_text_marker (j_decompress_ptr cinfo) {
           lastch = ch;
         }
     }
-    
+
     if (display_value)
         fprintf(stderr, "\n");
-    
-    return TRUE;
+
+    return true;
 }
 #endif
 
 
 
 static void
-print_marker(struct jpeg_marker_struct const marker) {
+printMarker(struct jpeg_marker_struct const marker) {
 
     if (marker.original_length != marker.data_length) {
         /* This should be impossible, because we asked for up to 65535
@@ -376,12 +382,12 @@ print_marker(struct jpeg_marker_struct const marker) {
         int i;
         JOCTET lastch;
 
-        lastch = 0;
+        lastch = 0;  /* initial value */
         for (i = 0; i < marker.data_length; i++) {
             /* Emit the character in a readable form.
              * Nonprintables are converted to \nnn form,
              * while \ is converted to \\.
-             * Newlines in CR, CR/LF, or LF form will be printed as one 
+             * Newlines in CR, CR/LF, or LF form will be printed as one
              * newline.
              */
             if (marker.data[i] == '\r') {
@@ -407,29 +413,29 @@ typedef struct rgb {unsigned int r; unsigned int g; unsigned int b;} rgb_type;
 
 
 static rgb_type *
-read_rgb(JSAMPLE *ptr, const enum colorspace color_space, 
-         const unsigned int maxval) {
+read_rgb(JSAMPLE *       const ptr,
+         enum Colorspace const colorspace,
+         unsigned int    const maxval) {
 /*----------------------------------------------------------------------------
   Return the RGB triple corresponding to the color of the JPEG pixel at
-  'ptr', which is in color space 'color_space'.  
+  'ptr', which is in color space 'color_space'.
 
   Assume 'maxval' is the maximum sample value in the input pixel, and also
   use it for the maximum sample value in the return value.
 -----------------------------------------------------------------------------*/
     static rgb_type rgb;  /* Our return value */
 
-    switch (color_space) {
+    switch (colorspace) {
     case RGB_COLORSPACE: {
         rgb.r = GETJSAMPLE(*(ptr+0));
-        rgb.g = GETJSAMPLE(*(ptr+1)); 
-        rgb.b = GETJSAMPLE(*(ptr+2)); 
-    }
-        break;
+        rgb.g = GETJSAMPLE(*(ptr+1));
+        rgb.b = GETJSAMPLE(*(ptr+2));
+    } break;
     case CMYK_NORMAL_COLORSPACE: {
-        const int c = GETJSAMPLE(*(ptr+0));
-        const int m = GETJSAMPLE(*(ptr+1));
-        const int y = GETJSAMPLE(*(ptr+2));
-        const int k = GETJSAMPLE(*(ptr+3));
+        int const c = GETJSAMPLE(*(ptr+0));
+        int const m = GETJSAMPLE(*(ptr+1));
+        int const y = GETJSAMPLE(*(ptr+2));
+        int const k = GETJSAMPLE(*(ptr+3));
 
         /* I swapped m and y below, because they looked wrong.
            -Bryan 2000.08.20
@@ -437,74 +443,71 @@ read_rgb(JSAMPLE *ptr, const enum colorspace color_space,
         rgb.r = ((maxval-k)*(maxval-c))/maxval;
         rgb.g = ((maxval-k)*(maxval-m))/maxval;
         rgb.b = ((maxval-k)*(maxval-y))/maxval;
-    }
-        break;
+    } break;
     case CMYK_ADOBE_COLORSPACE: {
-        const int c = GETJSAMPLE(*(ptr+0));
-        const int m = GETJSAMPLE(*(ptr+1));
-        const int y = GETJSAMPLE(*(ptr+2));
-        const int k = GETJSAMPLE(*(ptr+3));
+        int const c = GETJSAMPLE(*(ptr+0));
+        int const m = GETJSAMPLE(*(ptr+1));
+        int const y = GETJSAMPLE(*(ptr+2));
+        int const k = GETJSAMPLE(*(ptr+3));
 
         rgb.r = (k*c)/maxval;
         rgb.g = (k*m)/maxval;
         rgb.b = (k*y)/maxval;
-    }
-        break;
+    } break;
     default:
         pm_error("Internal error: unknown color space %d passed to "
-                 "read_rgb().", (int) color_space);
+                 "read_rgb().", (int) colorspace);
     }
-    return(&rgb);
+    return &rgb;
 }
 
 
 
-/* pnmbuffer is declared global because it would be improper to pass a
-   pointer to it as input to copy_pixel_row(), since it isn't
-   logically a parameter of the operation, but rather is private to
-   copy_pixel_row().  But it would be impractical to allocate and free
-   the storage with every call to copy_pixel_row().
-*/
-static xel * pnmbuffer;      /* Output buffer.  Input to pnm_writepnmrow() */
-
 static void
-copyPixelRow(JSAMPROW        const jpegbuffer,
-             unsigned int    const width, 
-             unsigned int    const samplesPerPixel, 
-             enum colorspace const colorSpace,
-             FILE *          const ofP,
-             int             const format,
-             xelval          const maxval) {
+convertPixelRow(JSAMPROW        const jpegbuffer,
+                unsigned int    const width,
+                unsigned int    const samplesPerPixel,
+                enum Colorspace const colorSpace,
+                int             const format,
+                xelval          const maxval,
+                xel *           const pnmbuffer) {
+/*----------------------------------------------------------------------------
+  Convert the pixels in 'jpegbuffer' from libjpeg format to libnetpbm
+  format in 'pnmbuffer'.
 
+  The row has 'width' pixels and the data in 'jpegbuffer' is formatted with
+  'samplesPerPixel' samples per pixel with colorspace 'colorSpace'.
+
+  The output Netpbm data is in format 'format' with maxval 'maxval'.
+-----------------------------------------------------------------------------*/
     JSAMPLE * ptr;
     unsigned int outputCursor;     /* Cursor into output buffer 'pnmbuffer' */
 
     ptr = &jpegbuffer[0];  /* Start at beginning of input row */
-    
+
     for (outputCursor = 0; outputCursor < width; ++outputCursor) {
         xel currentPixel;
         if (samplesPerPixel >= 3) {
-            const rgb_type * const rgb_p = read_rgb(ptr, colorSpace, maxval);
-            PPM_ASSIGN(currentPixel, rgb_p->r, rgb_p->g, rgb_p->b);
+            const rgb_type * const rgbP = read_rgb(ptr, colorSpace, maxval);
+            PPM_ASSIGN(currentPixel, rgbP->r, rgbP->g, rgbP->b);
         } else {
             PNM_ASSIGN1(currentPixel, GETJSAMPLE(*ptr));
         }
         ptr += samplesPerPixel;  /* move to next pixel of input */
         pnmbuffer[outputCursor] = currentPixel;
     }
-    pnm_writepnmrow(ofP, pnmbuffer, width, maxval, format, FALSE);
 }
 
 
 
 static void
-set_color_spaces(const J_COLOR_SPACE jpeg_color_space,
-                 int * const output_type_p,
-                 J_COLOR_SPACE * const out_color_space_p) {
+setColorSpaces(J_COLOR_SPACE   const jpegColorSpace,
+               int *           const outputTypeP,
+               J_COLOR_SPACE * const outColorSpaceP) {
 /*----------------------------------------------------------------------------
-   Decide what type of output (PPM or PGM) we shall generate and what 
+   Decide what type of output (PPM or PGM) we shall generate and what
    color space we must request from the JPEG decompressor, based on the
-   color space of the input JPEG image, 'jpeg_color_space'.
+   color space of the input JPEG image, 'jpegColorSpace'.
 
    Write to stderr a message telling which type we picked.
 
@@ -515,72 +518,72 @@ set_color_spaces(const J_COLOR_SPACE jpeg_color_space,
        CMYK or YCCK to RGB, but can translate YCCK to CMYK.
     */
 
-    switch (jpeg_color_space) {
+    switch (jpegColorSpace) {
     case JCS_UNKNOWN:
         pm_error("Input JPEG image has 'unknown' color space "
-                 "(JCS_UNKNOWN).\n"
+                 "(JCS_UNKNOWN).  "
                  "We cannot interpret this image.");
         break;
     case JCS_GRAYSCALE:
-        *output_type_p = PGM_TYPE;
-        *out_color_space_p = JCS_GRAYSCALE;
+        *outputTypeP    = PGM_TYPE;
+        *outColorSpaceP = JCS_GRAYSCALE;
         break;
     case JCS_RGB:
-        *output_type_p = PPM_TYPE;
-        *out_color_space_p = JCS_RGB;
+        *outputTypeP    = PPM_TYPE;
+        *outColorSpaceP = JCS_RGB;
         break;
     case JCS_YCbCr:
-        *output_type_p = PPM_TYPE;
-        *out_color_space_p = JCS_RGB;
+        *outputTypeP    = PPM_TYPE;
+        *outColorSpaceP = JCS_RGB;
         /* Design note:  We found this YCbCr->RGB conversion increases
            user mode CPU time by 2.5%.  2002.10.12
         */
         break;
     case JCS_CMYK:
-        *output_type_p = PPM_TYPE;
-        *out_color_space_p = JCS_CMYK;
+        *outputTypeP    = PPM_TYPE;
+        *outColorSpaceP = JCS_CMYK;
         break;
     case JCS_YCCK:
-        *output_type_p = PPM_TYPE;
-        *out_color_space_p = JCS_CMYK;
+        *outputTypeP    = PPM_TYPE;
+        *outColorSpaceP = JCS_CMYK;
         break;
     default:
-        pm_error("Internal error: unknown color space code %d passed "
-                 "to set_color_spaces().", jpeg_color_space);
+        pm_error("INTERNAL ERROR: unknown color space code %d passed "
+                 "to setColorSpaces().", jpegColorSpace);
     }
-    pm_message("WRITING %s FILE", 
-               *output_type_p == PPM_TYPE ? "PPM" : "PGM");
+    pm_message("WRITING %s FILE",
+               *outputTypeP == PPM_TYPE ? "PPM" : "PGM");
 }
 
 
 
 static const char *
-colorspace_name(const J_COLOR_SPACE jpeg_color_space) {
-
-    const char *retval;
-
-    switch(jpeg_color_space) {
-    case JCS_UNKNOWN: retval = "JCS_UNKNOWN"; break;
-    case JCS_GRAYSCALE: retval= "JCS_GRAYSCALE"; break;
-    case JCS_RGB: retval = "JCS_RGB"; break;
-    case JCS_YCbCr: retval = "JCS_YCbCr"; break;
-    case JCS_CMYK: retval = "JCS_CMYK"; break;
-    case JCS_YCCK: retval = "JCS_YCCK"; break;
-    default: retval = "invalid"; break;
+colorspaceName(J_COLOR_SPACE const jpegColorSpace) {
+
+    const char * retval;
+
+    switch(jpegColorSpace) {
+    case JCS_UNKNOWN:   retval = "JCS_UNKNOWN";   break;
+    case JCS_GRAYSCALE: retval = "JCS_GRAYSCALE"; break;
+    case JCS_RGB:       retval = "JCS_RGB";       break;
+    case JCS_YCbCr:     retval = "JCS_YCbCr";     break;
+    case JCS_CMYK:      retval = "JCS_CMYK";      break;
+    case JCS_YCCK:      retval = "JCS_YCCK";      break;
+    default:            retval = "invalid";       break;
     };
-    return(retval);
+    return retval;
 }
 
 
 
 static void
-print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){
+printVerboseInfoAboutHeader(struct jpeg_decompress_struct const cinfo){
 
     struct jpeg_marker_struct * markerP;
 
-    pm_message("input color space is %d (%s)\n", 
-               cinfo.jpeg_color_space, 
-               colorspace_name(cinfo.jpeg_color_space));
+    pm_message("input color space is %d (%s)",
+               cinfo.jpeg_color_space,
+               colorspaceName(cinfo.jpeg_color_space));
 
     /* Note that raw information about marker, including marker type code,
        was already printed by the jpeg library, because of the jpeg library
@@ -595,15 +598,15 @@ print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){
     for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) {
         if (markerP->marker == JPEG_COM)
             pm_message("Comment marker (COM):");
-        else if (markerP->marker >= JPEG_APP0 && 
+        else if (markerP->marker >= JPEG_APP0 &&
                  markerP->marker <= JPEG_APP0+15)
-            pm_message("Miscellaneous marker type APP%d:", 
+            pm_message("Miscellaneous marker type APP%d:",
                        markerP->marker - JPEG_APP0);
         else
             pm_message("Miscellaneous marker of unknown type (0x%X):",
                        markerP->marker);
-        
-        print_marker(*markerP);
+
+        printMarker(*markerP);
     }
 }
 
@@ -611,80 +614,84 @@ print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){
 
 static void
 beginJpegInput(struct jpeg_decompress_struct * const cinfoP,
-               const boolean verbose, 
-               const J_DCT_METHOD dct_method, 
-               const int max_memory_to_use, 
-               const boolean nosmooth) {
+               bool                            const verbose,
+               J_DCT_METHOD                    const dctMethod,
+               int                             const maxMemoryToUse,
+               bool                            const nosmooth) {
 /*----------------------------------------------------------------------------
    Read the JPEG header, create decompressor object (and
    allocate memory for it), set up decompressor.
 -----------------------------------------------------------------------------*/
     /* Read file header, set default decompression parameters */
-    jpeg_read_header(cinfoP, TRUE);
+    jpeg_read_header(cinfoP, true);
 
-    cinfoP->dct_method = dct_method;
-    if (max_memory_to_use != -1)
-        cinfoP->mem->max_memory_to_use = max_memory_to_use;
+    cinfoP->dct_method = dctMethod;
+    if (maxMemoryToUse != -1)
+        cinfoP->mem->max_memory_to_use = maxMemoryToUse;
     if (nosmooth)
-        cinfoP->do_fancy_upsampling = FALSE;
-    
+        cinfoP->do_fancy_upsampling = false;
+
 }
 
 
 
 static void
-print_comments(struct jpeg_decompress_struct const cinfo) {
-    
+printComments(struct jpeg_decompress_struct const cinfo) {
+
     struct jpeg_marker_struct * markerP;
 
     for (markerP = cinfo.marker_list;
-         markerP; markerP = markerP->next) 
+         markerP; markerP = markerP->next) {
         if (markerP->marker == JPEG_COM) {
             pm_message("COMMENT:");
-            print_marker(*markerP);
+            printMarker(*markerP);
         }
+    }
 }
 
 
 
 static void
-print_exif_info(struct jpeg_marker_struct const marker) {
+printExifInfo(struct jpeg_marker_struct const marker,
+              bool                      const wantTagTrace) {
 /*----------------------------------------------------------------------------
    Dump as informational messages the contents of the Jpeg miscellaneous
    marker 'marker', assuming it is an Exif header.
 -----------------------------------------------------------------------------*/
-    bool const wantTagTrace = false;
     exif_ImageInfo imageInfo;
     const char * error;
 
     assert(marker.data_length >= 6);
 
-    exif_parse(marker.data+6, marker.data_length-6, 
+    exif_parse(marker.data+6, marker.data_length-6,
                &imageInfo, wantTagTrace, &error);
 
     if (error) {
         pm_message("EXIF header is invalid.  %s", error);
         pm_strfree(error);
-    } else
+    } else {
         exif_showImageInfo(&imageInfo);
+
+        exif_terminateImageInfo(&imageInfo);
+    }
 }
 
 
 
-static boolean
-is_exif(struct jpeg_marker_struct const marker) {
+static bool
+isExif(struct jpeg_marker_struct const marker) {
 /*----------------------------------------------------------------------------
-   Return true iff the JPEG miscellaneous marker 'marker' is an Exif 
+   Return true iff the JPEG miscellaneous marker 'marker' is an Exif
    header.
 -----------------------------------------------------------------------------*/
-    boolean retval;
-    
+    bool retval;
+
     if (marker.marker == JPEG_APP0+1) {
         if (marker.data_length >=6 && memcmp(marker.data, "Exif", 4) == 0)
-            retval = TRUE;
-        else retval = FALSE;
+            retval = true;
+        else retval = false;
     }
-    else retval = FALSE;
+    else retval = false;
 
     return retval;
 }
@@ -692,69 +699,71 @@ is_exif(struct jpeg_marker_struct const marker) {
 
 
 static void
-dump_exif(struct jpeg_decompress_struct const cinfo) {
+dumpExif(struct jpeg_decompress_struct const cinfo,
+         bool                          const wantTrace) {
 /*----------------------------------------------------------------------------
    Dump as informational messages the contents of all EXIF headers in
    the image, interpreted.  An EXIF header is an APP1 marker.
 -----------------------------------------------------------------------------*/
     struct jpeg_marker_struct * markerP;
-    boolean found_one;
-
-    found_one = FALSE;  /* initial value */
+    bool foundOne;
 
-    for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) 
-        if (is_exif(*markerP)) {
+    for (markerP = cinfo.marker_list, foundOne = false;
+         markerP;
+         markerP = markerP->next) {
+        if (isExif(*markerP)) {
             pm_message("EXIF INFO:");
-            print_exif_info(*markerP);
-            found_one = TRUE;
+            printExifInfo(*markerP, wantTrace);
+            foundOne = true;
         }
-    if (!found_one)
+    }
+    if (!foundOne)
         pm_message("No EXIF info in image.");
 }
 
 
 
 static void
-save_exif(struct jpeg_decompress_struct const cinfo, 
-          const char *                  const exif_filespec) {
+saveExif(struct jpeg_decompress_struct const cinfo,
+         const char *                  const exifFileName) {
 /*----------------------------------------------------------------------------
-  Write the contents of the first Exif header in the image into the
-  file with filespec 'exif_filespec'.  Start with the two byte length
-  field.  If 'exif_filespec' is "-", write to standard output.
+  Write the contents of the first Exif header in the image into the file with
+  name 'exifFileName'.  Start with the two byte length field.  If
+  'exifFileName' is "-", write to standard output.
 
-  If there is no Exif header in the image, write just zero, as a two
-  byte pure binary integer.
+  If there is no Exif header in the image, write just zero, as a two byte pure
+  binary integer.
 -----------------------------------------------------------------------------*/
-    FILE * exif_file;
+    FILE * exifFileP;
     struct jpeg_marker_struct * markerP;
 
-    exif_file = pm_openw(exif_filespec);
+    exifFileP = pm_openw(exifFileName);
 
-    for (markerP = cinfo.marker_list; 
-         markerP && !is_exif(*markerP);
+    for (markerP = cinfo.marker_list;
+         markerP && !isExif(*markerP);
          markerP = markerP->next);
 
     if (markerP) {
-        pm_writebigshort(exif_file, markerP->data_length+2);
-        if (ferror(exif_file))
-            pm_error("Write of Exif header to %s failed on first byte.",
-                     exif_filespec);
+        pm_writebigshort(exifFileP, markerP->data_length+2);
+        if (ferror(exifFileP))
+            pm_error("Write of Exif header to file '%s' failed on first byte.",
+                     exifFileName);
         else {
             int rc;
 
-            rc = fwrite(markerP->data, 1, markerP->data_length, exif_file);
+            rc = fwrite(markerP->data, 1, markerP->data_length, exifFileP);
             if (rc != markerP->data_length)
                 pm_error("Write of Exif header to '%s' failed.  Wrote "
                          "length successfully, but then failed after "
-                         "%d characters of data.", exif_filespec, rc);
+                         "%d characters of data.", exifFileName, rc);
         }
     } else {
         /* There is no Exif header in the image */
-        pm_writebigshort(exif_file, 0);
-        if (ferror(exif_file))
-            pm_error("Write of Exif header file '%s' failed.", exif_filespec);
+        pm_writebigshort(exifFileP, 0);
+        if (ferror(exifFileP))
+            pm_error("Write of Exif header file '%s' failed.", exifFileName);
     }
-    pm_close(exif_file);
+    pm_close(exifFileP);
 }
 
 
@@ -762,25 +771,25 @@ save_exif(struct jpeg_decompress_struct const cinfo,
 static void
 tellDetails(struct jpeg_decompress_struct const cinfo,
             xelval                        const maxval,
-            int                           const output_type) {
+            int                           const outputType) {
 
-    print_verbose_info_about_header(cinfo);
+    printVerboseInfoAboutHeader(cinfo);
 
-    pm_message("Input image data precision = %d bits", 
+    pm_message("Input image data precision = %d bits",
                cinfo.data_precision);
     pm_message("Output file will have format %c%c "
-               "with max sample value of %d.", 
-               (char) (output_type/256), (char) (output_type % 256),
+               "with max sample value of %d.",
+               (char) (outputType/256), (char) (outputType % 256),
                maxval);
-}  
+}
 
 
 
-static enum colorspace
+static enum Colorspace
 computeColorSpace(struct jpeg_decompress_struct * const cinfoP,
-                  enum inklevel                   const inklevel) {
-    
-    enum colorspace colorSpace;
+                  enum Inklevel                   const inklevel) {
+
+    enum Colorspace colorSpace;
 
     if (cinfoP->out_color_space == JCS_GRAYSCALE)
         colorSpace = GRAYSCALE_COLORSPACE;
@@ -806,33 +815,45 @@ computeColorSpace(struct jpeg_decompress_struct * const cinfoP,
 
 static void
 convertRaster(struct jpeg_decompress_struct * const cinfoP,
-              enum colorspace                 const color_space,
+              enum Colorspace                 const colorspace,
               FILE *                          const ofP,
               xelval                          const format,
               unsigned int                    const maxval) {
-              
+/*----------------------------------------------------------------------------
+   Read the raster from the input and write it out in Netpbm format
+   to file *ofP, in format 'format', with maxval 'maxval'.
+-----------------------------------------------------------------------------*/
     JSAMPROW jpegbuffer;  /* Input buffer.  Filled by jpeg_scanlines() */
 
+    xel * pnmbuffer;      /* Output buffer */
+
     jpegbuffer = ((*cinfoP->mem->alloc_sarray)
                   ((j_common_ptr) cinfoP, JPOOL_IMAGE,
-                   cinfoP->output_width * cinfoP->output_components, 
+                   cinfoP->output_width * cinfoP->output_components,
                    (JDIMENSION) 1)
         )[0];
 
+    pnmbuffer = pnm_allocrow(cinfoP->output_width);
+
     while (cinfoP->output_scanline < cinfoP->output_height) {
         jpeg_read_scanlines(cinfoP, &jpegbuffer, 1);
-        if (ofP)
-            copyPixelRow(jpegbuffer, cinfoP->output_width, 
-                         cinfoP->out_color_components,
-                         color_space, ofP, format, maxval);
+        if (ofP) {
+            convertPixelRow(jpegbuffer, cinfoP->output_width,
+                            cinfoP->out_color_components,
+                            colorspace, format, maxval, pnmbuffer);
+
+            pnm_writepnmrow(ofP, pnmbuffer, cinfoP->output_width,
+                            maxval, format, false);
+        }
     }
+    pnm_freerow(pnmbuffer);
 }
 
 
 
 static void
-convertImage(FILE *                          const ofP, 
-             struct cmdlineInfo              const cmdline,
+convertImage(FILE *                          const ofP,
+             struct CmdlineInfo              const cmdline,
              struct jpeg_decompress_struct * const cinfoP) {
 
     int format;
@@ -840,23 +861,23 @@ convertImage(FILE *                          const ofP,
            or PGM_TYPE, which conveniently also pass as format values
            PPM_FORMAT and PGM_FORMAT.
         */
-    xelval maxval;  
+    xelval maxval;
         /* The maximum value of a sample (color component), both in the input
            and the output.
         */
-    enum colorspace color_space;
+    enum Colorspace colorspace;
         /* The color space of the pixels coming out of the JPEG decompressor */
 
-    beginJpegInput(cinfoP, cmdline.verbose, 
-                   cmdline.dct_method, 
-                   cmdline.max_memory_to_use, cmdline.nosmooth);
-                   
-    set_color_spaces(cinfoP->jpeg_color_space, &format,
-                     &cinfoP->out_color_space);
+    beginJpegInput(cinfoP, cmdline.verbose,
+                   cmdline.dctMethod,
+                   cmdline.maxMemoryToUse, cmdline.nosmooth);
+
+    setColorSpaces(cinfoP->jpeg_color_space, &format,
+                   &cinfoP->out_color_space);
 
     maxval = pm_bitstomaxval(cinfoP->data_precision);
 
-    if (cmdline.verbose) 
+    if (cmdline.verbose)
         tellDetails(*cinfoP, maxval, format);
 
     /* Calculate output image dimensions so we can allocate space */
@@ -868,22 +889,18 @@ convertImage(FILE *                          const ofP,
     if (ofP)
         /* Write pnm output header */
         pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height,
-                         maxval, format, FALSE);
+                         maxval, format, false);
 
-    pnmbuffer = pnm_allocrow(cinfoP->output_width);
-    
-    color_space = computeColorSpace(cinfoP, cmdline.inklevel);
-    
-    convertRaster(cinfoP, color_space, ofP, format, maxval);
+    colorspace = computeColorSpace(cinfoP, cmdline.inklevel);
+
+    convertRaster(cinfoP, colorspace, ofP, format, maxval);
 
     if (cmdline.comments)
-        print_comments(*cinfoP);
+        printComments(*cinfoP);
     if (cmdline.dumpexif)
-        dump_exif(*cinfoP);
-    if (cmdline.exif_filespec)
-        save_exif(*cinfoP, cmdline.exif_filespec);
-
-    pnm_freerow(pnmbuffer);
+        dumpExif(*cinfoP, cmdline.traceexif);
+    if (cmdline.exifFileName)
+        saveExif(*cinfoP, cmdline.exifFileName);
 
     /* Finish decompression and release decompressor memory. */
     jpeg_finish_decompress(cinfoP);
@@ -895,18 +912,18 @@ convertImage(FILE *                          const ofP,
 static void
 saveMarkers(struct jpeg_decompress_struct * const cinfoP) {
 
-    unsigned int app_type;
+    unsigned int appType;
     /* Get all the miscellaneous markers (COM and APPn) saved for our
        later access.
     */
     jpeg_save_markers(cinfoP, JPEG_COM, 65535);
-    for (app_type = 0; app_type <= 15; ++app_type) {
-        if (app_type == 0 || app_type == 14) {
+    for (appType = 0; appType <= 15; ++appType) {
+        if (appType == 0 || appType == 14) {
             /* The jpeg library uses APP0 and APP14 internally (see
                libjpeg.doc), so we don't mess with those.
             */
         } else
-            jpeg_save_markers(cinfoP, JPEG_APP0 + app_type, 65535);
+            jpeg_save_markers(cinfoP, JPEG_APP0 + appType, 65535);
     }
 }
 
@@ -914,10 +931,10 @@ saveMarkers(struct jpeg_decompress_struct * const cinfoP) {
 
 static void
 convertImages(FILE *                          const ofP,
-              struct cmdlineInfo              const cmdline,
+              struct CmdlineInfo              const cmdline,
               struct jpeg_decompress_struct * const cinfoP,
               struct sourceManager *          const sourceManagerP) {
-              
+
     if (cmdline.multiple) {
         unsigned int imageSequence;
         for (imageSequence = 0; dsDataLeft(sourceManagerP); ++imageSequence) {
@@ -943,19 +960,19 @@ convertImages(FILE *                          const ofP,
 
 
 int
-main(int argc, char **argv) {
+main(int argc, const char **argv) {
 
     FILE * ofP;
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     struct jpeg_decompress_struct cinfo;
     struct jpeg_error_mgr jerr;
     struct sourceManager * sourceManagerP;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
-    parseCommandLine(argc, argv, &cmdline);
+    parseCommandLine(argc, (char **)argv, &cmdline);
 
-    if (cmdline.exif_filespec && streq(cmdline.exif_filespec, "-"))
+    if (cmdline.exifFileName && streq(cmdline.exifFileName, "-"))
         /* He's got exif going to stdout, so there can be no image output */
         ofP = NULL;
     else
@@ -967,14 +984,14 @@ main(int argc, char **argv) {
     cinfo.err = jpeg_std_error(&jerr);
     jpeg_create_decompress(&cinfo);
 
-    if (cmdline.trace_level == 0 && cmdline.verbose) 
+    if (cmdline.traceLevel == 0 && cmdline.verbose)
         cinfo.err->trace_level = 1;
-    else 
-        cinfo.err->trace_level = cmdline.trace_level;
-    
+    else
+        cinfo.err->trace_level = cmdline.traceLevel;
+
     saveMarkers(&cinfo);
 
-    sourceManagerP = dsCreateSource(cmdline.input_filespec);
+    sourceManagerP = dsCreateSource(cmdline.inputFileName);
 
     cinfo.src = dsJpegSourceMgr(sourceManagerP);
 
@@ -985,14 +1002,17 @@ main(int argc, char **argv) {
     if (ofP) {
         int rc;
         rc = fclose(ofP);
-        if (rc == EOF) 
+        if (rc == EOF)
             pm_error("Error writing output file.  Errno = %s (%d).",
                      strerror(errno), errno);
     }
 
     dsDestroySource(sourceManagerP);
 
-    free(cmdline.input_filespec);
-  
+    free(cmdline.inputFileName);
+
     exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS);
 }
+
+
+
diff --git a/converter/other/pngx.c b/converter/other/pngx.c
index 8295b979..7840b2da 100644
--- a/converter/other/pngx.c
+++ b/converter/other/pngx.c
@@ -24,7 +24,7 @@ errorHandler(png_structp     const png_ptr,
 
     jmp_buf * jmpbufP;
 
-    /* this function, aside from the extra step of retrieving the "error
+    /* This function, aside from the extra step of retrieving the "error
        pointer" (below) and the fact that it exists within the application
        rather than within libpng, is essentially identical to libpng's
        default error handler.  The second point is critical:  since both
diff --git a/converter/ppm/hpcdtoppm/Makefile b/converter/ppm/hpcdtoppm/Makefile
index 5777a84f..9633017e 100644
--- a/converter/ppm/hpcdtoppm/Makefile
+++ b/converter/ppm/hpcdtoppm/Makefile
@@ -7,11 +7,29 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/config.mk
 
-all: hpcdtoppm
+SCRIPTS =  pcdovtoppm
+
+ifeq ($(file <hpcdtoppm-import/Makefile)x,x)
+  # The file does not exist, which means user did not augment the
+  # Netpbm source tree by adding hpcdtoppm source code.
+  #
+  # Therefore, we package the dummy 'hpcdtoppm' program that just tells the
+  # user how to get the real one.
+  #
+  # See README in this directory.
+  #
+  # (Note that empty file and nonexistent file look the same with
+  # $(file)).
+  SCRIPTS += hpcdtoppm
+else
+  SUBDIRS += hpcdtoppm-import
+endif
 
-SCRIPTS = hpcdtoppm pcdovtoppm
 MERGE_OBJECTS =
 
+.PHONY: all
+all: $(BINARIES) $(SUBDIRS:%=%/all)
+
 include $(SRCDIR)/common.mk
 
 install.bin install.merge: install.bin.local
diff --git a/converter/ppm/hpcdtoppm/README b/converter/ppm/hpcdtoppm/README
index a1c7c8c2..ec23ec1b 100644
--- a/converter/ppm/hpcdtoppm/README
+++ b/converter/ppm/hpcdtoppm/README
@@ -1,20 +1,25 @@
-This 'hpcdtoppm' directory is really just a placeholder for the real
-Hpcdtoppm source code.  
+The 'hpcdtoppm' code in this directory is just a dummy version of the program
+that tells you where to get the real code.
 
-The real Hpcdtoppm source code cannot be distributed on Sourceforge
-because a copyright holder does not license it in a way open enough to
-meet Sourceforge's requirements.
+The real "hpcdtoppm' source code cannot be distributed on Sourceforge because
+a copyright holder does not license it in a way open enough to meet
+Sourceforge's requirements.
 
-Therefore, the Netpbm maintainer distributes Hpcdtoppm via another channel
-and distributes this dummy directory in the main Netpbm source tree on 
-Sourceforge.  When you run the program named 'hpcdtoppm' in this directory,
-it tells you how to get the real Hpcdtoppm.
+Therefore, the Netpbm maintainer distributes 'hpcdtoppm' via another channel
+and distributes this dummy directory in the main Netpbm source tree on
+Sourceforge.
 
-When you get the real Hpcdtoppm tarball, just unpack it and replace
-this entire 'hpcdtoppm' directory with its contents.  Then build
-Netpbm normally.
+The code is at 
+
+  http://ibiblio.org/pub/Linux/apps/graphics/convert/hpcdtoppm-netpbm.tgz
+
+When you get the real 'hpcdtoppm' tarball, just unpack its contents into the
+'hpcdtoppm-import' subdirectory.  Then build Netpbm normally.  The build
+detects the presence of the code and builds and packages the real 'hpcdtoppm'
+program instead of the dummy one.
+
+Bear in mind when you get the real 'hpcdtoppm' that it is not as openly
+licensed as what's in the rest of the Netpbm source tree.  As you'll see in
+the license that comes with 'hpcdtoppm', you may _not_ sell it to someone
+else.)
 
-Bear in mind when you get the real Hpcdtoppm, that it is not as openly
-licensed as what's in the rest of the Netpbm source tree.  As you'll
-see in the license that comes with Hpcdtoppm, you may _not_ sell it to
-someone else.)
diff --git a/converter/ppm/hpcdtoppm/hpcdtoppm b/converter/ppm/hpcdtoppm/hpcdtoppm
index ec3f1763..2a61aa9d 100755
--- a/converter/ppm/hpcdtoppm/hpcdtoppm
+++ b/converter/ppm/hpcdtoppm/hpcdtoppm
@@ -8,17 +8,15 @@ The real 'hpcdtoppm' is a program that converts an image from Photo CD format
 to PPM format.  The program you are running now just issues the message you
 are reading.
 
-You can get a copy of Hpcdtoppm from 
-
-  http://metalab.unc.edu/pub/Linux/apps/graphics/convert/hpcdtoppm.linux.tar.gz
-
-and replace this stand-in program with the real one.
+Instructions for getting the 'hpcdtoppm' source code and building it into
+Netpbm are in the file converter/ppm/hpcdtoppm/README in the distributed
+Netpbm source tree.
 
 The point of this is that this stand-in 'hpcdtoppm' is distributed with Netpbm
-on Sourceforge.  Hpcdtoppm does not meet the requirements to be distributed on
-Sourceforge because a copyright holder does not permit people to sell copies.
-At one time, the real Hpcdtoppm was distributed with Netpbm on Sourceforge by
-mistake.
+on Sourceforge.  'hpcdtoppm' does not meet the requirements to be distributed
+on Sourceforge because a copyright holder does not permit people to sell
+copies.  At one time, the real 'hpcdtoppm' was distributed with Netpbm on
+Sourceforge by mistake.
 
 EOF
 
diff --git a/converter/ppm/hpcdtoppm/pcdovtoppm b/converter/ppm/hpcdtoppm/pcdovtoppm
index 1c6cb430..2a875690 100755
--- a/converter/ppm/hpcdtoppm/pcdovtoppm
+++ b/converter/ppm/hpcdtoppm/pcdovtoppm
@@ -144,7 +144,7 @@ tempdir=$(mktemp -d "${TMPDIR:-/tmp}/pcdovtoppm.XXXXXXXX") ||
     { echo "Could not create temporary file. Exiting." 1>&2; exit 1; }
 trap 'rm -rf $tempdir' 0
 
-tmpfile=$(tempfile -p pi -m 600)
+tmpfile=$(mktemp --tmpdir piXXXXXX)
 
 # Convert the PCD overview file to many PPM images
 if [ -f $1 ] ; then
diff --git a/doc/HISTORY b/doc/HISTORY
index ed39b795..0c2ce263 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,22 +4,25 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-23.03.23 BJH  Release 11.01.03
+23.03.25 BJH  Release 11.02.00
 
-              Fix 'jpegtopnm' build failure introduced in previous release.
+              jpegtopnm: Add -traceexif
 
-23.03.22 BJH  Release 11.01.02
+              pbmtextps: Add -asciihex, -ascii85.
+
+              pcdovtoppm: remove dependency on obsolete 'tempfile' program.
 
               jpegtopnm: Many fixes to -dumpexif.  Always broken.
               (-dumpexif was new in Netpbm 9.18 (September 2001))
 
-23.03.14 BJH  Release 11.01.01
-
               pamtopng: fix -chroma option: always rejected.  Always broken.
-              pamtopng was new in Netpbm 10.70 (June 2015).
+              (pamtopng was new in Netpbm 10.70 (June 2015)).
 
               pnmtopng: fix -rgb option: always rejected.  Always broken
-              -rgb was new in Netpbm 10.30 (October 2005).
+              (-rgb was new in Netpbm 10.30 (October 2005)).
+
+              build: change the way you add the separately distributed
+              'hpcdtoppm' code to the build.
 
 22.12.31 BJH  Release 11.01.00
 
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index a3e9124a..2a1173de 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -27,6 +27,7 @@
 
 #define _XOPEN_SOURCE 500
   /* Make sure popen() is in stdio.h, strdup() is in string.h */
+
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -41,6 +42,8 @@
 #include "pm_system.h"
 #include "pbm.h"
 
+enum InputFmt {INPUT_LITERAL, INPUT_ASCIIHEX, INPUT_ASCII85};
+
 static void
 validateFontName(const char * const name) {
 /*-----------------------------------------------------------------------------
@@ -74,8 +77,8 @@ validateFontName(const char * const name) {
 
 
 static void
-asciiHexEncode(char *          const inbuff,
-               char *          const outbuff) {
+asciiHexEncode(const char * const inbuff,
+               char *       const outbuff) {
 /*-----------------------------------------------------------------------------
   Convert the input text string to ASCII-Hex encoding.
 
@@ -86,69 +89,357 @@ asciiHexEncode(char *          const inbuff,
 
     unsigned int idx;
 
+    outbuff[0] = '<';
+
     for (idx = 0; inbuff[idx] != '\0'; ++idx) {
         unsigned int const item = (unsigned char) inbuff[idx];
 
-        outbuff[idx*2]   = hexits[item >> 4];
-        outbuff[idx*2+1] = hexits[item & 0xF];
+        outbuff[1 + idx * 2 + 0] = hexits[item >> 4];
+        outbuff[1 + idx * 2 + 1] = hexits[item & 0xF];
     }
 
-    outbuff[idx * 2] = '\0';
+    if (idx == 0)
+        pm_message("Empty input string");
+
+    outbuff[1 + idx * 2] = '>';
+    outbuff[1 + idx * 2 + 1] = '\0';
 }
 
 
 
 static void
-buildTextFromArgs(int           const argc,
-                  const char ** const argv,
-                  const char ** const asciiHexTextP) {
+failForInvalidChar(char         const c,
+                   const char * const type) {
+
+    if (c >= 0x32 || c < 0x7F)
+        pm_error("Invalid character '%c' in '%s' input string",   c, type);
+    else
+        pm_error("Invalid character 0x%02x in '%s' input string", c, type);
+}
+
+
+
+static void
+formatNonemptyAsciiHex(const char * const inbuff,
+                       char       * const outbuff,
+                       unsigned int const inLen) {
+
+    unsigned int inIdx, outIdx;
+        /* Cursors in input and output buffers, respectively */
+    unsigned int validCharCt;
+        /* Number of valid hex characters we've processed so far in input */
+    unsigned int startIdx, endIdx;
+        /* Limits of input buffer cursor ('inIdx') */
+
+     if (inbuff[0] == '<') {
+         startIdx = 1;
+         endIdx = inLen - 2;
+     } else {
+         startIdx = 0;
+         endIdx = inLen - 1;
+     }
+
+     validCharCt = 0;  /* No valid characters seen yet */
+
+     outIdx = 0;  /* Start at beginning of output buffer */
+
+     outbuff[outIdx++] = '<';
+
+     for (inIdx = startIdx; inIdx <= endIdx; ++inIdx) {
+         switch (inbuff[inIdx]) {
+         case '<':
+         case '>':
+             pm_error("Misplaced character '%c' in Ascii Hex input string",
+                      inbuff[inIdx]);
+           break;
+         case '\f':
+         case '\n':
+         case '\r':
+         case ' ':
+         case '\t':
+             /* ignore whitespace chars */
+             break;
+         default:
+             if ((inbuff [inIdx] >='0' && inbuff [inIdx] <='9') ||
+                 (inbuff [inIdx] >='A' && inbuff [inIdx] <='F') ||
+                 (inbuff [inIdx] >='a' && inbuff [inIdx] <='f')) {
+
+                 outbuff[outIdx++] = inbuff [inIdx];
+                 ++validCharCt;
+             } else
+                 failForInvalidChar(inbuff[inIdx], "Ascii Hex");
+             break;
+         }
+     }
+
+     if (validCharCt == 0)
+         pm_message("Empty Ascii Hex input string");
+     else if (validCharCt % 2 != 0)
+         pm_error("Number of characters in Ascii Hex input string "
+                  "is not even");
+
+     outbuff[outIdx++] = '>';
+     outbuff[outIdx++] = '\0';
+}
+
+
+
+static void
+formatAsciiHexString(const char * const inbuff,
+                     char       * const outbuff,
+                     unsigned int const inLen) {
 /*----------------------------------------------------------------------------
-   Build the array of text to be included in the Postscript program to
-   be rendered, from the arguments of this program.
-
-   We encode it in ASCII-Hex notation as opposed to using the plain text from
-   the command line because 1) the command line might have Postscript control
-   characters in it; and 2) the command line might have text in 8-bit or
-   multibyte code, but a Postscript program is supposed to be entirely
-   printable ASCII characters.
------------------------------------------------------------------------------*/
-    char * text;
+  Format the ASCII Hex input 'inbuff' as a Postscript ASCII Hex string,
+  e.g. "<313233>".  Input can be just the ASCII Hex (e.g. "313233") or already
+  formatted (e.g. "<313233>").  Input may also contain white space, which we
+  ignore -- our output never contains white space.  Though in Postscript, an
+  ASCII NUL character counts as white space, we consider it the end of the
+  input.
+
+  We consider white space outside of the <> delimiters to be an error.
+
+  Abort with error message if there is anything other than valid hex digits in
+  the ASCII hex string proper.  This is necessary to prevent code injection.
+----------------------------------------------------------------------------*/
+    if (inLen == 0 ||
+        (inLen == 2 && inbuff[0] == '<' && inbuff[inLen-1] == '>' )) {
+        pm_message("Empty Ascii Hex input string");
+        strncpy(outbuff, "<>", 3);
+    } else {
+        if (inbuff[0] == '<' && inbuff[inLen-1] != '>' )
+            pm_error("Ascii Hex input string starts with '<' "
+                     "but does not end with '>'");
+        else if (inbuff[0] != '<' && inbuff[inLen-1] == '>' )
+            pm_error("Ascii Hex input string ends with '>' "
+                     "but does not start with '<'");
+
+        formatNonemptyAsciiHex(inbuff, outbuff, inLen);
+    }
+}
+
+
+
+static void
+formatNonemptyAscii85(const char * const inbuff,
+                      char       * const outbuff,
+                      unsigned int const inLen) {
+
+    unsigned int inIdx, outIdx;
+        /* Cursors in input and output buffers, respectively */
+    unsigned int seqPos;
+        /* Position in 5-character Ascii-85 sequence where 'inIdx' points */
+    unsigned int startIdx, endIdx;
+        /* Limits of input buffer cursor ('inIdx') */
+
+    if (inLen > 4 && inbuff[0] == '<' && inbuff[1] == '~' &&
+        inbuff[inLen-2] == '~' && inbuff[inLen-1] == '>') {
+        startIdx = 2;
+        endIdx = inLen - 3;
+    } else {
+        startIdx = 0;
+        endIdx = inLen - 1;
+    }
+
+    seqPos = 0;  /* No 5-character Ascii-85 sequence encountered yet */
+    outIdx = 0;  /* Start filling output buffer from beginning */
+
+    outbuff[outIdx++] = '<';
+    outbuff[outIdx++] = '~';
+
+    for (inIdx = startIdx; inIdx <= endIdx; ++inIdx) {
+      switch (inbuff[inIdx]) {
+      case '~':
+          pm_error("Misplaced character '~' in Ascii 85 input string");
+          break;
+      case '\f':
+      case '\n':
+      case '\r':
+      case ' ':
+      case '\t':
+          break;
+      case 'z':
+          /* z extension */
+          if (seqPos > 0)
+              pm_error("Special 'z' character appears in the middle of a "
+                       "5-character Ascii-85 sequence, which is invalid");
+          else
+              outbuff[outIdx++] = inbuff[inIdx];
+          break;
+        default:
+          /* valid Ascii 85 char */
+          if (inbuff [inIdx] >='!' && inbuff [inIdx] <='u') {
+              outbuff[outIdx++] = inbuff[inIdx];
+              seqPos = (seqPos + 1) % 5;
+          } else
+              failForInvalidChar(inbuff[inIdx], "Ascii 85");
+          break;
+      }
+    }
+
+    if (outIdx == 2) {
+        pm_message("Empty Ascii 85 input string");
+    }
+
+    outbuff[outIdx++]   = '~';
+    outbuff[outIdx++] = '>';
+    outbuff[outIdx++] = '\0';
+}
+
+
+
+static void
+formatAscii85String(const char * const inbuff,
+                    char       * const outbuff,
+                    unsigned int const inLen) {
+/*----------------------------------------------------------------------------
+  Format the Ascii-85 input 'inbuff' as a Postscript Ascii-85 string,
+  e.g. "<~313233~>".  Input can be just the Ascii-85 (e.g. "313233") or
+  already formatted (e.g. "<~313233~>").  Input may also contain white space,
+  which we ignore -- our output never contains white space.  Though in
+  Postscript, an ASCII NUL character counts as white space, we consider it the
+  end of the input.
+
+  We consider white space outside of the <~~> delimiters to be an error.
+
+  Abort with error message if we encounter anything other than valid Ascii-85
+  encoding characters in the string proper.  Note that the Adobe variant
+  does not support the "y" extention.
+----------------------------------------------------------------------------*/
+    if (inLen == 0 || (inLen == 4 && strncmp (inbuff, "<~~>", 4) == 0)) {
+        pm_message("Empty Ascii 85 input string");
+        strncpy(outbuff,"<~~>", 5);
+    } else {
+        if (inLen >= 2) {
+            if (inbuff[0] == '<' && inbuff[1] == '~' &&
+                (inLen < 4 || inbuff[inLen-2] != '~'
+                 || inbuff[inLen-1] != '>' ))
+                pm_error("Ascii 85 input string starts with '<~' "
+                         "but does not end with '~>'");
+            else if (inbuff[inLen-2] == '~' && inbuff[inLen-1] == '>' &&
+                     (inLen < 4 || inbuff[0] != '<' || inbuff[1] != '~'))
+                pm_error("Ascii 85 input string ends with '~>' "
+                         "but does not start with '<~'");
+        }
+        formatNonemptyAscii85(inbuff, outbuff, inLen);
+    }
+}
+
+
+
+static void
+combineArgs(int            const argc,
+            const char **  const argv,
+            const char **  const textP,
+            unsigned int * const textSizeP) {
+
     unsigned int totalTextSize;
+    char * text;
+    size_t * argSize;
     unsigned int i;
+    size_t idx;
+
+    MALLOCARRAY_NOFAIL(argSize, argc);
+        /* argv[0] not accessed; argSize[0] not used */
+
+    for (i = 1, totalTextSize = 0; i < argc; ++i) {
+        argSize[i] = strlen(argv[i]);
+        totalTextSize += argSize[i];
+    }
+
+    totalTextSize = totalTextSize + (argc - 2);  /* adjust for spaces */
+
+    MALLOCARRAY(text, totalTextSize + 1); /* add one for \0 at end */
+    if (text == NULL)
+        pm_error("out of memory allocating buffer for "
+                 "%u characters of text", totalTextSize);
+
+    strncpy(text, argv[1], argSize[1]);
+    for (i = 2, idx = argSize[1]; i < argc; ++i) {
+        text[idx++] = ' ';
+        strncpy(&text[idx], argv[i], argSize[i]);
+        idx += argSize[i];
+    }
+
+    assert(idx == totalTextSize);
+
+    text[idx++] = '\0';
+
+    assert(strlen(text) == totalTextSize);
+
+    *textP     = text;
+    *textSizeP = totalTextSize;
+
+    free(argSize);
+}
+
 
-    text = strdup("");
-    totalTextSize = 1;
+
+static void
+buildTextFromArgs(int           const argc,
+                  const char ** const argv,
+                  const char ** const inputTextP,
+                  enum InputFmt const inputFmt) {
+/*----------------------------------------------------------------------------
+   Build the string of text to be included in the Postscript program to be
+   rendered, from the arguments of this program.
+
+   We encode it in either ASCII-Hex or ASCII-85 as opposed to using the plain
+   text from the command line because 1) the command line might have
+   Postscript control characters in it; and 2) the command line might have
+   text in 8-bit or multibyte code, but a Postscript program is supposed to be
+   entirely printable ASCII characters.
+
+   Official Postscript specifications have a limit on the length of a program
+   line, which means there is a limit on the length of text string such as we
+   generate.  But we don't worry about that because Ghostscript actually
+   accepts very long program lines.
+-----------------------------------------------------------------------------*/
+    const char * text;
+        /* All the arguments ('argv') concatenated */
+    unsigned int textSize;
+        /* Length of 'text' */
 
     if (argc-1 < 1)
         pm_error("No text");
 
-    for (i = 1; i < argc; ++i) {
-        if (i > 1) {
-            totalTextSize += 1;
-            text = realloc(text, totalTextSize);
-            if (text == NULL)
-                pm_error("out of memory");
-            strcat(text, " ");
-        }
-        totalTextSize += strlen(argv[i]);
-        text = realloc(text, totalTextSize);
-        if (text == NULL)
-            pm_error("out of memory");
-        strcat(text, argv[i]);
-    }
+    combineArgs(argc, argv, &text, &textSize);
 
-    {
+    switch (inputFmt) {
+    case INPUT_LITERAL: {
         char * asciiHexText;
 
-        MALLOCARRAY(asciiHexText, totalTextSize * 2);
-
+        MALLOCARRAY(asciiHexText, textSize * 2 + 3);
         if (!asciiHexText)
             pm_error("Unable to allocate memory for hex encoding of %u "
-                     "characters of text", totalTextSize);
+                     "characters of text", textSize);
 
         asciiHexEncode(text, asciiHexText);
-        *asciiHexTextP = asciiHexText;
+        *inputTextP = asciiHexText;
+    } break;
+    case INPUT_ASCIIHEX: {
+        char * asciiHexText;
+
+        MALLOCARRAY(asciiHexText, textSize + 3);
+        if (!asciiHexText)
+            pm_error("Unable to allocate memory for hex encoding of %u "
+                     "characters of text", textSize);
+
+        formatAsciiHexString(text, asciiHexText, textSize);
+        *inputTextP = asciiHexText;
+    } break;
+    case INPUT_ASCII85: {
+        char * ascii85Text;
+
+        MALLOCARRAY(ascii85Text, textSize + 5);
+        if (!ascii85Text)
+            pm_error("Unable to allocate memory for hex encoding of %u "
+                     "characters of text", textSize);
+
+        formatAscii85String(text, ascii85Text, textSize);
+        *inputTextP = ascii85Text;
+    } break;
     }
+
     pm_strfree(text);
 }
 
@@ -158,20 +449,23 @@ struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    unsigned int res;
-    float        fontsize;
-    const char * font;
-    float        stroke;
-    float        ascent;
-    float        descent;
-    float        leftmargin;
-    float        rightmargin;
-    float        topmargin;
-    float        bottommargin;
-    unsigned int pad;
-    unsigned int verbose;
-    unsigned int dump;
-    const char * text;
+    unsigned int  res;
+    float         fontsize;
+    const char *  font;
+    float         stroke;
+    float         ascent;
+    float         descent;
+    float         leftmargin;
+    float         rightmargin;
+    float         topmargin;
+    float         bottommargin;
+    unsigned int  pad;
+    unsigned int  verbose;
+    unsigned int  dump;
+    const char *  text;
+        /* Text to render, in Postscript format, either Ascii-hex
+           (e.g. <313233>) or Ascii-85 (e.g. <~aBc-~>)
+        */
 };
 
 
@@ -192,6 +486,7 @@ parseCommandLine(int argc, const char ** argv,
     unsigned int cropSpec, ascentSpec, descentSpec;
     unsigned int leftmarginSpec, rightmarginSpec;
     unsigned int topmarginSpec, bottommarginSpec;
+    unsigned int asciihexSpec, ascii85Spec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -220,6 +515,10 @@ parseCommandLine(int argc, const char ** argv,
             NULL,                    &cropSpec,                 0);
     OPTENT3(0, "pad",           OPT_FLAG,
             NULL,                    &cmdlineP->pad,            0);
+    OPTENT3(0, "asciihex",      OPT_FLAG,
+            NULL,                    &asciihexSpec,             0);
+    OPTENT3(0, "ascii85",       OPT_FLAG,
+            NULL,                    &ascii85Spec,              0);
     OPTENT3(0, "verbose",       OPT_FLAG,
             NULL,                    &cmdlineP->verbose,        0);
     OPTENT3(0, "dump-ps",       OPT_FLAG,
@@ -236,8 +535,6 @@ parseCommandLine(int argc, const char ** argv,
     cmdlineP->leftmargin  = 0;
     cmdlineP->topmargin   = 0;
     cmdlineP->bottommargin = 0;
-    cropSpec       = FALSE;
-    cmdlineP->pad  = FALSE;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -264,7 +561,7 @@ parseCommandLine(int argc, const char ** argv,
     if (cmdlineP->bottommargin <0)
         pm_error("-bottommargin must not be negative");
 
-    if (cropSpec == TRUE) {
+    if (cropSpec) {
         if (ascentSpec || descentSpec ||
             leftmarginSpec || rightmarginSpec ||
             topmarginSpec || bottommarginSpec ||
@@ -280,8 +577,24 @@ parseCommandLine(int argc, const char ** argv,
              cmdlineP->leftmargin = cmdlineP->fontsize / 2;
     }
 
-    buildTextFromArgs(argc, argv, &cmdlineP->text);
+    {
+        enum InputFmt inputFmt;
+
+        if (asciihexSpec) {
+            if (ascii85Spec)
+                pm_error("You cannot specify both -asciihex and -ascii85");
+            else
+                inputFmt = INPUT_ASCIIHEX;
+        } else if (ascii85Spec)
+            inputFmt = INPUT_ASCII85;
+        else
+            inputFmt = INPUT_LITERAL;
+
+        if (argc-1 < 1)
+            pm_error("No text");
 
+        buildTextFromArgs(argc, argv, &cmdlineP->text, inputFmt);
+    }
     free(option_def);
 }
 
@@ -330,7 +643,7 @@ postscriptProgram(struct CmdlineInfo const cmdline) {
         "/FindFont {/%s findfont} def\n"
         "/fontsize %f def\n"
         "/pensize %f def\n"
-        "/textstring <%s> def\n"
+        "/textstring %s def\n"
         "/ascent %f def\n"
         "/descent %f def\n"
         "/leftmargin %f def\n"
@@ -391,6 +704,13 @@ postscriptProgram(struct CmdlineInfo const cmdline) {
     const char * retval;
     const char * psVariable;
 
+    /* According to Adobe, maximum line length in a Postscript program is
+       255 characters excluding the newline.  The following may generated a
+       line longer than that if 'cmdline.text' or 'cmdline.font' is long.
+       However, Ghostscript accepts much longer lines, so we don't worry
+       about that.
+    */
+
     pm_asprintf(&psVariable, psTemplate, cmdline.font,
                 cmdline.fontsize, cmdline.stroke, cmdline.text,
                 cmdline.ascent, cmdline.descent,
diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c
index cc8f165e..00f9c341 100644
--- a/lib/util/shhopt.c
+++ b/lib/util/shhopt.c
@@ -92,51 +92,71 @@ optStructCount(const optEntry opt[])
 
 
 
-static int
-optMatch(optEntry     const opt[],
-         const char * const s,
-         int          const lng) {
+enum Shortlong {SL_SHORT, SL_LONG};
+
+
+static void
+optMatch(optEntry       const opt[],
+         const char *   const targetOpt,
+         enum Shortlong const shortLong,
+         bool *         const foundP,
+         int *          const optIndexP) {
 /*------------------------------------------------------------------------
  |  FUNCTION      Find a matching option.
  |
- |  INPUT         opt     array of possible options.
- |                s       string to match, without `-' or `--'.
- |                lng     match long option, otherwise short.
+ |  INPUT         opt        array of valid option names.
+ |                targetOpt  option string to match, without `-' or `--'.
+ |                           e.g. "verbose" or "height=5"
+ |                shortLong  whether to match short option or long
  |
- |  RETURNS       Index to the option if found, -1 if not found.
+ |  RETURNS       *foundP     there is a matching option class the table
+ !                *optIndexP  index in the option class table
+ |                            meaningless if *foundP is false
  |
  |  DESCRIPTION   Short options are matched from the first character in
  |                the given string.
+ |
+ |                Where multiple entries in opt[] match, return the first.
  */
 
-    unsigned int const nopt = optStructCount(opt);
+    unsigned int const optCt = optStructCount(opt);
 
     unsigned int q;
     unsigned int matchlen;
-    const char * p;
+    bool found;
+    unsigned int optIndex;
 
-    matchlen = 0;  /* initial value */
-
-    if (lng) {
-        if ((p = strchr(s, '=')) != NULL)
-            matchlen = p - s;
+    if (shortLong == SL_LONG) {
+        const char * const equalPos = strchr(targetOpt, '=');
+        if (equalPos)
+            matchlen = equalPos - &targetOpt[0];
         else
-            matchlen = strlen(s);
-    }
-    for (q = 0; q < nopt; ++q) {
-        if (lng) {
+            matchlen = strlen(targetOpt);
+    } else
+        matchlen = 0;
+
+    for (q = 0, found = false; q < optCt && !found; ++q) {
+        switch (shortLong) {
+        case SL_LONG: {
             if (opt[q].longName) {
-                if (strncmp(s, opt[q].longName, matchlen) == 0)
-                    return q;
+                if (strneq(targetOpt, opt[q].longName, matchlen)) {
+                    found = true;
+                    optIndex = q;
+                }
             }
-        } else {
+        }
+        case SL_SHORT: {
             if (opt[q].shortName) {
-                if (s[0] == opt[q].shortName)
-                    return q;
+                if (targetOpt[0] == opt[q].shortName) {
+                    found = true;
+                    optIndex = q;
+                }
             }
         }
+        }
     }
-    return -1;
+    *foundP    = found;
+    *optIndexP = optIndex;
 }
 
 
@@ -556,13 +576,12 @@ pm_optSetFatalFunc(void (*f)(const char *, ...)) {
 void
 pm_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;
+    int ai;        /* argv index. */
+    int optarg;    /* argv index of option argument, or -1 if none. */
+    int done;
+    char * arg;      /* Pointer to argument to an option. */
+    char * o;        /* pointer to an option character */
+    char * p;
 
     optEntry *opt_table;  /* malloc'ed array */
 
@@ -579,7 +598,7 @@ pm_optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
          *  "--" indicates that the rest of the argv-array does not
          *  contain options.
          */
-        if (strcmp(argv[ai], "--") == 0) {
+        if (streq(argv[ai], "--")) {
             argvRemove(argc, argv, ai);
             break;
         }
@@ -587,10 +606,13 @@ pm_optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
         if (allowNegNum && argv[ai][0] == '-' && ISDIGIT(argv[ai][1])) {
             ++ai;
             continue;
-        } else if (strncmp(argv[ai], "--", 2) == 0) {
+        } else if (strneq(argv[ai], "--", 2)) {
+            bool found;
+            int mi;
             /* long option */
             /* find matching option */
-            if ((mi = optMatch(opt_table, argv[ai] + 2, 1)) < 0)
+            optMatch(opt_table, argv[ai] + 2, SL_LONG, &found, &mi);
+            if (!found)
                 optFatal("unrecognized option `%s'", argv[ai]);
 
             /* possibly locate the argument to this option. */
@@ -629,8 +651,11 @@ pm_optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
             done = 0;
             optarg = -1;
             while (*o && !done) {
+                bool found;
+                int mi;
                 /* find matching option */
-                if ((mi = optMatch(opt_table, o, 0)) < 0)
+                optMatch(opt_table, o, SL_SHORT, &found, &mi);
+                if (!found)
                     optFatal("unrecognized option `-%c'", *o);
 
                 /* does this option take an argument? */
@@ -679,7 +704,6 @@ parse_short_option_token(char *argv[], const int argc, const int ai,
 -----------------------------------------------------------------------------*/
     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
@@ -691,8 +715,11 @@ parse_short_option_token(char *argv[], const int argc, const int ai,
     o = argv[ai] + 1;
     processed_arg = 0;  /* initial value */
     while (*o && !processed_arg) {
+        bool found;
+        int mi;   /* index into option table */
 		/* find matching option */
-		if ((mi = optMatch(opt_table, o, 0)) < 0)
+		optMatch(opt_table, o, SL_SHORT, &found, &mi);
+		if (!found)
 		    optFatal("unrecognized option `-%c'", *o);
 
 		/* does this option take an argument? */
@@ -721,7 +748,7 @@ static void
 fatalUnrecognizedLongOption(const char * const optionName,
                             optEntry     const optTable[]) {
 
-    unsigned int const nopt = optStructCount(optTable);
+    unsigned int const optCt = optStructCount(optTable);
 
     unsigned int q;
 
@@ -730,7 +757,7 @@ fatalUnrecognizedLongOption(const char * const optionName,
     optList[0] = '\0';  /* initial value */
 
     for (q = 0;
-         q < nopt && strlen(optList) + 1 <= sizeof(optList);
+         q < optCt && strlen(optList) + 1 <= sizeof(optList);
          ++q) {
 
         const optEntry * const optEntryP = &optTable[q];
@@ -777,6 +804,7 @@ parse_long_option(char *   const argv[],
          "=".  NULL if no "=" in the token.
          */
     char *arg;     /* The argument of an option; NULL if none */
+    bool found;
     int mi;    /* index into option table */
 
     /* The current token is an option, and its name starts at
@@ -784,7 +812,8 @@ parse_long_option(char *   const argv[],
     */
     *tokens_consumed_p = 1;  /* initial assumption */
     /* find matching option */
-    if ((mi = optMatch(opt_table, &argv[ai][namepos], 1)) < 0)
+    optMatch(opt_table, &argv[ai][namepos], SL_LONG, &found, &mi);
+    if (!found)
         fatalUnrecognizedLongOption(argv[ai], opt_table);
 
     /* possibly locate the argument to this option. */
diff --git a/other/pnmcolormap.c b/other/pnmcolormap.c
index f10cc15c..fbe85d4e 100644
--- a/other/pnmcolormap.c
+++ b/other/pnmcolormap.c
@@ -38,19 +38,39 @@ enum MethodForRep {REP_CENTER_BOX, REP_AVERAGE_COLORS, REP_AVERAGE_PIXELS};
 enum MethodForSplit {SPLIT_MAX_PIXELS, SPLIT_MAX_COLORS, SPLIT_MAX_SPREAD};
 
 struct Box {
-    unsigned int index;
+/*----------------------------------------------------------------------------
+   A box contains an extent of a color frequency table, i.e. the colors
+   with some consecutive index values in the color frequency table.
+-----------------------------------------------------------------------------*/
+    unsigned int startIndex;
+        /* First index in the extent */
     unsigned int colorCt;
+        /* Size of the extent (Number of colors in it -- at least 1) */
     unsigned int sum;
+        /* Number of pixels of all colors in the extent */
     unsigned int maxdim;
-        /* which dimension has the largest spread.  RGB plane number. */
+        /* Which dimension has the largest spread.  RGB plane number. */
+        /* Meaningless if box contains only 1 color */
     sample       spread;
         /* spread in dimension 'maxdim' */
+        /* Meaningless if box contains only 1 color */
 };
 
 struct BoxVector {
+    tupletable2 colorFreqTable;
+        /* The colors and their frequencies (number of pixels in the image of
+           that color), ordered into consecutive boxes, as defined by 'box'.
+        */
+    unsigned int colorDepth;
+        /* Number of planes in the tuples of 'colorFreqTable' */
     struct Box * box;  /* malloc'ed array */
+        /* An array of boxes that contain consecutive extents of
+           'colorFreqTable'.  The list covers the entire table.
+        */
     unsigned int boxCt;
+        /* Number of boxes in the above list */
     unsigned int capacity;
+        /* Number of boxes the array is capable of containing */
 };
 
 struct CmdlineInfo {
@@ -70,13 +90,14 @@ struct CmdlineInfo {
     unsigned int sort;
     unsigned int square;
     unsigned int verbose;
+    unsigned int debug;
 };
 
 
 
 static void
 parseCommandLine (int argc, const char ** argv,
-                  struct CmdlineInfo *cmdlineP) {
+                  struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.
@@ -120,9 +141,11 @@ parseCommandLine (int argc, const char ** argv,
     OPTENT3(0, "sort",     OPT_FLAG,   NULL,
             &cmdlineP->sort,       0 );
     OPTENT3(0, "square",   OPT_FLAG,   NULL,
-            &cmdlineP->square,       0 );
+            &cmdlineP->square,     0 );
     OPTENT3(0, "verbose",   OPT_FLAG,   NULL,
-            &cmdlineP->verbose,       0 );
+            &cmdlineP->verbose,    0 );
+    OPTENT3(0, "debug",     OPT_FLAG,   NULL,
+            &cmdlineP->debug,      0 );
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -417,7 +440,7 @@ computeBoxSpread(const struct Box *    const boxP,
     MALLOCARRAY_NOFAIL(minval, depth);
     MALLOCARRAY_NOFAIL(maxval, depth);
 
-    findBoxBoundaries(colorFreqTable, depth, boxP->index, boxP->colorCt,
+    findBoxBoundaries(colorFreqTable, depth, boxP->startIndex, boxP->colorCt,
                       minval, maxval);
 
     switch (methodForLargest) {
@@ -460,15 +483,18 @@ newBoxVector(tupletable2           const colorFreqTable,
 
     struct BoxVector boxVector;
 
+    boxVector.colorFreqTable = colorFreqTable;
+    boxVector.colorDepth     = depth;
+
     MALLOCARRAY(boxVector.box, capacity);
 
     if (!boxVector.box)
         pm_error("out of memory allocating box vector table");
 
     /* Set up the initial box. */
-    boxVector.box[0].index   = 0;
-    boxVector.box[0].colorCt = colorCt;
-    boxVector.box[0].sum     = sum;
+    boxVector.box[0].startIndex = 0;
+    boxVector.box[0].colorCt    = colorCt;
+    boxVector.box[0].sum        = sum;
 
     computeBoxSpread(&boxVector.box[0], colorFreqTable, depth,
                      methodForLargest,
@@ -602,8 +628,6 @@ averagePixels(int          const boxStart,
 static tupletable2
 colormapFromBv(unsigned int      const colorCt,
                struct BoxVector  const boxVector,
-               tupletable2       const colorFreqTable,
-               unsigned int      const depth,
                enum MethodForRep const methodForRep) {
     /*
     ** Ok, we've got enough boxes.  Now choose a representative color for
@@ -616,26 +640,26 @@ colormapFromBv(unsigned int      const colorCt,
     tupletable2 colormap;
     unsigned int boxIdx;
 
-    colormap = newColorMap(colorCt, depth);
+    colormap = newColorMap(colorCt, boxVector.colorDepth);
 
     for (boxIdx = 0; boxIdx < boxVector.boxCt; ++boxIdx) {
         switch (methodForRep) {
         case REP_CENTER_BOX:
-            centerBox(boxVector.box[boxIdx].index,
+            centerBox(boxVector.box[boxIdx].startIndex,
                       boxVector.box[boxIdx].colorCt,
-                      colorFreqTable, depth,
+                      boxVector.colorFreqTable, boxVector.colorDepth,
                       colormap.table[boxIdx]->tuple);
             break;
         case REP_AVERAGE_COLORS:
-            averageColors(boxVector.box[boxIdx].index,
+            averageColors(boxVector.box[boxIdx].startIndex,
                           boxVector.box[boxIdx].colorCt,
-                          colorFreqTable, depth,
+                          boxVector.colorFreqTable, boxVector.colorDepth,
                           colormap.table[boxIdx]->tuple);
             break;
         case REP_AVERAGE_PIXELS:
-            averagePixels(boxVector.box[boxIdx].index,
+            averagePixels(boxVector.box[boxIdx].startIndex,
                           boxVector.box[boxIdx].colorCt,
-                          colorFreqTable, depth,
+                          boxVector.colorFreqTable, boxVector.colorDepth,
                           colormap.table[boxIdx]->tuple);
             break;
         default:
@@ -651,20 +675,17 @@ colormapFromBv(unsigned int      const colorCt,
 static void
 splitBox(struct BoxVector *    const boxVectorP,
          unsigned int          const boxIdx,
-         tupletable2           const colorFreqTable,
-         unsigned int          const depth,
          enum MethodForLargest const methodForLargest,
          enum MethodForSplit   const methodForSplit) {
 /*----------------------------------------------------------------------------
-   Split Box 'boxIdx' in the box vector 'boxVector' (so that bv contains one
-   more box than it did as input).  Split it so that each new box represents
-   about half of the pixels in the distribution given by 'colorFreqTable' for
-   the colors in the original box, but with distinct colors in each of the two
-   new boxes.
+   Split Box 'boxIdx' in the box vector 'boxVector' (so that 'boxVector'
+   contains one more box than it did as input).  Split it so that each new box
+   represents about half of the pixels in the image for the colors in the
+   original box, but with distinct colors in each of the two new boxes.
 
    Assume the box contains at least two colors.
 -----------------------------------------------------------------------------*/
-    unsigned int const boxStart = boxVectorP->box[boxIdx].index;
+    unsigned int const boxStart = boxVectorP->box[boxIdx].startIndex;
     unsigned int const boxSize  = boxVectorP->box[boxIdx].colorCt;
     unsigned int const sum      = boxVectorP->box[boxIdx].sum;
 
@@ -682,8 +703,8 @@ splitBox(struct BoxVector *    const boxVectorP,
        parameter to compareplane(), which is called by qsort().
     */
     compareplanePlane = boxVectorP->box[boxIdx].maxdim;
-    qsort((char*) &colorFreqTable.table[boxStart], boxSize,
-          sizeof(colorFreqTable.table[boxStart]),
+    qsort((char*) &boxVectorP->colorFreqTable.table[boxStart], boxSize,
+          sizeof(boxVectorP->colorFreqTable.table[boxStart]),
           compareplane);
 
     {
@@ -692,9 +713,10 @@ splitBox(struct BoxVector *    const boxVectorP,
         */
         unsigned int i;
 
-        lowerSum = colorFreqTable.table[boxStart]->value; /* initial value */
+        lowerSum = boxVectorP->colorFreqTable.table[boxStart]->value;
+            /* initial value */
         for (i = 1; i < boxSize - 1 && lowerSum < sum/2; ++i) {
-            lowerSum += colorFreqTable.table[boxStart + i]->value;
+            lowerSum += boxVectorP->colorFreqTable.table[boxStart + i]->value;
         }
         medianIndex = i;
     }
@@ -704,16 +726,18 @@ splitBox(struct BoxVector *    const boxVectorP,
 
         oldBoxP->colorCt = medianIndex;
         oldBoxP->sum     = lowerSum;
-        computeBoxSpread(oldBoxP, colorFreqTable, depth, methodForLargest,
+        computeBoxSpread(oldBoxP, boxVectorP->colorFreqTable,
+                         boxVectorP->colorDepth, methodForLargest,
                          &oldBoxP->maxdim, &oldBoxP->spread);
     }
     {
         struct Box * const newBoxP = &boxVectorP->box[boxVectorP->boxCt];
 
-        newBoxP->index   = boxStart + medianIndex;
-        newBoxP->colorCt = boxSize - medianIndex;
-        newBoxP->sum     = sum - lowerSum;
-        computeBoxSpread(newBoxP, colorFreqTable, depth, methodForLargest,
+        newBoxP->startIndex = boxStart + medianIndex;
+        newBoxP->colorCt    = boxSize - medianIndex;
+        newBoxP->sum        = sum - lowerSum;
+        computeBoxSpread(newBoxP, boxVectorP->colorFreqTable,
+                         boxVectorP->colorDepth, methodForLargest,
                          &newBoxP->maxdim, &newBoxP->spread);
         ++boxVectorP->boxCt;
     }
@@ -724,19 +748,56 @@ splitBox(struct BoxVector *    const boxVectorP,
 
 
 static void
+reportBoxVector(struct BoxVector const boxVector) {
+
+    unsigned int i;
+
+    pm_message("All colors of image, sorted into %u boxes:", boxVector.boxCt);
+
+    for (i = 0; i < boxVector.boxCt; ++i) {
+        const struct Box * const boxP = &boxVector.box[i];
+
+        unsigned int j;
+
+        pm_message("Box %u, %u colors starting with index %u (%u pixels):",
+                   i, boxP->colorCt, boxP->startIndex, boxP->sum);
+        if (boxP->colorCt > 1)
+            pm_message("Largest spread is %lu, in plane %u",
+                       boxP->spread, boxP->maxdim);
+
+        for (j = 0; j < boxP->colorCt; ++j) {
+            unsigned int colorIdx = boxP->startIndex + j;
+
+            assert(colorIdx < boxVector.colorFreqTable.size);
+
+            tuple const color =
+                boxVector.colorFreqTable.table[colorIdx]->tuple;
+
+            pm_message("(%lu, %lu, %lu)",
+                       color[PAM_RED_PLANE],
+                       color[PAM_GRN_PLANE],
+                       color[PAM_BLU_PLANE]);
+        }
+    }
+}
+
+
+
+static void
 mediancut(tupletable2           const colorFreqTable,
           unsigned int          const depth,
           unsigned int          const newColorCt,
           enum MethodForLargest const methodForLargest,
           enum MethodForRep     const methodForRep,
           enum MethodForSplit   const methodForSplit,
+          bool                  const wantBvReport,
           tupletable2 *         const colormapP) {
 /*----------------------------------------------------------------------------
    Compute a set of only 'newColorCt' colors that best represent an
    image whose pixels are summarized by the histogram
    'colorFreqTable'.  Each tuple in that table has depth 'depth'.
    colorFreqTable.table[i] tells the number of pixels in the subject image
-   have a particular color.
+   that have a particular color.
 
    As a side effect, sort 'colorFreqTable'.
 -----------------------------------------------------------------------------*/
@@ -763,11 +824,16 @@ mediancut(tupletable2           const colorFreqTable,
         if (boxIdx >= boxVector.boxCt)
             multicolorBoxesExist = FALSE;
         else
-            splitBox(&boxVector, boxIdx, colorFreqTable, depth,
-                     methodForLargest, methodForSplit);
+            splitBox(&boxVector, boxIdx, methodForLargest, methodForSplit);
+                /* Side effect: sorts the extent of 'colorfreqTable' that is
+                   in the box
+                */
     }
-    *colormapP = colormapFromBv(newColorCt, boxVector, colorFreqTable,
-                                depth, methodForRep);
+
+    if (wantBvReport)
+        reportBoxVector(boxVector);
+
+    *colormapP = colormapFromBv(newColorCt, boxVector, methodForRep);
 
     destroyBoxVector(boxVector);
 }
@@ -900,6 +966,7 @@ computeColorMapFromInput(FILE *                const ifP,
                          enum MethodForLargest const methodForLargest,
                          enum MethodForRep     const methodForRep,
                          enum MethodForSplit   const methodForSplit,
+                         bool                  const wantBvReport,
                          int *                 const formatP,
                          struct pam *          const freqPamP,
                          tupletable2 *         const colormapP) {
@@ -923,6 +990,9 @@ computeColorMapFromInput(FILE *                const ifP,
    relevant to our colormap mission; just a fringe benefit).
 -----------------------------------------------------------------------------*/
     tupletable2 colorFreqTable;
+        /* Table of all colors in the image, with the number of pixels of
+           each color.
+        */
 
     computeHistogram(ifP, formatP, freqPamP, &colorFreqTable);
 
@@ -937,7 +1007,7 @@ computeColorMapFromInput(FILE *                const ifP,
             pm_message("choosing %u colors...", reqColors);
             mediancut(colorFreqTable, freqPamP->depth,
                       reqColors, methodForLargest, methodForRep,
-                      methodForSplit, colormapP);
+                      methodForSplit, wantBvReport, colormapP);
             pnm_freetupletable2(freqPamP, colorFreqTable);
         }
     }
@@ -1113,6 +1183,7 @@ main(int argc, const char * argv[] ) {
                              cmdline.methodForLargest,
                              cmdline.methodForRep,
                              cmdline.methodForSplit,
+                             cmdline.debug,
                              &format, &colormapPam, &colormap);
 
     pm_close(ifP);
diff --git a/test/Test-Order b/test/Test-Order
index 6aab22bd..16eeaf8e 100644
--- a/test/Test-Order
+++ b/test/Test-Order
@@ -51,6 +51,7 @@ pbmminkowski.test
 pgmminkowski.test
 
 pnmcolormap.test
+pnmcolormap2.test
 
 # Basic (internal) converter tests
 
diff --git a/test/ilbm-roundtrip.test b/test/ilbm-roundtrip.test
index e7a1a4e5..3d7d63ef 100755
--- a/test/ilbm-roundtrip.test
+++ b/test/ilbm-roundtrip.test
@@ -33,7 +33,25 @@ rm ${test_ppm}
 
 echo "Test 5.  Should print 482756572 101484 twice"
 mapfile=${tmpdir}/mapfile
-pnmcolormap 32 testimg.ppm > ${mapfile}
+
+# The following was produced by running "pnmcolormap -plain 32 testimg.ppm"
+# We use a prefabricated mapfile because pnmcolormap is known to
+# produce slightly different output on different systems.
+
+cat > ${mapfile} << EOF
+P3
+32 1
+255
+106 82 80 46 43 34 128 62 46 189 68 63 209 41 43 209 65 46 241 62 72
+ 240 189 195 
+122 142 103 92 131 68 159 129 188 177 205 195 96 132 107 193 150 107
+ 242 253 238 241 73 108 
+121 75 67 182 67 47 98 88 48 237 36 47 66 45 42 68 84 43 182 45 41
+ 70 77 67 
+96 45 45 237 62 47 150 59 41 189 76 103 48 69 31 52 68 52 48 45 51
+ 105 80 130 
+EOF
+
 ppmtoilbm -map ${mapfile} testimg.ppm | ilbmtoppm | tee ${test_ppm} | cksum
 ppmtoilbm -map ${mapfile} ${test_ppm} | ilbmtoppm | cksum
 
diff --git a/test/pbmtextps-dump.ok b/test/pbmtextps-dump.ok
index 9fe3b2ea..11a4c5fd 100644
--- a/test/pbmtextps-dump.ok
+++ b/test/pbmtextps-dump.ok
@@ -43,6 +43,13 @@ Test 1
 -stroke 1
 < /pensize -1.000000 def
 > /pensize 1.000000 def
+Test 2
+30 31 32 20 41 42 43 2d 78 79 7a 2e
+1
+303132204142432d78797a2e
+1
+<303132 20 414243 2d 78797a 2e>
+1
 Test Invalid
 Expected failure 1 (-fontsize) 1
 Expected failure 2 (-fontsize 0) 1
@@ -62,4 +69,20 @@ Expected failure 15 (-descent) 1
 Expected failure 16 (-descent -1) 1
 Expected failure 17 (-stroke=A) 1
 Expected failure 18 (-pad -crop) 1
-Expected failure 19 (-font="") 1
+Expected failure 19 (-asciihex <a>) 1
+Expected failure 20 (-asciihex ) 1
+Expected failure 21 (-asciihex <53756c667572) 1
+Expected failure 22 (-asciihex 53756c667572>) 1
+Expected failure 23 (-asciihex <5375<6c667572>) 1
+Expected failure 24 (-asciihex <53756c>667572>) 1
+Expected failure 25 (-ascii85 <~@<6O!FD5W(~) 1
+Expected failure 26 (-ascii85 ~@<6O!FD5W(~>) 1
+Expected failure 27 (-ascii85 <~@<6O<~!FD5W(~>) 1
+Expected failure 28 (-ascii85 <~@<6O~>!FD5W(~>) 1
+Expected failure 29 (-ascii85 <~@<6O!FD5W(~~>) 1
+Expected failure 30 (-ascii85 v) 1
+Expected failure 31 (-ascii85 y) 1
+Expected failure 32 (-ascii85 1z) 1
+Expected failure 33 (-ascii85 z1z) 1
+Expected failure 34 (-ascii85 <~0123z~>) 1
+Expected failure 35 (-font="") 1
diff --git a/test/pbmtextps-dump.test b/test/pbmtextps-dump.test
index 3b3fbadd..82856f7e 100755
--- a/test/pbmtextps-dump.test
+++ b/test/pbmtextps-dump.test
@@ -6,15 +6,15 @@
 # Ghostscript is not required.
 
 tmpdir=${tmpdir:-/tmp}
-text_pbm=${tmpdir}/text.pbm
-text_ps=${tmpdir}/text.ps
+text1_ps=${tmpdir}/text1.ps
+text2_ps=${tmpdir}/text2.ps
 
 text="UNIX Philosophy: Do one thing and do it well."
 
 # Test 1:
 echo "Test 1"
 
-pbmtextps -dump-ps ${text} > ${text_ps}
+pbmtextps -dump-ps ${text} > ${text1_ps}
 
 # Font name is random sequence of alphanumerical characters.
 # Should not match any real name.
@@ -34,10 +34,28 @@ for flag in \
   "-stroke 1"
   do
   echo ${flag}
-  pbmtextps -dump-ps ${flag} ${text} | diff ${text_ps} - | grep "^[<>]"
+  pbmtextps -dump-ps ${flag} ${text} | diff ${text1_ps} - | grep "^[<>]"
   done
 
-rm ${text_ps}
+rm ${text1_ps}
+
+
+# Test 2:
+echo "Test 2"
+
+pbmtextps -dump-ps "012 ABC-xyz." > ${text2_ps}
+
+for hextext in \
+  "30 31 32  20	 41 42 43  2d	78 79 7a  2e" \
+  "303132204142432d78797a2e" \
+  "<303132 20 414243 2d 78797a 2e>" 
+  do
+  echo ${hextext}
+  pbmtextps -dump-ps -asciihex ${hextext} | diff ${text2_ps} - | grep "^[<>]"
+  echo $?
+  done
+
+rm ${text2_ps}
 
 
 echo "Test Invalid"
@@ -69,7 +87,7 @@ for error_flag in \
   "-descent" \
   "-descent -1" \
   "-stroke=A" \
-  "-pad -crop" 
+  "-pad -crop"
   do
     pbmtextps ${error_flag} -dump-ps ${text} >${test_out} || \
     printf "Expected failure $n (${error_flag})";
@@ -78,6 +96,40 @@ for error_flag in \
     n=$((n + 1))
   done
 
+for asciihex_string in \
+  "<a>" \
+  "" \
+  "<53756c667572" \
+  "53756c667572>" \
+  "<5375<6c667572>" \
+  "<53756c>667572>"
+  do
+    pbmtextps -dump-ps -asciihex ${asciihex_string} >${test_out} || \
+    printf "Expected failure $n (-asciihex ${asciihex_string})";
+    test -s ${test_out}; echo " "$?
+    rm -f ${test_out}
+    n=$((n + 1))
+  done
+
+for ascii85_string in \
+  '<~@<6O!FD5W(~'\
+  '~@<6O!FD5W(~>'\
+  "<~@<6O<~!FD5W(~>"\
+  "<~@<6O~>!FD5W(~>"\
+  "<~@<6O!FD5W(~~>"\
+  "v"\
+  "y"\
+  "1z"\
+  "z1z"\
+  "<~0123z~>"
+  do
+    pbmtextps -dump-ps -ascii85 ${ascii85_string} >${test_out} || \
+    printf "Expected failure $n (-ascii85 ${ascii85_string})";
+    test -s ${test_out}; echo " "$?
+    rm -f ${test_out}
+    n=$((n + 1))
+  done
+
   pbmtextps -font="" -dump-ps ${text} >${test_out} || \
   printf "Expected failure $n (-font=\"\")";
   test -s ${test_out}; echo " "$?
diff --git a/test/pbmtextps.ok b/test/pbmtextps.ok
index d407cb0a..2063ac4a 100644
--- a/test/pbmtextps.ok
+++ b/test/pbmtextps.ok
@@ -4,7 +4,9 @@ Test 1.  Should print 0 five times.
 0
 0
 0
-Test 2.  Should print P1 1 1 0 three times
+Test 2.  Should print P1 1 1 0 five times
+P1 1 1 0 
+P1 1 1 0 
 P1 1 1 0 
 P1 1 1 0 
 P1 1 1 0 
diff --git a/test/pbmtextps.test b/test/pbmtextps.test
index 975f960c..55f3f96a 100755
--- a/test/pbmtextps.test
+++ b/test/pbmtextps.test
@@ -24,7 +24,7 @@ pbmtextps -descent=2 ${text} | cmp -s - ${text_pbm}
 rm ${text_pbm}
 
 
-echo "Test 2.  Should print P1 1 1 0 three times"
+echo "Test 2.  Should print P1 1 1 0 five times"
 # blank images
 
 pbmtextps " " | pnmcrop -plain -blank-image=minimize |\
@@ -32,7 +32,11 @@ pbmtextps " " | pnmcrop -plain -blank-image=minimize |\
 pbmtextps -fontsize=12   " " | pnmcrop -plain -blank-image=minimize |\
   tr '\n' ' ' ; echo
 pbmtextps -resolution=50 " " | pnmcrop -plain -blank-image=minimize |\
-  tr '\n' ' ' ; echo 
+  tr '\n' ' ' ; echo
+pbmtextps -asciihex "20" | pnmcrop -plain -blank-image=minimize |\
+  tr '\n' ' ' ; echo
+pbmtextps -ascii85  "+9" | pnmcrop -plain -blank-image=minimize |\
+  tr '\n' ' ' ; echo
 
 
 
@@ -49,5 +53,3 @@ pbmtextps "+" | pnmcrop -left -right | pbmminkowski | grep "eulerchi"
 pbmtextps "+" | pnmcrop | pbmminkowski | grep "eulerchi"
 
 pbmtextps "o" | pnmcrop | pbmminkowski | grep "eulerchi"
-
-
diff --git a/test/pnmcolormap.ok b/test/pnmcolormap.ok
index 126dfea4..0c6f0d71 100644
--- a/test/pnmcolormap.ok
+++ b/test/pnmcolormap.ok
@@ -1,12 +1,36 @@
+Test 1.
 P1
 2 1
 10
+
 P1
 2 1
 10
+
 P1
 2 1
 10
+
+Test 2.
+P3
+3 1
+255
+0 0 255 64 0 191 128 0 128 
+
+P3
+2 2
+255
+0 0 255 64 0 191 
+128 0 128 128 0 128 
+
+Test 3.
+ok
+ok
+ok
+ok
+ok
+
+Test Invalid.
 Expected failure 1 1
 Expected failure 2 1
 Expected failure 3 1
diff --git a/test/pnmcolormap.test b/test/pnmcolormap.test
index 1a7ec266..11d47385 100755
--- a/test/pnmcolormap.test
+++ b/test/pnmcolormap.test
@@ -1,10 +1,39 @@
 #! /bin/sh
 # This script tests: pnmcolormap
-# Also requires:
+# Also requires: ppmpat
+
+echo "Test 1."
 
 pnmcolormap -plain -sort 2 maze.pbm
+echo
 pnmcolormap -plain -sort -square 2 maze.pbm
+echo
 pnmcolormap -plain -sort all maze.pbm
+echo
+tmpdir=${tmpdir:-/tmp}
+tartan_ppm=${tmpdir}/tartan.ppm
+
+echo "Test 2."
+
+ppmpat -tartan -color="rgb:00/00/ff,rgb:00/80/ff,rgb:80/00/80" 20 20 |\
+    tee ${tartan_ppm} | pnmcolormap -plain -sort all
+echo
+pnmcolormap -plain -sort -square all ${tartan_ppm}
+rm ${tartan_ppm}
+echo
+echo "Test 3."
+# Explicitly specify default options.  Output should not vary.
+
+map=${tmpdir}/map.ppm
+
+pnmcolormap 64 testimg.ppm > ${map} && echo ok || echo bad
+test -s ${map} && echo ok || echo bad
+pnmcolormap -center 64 testimg.ppm | cmp -s ${map} - && echo ok || echo bad
+pnmcolormap -spreadbrightness 64 testimg.ppm | cmp -s ${map} - && echo ok || echo bad
+pnmcolormap -splitpixelct 64 testimg.ppm | cmp -s ${map} - && echo ok || echo bad
+rm ${map}
+echo
+echo "Test Invalid."
 
 echo 1>&2
 echo "Invalid command-line arguments." 1>&2
diff --git a/test/pnmcolormap2.ok b/test/pnmcolormap2.ok
new file mode 100644
index 00000000..2eab38b8
--- /dev/null
+++ b/test/pnmcolormap2.ok
@@ -0,0 +1,9 @@
+Test.  Should print 'match' eight times.
+match
+match
+match
+match
+match
+match
+match
+match
diff --git a/test/pnmcolormap2.test b/test/pnmcolormap2.test
new file mode 100755
index 00000000..bb870e7f
--- /dev/null
+++ b/test/pnmcolormap2.test
@@ -0,0 +1,55 @@
+#! /bin/sh
+# This script tests: pnmcolormap
+# Also requires: pnmremap pnmpsnr
+
+tmpdir=${tmpdir:-/tmp}
+map=${tmpdir}/map.ppm
+
+echo "Test.  Should print 'match' eight times."
+# Threshold values (targetN=xx.xx) here were produced by calculating
+# the S/N ratio with reduced colors.
+
+# colors in following tests / colors for calculating threshold
+# 100 /  90
+# 200 / 180
+#  30 /  25
+
+pnmcolormap 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=33.42 -target2=35.14 -target3=34.35 testimg.ppm -
+rm ${map}
+
+pnmcolormap -meancolor 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=34.91 -target2=36.86 -target3=35.84 testimg.ppm -
+rm ${map}
+
+pnmcolormap -meanpixel 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=34.95 -target2=36.77 -target3=35.81 testimg.ppm -
+rm ${map}
+
+pnmcolormap -spreadluminosity 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=33.71 -target2=32.91 -target3=33.93 testimg.ppm -
+rm ${map}
+
+pnmcolormap -splitcolorct 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=33.97 -target2=35.34 -target3=34.23 testimg.ppm -
+rm ${map}
+
+pnmcolormap -splitspread 100 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=32.98 -target2=35.06 -target3=33.19 testimg.ppm -
+rm ${map}
+
+pnmcolormap 200 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=36.14 -target2=36.87 -target3=36.79 testimg.ppm -
+rm ${map}
+
+pnmcolormap 30 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+  pnmpsnr -target1=28.53 -target2=31.62 -target3=29.99 testimg.ppm -
+rm ${map}
diff --git a/version.mk b/version.mk
index 76e48696..d579dc15 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 11
-NETPBM_MINOR_RELEASE = 1
-NETPBM_POINT_RELEASE = 3
+NETPBM_MINOR_RELEASE = 2
+NETPBM_POINT_RELEASE = 0