about summary refs log tree commit diff
path: root/converter/other/pnmtops.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pnmtops.c')
-rw-r--r--converter/other/pnmtops.c1711
1 files changed, 1239 insertions, 472 deletions
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
index 20395952..c1dadc3e 100644
--- a/converter/other/pnmtops.c
+++ b/converter/other/pnmtops.c
@@ -7,15 +7,19 @@
       1) Use built in Postscript filters /ASCII85Decode, /ASCIIHexDecode,
          /RunLengthDecode, and /FlateDecode;
 
-         We use methods we learned from Dirk Krause's program Bmeps and
-         raster encoding code copied almost directly from Bmeps.
+         We use methods we learned from Dirk Krause's program Bmeps.
+         Previous versions used raster encoding code based on Bmeps
+         code.  This program does not used any code from Bmeps.
 
       2) Use our own filters and redefine /readstring .  This is aboriginal
-         Netpbm code, from when Postscript was young.
+         Netpbm code, from when Postscript was young.  The filters are
+         nearly identical to /ASCIIHexDecode and /RunLengthDecode.  We
+         use the same raster encoding code with slight modifications.
 
-   (2) is the default, because it's been working for ages and we have
-   more confidence in it.  But (1) gives more options.  The user
-   selects (1) with the -psfilter option.
+   (2) is the default.  (1) gives more options, but relies on features
+   introduced in Postscript Level 2, which appeared in 1991.  Postcript
+   devices made before 1991 can't handle them.  The user selects (1)
+   with the -psfilter option.
 
    We also do a few other bold new things only when the user specifies
    -psfilter, because we're not sure they work for everyone.
@@ -31,23 +35,63 @@
 
 #define _BSD_SOURCE  /* Make sure string.h contains strdup() */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
-
-#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
 #include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#ifndef NOFLATE
+#include <zlib.h>
+#endif
 
 #include "pm_c_util.h"
 #include "pam.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "nstring.h"
-#include "bmepsoe.h"
+#include "runlength.h"
+
+
+
+static void
+setSignals() {
+/*----------------------------------------------------------------------------
+   Set up the process-global signal-related state.
+
+   Note that we can't rely on defaults, because much of this is inherited
+   from the process that forked and exec'ed this program.
+-----------------------------------------------------------------------------*/
+    /* See waitForChildren() for why we do this to SIGCHLD */
+
+    struct sigaction sigchldAction;
+    int rc;
+    sigset_t emptySet;
+
+    sigemptyset(&emptySet);
+
+    sigchldAction.sa_handler = SIG_DFL;
+    sigchldAction.sa_mask = emptySet;
+    sigchldAction.sa_flags = SA_NOCLDSTOP;
+
+    rc = sigaction(SIGCHLD, &sigchldAction, NULL);
+
+    if (rc != 0)
+        pm_error("sigaction() to set up signal environment failed, "
+                 "errno = %d (%s)", errno, strerror(errno));
+}
+
+
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char * inputFileName;  /* Filespecs of input file */
-    float scale;
+    float        scale;
     unsigned int dpiX;     /* horiz component of DPI option */
     unsigned int dpiY;     /* vert component of DPI option */
     unsigned int width;              /* in 1/72 inch */
@@ -59,6 +103,8 @@ struct cmdlineInfo {
     unsigned int imagewidth;         /* in 1/72 inch; zero if unspec */
     unsigned int imageheight;        /* in 1/72 inch; zero if unspec */
     unsigned int equalpixels;
+    unsigned int bitspersampleSpec;
+    unsigned int bitspersample;
     unsigned int setpage;
     bool         showpage;
     unsigned int level;
@@ -69,24 +115,29 @@ struct cmdlineInfo {
     unsigned int dict;
     unsigned int vmreclaim;
     unsigned int verbose;
+    unsigned int debug;
 };
 
-
+static bool debug;
 static bool verbose;
 
 
+
 static void
 parseDpi(const char *   const dpiOpt, 
          unsigned int * const dpiXP, 
          unsigned int * const dpiYP) {
 
     char *dpistr2;
-    unsigned int dpiX, dpiY;
+    unsigned long int dpiX, dpiY;
 
     dpiX = strtol(dpiOpt, &dpistr2, 10);
-    if (dpistr2 == dpiOpt) 
+    if (dpistr2 == dpiOpt)
         pm_error("Invalid value for -dpi: '%s'.  Must be either number "
                  "or NxN ", dpiOpt);
+    else if (dpiX > INT_MAX)
+        pm_error("Invalid value for -dpi: '%s'.  "
+                 "Value too large for computation", dpiOpt);
     else {
         if (*dpistr2 == '\0') {
             *dpiXP = dpiX;
@@ -95,8 +146,11 @@ parseDpi(const char *   const dpiOpt,
             char * dpistr3;
 
             dpistr2++;  /* Move past 'x' */
-            dpiY = strtol(dpistr2, &dpistr3, 10);        
-            if (dpistr3 != dpistr2 && *dpistr3 == '\0') {
+            dpiY = strtol(dpistr2, &dpistr3, 10);
+            if (dpiY > INT_MAX)
+                pm_error("Invalid value for -dpi: '%s'.  "
+                         "Value too large for computation", dpiOpt);
+            else if (dpistr3 != dpistr2 && *dpistr3 == '\0') {
                 *dpiXP = dpiX;
                 *dpiYP = dpiY;
             } else {
@@ -110,7 +164,50 @@ parseDpi(const char *   const dpiOpt,
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+validateBps_1_2_4_8_12(unsigned int const bitsPerSample) {
+
+    switch (bitsPerSample) {
+    case 1:
+    case 2:
+    case 4:
+    case 8:
+    case 12:
+        break;
+    default:
+        pm_error("Invalid -bitspersample value: %u.  Must be "
+                 "1, 2, 4, 8, or 12", bitsPerSample);
+    }
+}
+
+
+
+static void
+validateCompDimension(unsigned int const value,
+                      unsigned int const scaleFactor,
+                      const char * const vname) {
+/*----------------------------------------------------------------------------
+  Validate that the image dimension (width or height) 'value' isn't so big
+  that in this program's calculations, involving scale factor 'scaleFactor',
+  it would cause a register overflow.  If it is, abort the program and refer
+  to the offending dimension as 'vname' in the error message.
+
+  Note that this early validation approach (calling this function) means
+  the actual computations don't have to be complicated with arithmetic
+  overflow checks, so they're easier to read.
+-----------------------------------------------------------------------------*/
+    if (value > 0) {
+        unsigned int const maxWidthHeight = INT_MAX - 2;
+        unsigned int const maxScaleFactor = maxWidthHeight / value;
+
+        if (scaleFactor > maxScaleFactor)
+            pm_error("%s is too large for compuations: %u", vname, value);
+    }
+}
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 
     unsigned int imagewidthSpec, imageheightSpec;
@@ -120,19 +217,19 @@ parseCommandLine(int argc, char ** argv,
     float width, height;
     unsigned int noturn;
     unsigned int showpage, noshowpage;
-    const char *dpiOpt;
-    unsigned int dpiSpec;
+    const char * dpiOpt;
+    unsigned int dpiSpec, scaleSpec, widthSpec, heightSpec;
 
     optStruct3 opt;
     unsigned int option_def_index = 0;
-    optEntry *option_def;
+    optEntry * option_def;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
-    OPTENT3(0, "scale",       OPT_FLOAT, &cmdlineP->scale, NULL,         0);
+    OPTENT3(0, "scale",       OPT_FLOAT, &cmdlineP->scale, &scaleSpec,   0);
     OPTENT3(0, "dpi",         OPT_STRING, &dpiOpt,         &dpiSpec,     0);
-    OPTENT3(0, "width",       OPT_FLOAT, &width,           NULL,         0);
-    OPTENT3(0, "height",      OPT_FLOAT, &height,          NULL,         0);
+    OPTENT3(0, "width",       OPT_FLOAT, &width,           &widthSpec,   0);
+    OPTENT3(0, "height",      OPT_FLOAT, &height,          &heightSpec,  0);
     OPTENT3(0, "psfilter",    OPT_FLAG,  NULL, &cmdlineP->psfilter,      0);
     OPTENT3(0, "turn",        OPT_FLAG,  NULL, &cmdlineP->mustturn,      0);
     OPTENT3(0, "noturn",      OPT_FLAG,  NULL, &noturn,                  0);
@@ -144,6 +241,8 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "equalpixels", OPT_FLAG,  NULL, &cmdlineP->equalpixels,   0);
     OPTENT3(0, "imagewidth",  OPT_FLOAT, &imagewidth,  &imagewidthSpec,  0);
     OPTENT3(0, "imageheight", OPT_FLOAT, &imageheight, &imageheightSpec, 0);
+    OPTENT3(0, "bitspersample", OPT_UINT, &cmdlineP->bitspersample,
+            &cmdlineP->bitspersampleSpec, 0);
     OPTENT3(0, "nosetpage",   OPT_FLAG,  NULL, &nosetpage,               0);
     OPTENT3(0, "setpage",     OPT_FLAG,  NULL, &cmdlineP->setpage,       0);
     OPTENT3(0, "noshowpage",  OPT_FLAG,  NULL, &noshowpage,              0);
@@ -152,19 +251,15 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "vmreclaim",   OPT_FLAG,  NULL, &cmdlineP->vmreclaim,     0);
     OPTENT3(0, "showpage",    OPT_FLAG,  NULL, &showpage,                0);
     OPTENT3(0, "verbose",     OPT_FLAG,  NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "debug",       OPT_FLAG,  NULL, &cmdlineP->debug,         0);
     OPTENT3(0, "level",       OPT_UINT, &cmdlineP->level, 
             &cmdlineP->levelSpec,              0);
     
-    /* DEFAULTS */
-    cmdlineP->scale = 1.0;
-    width = 8.5;
-    height = 11.0;
-
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;
     opt.allowNegNum = FALSE;
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
 
     if (cmdlineP->mustturn && noturn)
         pm_error("You cannot specify both -turn and -noturn");
@@ -175,6 +270,15 @@ parseCommandLine(int argc, char ** argv,
     if (cmdlineP->setpage && nosetpage)
         pm_error("You cannot specify both -setpage and -nosetpage");
 
+    if (!scaleSpec)
+        cmdlineP->scale = 1.0;
+
+    if (!widthSpec)
+        width = 8.5;
+
+    if (!heightSpec)
+        height = 11.0;
+
     if (dpiSpec)
         parseDpi(dpiOpt, &cmdlineP->dpiX, &cmdlineP->dpiY);
     else {
@@ -185,16 +289,23 @@ parseCommandLine(int argc, char ** argv,
     cmdlineP->center  =  !nocenter;
     cmdlineP->canturn =  !noturn;
     cmdlineP->showpage = !noshowpage;
+
+    validateCompDimension(width, 72, "-width value");
+    validateCompDimension(height, 72, "-height value");
     
     cmdlineP->width  = width * 72;
     cmdlineP->height = height * 72;
 
-    if (imagewidthSpec)
+    if (imagewidthSpec) {
+        validateCompDimension(imagewidth, 72, "-imagewidth value");
         cmdlineP->imagewidth = imagewidth * 72;
+    }
     else
         cmdlineP->imagewidth = 0;
-    if (imageheightSpec)
+    if (imageheightSpec) {
+        validateCompDimension(imagewidth, 72, "-imageheight value");
         cmdlineP->imageheight = imageheight * 72;
+    }
     else
         cmdlineP->imageheight = 0;
 
@@ -203,6 +314,9 @@ parseCommandLine(int argc, char ** argv,
         pm_error("You must specify -psfilter in order to specify "
                  "-flate or -ascii85");
 
+    if (cmdlineP->bitspersampleSpec)
+        validateBps_1_2_4_8_12(cmdlineP->bitspersample);
+
     if (argc-1 == 0) 
         cmdlineP->inputFileName = "-";
     else if (argc-1 != 1)
@@ -211,320 +325,715 @@ parseCommandLine(int argc, char ** argv,
     else
         cmdlineP->inputFileName = argv[1];
 
+    free(option_def); 
 }
 
 
-/*===========================================================================
-  The native output encoder.  This is archaic and uses global variables.
-  It is probably obsoleted by the bmeps output encoder; we just haven't
-  had a chance to verify that yet.
-===========================================================================*/
 
+static bool
+progIsFlateCapable(void) {
+
+    return
+#ifdef NOFLATE
+        false
+#else
+        true
+#endif
+        ;
+}
+
+
+
+static const char *
+basebasename(const char * const filespec) {
 /*----------------------------------------------------------------------------
-   The following global variables are the native output encoder state.
+  Return filename up to first period
 -----------------------------------------------------------------------------*/
-static unsigned int itemsinline;
-    /* The number of items in the line we are currently building */
-static unsigned int bitsinitem;
-    /* The number of bits filled so far in the item we are currently
-       building 
-    */
-static unsigned int rlebitsinitem;
-    /* The number of bits filled so far in the item we are currently
-       building 
-    */
-static unsigned int bitspersample;
-static unsigned int item, bitshift, items;
-static unsigned int rleitem, rlebitshift;
-static unsigned int repeat, itembuf[128], count, repeatitem, repeatcount;
+    char const dirsep = '/';
+    const char * const lastSlashPos = strrchr(filespec, dirsep);
+
+    char * name;
+    const char * filename;
+
+    if (lastSlashPos)
+        filename = lastSlashPos + 1;
+    else
+        filename = filespec;
+
+    name = strdup(filename);
+    if (name != NULL) {
+        char * const dotPosition = strchr(name, '.');
+
+        if (dotPosition)
+            *dotPosition = '\0';
+    }
+    return name;
+}
 
 
 
 static void
-initNativeOutputEncoder(bool const rle, unsigned int const bitspersample) {
-/*----------------------------------------------------------------------------
-   Initialize the native output encoder.  Call this once per
-   Postscript image that you will write with putitem(), before for the
-   first putitem().
+writeFile(const unsigned char * const buffer,
+          size_t                const writeCt,
+          const char *          const name,
+          FILE *                const ofP) {
 
-   We initialize the item putter state variables, which are the
-   global variable defined above.
------------------------------------------------------------------------------*/
-    itemsinline = 0;
-    items = 0;
+    size_t writtenCt;
+
+    writtenCt = fwrite(buffer, 1, writeCt, ofP);
+
+    if (writtenCt != writeCt)
+        pm_error("Error writing to %s output file", name);
+}
+
+
+
+static void
+writeFileChar(const char * const buffer,
+              size_t       const writeCt,
+              const char * const name,
+              FILE *       const ofP) {
+
+    writeFile((const unsigned char *)buffer, writeCt, name, ofP);
+}
 
-    if (rle) {
-        rleitem = 0;
-        rlebitsinitem = 0;
-        rlebitshift = 8 - bitspersample;
-        repeat = 1;
-        count = 0;
-    } else {
-        item = 0;
-        bitsinitem = 0;
-        bitshift = 8 - bitspersample;
-    }
 
+
+#define MAX_FILTER_CT 10
+    /* The maximum number of filters this code is capable of applying */
+
+
+
+static void
+initPidList(pid_t * const pidList) {
+
+    pidList[0] = (pid_t)0;  /* end of list marker */
 }
 
 
 
 static void
-putitem(void) {
-    const char* const hexits = "0123456789abcdef";
+addToPidList(pid_t * const pidList,
+             pid_t   const newPid) {
+
+    unsigned int i;
+
+    for (i = 0; i < MAX_FILTER_CT && pidList[i]; ++i);
 
-    if (itemsinline == 30) {
-        putchar('\n');
-        itemsinline = 0;
+    assert(i < MAX_FILTER_CT);
+
+    pidList[i] = newPid;
+    pidList[i+1] = (pid_t)0;  /* end of list marker */
+}
+
+
+
+/*===========================================================================
+  The output encoder
+  ===========================================================================*/
+    
+enum OutputType {AsciiHex, Ascii85};
+
+typedef struct {
+    enum OutputType    outputType;
+    bool               compressRle;
+    bool               compressFlate;
+    unsigned int       runlengthRefresh;
+} OutputEncoder;
+
+
+
+static unsigned int
+bytesPerRow (unsigned int const cols,
+             unsigned int const bitsPerSample) {
+/*----------------------------------------------------------------------------
+  Size of row buffer, padded up to byte boundary, given that the image
+  has 'cols' samples per row, 'bitsPerSample' bits per sample.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    assert(bitsPerSample==1 || bitsPerSample==2 || bitsPerSample==4 || 
+           bitsPerSample==8 || bitsPerSample==12);
+
+    switch (bitsPerSample) {
+    case 1:
+    case 2:
+    case 4:
+        retval = cols / (8/bitsPerSample)
+            + (cols % (8/bitsPerSample) > 0 ? 1 : 0);
+        /* A more straightforward calculation would be
+           (cols * bitsPerSample + 7) / 8 ,
+           but this overflows when icols is large.
+        */
+        break;
+    case 8:
+        retval = cols;
+        break;
+    case 12:
+        retval = cols + (cols+1)/2;
+        break;
     }
-    assert(item >> 8 == 0);
-    putchar(hexits[item >> 4]);
-    putchar(hexits[item & 15]);
-    ++itemsinline;
-    ++items;
-    item = 0;
-    bitsinitem = 0;
-    bitshift = 8 - bitspersample;
+
+    return retval;
 }
 
 
 
 static void
-flushitem() {
-    if (bitsinitem > 0)
-        putitem();
+initOutputEncoder(OutputEncoder  * const oeP,
+                  unsigned int     const icols,
+                  unsigned int     const bitsPerSample,
+                  bool             const rle,
+                  bool             const flate,
+                  bool             const ascii85,
+                  bool             const psFilter) {
+
+    oeP->outputType = ascii85 ? Ascii85 : AsciiHex;
+
+    if (rle) {
+        oeP->compressRle = true;
+        oeP->runlengthRefresh =
+             psFilter ? 1024*1024*16 : bytesPerRow(icols, bitsPerSample);
+    } else
+        oeP->compressRle = false;
+
+    if (flate) {
+        assert(psFilter);
+        oeP->compressFlate = true;
+    } else
+        oeP->compressFlate = false;
+
+    if (ascii85) {
+        assert(psFilter);
+        oeP->outputType = Ascii85;
+    } else
+        oeP->outputType = AsciiHex;
 }
 
 
 
+typedef void FilterFn(FILE *          const ifP,
+                      FILE *          const ofP,
+                      OutputEncoder * const oeP);
+    /* This is a function that can be run in a separate process to do
+       arbitrary modifications of the raster data stream.
+    */
+       
+
+
+#ifndef NOFLATE
+static void
+initZlib(z_stream * const strmP) {
+
+    int const level = 9; /* maximum compression.  see zlib.h */
+
+    int ret;
+
+    /* allocate deflate state */
+    strmP->zalloc = Z_NULL;
+    strmP->zfree  = Z_NULL;
+    strmP->opaque = Z_NULL;
+
+    ret = deflateInit(strmP, level);
+    if (ret != Z_OK)
+        pm_error("Failed to initialize zlib.");
+}
+#endif
+
+
+
+static FilterFn flateFilter;
+
 static void 
-putxelval(xelval const xv) {
-    if (bitsinitem == 8)
-        putitem();
-    item += xv << bitshift;
-    bitsinitem += bitspersample;
-    bitshift -= bitspersample;
+flateFilter(FILE *          const ifP,
+            FILE *          const ofP,
+            OutputEncoder * const oeP) {
+
+#ifndef NOFLATE
+
+    /* This code is based on def() in zpipe.c.  zpipe is an example program
+       which comes with the zlib source package.  zpipe.c is public domain and
+       is available from the Zlib website: http://www.zlib.net/
+
+       See zlib.h for details on zlib parameters Z_NULL, Z_OK, etc.
+    */
+    unsigned int const chunkSz = 128*1024;
+        /* 128K recommended in zpipe.c.  4096 is not efficient but works. */
+
+    int flush;
+    z_stream strm;
+    unsigned char * in;
+    unsigned char * out;
+
+    in  = pm_allocrow(chunkSz, 1);
+    out = pm_allocrow(chunkSz, 1);
+
+    initZlib(&strm);
+
+    /* compress until end of file */
+    do {
+        strm.avail_in = fread(in, 1, chunkSz, ifP);
+        if (ferror(ifP)) {
+            deflateEnd(&strm);
+            pm_error("Error reading from internal pipe during "
+                     "flate compression.");
+        }
+        flush = feof(ifP) ? Z_FINISH : Z_NO_FLUSH;
+        strm.next_in = in;
+
+        /* run deflate() on input until output buffer not full, finish
+           compression if we have reached end of input.
+        */
+        do {
+            unsigned int have;
+
+            strm.avail_out = chunkSz;
+            strm.next_out = out;
+            deflate(&strm, flush);
+            have = chunkSz - strm.avail_out;
+            writeFile(out, have, "flate filter", ofP);
+        } while (strm.avail_out == 0);
+        assert(strm.avail_in == 0);     /* all input is used */
+
+        /* done when last data in file processed */
+    } while (flush != Z_FINISH);
+
+    free(in);
+    free(out); 
+    deflateEnd(&strm);
+    fclose(ifP);
+    fclose(ofP);
+#else
+    assert(false);    /* filter is never used */ 
+#endif
 }
 
 
 
+/* Run length encoding
+
+   In this simple run-length encoding scheme, compressed and uncompressed
+   strings follow a single index byte N.  N 0-127 means the next N+1
+   bytes are uncompressed; 129-255 means the next byte is to be repeated
+   257-N times.
+
+   In native (non-psfilter) mode, the run length filter must flush at
+   the end of every row.  But the entire raster is sent to the run length
+   filter as one continuous stream.  The run length filter learns the
+   refresh interval from oeP->runlengthRefresh.  In ps-filter mode the
+   run length filter ignores row boundaries and flushes every 4096 bytes.
+*/
+
+static FilterFn rleFilter;
+
 static void
-rleputbuffer() {
-    if (repeat) {
-        item = 256 - count;
-        putitem();
-        item = repeatitem;
-        putitem();
-    } else {
-        unsigned int i;
-    
-        item = count - 1;
-        putitem();
-        for (i = 0; i < count; ++i) {
-            item = itembuf[i];
-            putitem();
-        }
+rleFilter (FILE *          const ifP,
+           FILE *          const ofP,
+           OutputEncoder * const oeP) {
+
+    unsigned int const inSize = oeP->runlengthRefresh;
+
+    bool eof;
+    unsigned char * inbuf;
+    unsigned char * outbuf;
+    size_t outSize;
+
+    MALLOCARRAY(inbuf, inSize);
+    if (inbuf == NULL)
+        pm_error("Failed to allocate %u bytes of memory for RLE filter",
+                  inSize);
+    pm_rlenc_allocoutbuf(&outbuf, inSize, PM_RLE_PACKBITS);
+
+    for (eof = false; !eof; ) {
+        size_t const bytesRead = fread(inbuf, 1, inSize, ifP);
+
+        if (feof(ifP))
+            eof = true;
+        else if (ferror(ifP) || bytesRead == 0)
+            pm_error("Internal read error: RLE compression");
+
+        pm_rlenc_compressbyte(inbuf, outbuf, PM_RLE_PACKBITS,
+                              bytesRead, &outSize);
+        writeFile(outbuf, outSize, "rlePutBuffer", ofP);
     }
-    repeat = 1;
-    count = 0;
+
+    fclose(ifP);
+    fclose(ofP);
 }
 
 
 
+static FilterFn asciiHexFilter;
+
 static void
-rleputitem() {
-    int i;
+asciiHexFilter(FILE *          const ifP,
+               FILE *          const ofP,
+               OutputEncoder * const oeP) {
 
-    if ( count == 128 )
-        rleputbuffer();
+    char const hexits[16] = "0123456789abcdef";
 
-    if ( repeat && count == 0 )
-    { /* Still initializing a repeat buf. */
-        itembuf[count] = repeatitem = rleitem;
-        ++count;
-    }
-    else if ( repeat )
-    { /* Repeating - watch for end of run. */
-        if ( rleitem == repeatitem )
-        { /* Run continues. */
-            itembuf[count] = rleitem;
-            ++count;
-        }
-        else
-        { /* Run ended - is it long enough to dump? */
-            if ( count > 2 )
-            { /* Yes, dump a repeat-mode buffer and start a new one. */
-                rleputbuffer();
-                itembuf[count] = repeatitem = rleitem;
-                ++count;
-            }
-            else
-            { /* Not long enough - convert to non-repeat mode. */
-                repeat = 0;
-                itembuf[count] = repeatitem = rleitem;
-                ++count;
-                repeatcount = 1;
+    bool eof;
+    unsigned char inbuff[40], outbuff[81];
+
+    for (eof = false; !eof; ) {
+        size_t readCt;
+
+        readCt = fread(inbuff, 1, 40, ifP);
+
+        if (readCt == 0)
+            eof = true;
+        else {
+            unsigned int i;
+
+            for (i = 0; i < readCt; ++i) {
+                int const item = inbuff[i]; 
+                outbuff[i*2]   = hexits[item >> 4];
+                outbuff[i*2+1] = hexits[item & 15];
             }
+            outbuff[readCt * 2] = '\n';
+            writeFile(outbuff, readCt * 2 + 1, "asciiHex filter", ofP);
         }
     }
-    else
-    { /* Not repeating - watch for a run worth repeating. */
-        if ( rleitem == repeatitem )
-        { /* Possible run continues. */
-            ++repeatcount;
-            if ( repeatcount > 3 )
-            { /* Long enough - dump non-repeat part and start repeat. */
-                count = count - ( repeatcount - 1 );
-                rleputbuffer();
-                count = repeatcount;
-                for ( i = 0; i < count; ++i )
-                    itembuf[i] = rleitem;
+
+    fclose(ifP);
+    fclose(ofP);
+}
+
+
+
+static FilterFn ascii85Filter;
+
+static void
+ascii85Filter(FILE *          const ifP,
+              FILE *          const ofP,
+              OutputEncoder * const oeP) {
+
+    bool eof;
+    char outbuff[5];
+    unsigned long int value; /* requires 32 bits */
+    int count;
+    int outcount;
+
+    value = 0;  /* initial value */
+    count = 0;  /* initial value */
+    outcount = 0; /* initial value */
+
+    for (eof = false; !eof; ) {
+        int c;
+
+        c = fgetc(ifP);
+
+        if (c == EOF)
+            eof = true;
+        else {
+            value = value*256 + c;
+            ++count;
+
+            if (value == 0 && count == 4) {
+                writeFileChar("z", 1, "ASCII 85 filter", ofP);
+                    /* Ascii85 encoding z exception */
+                ++outcount;
+                count = 0;
+            } else if (count == 4) {
+                outbuff[4] = value % 85 + 33;  value/=85; 
+                outbuff[3] = value % 85 + 33;  value/=85;
+                outbuff[2] = value % 85 + 33;  value/=85;
+                outbuff[1] = value % 85 + 33;
+                outbuff[0] = value / 85 + 33;
+
+                writeFileChar(outbuff, count + 1, "ASCII 85 filter", ofP);
+
+                count = value = 0;
+                outcount += 5; 
             }
-            else
-            { /* Not long enough yet - continue as non-repeat buf. */
-                itembuf[count] = rleitem;
-                ++count;
+
+            if (outcount > 75) {
+                writeFileChar("\n", 1, "ASCII 85 filter", ofP);
+                outcount = 0;
             }
         }
-        else
-        { /* Broken run. */
-            itembuf[count] = repeatitem = rleitem;
-            ++count;
-            repeatcount = 1;
-        }
     }
 
-    rleitem = 0;
-    rlebitsinitem = 0;
-    rlebitshift = 8 - bitspersample;
-}
+    if (count > 0) { /* EOF, flush */
+        assert (count < 4);
 
+        value <<= (4 - count) * 8;   value/=85;
+        outbuff[3] = value % 85 + 33;  value/=85;
+        outbuff[2] = value % 85 + 33;  value/=85;
+        outbuff[1] = value % 85 + 33;
+        outbuff[0] = value / 85 + 33;
+        outbuff[count + 1] = '\n';
 
+        writeFileChar(outbuff, count + 2, "ASCII 85 filter", ofP);
+    }
 
-static void 
-rleputxelval(xelval const xv) {
-    if (rlebitsinitem == 8)
-        rleputitem();
-    rleitem += xv << rlebitshift;
-    rlebitsinitem += bitspersample;
-    rlebitshift -= bitspersample;
+    fclose(ifP);
+    fclose(ofP);
 }
 
 
 
 static void
-rleflush() {
-    if (rlebitsinitem > 0)
-        rleputitem();
-    if (count > 0)
-        rleputbuffer();
+makePipe(int * const pipeFdArray) {
+
+    int rc;
+    rc = pm_pipe(pipeFdArray);
+    if (rc == -1)
+        pm_error("pipe() failed, errno = %d (%s)", errno, strerror(errno));
 }
 
 
+
 static void
-flushNativeOutput(bool const rle) {
-    if (rle)
-        rleflush();
-    else
-        flushitem();
-    printf("\n");
-}
-        
-/*===========================================================================
-  The BMEPS output encoder.
-===========================================================================*/
+closeAllBut(int const saveFd0,
+            int const saveFd1,
+            int const saveFd2) {
+/*----------------------------------------------------------------------------
+   Close every file descriptor in this process except 'saveFd0',
+   'saveFd1', and 'saveFd2'.
 
-/* This code is just a wrapper around the output encoder that is part of
-   Bmeps, to give it better modularity.
-*/
+   This is helpful because even if this process doesn't touch other file
+   desriptors, its very existence will keep the files open.
+-----------------------------------------------------------------------------*/
+    
+    /* Unix provides no good way to do this; we just assume file descriptors
+       above 9 are not used in this program; Caller must ensure that is true.
+    */
+    int fd;
 
-struct bmepsoe {
-    Output_Encoder * oeP;
-    int * rleBuffer;
-    Byte * flateInBuffer;
-    Byte * flateOutBuffer;
-};
+    for (fd = 0; fd < 10; ++fd) {
+        if (fd != saveFd0 && fd != saveFd1 && fd != saveFd2)
+            close(fd);
+    }
+}
 
 
 
 static void
-createBmepsOutputEncoder(struct bmepsoe ** const bmepsoePP,
-                         FILE *            const ofP,
-                         bool              const rle,
-                         bool              const flate,
-                         bool              const ascii85) {
-
-    unsigned int const FLATE_IN_SIZE = 16384;
-    unsigned int const FLATE_OUT_SIZE = 17408;
-
-    struct bmepsoe * bmepsoeP;
-    int mode;
-
-    MALLOCVAR_NOFAIL(bmepsoeP);
-    MALLOCVAR_NOFAIL(bmepsoeP->oeP);
-    MALLOCARRAY_NOFAIL(bmepsoeP->rleBuffer, 129);
-    MALLOCARRAY_NOFAIL(bmepsoeP->flateInBuffer, FLATE_IN_SIZE);
-    MALLOCARRAY_NOFAIL(bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);
-
-    mode = 0;
-    if (rle)
-        mode |= OE_RL;
-    if (flate)
-        mode |= OE_FLATE;
-    if (ascii85)
-        mode |= OE_ASC85;
+spawnFilter(FILE *          const ofP,
+            FilterFn *      const filterFn,
+            OutputEncoder * const oeP,
+            FILE **         const feedFilePP,
+            pid_t *         const pidP) {
+/*----------------------------------------------------------------------------
+   Fork a child process to run filter function 'filterFn' and send its
+   output to *ofP.
+
+   Create a pipe for feeding the filter and return as *feedFilePP the
+   stream to which Caller can write to push stuff into the filter.
+
+   *oeP is the parameter to 'filterFn'.
+-----------------------------------------------------------------------------*/
+    int pipeFd[2];
+    pid_t rc;
+
+    makePipe(pipeFd);
+    
+    rc = fork();
+
+    if (rc == (pid_t)-1)
+        pm_error("fork() of filter process failed.  errno=%d (%s)", 
+                 errno, strerror(errno));
+    else if (rc == 0) {
+        /* This is the child process */
+ 
+        FILE * ifP;
+
+        ifP = fdopen(pipeFd[0], "r");
+
+        if (!ifP)
+            pm_error("filter process failed to make "
+                     "file stream (\"FILE\") "
+                     "out of the file descriptor which is input to the "
+                     "filter.  errno=%d (%s)",
+                     errno, strerror(errno));
+
+        closeAllBut(fileno(ifP), fileno(ofP), STDERR_FILENO);
+
+        filterFn(ifP, ofP, oeP);
+
+        exit(EXIT_SUCCESS);
+    } else {
+        /* This is the parent process */
+
+        pid_t const childPid = rc;
+
+        close(pipeFd[0]);
 
-    oe_init(bmepsoeP->oeP, ofP, mode, 9, 
-            bmepsoeP->rleBuffer, 
-            bmepsoeP->flateInBuffer, FLATE_IN_SIZE,
-            bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);
+        *feedFilePP = fdopen(pipeFd[1], "w");
 
-    *bmepsoePP = bmepsoeP;
+        *pidP = childPid;
+    }
 }
 
 
 
 static void
-destroyBmepsOutputEncoder(struct bmepsoe * const bmepsoeP) {
-    
-    free(bmepsoeP->rleBuffer);
-    free(bmepsoeP->flateInBuffer);
-    free(bmepsoeP->flateOutBuffer);
+addFilter(const char *    const description,
+          FilterFn *      const filter,
+          OutputEncoder * const oeP,
+          FILE **         const feedFilePP,
+          pid_t *         const pidList) {
+/*----------------------------------------------------------------------------
+   Add a filter to the front of the chain.
+
+   Spawn a process to do the filtering, by running function 'filter'.
+
+   *feedFilePP is the present head of the chain.  We make the new filter
+   process write its output to that and get its input from a new pipe.
+   We update *feedFilePP to the sending end of the new pipe.
+
+   Add to the list pidList[] the PID of the process we spawn.
+-----------------------------------------------------------------------------*/
+    FILE * const oldFeedFileP = *feedFilePP;
+
+    FILE * newFeedFileP;
+    pid_t pid;
+
+    spawnFilter(oldFeedFileP, filter, oeP, &newFeedFileP, &pid);
+            
+    if (verbose)
+        pm_message("%s filter spawned: pid %u",
+                   description, (unsigned)pid);
     
-    free(bmepsoeP);
+    if (debug) {
+        int const outFd    = fileno(oldFeedFileP);
+        int const supplyFd = fileno(newFeedFileP);
+        pm_message("PID %u writes to FD %u, its supplier writes to FD %u",
+                   (unsigned)pid, outFd, supplyFd);
+    }
+    fclose(oldFeedFileP);  /* Child keeps this open now */
+
+    addToPidList(pidList, pid);
+
+    *feedFilePP = newFeedFileP;
 }
 
 
 
 static void
-outputBmepsSample(struct bmepsoe * const bmepsoeP,
-                  unsigned int     const sampleValue,
-                  unsigned int     const bitsPerSample) {
+spawnFilters(FILE *          const ofP,
+             OutputEncoder * const oeP,
+             FILE **         const feedFilePP,
+             pid_t *         const pidList) {
+/*----------------------------------------------------------------------------
+   Get all the child processes for the filters running and connected.
+   Return at *feedFileP the file stream to which to write the raw data,
+   with the filtered data going to *ofP.
 
-    if (bitsPerSample == 8)
-        oe_byte_add(bmepsoeP->oeP, sampleValue);
-    else {
-        unsigned int m;
+   Filter according to *oeP.
+-----------------------------------------------------------------------------*/
 
-        for (m = 1 << (bitsPerSample-1); m != 0; m >>= 1)
-            /* depends on oe_bit_add accepting any value !=0 as 1 */
-            oe_bit_add(bmepsoeP->oeP, sampleValue & m); 
-    }
+    /* Build up the pipeline from the final to the initial stage.  The
+       result is one of:
+
+          FEED | convertRow | asciiHexFilter | *ofP
+          FEED | convertRow | ascii85Filter | *ofP
+          FEED | convertRow | rleFilter   | asciiHexFilter | *ofP
+          FEED | convertRow | flateFilter | asciiHexFilter | *ofP
+          FEED | convertRow | flateFilter | rleFilter | asciiHexFilter | *ofP
+    */
+
+    FILE * feedFileP;
+        /* The current head of the filter chain; changes as we add filters */
+
+    initPidList(pidList);
+
+    feedFileP = ofP;  /* Initial state: no filter at all */
+
+    addFilter(
+        "output",
+        oeP->outputType == Ascii85 ? &ascii85Filter : asciiHexFilter,
+        oeP,
+        &feedFileP,
+        pidList);
+
+    if (oeP->compressFlate)
+        addFilter("flate", flateFilter, oeP, &feedFileP, pidList);
+
+    if (oeP->compressRle)
+        addFilter("rle", rleFilter, oeP, &feedFileP, pidList);
+
+    *feedFilePP = feedFileP;
 }
 
 
 
 static void
-flushBmepsOutput(struct bmepsoe * const bmepsoeP) {
-    oe_byte_flush(bmepsoeP->oeP);
+waitForChildren(const pid_t * const pidList) {
+/*----------------------------------------------------------------------------
+   Wait for all child processes with PIDs in pidList[] to exit.
+   In pidList[], end-of-list is marked with a special zero value.
+-----------------------------------------------------------------------------*/
+    /* There's an odd behavior in Unix such that if you have set the
+       action for SIGCHLD to ignore the signal (even though ignoring the
+       signal is the default), the process' children do not become
+       zombies.  Consequently, waitpid() always fails with ECHILD - but
+       nonetheless waits for the child to exit.
+    
+       We expect the process not to have the action for SIGCHLD set that
+       way.
+    */
+    unsigned int i;
+
+    for (i = 0; pidList[i]; ++i) {
+        pid_t rc;
+        int status;
+
+        if (verbose)
+            pm_message("Waiting for PID %u to exit", (unsigned)pidList[i]);
+
+        rc = waitpid(pidList[i], &status, 0);
+        if (rc == -1)
+            pm_error ("waitpid() for child %u failed, errno=%d (%s)",
+                      i, errno, strerror(errno));
+        else if (status != EXIT_SUCCESS)
+            pm_error ("Child process %u terminated abnormally", i);
+    }
+    if (verbose)
+        pm_message("All children have exited");
 }
 
 
+
 /*============================================================================
-   END OF OUTPUT ENCODERS
+  END OF OUTPUT ENCODERS
 ============================================================================*/
 
 
+
+static void
+validateComputableBoundingBox(float const scols, 
+                              float const srows,
+                              float const llx, 
+                              float const lly) {
+
+    float const bbWidth  = llx + scols + 0.5;
+    float const bbHeight = lly + srows + 0.5;
+
+    if (bbHeight < INT_MIN || bbHeight > INT_MAX ||
+        bbWidth  < INT_MIN || bbWidth  > INT_MAX)
+        pm_error("Bounding box dimensions %.1f x %.1f are too large "
+                 "for computations.  "
+                 "This probably means input image width, height, "
+                 "or scale factor is too large", bbWidth, bbHeight);
+}
+
+
+
+static void
+warnUserRescaling(float const scale) {
+
+    const char * const baseMsg = "warning, image too large for page";
+
+    if (pm_have_float_format())
+        pm_message("%s; rescaling to %g", baseMsg, scale);
+    else
+        pm_message("%s; rescaling", baseMsg);
+}
+
+
+
 static void
 computeImagePosition(int     const dpiX, 
                      int     const dpiY, 
@@ -545,39 +1054,41 @@ computeImagePosition(int     const dpiX,
                      float * const llyP,
                      bool *  const turnedP ) {
 /*----------------------------------------------------------------------------
-   Determine where on the page the image is to go.  This means position,
-   dimensions, and orientation.
+  Determine where on the page the image is to go.  This means position,
+  dimensions, and orientation.
 
-   icols/irows are the dimensions of the PNM input in xels.
+  icols/irows are the dimensions of the PNM input in xels.
 
-   'mustturn' means we are required to rotate the image.
+  'mustturn' means we are required to rotate the image.
 
-   'canturn' means we may rotate the image if it fits better, but don't
-   have to.
+  'canturn' means we may rotate the image if it fits better, but don't
+  have to.
 
-   *scolsP, *srowsP are the dimensions of the image in 1/72 inch.
+  *scolsP, *srowsP are the dimensions of the image in 1/72 inch.
 
-   *llxP, *llyP are the coordinates, in 1/72 inch, of the lower left
-   corner of the image on the page.
+  *llxP, *llyP are the coordinates in the Postcript frame, of the lower left
+  corner of the image on the page.  The Postscript frame is different from the
+  Neptbm frame: units are 1/72 inch (1 point) and (0,0) is the lower left
+  corner.
 
-   *turnedP is true iff the image is to be rotated 90 degrees on the page.
+  *turnedP is true iff the image is to be rotated 90 degrees on the page.
 
-   imagewidth/imageheight are the requested dimensions of the image on
-   the page, in 1/72 inch.  Image will be as large as possible within
-   those dimensions.  Zero means unspecified, so 'scale', 'pagewid',
-   'pagehgt', 'irows', and 'icols' determine image size.
+  imagewidth/imageheight are the requested dimensions of the image on
+  the page, in 1/72 inch.  Image will be as large as possible within
+  those dimensions.  Zero means unspecified, so 'scale', 'pagewid',
+  'pagehgt', 'irows', and 'icols' determine image size.
 
-   'equalpixels' means the user wants one printed pixel per input pixel.
-   It is inconsistent with imagewidth or imageheight != 0
+  'equalpixels' means the user wants one printed pixel per input pixel.
+  It is inconsistent with imagewidth or imageheight != 0
 
-   'requestedScale' is meaningful only when imageheight/imagewidth == 0
-   and equalpixels == FALSE.  It tells how many inches the user wants
-   72 pixels of input to occupy, if it fits on the page.
+  'requestedScale' is meaningful only when imageheight/imagewidth == 0
+  and equalpixels == FALSE.  It tells how many inches the user wants
+  72 pixels of input to occupy, if it fits on the page.
 -----------------------------------------------------------------------------*/
     int cols, rows;
-        /* Number of columns, rows of input xels in the output, as
-           rotated if applicable
-        */
+    /* Number of columns, rows of input xels in the output, as
+       rotated if applicable
+    */
     bool shouldturn;  /* The image fits the page better if we turn it */
     
     if (icols > irows && pagehgt > pagewid)
@@ -608,7 +1119,7 @@ computeImagePosition(int     const dpiX,
             scale = (float) imagewidth/cols;
         else
             scale = MIN((float)imagewidth/cols, (float)imageheight/rows);
-        
+    
         *scolsP = cols*scale;
         *srowsP = rows*scale;
     } else {
@@ -617,8 +1128,8 @@ computeImagePosition(int     const dpiX,
         */
         const int devpixX = dpiX / 72.0 + 0.5;        
         const int devpixY = dpiY / 72.0 + 0.5;        
-            /* How many device pixels make up 1/72 inch, rounded to
-               nearest integer */
+        /* How many device pixels make up 1/72 inch, rounded to
+           nearest integer */
         const float pixfacX = 72.0 / dpiX * devpixX;  /* 1, approx. */
         const float pixfacY = 72.0 / dpiY * devpixY;  /* 1, approx. */
         float scale;
@@ -628,10 +1139,9 @@ computeImagePosition(int     const dpiX,
 
         *scolsP = scale * cols * pixfacX;
         *srowsP = scale * rows * pixfacY;
-        
+    
         if (scale != requestedScale)
-            pm_message("warning, image too large for page, rescaling to %g", 
-                       scale );
+            warnUserRescaling(scale);
 
         /* Before May 2001, Pnmtops enforced a 5% margin around the page.
            If the image would be too big to leave a 5% margin, Pnmtops would
@@ -644,6 +1154,7 @@ computeImagePosition(int     const dpiX,
     *llxP = (center) ? ( pagewid - *scolsP ) / 2 : 0;
     *llyP = (center) ? ( pagehgt - *srowsP ) / 2 : 0;
 
+    validateComputableBoundingBox( *scolsP, *srowsP, *llxP, *llyP);
 
     if (verbose)
         pm_message("Image will be %3.2f points wide by %3.2f points high, "
@@ -677,7 +1188,7 @@ determineDictionaryRequirement(bool           const userWantsDict,
 static void
 defineReadstring(bool const rle) {
 /*----------------------------------------------------------------------------
-   Write to Standard Output Postscript statements to define /readstring.
+  Write to Standard Output Postscript statements to define /readstring.
 -----------------------------------------------------------------------------*/
     if (rle) {
         printf("/rlestr1 1 string def\n");
@@ -692,7 +1203,7 @@ defineReadstring(bool const rle) {
         printf("    readhexstring pop\n");            /* s */
         printf("    length\n");               /* nr */
         printf("  } {\n");                    /* c */
-        printf("    256 exch sub dup\n");         /* n n */
+        printf("    257 exch sub dup\n");         /* n n */
         printf("    currentfile rlestr1 readhexstring pop\n");/* n n s1 */
         printf("    0 get\n");                /* n n c */
         printf("    exch 0 exch 1 exch 1 sub {\n");       /* n c 0 1 n-1*/
@@ -725,13 +1236,14 @@ static void
 setupReadstringNative(bool         const rle,
                       bool         const color,
                       unsigned int const icols, 
-                      unsigned int const padright, 
-                      unsigned int const bps) {
+                      unsigned int const bitsPerSample) {
 /*----------------------------------------------------------------------------
-   Write to Standard Output statements to define /readstring and also
-   arguments for it (/picstr or /rpicstr, /gpicstr, and /bpicstr).
+  Write to Standard Output statements to define /readstring and also
+  arguments for it (/picstr or /rpicstr, /gpicstr, and /bpicstr).
 -----------------------------------------------------------------------------*/
-    unsigned int const bytesPerRow = (icols + padright) * bps / 8;
+    unsigned int const bytesPerRow = icols / (8/bitsPerSample) +
+        (icols % (8/bitsPerSample) > 0 ? 1 : 0);
+        /* Size of row buffer, padded up to byte boundary. */
 
     defineReadstring(rle);
     
@@ -754,13 +1266,17 @@ putFilters(unsigned int const postscriptLevel,
 
     assert(postscriptLevel > 1);
     
+    /* We say to decode flate, then rle, so Caller must ensure it encodes
+       rel, then flate.
+    */
+
     if (ascii85)
         printf("/ASCII85Decode filter ");
     else 
         printf("/ASCIIHexDecode filter ");
     if (flate)
         printf("/FlateDecode filter ");
-    if (rle) /* bmeps encodes rle before flate, so must decode after! */
+    if (rle) 
         printf("/RunLengthDecode filter ");
 }
 
@@ -785,10 +1301,9 @@ putSetup(unsigned int const dictSize,
          bool         const rle,
          bool         const color,
          unsigned int const icols,
-         unsigned int const padright,
-         unsigned int const bps) {
+         unsigned int const bitsPerSample) {
 /*----------------------------------------------------------------------------
-   Put the setup section in the Postscript program on Standard Output.
+  Put the setup section in the Postscript program on Standard Output.
 -----------------------------------------------------------------------------*/
     printf("%%%%BeginSetup\n");
 
@@ -797,7 +1312,7 @@ putSetup(unsigned int const dictSize,
         printf("%u dict begin\n", dictSize);
     
     if (!psFilter)
-        setupReadstringNative(rle, color, icols, padright, bps);
+        setupReadstringNative(rle, color, icols, bitsPerSample);
 
     printf("%%%%EndSetup\n");
 }
@@ -808,8 +1323,8 @@ static void
 putImage(bool const psFilter,
          bool const color) {
 /*----------------------------------------------------------------------------
-   Put the image/colorimage statement in the Postscript program on
-   Standard Output.
+  Put the image/colorimage statement in the Postscript program on
+  Standard Output.
 -----------------------------------------------------------------------------*/
     if (color) {
         if (psFilter)
@@ -864,8 +1379,7 @@ putInit(unsigned int const postscriptLevel,
         float        const srows,
         float        const llx, 
         float        const lly,
-        int          const padright, 
-        int          const bps,
+        int          const bitsPerSample,
         int          const pagewid, 
         int          const pagehgt,
         bool         const color, 
@@ -877,8 +1391,8 @@ putInit(unsigned int const postscriptLevel,
         bool         const psFilter,
         unsigned int const dictSize) {
 /*----------------------------------------------------------------------------
-   Write out to Standard Output the headers stuff for the Postscript
-   program (everything up to the raster).
+  Write out to Standard Output the headers stuff for the Postscript
+  program (everything up to the raster).
 -----------------------------------------------------------------------------*/
     /* The numbers in the %! line often confuse people. They are NOT the
        PostScript language level.  The first is the level of the DSC comment
@@ -897,7 +1411,7 @@ putInit(unsigned int const postscriptLevel,
         (int) (llx + scols + 0.5), (int) (lly + srows + 0.5));
     printf("%%%%EndComments\n");
 
-    putSetup(dictSize, psFilter, rle, color, icols, padright, bps);
+    putSetup(dictSize, psFilter, rle, color, icols, bitsPerSample);
 
     printf("%%%%Page: 1 1\n");
     if (setpage)
@@ -908,7 +1422,7 @@ putInit(unsigned int const postscriptLevel,
     printf("%g %g scale\n", scols, srows);
     if (turned)
         printf("0.5 0.5 translate  90 rotate  -0.5 -0.5 translate\n");
-    printf("%d %d %d\n", icols, irows, bps);
+    printf("%d %d %d\n", icols, irows, bitsPerSample);
     printf("[ %d 0 0 -%d 0 %d ]\n", icols, irows, irows);
 
     if (psFilter)
@@ -917,6 +1431,7 @@ putInit(unsigned int const postscriptLevel,
         putInitReadstringNative(color);
 
     printf("\n");
+    fflush(stdout);
 }
 
 
@@ -957,8 +1472,57 @@ putEnd(bool         const showpage,
 
 
 static void
+validateBpsRequest(unsigned int const bitsPerSampleReq,
+                   unsigned int const postscriptLevel,
+                   bool         const psFilter) {
+
+    if (postscriptLevel < 2 && bitsPerSampleReq > 8)
+        pm_error("You requested %u bits per sample, but in Postscript "
+                 "level 1, 8 is the maximum.  You can get 12 with "
+                 "-level 2 and -psfilter", bitsPerSampleReq);
+    else if (!psFilter && bitsPerSampleReq > 8)
+        pm_error("You requested %u bits per sample, but without "
+                 "-psfilter, the maximum is 8", bitsPerSampleReq);
+}
+
+    
+
+static unsigned int
+bpsFromInput(unsigned int const bitsRequiredByMaxval,
+             unsigned int const postscriptLevel,
+             bool         const psFilter) {
+
+    unsigned int retval;
+
+    if (bitsRequiredByMaxval <= 1)
+        retval = 1;
+    else if (bitsRequiredByMaxval <= 2)
+        retval = 2;
+    else if (bitsRequiredByMaxval <= 4)
+        retval = 4;
+    else if (bitsRequiredByMaxval <= 8)
+        retval = 8;
+    else {
+        /* Post script level 2 defines a format with 12 bits per sample,
+           but I don't know the details of that format (both RLE and
+           non-RLE variations) and existing native raster generation code
+           simply can't handle bps > 8.  But the built-in filters know
+           how to do 12 bps.
+        */
+        if (postscriptLevel >= 2 && psFilter)
+            retval = 12;
+        else
+            retval = 8;
+    }
+    return retval;
+}
+
+
+
+static void
 warnUserAboutReducedDepth(unsigned int const bitsGot,
                           unsigned int const bitsWanted,
+                          bool         const userRequested,
                           unsigned int const postscriptLevel,
                           bool         const psFilter) {
 
@@ -967,15 +1531,19 @@ warnUserAboutReducedDepth(unsigned int const bitsGot,
                    "though the input has %u bits.",
                    bitsGot, bitsWanted);
 
-        if (postscriptLevel < 2)
-            pm_message("Postscript level %u has a maximum depth of 8 bits.  "
-                       "You could get up to 12 with -level=2 and -psfilter.",
-                       postscriptLevel);
-        else {
-            if (!psFilter)
-                pm_message("You can get up to 12 bits with -psfilter");
-            else
-                pm_message("The Postscript maximum is 12.");
+        if (!userRequested) {
+            if (postscriptLevel < 2)
+                pm_message("Postscript level %u has a maximum depth of "
+                           "8 bits.  "
+                           "You could get up to 12 with -level=2 "
+                           "and -psfilter.",
+                           postscriptLevel);
+            else {
+                if (!psFilter)
+                    pm_message("You can get up to 12 bits with -psfilter");
+                else
+                    pm_message("The Postscript maximum is 12.");
+            }
         }
     }
 }
@@ -986,76 +1554,237 @@ static void
 computeDepth(xelval         const inputMaxval,
              unsigned int   const postscriptLevel, 
              bool           const psFilter,
-             unsigned int * const bitspersampleP,
-             unsigned int * const psMaxvalP) {
+             unsigned int   const bitsPerSampleReq,
+             unsigned int * const bitsPerSampleP) {
 /*----------------------------------------------------------------------------
-   Figure out how many bits will represent each sample in the Postscript
-   program, and the maxval of the Postscript program samples.  The maxval
-   is just the maximum value allowable in the number of bits.
+  Figure out how many bits will represent each sample in the Postscript
+  program, and the maxval of the Postscript program samples.  The maxval
+  is just the maximum value allowable in the number of bits.
+
+  'bitsPerSampleReq' is the bits per sample that the user requests, or
+  zero if he made no request.
 -----------------------------------------------------------------------------*/
     unsigned int const bitsRequiredByMaxval = pm_maxvaltobits(inputMaxval);
 
-    if (bitsRequiredByMaxval <= 1)
-        *bitspersampleP = 1;
-    else if (bitsRequiredByMaxval <= 2)
-        *bitspersampleP = 2;
-    else if (bitsRequiredByMaxval <= 4)
-        *bitspersampleP = 4;
-    else if (bitsRequiredByMaxval <= 8)
-        *bitspersampleP = 8;
-    else {
-        /* Post script level 2 defines a format with 12 bits per sample,
-           but I don't know the details of that format (both RLE and
-           non-RLE variations) and existing native raster generation code
-           simply can't handle bps > 8.  But the built-in filters know
-           how to do 12 bps.
-        */
-        if (postscriptLevel >= 2 && psFilter)
-            *bitspersampleP = 12;
-        else
-            *bitspersampleP = 8;
+    if (bitsPerSampleReq != 0) {
+        validateBpsRequest(bitsPerSampleReq, postscriptLevel, psFilter);
+        *bitsPerSampleP = bitsPerSampleReq;
+    } else {
+        *bitsPerSampleP = bpsFromInput(bitsRequiredByMaxval,
+                                       postscriptLevel, psFilter);
     }
-
-    warnUserAboutReducedDepth(*bitspersampleP, bitsRequiredByMaxval,
+    warnUserAboutReducedDepth(*bitsPerSampleP, bitsRequiredByMaxval,
+                              bitsPerSampleReq != 0,
                               postscriptLevel, psFilter);
 
-    *psMaxvalP = pm_bitstomaxval(*bitspersampleP);
-
-    if (verbose)
+    if (verbose) {
+        unsigned int const psMaxval = pm_bitstomaxval(*bitsPerSampleP);
         pm_message("Input maxval is %u.  Postscript raster will have "
                    "%u bits per sample, so maxval = %u",
-                   inputMaxval, *bitspersampleP, *psMaxvalP);
+                   inputMaxval, *bitsPerSampleP, psMaxval);
+    }
 }    
 
 
 
+/*===========================================================================
+  The bit accumulator
+===========================================================================*/
+
+typedef struct {
+    unsigned int value;
+    unsigned int consumed;
+} BitAccumulator;
+
+
+
 static void
-convertRowNative(struct pam * const pamP, 
-                 tuple *      const tuplerow, 
-                 unsigned int const psMaxval, 
-                 bool         const rle, 
-                 unsigned int const padright) {
+ba_init(BitAccumulator * const baP) {
+
+    baP->value    = 0;
+    baP->consumed = 0;
+}
+
+
+
+static void
+ba_add12(BitAccumulator * const baP,
+         unsigned int     const new12,
+         FILE           * const fP) {
+/*----------------------------------------------------------------------------
+  Read a 12-bit string into the bit accumulator baP->value.
+  On every other call, combine two 12-bit strings and write out three bytes.
+-----------------------------------------------------------------------------*/
+    assert (baP->consumed == 12 || baP->consumed == 0);
+
+    if (baP->consumed == 12){
+        char const oldHi8 = (baP->value) >> 4;
+        char const oldLo4 = (baP->value) & 0x0f;
+        char const newHi4 = new12 >> 8;
+        char const newLo8 = new12 & 0xff;
+
+        fputc(oldHi8, fP);
+        fputc((oldLo4 << 4) | newHi4 , fP);
+        fputc(newLo8, fP);
+        baP->value = 0; baP->consumed = 0;
+    } else {
+        baP->value = new12;  baP->consumed = 12;
+    }
+}
+
+
+
+static void
+ba_add(BitAccumulator * const baP,
+       unsigned int     const b,
+       unsigned int     const bitsPerSample,
+       FILE           * const fP) {
+/*----------------------------------------------------------------------------
+  Combine bit sequences that do not fit into a byte.
+
+  Used when bitsPerSample =1, 2, 4.  
+  Logic also works for bitsPerSample = 8, 16.
+
+  The accumulator, baP->value is unsigned int (usually 32 bits), but
+  only 8 bits are used.
+-----------------------------------------------------------------------------*/
+    unsigned int const bufSize = 8;
+
+    assert (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4);
+
+    baP->value = (baP->value << bitsPerSample) | b ;
+    baP->consumed += bitsPerSample;
+    if (baP->consumed == bufSize) {
+        /* flush */
+        fputc( baP->value, fP);
+        baP->value = 0;
+        baP->consumed = 0;
+    }
+}
+
+
+
+static void
+ba_flush(BitAccumulator * const baP,
+         FILE *           const fP) {
+/*----------------------------------------------------------------------------
+  Flush partial bits in baP->consumed.
+-----------------------------------------------------------------------------*/
+    if (baP->consumed == 12) {
+        char const oldHi8 = (baP->value) >> 4;
+        char const oldLo4 = (baP->value) & 0x0f;
+        fputc(oldHi8, fP);
+        fputc(oldLo4 << 4, fP);
+    } else if (baP->consumed == 8)
+        fputc(baP->value , fP);
+    else if (baP->consumed > 0) {
+        unsigned int const leftShift = 8 - baP->consumed;
+        assert(baP->consumed <= 8);  /* why? */
+        baP->value <<= leftShift;
+        fputc(baP->value , fP);
+    }
+    baP->value = 0;
+    baP->consumed = 0;
+}
+
+
+
+static void
+outputSample(BitAccumulator * const baP,
+             unsigned int     const sampleValue,
+             unsigned int     const bitsPerSample,
+             FILE           * const fP) {
+
+    if (bitsPerSample == 8)
+        fputc(sampleValue, fP);
+    else if (bitsPerSample == 12)
+        ba_add12(baP, sampleValue, fP);
+    else
+        ba_add(baP, sampleValue, bitsPerSample, fP);
+}
+
+
+
+static void
+flushOutput(BitAccumulator * const baP,
+            FILE *           const fP) {
+    ba_flush(baP, fP);
+}
+
+
+
+/*----------------------------------------------------------------------
+  Row converters
+
+  convertRowPbm is a fast routine for PBM images.
+  It is used only when the input is PBM and the user does not specify
+  a -bitspersample value greater than 1.  It is not used when the input
+  image is PGM or PPM and the output resolution is brought down to one
+  bit per pixel by -bitpersample=1 .
+
+  convertRowNative and convertRowPsFilter are the general converters.
+  They are quite similar, the differences being:
+  (1) Native output separates the color planes: 
+  (RRR...RRR GGG...GGG BBB...BBB),
+  whereas psFilter does not:
+  (RGB RGB RGB RGB ......... RGB).
+  (2) Native flushes the run-length encoder at the end of each row if
+  grayscale, at the end of each plane if color.
+
+  Both convertRowNative and convertRowPsFilter can handle PBM, though we
+  don't use them.
+
+  If studying the code, read convertRowPbm first.  convertRowNative and
+  convertRowPsFilter are constructs that pack raster data into a form
+  similar to a binary PBM bitrow.
+  ----------------------------------------------------------------------*/
+
+static void
+convertRowPbm(struct pam *     const pamP,
+              unsigned char  * const bitrow,
+              bool             const psFilter,
+              FILE *           const fP) {
+/*---------------------------------------------------------------------
+  Feed PBM raster data directly to the output encoder.
+  Invert bits: 0 is "white" in PBM, 0 is "black" in postscript.
+----------------------------------------------------------------------*/
+    unsigned int colChar;
+    unsigned int const colChars = pbm_packed_bytes(pamP->width);
+
+    pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width, pamP->format);
+
+    for (colChar = 0; colChar < colChars; ++colChar)
+        bitrow[colChar] =  ~ bitrow[colChar];
+
+    /* Zero clear padding beyond right edge */
+    pbm_cleanrowend_packed(bitrow, pamP->width);
+    writeFile(bitrow, colChars, "PBM reader", fP);
+}
+
+
+
+static void
+convertRowNative(struct pam *     const pamP, 
+                 tuple *                tuplerow, 
+                 unsigned int     const bitsPerSample,
+                 FILE           * const fP) { 
+
+    unsigned int const psMaxval = pm_bitstomaxval(bitsPerSample);
 
     unsigned int plane;
+    BitAccumulator ba;
+
+    ba_init(&ba);
+
+    pnm_readpamrow(pamP, tuplerow);
+    pnm_scaletuplerow(pamP, tuplerow, tuplerow, psMaxval);
 
     for (plane = 0; plane < pamP->depth; ++plane) {
         unsigned int col;
-        for (col= 0; col < pamP->width; ++col) {
-            sample const scaledSample = 
-                pnm_scalesample(tuplerow[col][plane], pamP->maxval, psMaxval);
+        for (col= 0; col < pamP->width; ++col)
+            outputSample(&ba, tuplerow[col][plane], bitsPerSample, fP);
 
-            if (rle)
-                rleputxelval(scaledSample);
-            else
-                putxelval(scaledSample);
-        }
-        for (col = 0; col < padright; ++col)
-            if (rle)
-                rleputxelval(0);
-        else
-            putxelval(0);
-        if (rle)
-            rleflush();
+        flushOutput(&ba, fP);
     }
 }
 
@@ -1063,34 +1792,27 @@ convertRowNative(struct pam * const pamP,
 
 static void
 convertRowPsFilter(struct pam *     const pamP,
-                   tuple *          const tuplerow,
-                   struct bmepsoe * const bmepsoeP,
-                   unsigned int     const psMaxval) {
+                   tuple *                tuplerow,
+                   unsigned int     const bitsPerSample,
+                   FILE           * const fP) { 
 
-    unsigned int const bitsPerSample = pm_maxvaltobits(psMaxval);
-    unsigned int const stragglers =
-        (((bitsPerSample * pamP->depth) % 8) * pamP->width) % 8;
-        /* Number of bits at the right edge that don't fill out a
-           whole byte */
+    unsigned int const psMaxval = pm_bitstomaxval(bitsPerSample);
 
     unsigned int col;
-    tuple scaledTuple;
-    
-    scaledTuple = pnm_allocpamtuple(pamP);
+    BitAccumulator ba;
+
+    ba_init(&ba);
+
+    pnm_readpamrow(pamP, tuplerow);
+    pnm_scaletuplerow(pamP, tuplerow, tuplerow, psMaxval);
 
     for (col = 0; col < pamP->width; ++col) {
         unsigned int plane;
-        pnm_scaletuple(pamP, scaledTuple, tuplerow[col], psMaxval);
-        
         for (plane = 0; plane < pamP->depth; ++plane)
-            outputBmepsSample(bmepsoeP, scaledTuple[plane], bitsPerSample);
-    }
-    if (stragglers > 0) {
-        unsigned int i;
-        for (i = stragglers; i < 8; ++i)
-            oe_bit_add(bmepsoeP->oeP, 0);
+            outputSample(&ba, tuplerow[col][plane], bitsPerSample, fP);
     }
-    pnm_freepamtuple(scaledTuple);
+    flushOutput(&ba, fP);
+
 }
 
 
@@ -1146,50 +1868,111 @@ selectPostscriptLevel(bool           const levelIsGiven,
 
 
 static void
-convertPage(FILE * const ifP, 
-            int    const turnflag, 
-            int    const turnokflag, 
-            bool   const psFilter,
-            bool   const rle, 
-            bool   const flate,
-            bool   const ascii85,
-            bool   const setpage,
-            bool   const showpage,
-            bool   const center, 
-            float  const scale,
-            int    const dpiX, 
-            int    const dpiY, 
-            int    const pagewid, 
-            int    const pagehgt,
-            int    const imagewidth, 
-            int    const imageheight, 
-            bool   const equalpixels,
-            char   const name[],
-            bool   const dict,
-            bool   const vmreclaim,
-            bool   const levelIsGiven,
+convertRaster(struct pam * const inpamP,
+              unsigned int const bitsPerSample,
+              bool         const psFilter,
+              FILE *       const fP) {
+/*----------------------------------------------------------------------------
+   Read the raster described by *inpamP, and write a bit stream of samples
+   to *fP.  This stream has to be compressed and converted to text before it
+   can be part of a Postscript program.
+   
+   'psFilter' means to do the conversion using built in Postscript filters, as
+   opposed to our own filters via /readstring.
+
+   'bitsPerSample' is how many bits each sample is to take in the Postscript
+   output.
+-----------------------------------------------------------------------------*/
+    if (PAM_FORMAT_TYPE(inpamP->format) == PBM_TYPE && bitsPerSample == 1)  {
+        unsigned char * bitrow;
+        unsigned int row;
+
+        bitrow = pbm_allocrow_packed(inpamP->width);
+
+        for (row = 0; row < inpamP->height; ++row)
+            convertRowPbm(inpamP, bitrow, psFilter, fP);
+
+        pbm_freerow(bitrow);
+    } else  {
+        tuple *tuplerow;
+        unsigned int row;
+        
+        tuplerow = pnm_allocpamrow(inpamP);
+
+        for (row = 0; row < inpamP->height; ++row) {
+            if (psFilter)
+                convertRowPsFilter(inpamP, tuplerow, bitsPerSample, fP);
+            else
+                convertRowNative(inpamP, tuplerow, bitsPerSample, fP);
+        }
+        pnm_freepamrow(tuplerow);
+    }
+}
+
+
+
+/* FILE MANAGEMENT: File management is pretty hairy here.  A filter, which
+   runs in its own process, needs to be able to cause its output file to
+   close because it might be an internal pipe and the next stage needs to
+   know output is done.  So the forking process must close its copy of the
+   file descriptor.  BUT: if the output of the filter is not an internal
+   pipe but this program's output, then we don't want it closed when the
+   filter terminates because we'll need it to be open for the next image
+   the program converts (with a whole new chain of filters).
+   
+   To prevent the progam output file from getting closed, we pass a
+   duplicate of it to spawnFilters() and keep the original open.
+*/
+
+
+
+static void
+convertPage(FILE *       const ifP, 
+            int          const turnflag, 
+            int          const turnokflag, 
+            bool         const psFilter,
+            bool         const rle, 
+            bool         const flate,
+            bool         const ascii85,
+            bool         const setpage,
+            bool         const showpage,
+            bool         const center, 
+            float        const scale,
+            int          const dpiX, 
+            int          const dpiY, 
+            int          const pagewid, 
+            int          const pagehgt,
+            int          const imagewidth, 
+            int          const imageheight, 
+            bool         const equalpixels,
+            unsigned int const bitsPerSampleReq,
+            char         const name[],
+            bool         const dict,
+            bool         const vmreclaim,
+            bool         const levelIsGiven,
             unsigned int const levelGiven) {
     
     struct pam inpam;
-    tuple* tuplerow;
-    unsigned int padright;
-        /* Number of bits we must add to the right end of each Postscript
-           output line in order to have an integral number of bytes of output.
-           E.g. at 2 bits per sample with 10 columns, this would be 4.
-        */
-    int row;
-    unsigned int psMaxval;
-        /* The maxval of the Postscript program */
     float scols, srows;
     float llx, lly;
     bool turned;
     bool color;
     unsigned int postscriptLevel;
-    struct bmepsoe * bmepsoeP;
-    unsigned int dictSize;
+    unsigned int bitsPerSample;
+    unsigned int dictSize;  
         /* Size of Postscript dictionary we should define */
+    OutputEncoder oe;
+    pid_t filterPidList[MAX_FILTER_CT + 1];
+
+    FILE * feedFileP;
+        /* The file stream which is the head of the filter chain; we write to
+           this and filtered stuff comes out the other end.
+        */
+    FILE * filterChainOfP;
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    validateCompDimension(inpam.width, 16, "Input image width");
     
     if (!STRSEQ(inpam.tuple_type, PAM_PBM_TUPLETYPE) &&
         !STRSEQ(inpam.tuple_type, PAM_PGM_TUPLETYPE) &&
@@ -1206,13 +1989,9 @@ convertPage(FILE * const ifP,
     if (color)
         pm_message("generating color Postscript program.");
 
-    computeDepth(inpam.maxval, postscriptLevel, psFilter,
-                 &bitspersample, &psMaxval);
-    {
-        unsigned int const realBitsPerLine = inpam.width * bitspersample;
-        unsigned int const paddedBitsPerLine = ((realBitsPerLine + 7) / 8) * 8;
-        padright = (paddedBitsPerLine - realBitsPerLine) / bitspersample;
-    }
+    computeDepth(inpam.maxval, postscriptLevel, psFilter, bitsPerSampleReq,
+                 &bitsPerSample);
+
     /* In positioning/scaling the image, we treat the input image as if
        it has a density of 72 pixels per inch.
     */
@@ -1225,84 +2004,65 @@ convertPage(FILE * const ifP,
     determineDictionaryRequirement(dict, psFilter, &dictSize);
     
     putInit(postscriptLevel, name, inpam.width, inpam.height, 
-            scols, srows, llx, lly, padright, bitspersample, 
+            scols, srows, llx, lly, bitsPerSample, 
             pagewid, pagehgt, color,
             turned, rle, flate, ascii85, setpage, psFilter, dictSize);
 
-    createBmepsOutputEncoder(&bmepsoeP, stdout, rle, flate, ascii85);
-    initNativeOutputEncoder(rle, bitspersample);
+    initOutputEncoder(&oe, inpam.width, bitsPerSample,
+                      rle, flate, ascii85, psFilter);
 
-    tuplerow = pnm_allocpamrow(&inpam);
-
-    for (row = 0; row < inpam.height; ++row) {
-        pnm_readpamrow(&inpam, tuplerow);
-        if (psFilter)
-            convertRowPsFilter(&inpam, tuplerow, bmepsoeP, psMaxval);
-        else
-            convertRowNative(&inpam, tuplerow, psMaxval, rle, padright);
-    }
+    fflush(stdout);
+    filterChainOfP = fdopen(dup(fileno(stdout)), "w");
+        /* spawnFilters() closes this.  See FILE MANAGEMENT above */
 
-    pnm_freepamrow(tuplerow);
+    spawnFilters(filterChainOfP, &oe, &feedFileP, filterPidList);
+ 
+    convertRaster(&inpam, bitsPerSample, psFilter, feedFileP);
 
-    if (psFilter)
-        flushBmepsOutput(bmepsoeP);
-    else
-        flushNativeOutput(rle);
+    fflush(feedFileP);
+    fclose(feedFileP);
 
-    destroyBmepsOutputEncoder(bmepsoeP);
+    waitForChildren(filterPidList);
 
     putEnd(showpage, psFilter, ascii85, dictSize, vmreclaim);
 }
 
 
 
-static const char *
-basebasename(const char * const filespec) {
-/*----------------------------------------------------------------------------
-    Return filename up to first period
------------------------------------------------------------------------------*/
-    char const dirsep = '/';
-    const char * const lastSlashPos = strrchr(filespec, dirsep);
-
-    char * name;
-    const char * filename;
-
-    if (lastSlashPos)
-        filename = lastSlashPos + 1;
-    else
-        filename = filespec;
-
-    name = strdup(filename);
-    if (name != NULL) {
-        char * const dotPosition = strchr(name, '.');
-
-        if (dotPosition)
-            *dotPosition = '\0';
-    }
-    return name;
-}
-
-
-
 int
-main(int argc, char * argv[]) {
+main(int argc, const char * argv[]) {
 
-    FILE* ifp;
-    const char *name;  /* malloc'ed */
+    FILE * ifP;
+    const char * name;  /* malloc'ed */
     struct cmdlineInfo cmdline;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
+
+    setSignals();
 
     parseCommandLine(argc, argv, &cmdline);
 
-    verbose = cmdline.verbose;
+    verbose = cmdline.verbose || cmdline.debug;
+    debug   = cmdline.debug;
 
-    ifp = pm_openr(cmdline.inputFileName);
+    if (cmdline.flate && !progIsFlateCapable())
+        pm_error("This program cannot do flate compression.  "
+                 "(There are other versions of the program that do, "
+                 "though -- it's a build-time option");
+
+    ifP = pm_openr(cmdline.inputFileName);
 
     if (streq(cmdline.inputFileName, "-"))
         name = strdup("noname");
     else
         name = basebasename(cmdline.inputFileName);
+
+    /* This program manages file descriptors in a way that assumes
+       that new files will get file descriptor numbers less than 10,
+       so we close superfluous files now to make sure that's true.
+    */
+    closeAllBut(fileno(ifP), fileno(stdout), fileno(stderr));
+
     {
         int eof;  /* There are no more images in the input file */
         unsigned int imageSeq;
@@ -1320,7 +2080,7 @@ main(int argc, char * argv[]) {
 
         eof = FALSE;  /* There is always at least one image */
         for (imageSeq = 0; !eof; ++imageSeq) {
-            convertPage(ifp, cmdline.mustturn, cmdline.canturn, 
+            convertPage(ifP, cmdline.mustturn, cmdline.canturn, 
                         cmdline.psfilter,
                         cmdline.rle, cmdline.flate, cmdline.ascii85, 
                         cmdline.setpage, cmdline.showpage,
@@ -1328,15 +2088,17 @@ main(int argc, char * argv[]) {
                         cmdline.dpiX, cmdline.dpiY,
                         cmdline.width, cmdline.height, 
                         cmdline.imagewidth, cmdline.imageheight, 
-                        cmdline.equalpixels, name, 
+                        cmdline.equalpixels,
+                        cmdline.bitspersampleSpec ? cmdline.bitspersample : 0,
+                        name, 
                         cmdline.dict, cmdline.vmreclaim,
                         cmdline.levelSpec, cmdline.level);
-            pnm_nextimage(ifp, &eof);
+            pnm_nextimage(ifP, &eof);
         }
     }
-    strfree(name);
+    pm_strfree(name);
 
-    pm_close(ifp);
+    pm_close(ifP);
 
     return 0;
 }
@@ -1357,4 +2119,9 @@ main(int argc, char * argv[]) {
 ** -nocenter option added November 1993 by Wolfgang Stuerzlinger,
 **  wrzl@gup.uni-linz.ac.at.
 **
+** July 2011 afu
+** row convertors rewritten, fast PBM-only row convertor added,
+** rle compression slightly modified, flate compression added
+** ascii85 output end added.
+**
 */