about summary refs log tree commit diff
path: root/generator
diff options
context:
space:
mode:
Diffstat (limited to 'generator')
-rw-r--r--generator/Makefile9
-rw-r--r--generator/pamcrater.c2
-rw-r--r--generator/pamgauss.c264
-rw-r--r--generator/pamgradient.c29
-rw-r--r--generator/pamshadedrelief.c2
-rw-r--r--generator/pamtris/Makefile27
-rw-r--r--generator/pamtris/boundaries.c262
-rw-r--r--generator/pamtris/boundaries.h72
-rw-r--r--generator/pamtris/framebuffer.c339
-rw-r--r--generator/pamtris/framebuffer.h75
-rw-r--r--generator/pamtris/input.c695
-rw-r--r--generator/pamtris/input.h27
-rw-r--r--generator/pamtris/limits_pamtris.h11
-rw-r--r--generator/pamtris/pamtris.c171
-rw-r--r--generator/pamtris/triangle.c327
-rw-r--r--generator/pamtris/triangle.h25
-rw-r--r--generator/pamtris/utils.c266
-rw-r--r--generator/pamtris/utils.h58
-rw-r--r--generator/pamtris/varying.h12
-rw-r--r--generator/pbmmake.c16
-rw-r--r--generator/pbmtext.c1596
-rw-r--r--generator/pbmtextps.c736
-rwxr-xr-xgenerator/pgmcrater20
-rw-r--r--generator/pgmkernel.c4
-rw-r--r--generator/pgmmake.c70
-rw-r--r--generator/pgmnoise.c21
-rw-r--r--generator/pgmramp.c31
-rw-r--r--generator/ppmcie.c38
-rw-r--r--generator/ppmforge.c2
-rw-r--r--generator/ppmpat.c582
-rwxr-xr-xgenerator/ppmrainbow45
-rw-r--r--generator/ppmwheel.c309
32 files changed, 4949 insertions, 1194 deletions
diff --git a/generator/Makefile b/generator/Makefile
index d0ea6b60..d54a6cc5 100644
--- a/generator/Makefile
+++ b/generator/Makefile
@@ -7,6 +7,8 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/config.mk
 
+SUBDIRS = pamtris
+
 # We tend to separate out the build targets so that we don't have
 # any more dependencies for a given target than it really needs.
 # That way, if there is a problem with a dependency, we can still
@@ -16,10 +18,13 @@ include $(BUILDDIR)/config.mk
 
 PORTBINARIES = pamcrater pamgauss pamgradient \
 	       pamseq pamshadedrelief pamstereogram \
-	       pbmpage pbmmake pbmtext pbmtextps pbmupc \
+	       pbmpage pbmmake pbmtext pbmupc \
 	       pgmkernel pgmmake pgmnoise pgmramp \
 	       ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \
 
+ifneq ($(DONT_HAVE_PROCESS_MGMT),Y)
+PORTBINARIES += pbmtextps 
+endif
 # We don't include programs that have special library dependencies in the
 # merge scheme, because we don't want those dependencies to prevent us
 # from building all the other programs.
@@ -36,6 +41,6 @@ OBJECTS = $(BINARIES:%=%.o)
 MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
 
 .PHONY: all
-all: $(BINARIES)
+all: $(BINARIES) $(SUBDIRS:%=%/all)
 
 include $(SRCDIR)/common.mk
diff --git a/generator/pamcrater.c b/generator/pamcrater.c
index 8c1fda40..43c27dbc 100644
--- a/generator/pamcrater.c
+++ b/generator/pamcrater.c
@@ -41,7 +41,7 @@
    right edge. Make craters wrap around the image (enables tiling of image).
  */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <assert.h>
 #include <math.h>
diff --git a/generator/pamgauss.c b/generator/pamgauss.c
index 2dd6a726..9656708b 100644
--- a/generator/pamgauss.c
+++ b/generator/pamgauss.c
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -18,7 +19,9 @@ struct CmdlineInfo {
     unsigned int width;
     unsigned int height;
     unsigned int maxval;
-    float sigma;
+    float        sigma;
+    unsigned int oversample;
+    unsigned int maximize;
     const char * tupletype;
 };
 
@@ -35,23 +38,27 @@ parseCommandLine(int argc, const char ** argv,
   Note that some string information we return as *cmdlineP is in the storage 
   argv[] points to.
 -----------------------------------------------------------------------------*/
-    optEntry *option_def;
+    optEntry * option_def;
         /* Instructions to OptParseOptions2 on how to parse our options.
          */
     optStruct3 opt;
 
-    unsigned int tupletypeSpec, maxvalSpec, sigmaSpec;
+    unsigned int tupletypeSpec, maxvalSpec, sigmaSpec, oversampleSpec;
     unsigned int option_def_index;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0,   "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
-            &tupletypeSpec,     0);
+            &tupletypeSpec,      0);
     OPTENT3(0,   "maxval",     OPT_UINT,   &cmdlineP->maxval, 
-            &maxvalSpec,        0);
+            &maxvalSpec,         0);
     OPTENT3(0,   "sigma",      OPT_FLOAT,  &cmdlineP->sigma, 
-            &sigmaSpec,        0);
+            &sigmaSpec,          0);
+    OPTENT3(0,   "maximize",   OPT_FLAG,   NULL,
+            &cmdlineP->maximize, 0);
+    OPTENT3(0,   "oversample", OPT_UINT,   &cmdlineP->oversample,
+            &oversampleSpec,     0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -86,6 +93,13 @@ parseCommandLine(int argc, const char ** argv,
             pm_error("-maxval must be at least 1");
     }    
 
+    if (oversampleSpec) {
+        if (cmdlineP->oversample < 1)
+            pm_error("The oversample factor (-oversample) "
+                     "must be at least 1.");
+    } else
+        cmdlineP->oversample = ceil(5.0 / cmdlineP->sigma);
+
     if (argc-1 < 2)
         pm_error("Need two arguments: width and height.");
     else if (argc-1 > 2)
@@ -107,12 +121,13 @@ parseCommandLine(int argc, const char ** argv,
 
 
 static double
-distFromCenter(struct pam * const pamP,
-               int          const col,
-               int          const row) {
-
-    return sqrt(SQR(0.5 + col - (double)pamP->width/2) +
-                SQR(0.5 + row - (double)pamP->height/2));
+distFromCenter(unsigned int const width,
+               unsigned int const height,
+               double       const x,
+               double       const y)
+{
+    return sqrt(SQR(x - (double)width  / 2) +
+                SQR(y - (double)height / 2));
 }
 
 
@@ -121,87 +136,214 @@ static double
 gauss(double const arg,
       double const sigma) {
 /*----------------------------------------------------------------------------
-   Compute the value of the gaussian function with sigma parameter 'sigma'
-   and mu parameter zero of argument 'arg'.
+   Compute the value of the gaussian function centered at zero with
+   standard deviation 'sigma' and amplitude 1, at 'arg'.
 -----------------------------------------------------------------------------*/
-    double const pi = 3.14159;
-    double const coefficient = 1 / (sigma * sqrt(2*pi));
-    double const exponent = - SQR(arg-0) / (2 * SQR(sigma));
+    double const exponent = - SQR(arg) / (2 * SQR(sigma));
 
-    return coefficient * exp(exponent);
+    return exp(exponent);
 }
 
 
 
 static double
-imageNormalizer(struct pam * const pamP,
-                double       const sigma) {
+pixelValue(unsigned int const width,
+           unsigned int const height,
+           unsigned int const row,
+           unsigned int const col,
+           unsigned int const subpixDivision,
+           double       const sigma) {
 /*----------------------------------------------------------------------------
-   Compute the value that has to be multiplied by the value of the 
-   one-dimensional gaussian function of the distance from center in
-   order to get the value for a normalized two-dimensional gaussian
-   function.  Normalized here means that the volume under the whole
-   curve is 1, just as the area under a whole one-dimensional gaussian
-   function is 1.
+  The gaussian value for the pixel at row 'row', column 'col' in an image
+  described by *pamP.
+
+  This is the mean of the values of the gaussian function computed at
+  all the subpixel locations within the pixel when it is divided into
+  subpixels 'subpixDivision' times horizontally and vertically.
+
+  The gaussian function has standard deviation 'sigma' and amplitude 1.
 -----------------------------------------------------------------------------*/
-    double volume;
+    double const offset = 1.0 / (subpixDivision * 2);
+    double const y0     = (double)row + offset; 
+    double const x0     = (double)col + offset;
+
+    double const subpixSize = 1.0 / subpixDivision;
 
+    unsigned int i;
+    double total;
+        /* Running total of the gaussian values at all subpixel locations */
+
+    for (i = 0, total = 0.0; i < subpixDivision; ++i) {
+        /* Sum up one column of subpixels */
+
+        unsigned int j;
+
+        for (j = 0; j < subpixDivision; ++j) {
+            double const dist =
+                distFromCenter(width, height,
+                               x0 + i * subpixSize,
+                               y0 + j * subpixSize);
+
+            total += gauss(dist, sigma);
+        }
+    }
+
+    return total / SQR(subpixDivision);
+}
+
+
+
+static double **
+gaussianKernel(unsigned int const width,
+               unsigned int const height,
+               unsigned int const subpixDivision,
+               double       const sigma) {
+/*----------------------------------------------------------------------------
+   A Gaussian matrix 'width' by 'height', with each value being the mean
+   of a Gaussian function evaluated at 'subpixDivision' x 'subpixDivision'
+   locations.
+
+   Return value is newly malloc'ed storage that Caller must free.
+-----------------------------------------------------------------------------*/
+    double ** kernel;
     unsigned int row;
 
-    volume = 0.0;   /* initial value */
+    MALLOCARRAY2(kernel, height, width);
 
-    for (row = 0; row < pamP->height; ++row) {
+    if (!kernel)
+        pm_error("Unable to allocate %u x %u array in which to build kernel",
+                 height, width);
+
+    for (row = 0; row < height; ++row) {
         unsigned int col;
-        for (col = 0; col < pamP->width; ++col)
-            volume += gauss(distFromCenter(pamP, col, row), sigma);
+        for (col = 0; col < width; ++col) {
+            double const gaussval =
+                pixelValue(width, height, row, col, subpixDivision, sigma);
+            kernel[row][col] = gaussval;
+        }
     }
-    return 1.0 / volume;
+    return kernel;
 }
 
 
 
-int
-main(int argc, const char **argv) {
+static double
+maximumKernelValue(double **    const kernel,
+                   unsigned int const width,
+                   unsigned int const height) {
 
-    struct CmdlineInfo cmdline;
+    /* As this is Gaussian in both directions, centered at the center,
+       we know the maximum value is at the center.
+    */
+    return kernel[height/2][width/2];
+}        
+
+
+
+static double
+totalKernelValue(double **    const kernel,
+                 unsigned int const width,
+                 unsigned int const height) {
+
+    double total;
+    unsigned int row;
+
+    for (row = 0, total = 0.0; row < height; ++row) {
+        unsigned int col;
+        
+        for (col = 0; col < width; ++col)
+            total += kernel[row][col];
+    }
+
+    return total;
+}        
+
+
+
+static void
+initpam(struct pam * const pamP,
+        unsigned int const width,
+        unsigned int const height,
+        sample       const maxval,
+        const char * const tupleType,
+        FILE *       const ofP) {
+
+    pamP->size        = sizeof(*pamP);
+    pamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    pamP->file        = ofP;
+    pamP->format      = PAM_FORMAT;
+    pamP->plainformat = 0;
+    pamP->width       = width;
+    pamP->height      = height;
+    pamP->depth       = 1;
+    pamP->maxval      = maxval;
+    strcpy(pamP->tuple_type, tupleType);
+}
+
+
+
+static void
+writePam(double **    const kernel,
+         unsigned int const width,
+         unsigned int const height,
+         sample       const maxval,
+         const char * const tupleType,
+         double       const normalizer,
+         FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+   Write the kernel 'kernel', which is 'width' by 'height', as a PAM image
+   with maxval 'maxval' and tuple type 'tupleType' to file *ofP.
+
+   Divide the kernel values by 'normalizer' to get the normalized PAM sample
+   value.  Assume that no value in 'kernel' is greater that 'normalizer'.
+-----------------------------------------------------------------------------*/
     struct pam pam;
-    int row;
-    double normalizer;
+    unsigned int row;
     tuplen * tuplerown;
-    
-    pm_proginit(&argc, argv);
-   
-    parseCommandLine(argc, argv, &cmdline);
 
-    pam.size        = sizeof(pam);
-    pam.len         = PAM_STRUCT_SIZE(tuple_type);
-    pam.file        = stdout;
-    pam.format      = PAM_FORMAT;
-    pam.plainformat = 0;
-    pam.width       = cmdline.width;
-    pam.height      = cmdline.height;
-    pam.depth       = 1;
-    pam.maxval      = cmdline.maxval;
-    strcpy(pam.tuple_type, cmdline.tupletype);
-
-    normalizer = imageNormalizer(&pam, cmdline.sigma);
-    
+    initpam(&pam, width, height, maxval, tupleType, ofP);
+
     pnm_writepaminit(&pam);
-   
+
     tuplerown = pnm_allocpamrown(&pam);
 
     for (row = 0; row < pam.height; ++row) {
-        int col;
+        unsigned int col;
         for (col = 0; col < pam.width; ++col) {
-            double const gauss1 = gauss(distFromCenter(&pam, col, row),
-                                        cmdline.sigma);
-
-            tuplerown[col][0] = gauss1 * normalizer;
+            tuplerown[col][0] = kernel[row][col] / normalizer;
+        
+            assert(tuplerown[col][0] <= 1.0);
         }
         pnm_writepamrown(&pam, tuplerown);
     }
-    
+
     pnm_freepamrown(tuplerown);
+}
+    
+
+
+int
+main(int argc, const char **argv) {
+    struct CmdlineInfo cmdline;
+    double ** kernel;
+    double    normalizer;
+    
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
 
+    kernel = gaussianKernel(cmdline.width, cmdline.height, cmdline.oversample,
+                            cmdline.sigma);
+
+    normalizer = cmdline.maximize ?
+        maximumKernelValue(kernel, cmdline.width, cmdline.height) :
+        totalKernelValue(kernel, cmdline.width, cmdline.height);
+
+    writePam(kernel,
+             cmdline.width, cmdline.height, cmdline.maxval, cmdline.tupletype,
+             normalizer, stdout);
+
+    pm_freearray2((void **)kernel);
+    
     return 0;
 }
diff --git a/generator/pamgradient.c b/generator/pamgradient.c
index 57e78288..526efdae 100644
--- a/generator/pamgradient.c
+++ b/generator/pamgradient.c
@@ -7,7 +7,7 @@
 
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     tuple colorTopLeft;
     tuple colorTopRight;
     tuple colorBottomLeft;
@@ -19,13 +19,13 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char **argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
-  Convert program invocation arguments (argc,argv) into a format the 
-  program can use easily, struct cmdlineInfo.  Validate arguments along
+  Convert program invocation arguments (argc,argv) into a format the
+  program can use easily, struct CmdlineInfo.  Validate arguments along
   the way and exit program with message if invalid.
 
-  Note that some string information we return as *cmdlineP is in the storage 
+  Note that some string information we return as *cmdlineP is in the storage
   argv[] points to.
 -----------------------------------------------------------------------------*/
     optEntry * option_def;
@@ -55,14 +55,14 @@ parseCommandLine(int argc, const char **argv,
             pm_error("The value you specified for -maxval (%u) is too big.  "
                      "Max allowed is %u", cmdlineP->maxval,
                      PAM_OVERALL_MAXVAL);
-        
+
         if (cmdlineP->maxval < 1)
             pm_error("You cannot specify 0 for -maxval");
-    }    
+    }
 
     if (argc-1 != 6) {
         pm_error("Need 6 arguments: colorTopLeft, colorTopRight, "
-                 "colorBottomLeft, colorBottomRight, width, height"); 
+                 "colorBottomLeft, colorBottomRight, width, height");
     } else {
         cmdlineP->colorTopLeft     = pnm_parsecolor(argv[1], cmdlineP->maxval);
         cmdlineP->colorTopRight    = pnm_parsecolor(argv[2], cmdlineP->maxval);
@@ -77,12 +77,13 @@ parseCommandLine(int argc, const char **argv,
             pm_error("height argument must be a positive number.  You "
                      "specified '%s'", argv[6]);
     }
+    free(option_def);
 }
 
 
 
 static void
-freeCmdline(struct cmdlineInfo const cmdline) {
+freeCmdline(struct CmdlineInfo const cmdline) {
 
     pnm_freepamtuple(cmdline.colorTopLeft);
     pnm_freepamtuple(cmdline.colorTopRight);
@@ -99,7 +100,7 @@ interpolate(struct pam * const pamP,
             tuple        const last) {
 
     unsigned int plane;
-    
+
     for (plane = 0; plane < pamP->depth; ++plane) {
         int const spread = last[plane] - first[plane];
 
@@ -155,13 +156,13 @@ createEdge(const struct pam * const pamP,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     struct pam pam;
     tuple * tupleRow;
     tuple * leftEdge;
     tuple * rightEdge;
     unsigned int row;
-    
+
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
@@ -187,7 +188,7 @@ main(int argc, const char *argv[]) {
     }
 
     pnm_writepaminit(&pam);
-    
+
     tupleRow = pnm_allocpamrow(&pam);
 
     leftEdge  = createEdge(&pam,
@@ -198,7 +199,7 @@ main(int argc, const char *argv[]) {
     /* interpolate each row between the left edge and the right edge */
     for (row = 0; row < pam.height; ++row) {
         interpolate(&pam, tupleRow, leftEdge[row], rightEdge[row]);
-        pnm_writepamrow(&pam, tupleRow); 
+        pnm_writepamrow(&pam, tupleRow);
     }
 
     pm_close(stdout);
diff --git a/generator/pamshadedrelief.c b/generator/pamshadedrelief.c
index 89996c83..35d1e3e0 100644
--- a/generator/pamshadedrelief.c
+++ b/generator/pamshadedrelief.c
@@ -38,7 +38,7 @@
    edge.
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <assert.h>
 #include <math.h>
diff --git a/generator/pamtris/Makefile b/generator/pamtris/Makefile
new file mode 100644
index 00000000..d27606e3
--- /dev/null
+++ b/generator/pamtris/Makefile
@@ -0,0 +1,27 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = generator/pamtris
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/config.mk
+
+PORTBINARIES = pamtris
+
+MERGEBINARIES = $(PORTBINARIES)
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+
+ADDL_OBJECTS = boundaries.o framebuffer.o input.o triangle.o utils.o
+
+OBJECTS = pamtris.o $(ADDL_OBJECTS)
+
+MERGE_OBJECTS = pamtris.o2 $(ADDL_OBJECTS)
+
+.PHONY: all
+all: $(BINARIES)
+
+pamtris:%:%.o $(ADDL_OBJECTS)
+
+include $(SRCDIR)/common.mk
diff --git a/generator/pamtris/boundaries.c b/generator/pamtris/boundaries.c
new file mode 100644
index 00000000..7045cbc7
--- /dev/null
+++ b/generator/pamtris/boundaries.c
@@ -0,0 +1,262 @@
+/*=============================================================================
+                                 boundaries.c
+===============================================================================
+   Boundary buffer functions
+
+   New triangles are drawn one row at a time, and for every such row we have
+   left and right boundary columns within the frame buffer such that the
+   fraction of the triangle's area within that scanline is enclosed between
+   those two points (inclusive). Those coordinates may correspond to columns
+   outside the frame buffer's actual limits, in which case proper
+   post-processing should be made wherever such coordinates are used to
+   actually plot anything into the frame buffer.
+=============================================================================*/
+
+#include <stdlib.h>
+
+#include <netpbm/mallocvar.h>
+#include <netpbm/pm.h>
+
+#include "varying.h"
+#include "utils.h"
+
+
+#include "boundaries.h"
+
+
+
+void
+init_boundary_buffer(boundary_info * const bi,
+                     int16_t         const height) {
+
+    MALLOCARRAY(bi->buffer, height * 2);
+
+    if (!bi->buffer) {
+        pm_error("unable to get memory for %u-row high boundary buffer.",
+                 height);
+    }
+}
+
+
+
+void
+free_boundary_buffer(boundary_info * bi) {
+    free(bi->buffer);
+}
+
+
+
+bool
+gen_triangle_boundaries(Xy              const xy,
+                        boundary_info * const bi,
+                        int16_t         const width,
+                        int16_t         const height) {
+/*----------------------------------------------------------------------------
+  Generate an entry in the boundary buffer for the boundaries of every
+  VISIBLE row of a particular triangle. In case there is no such row,
+  start_scanline is accordingly set to -1. "xy" is a 3-element array
+  of pairs of integers representing the coordinates of the vertices of
+  a triangle. Those vertices MUST be already sorted in order from the
+  uppermost to the lowermost vertex (which is what draw_triangle, the
+  only function which uses this one, does with the help of sort3).
+
+  The return value indicates whether the middle vertex is to the left of
+  the line connecting the top vertex to the bottom vertex or not.
+-----------------------------------------------------------------------------*/
+    int16_t leftmost_x;
+    int16_t rightmost_x;
+    int mid_is_to_the_left;
+    varying top_x;
+    varying mid_x;
+    varying bot_x;
+    varying top2mid;
+    varying top2bot;
+    varying mid2bot;
+    varying* upper_left;
+    varying* lower_left;
+    varying* upper_right;
+    varying* lower_right;
+    varying* left[2];
+    varying* right[2];
+    int16_t* num_rows_ptr[2];
+    int32_t y;
+    int32_t i;
+    uint8_t k;
+
+    leftmost_x = xy._[0][0];   /* initial value */
+    rightmost_x = xy._[0][0];  /* initial value */
+
+    bi->start_scanline = -1;
+    bi->num_upper_rows = 0;
+    bi->num_lower_rows = 0;
+
+    if (xy._[2][1] < 0 || xy._[0][1] >= height) {
+        /* Triangle is either completely above the uppermost scanline or
+           completely below the lowermost scanline.
+        */
+
+        return false; /* Actual value doesn't matter. */
+    }
+
+    {
+        unsigned int i;
+
+        for (i = 1; i < 3; i++) {
+            if (xy._[i][0] < leftmost_x) {
+                leftmost_x = xy._[i][0];
+            }
+
+            if (xy._[i][0] > rightmost_x) {
+                rightmost_x = xy._[i][0];
+            }
+        }
+    }
+    if (rightmost_x < 0 || leftmost_x >= width) {
+        /* Triangle is either completely to the left of the leftmost
+           framebuffer column or completely to the right of the rightmost
+           framebuffer column.
+        */
+        return false; /* Actual value doesn't matter. */
+    }
+
+    if (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]) {
+        /* Triangle is degenarate: its visual representation consists only of
+           a horizontal straight line.
+        */
+
+        bi->start_scanline = xy._[0][1];
+
+        return false; /* Actual value doesn't matter. */
+    }
+
+    mid_is_to_the_left = 2;
+
+    int32_to_varying_array(&xy._[0][0], &top_x, 1);
+    int32_to_varying_array(&xy._[1][0], &mid_x, 1);
+    int32_to_varying_array(&xy._[2][0], &bot_x, 1);
+
+    if (xy._[0][1] == xy._[1][1]) {
+        /* Triangle has only a lower part. */
+        k = 1;
+
+        mid_is_to_the_left = 0;
+    } else {
+        k = 0;
+
+        if (xy._[1][1] == xy._[2][1]) {
+            /* Triangle has only an upper part (plus the row of the middle
+               vertex).
+            */
+            mid_is_to_the_left = 1;
+        }
+    }
+
+    prepare_for_interpolation(&top_x, &mid_x, &top2mid, xy._[1][1] - xy._[0][1], 1);
+    prepare_for_interpolation(&top_x, &bot_x, &top2bot, xy._[2][1] - xy._[0][1], 1);
+    prepare_for_interpolation(&mid_x, &bot_x, &mid2bot, xy._[2][1] - xy._[1][1], 1);
+
+    if (mid_is_to_the_left == 2) {
+        mid_is_to_the_left = top2mid.s < top2bot.s;
+    }
+
+    if (mid_is_to_the_left) {
+        upper_left     = &top2mid;
+        lower_left     = &mid2bot;
+        upper_right    = &top2bot;
+        lower_right    = upper_right;
+    } else {
+        upper_right    = &top2mid;
+        lower_right    = &mid2bot;
+        upper_left     = &top2bot;
+        lower_left     = upper_left;
+    }
+
+    left[0] = upper_left;
+    left[1] = lower_left;
+    right[0] = upper_right;
+    right[1] = lower_right;
+
+    num_rows_ptr[0] = &bi->num_upper_rows;
+    num_rows_ptr[1] = &bi->num_lower_rows;
+
+    y = xy._[0][1];
+
+    i = 0;
+
+    while (k < 2) {
+        int32_t end;
+
+        end = xy._[k + 1][1] + k;  /* initial value */
+
+        if (y < 0) {
+            int32_t delta;
+
+            if (end > 0) {
+                delta = -y;
+            } else {
+                delta = xy._[k + 1][1] - y;
+            }
+
+            y += delta;
+
+            multi_step_up(left[k], delta, 1);
+            multi_step_up(right[k], delta, 1);
+
+            if (y < 0) {
+                k++;
+                continue;
+            }
+        } else if(y >= height) {
+            return mid_is_to_the_left;
+        }
+
+        if (end > height) {
+            end = height;
+        }
+
+        while (y < end) {
+            if (round_varying(*left[k]) >= width || round_varying(*right[k]) < 0) {
+                if (bi->start_scanline > -1) {
+                    return mid_is_to_the_left;
+                }
+            } else {
+                if (bi->start_scanline == -1) {
+                    bi->start_scanline = y;
+                }
+
+                bi->buffer[i++] = round_varying(*left[k]);
+                bi->buffer[i++] = round_varying(*right[k]);
+
+                (*(num_rows_ptr[k]))++;
+            }
+
+            step_up(left[k], 1);
+            step_up(right[k], 1);
+
+            y++;
+        }
+        k++;
+    }
+    return mid_is_to_the_left;
+}
+
+
+
+void
+get_triangle_boundaries(uint16_t              const row_index,
+                        int32_t *             const left,
+                        int32_t *             const right,
+                        const boundary_info * const bi) {
+/*----------------------------------------------------------------------------
+  Return the left and right boundaries for a given VISIBLE triangle row (the
+  row index is relative to the first visible row). These values may be out of
+  the horizontal limits of the frame buffer, which is necessary in order to
+  compute correct attribute interpolations.
+-----------------------------------------------------------------------------*/
+    uint32_t const i  = row_index << 1;
+
+    *left       = bi->buffer[i];
+    *right      = bi->buffer[i + 1];
+}
+
+
diff --git a/generator/pamtris/boundaries.h b/generator/pamtris/boundaries.h
new file mode 100644
index 00000000..70f7f90d
--- /dev/null
+++ b/generator/pamtris/boundaries.h
@@ -0,0 +1,72 @@
+#ifndef BOUNDARIES_H_INCLUDED
+#define BOUNDARIES_H_INCLUDED
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "triangle.h"
+
+typedef struct boundary_info {
+/*----------------------------------------------------------------------------
+  Information about visible triangle rows' boundaries. Also see the
+  "boundary buffer functions" below.
+
+  A "visible" triangle row is one which:
+
+    1. Corresponds to a frame buffer row whose index (from top to bottom) is
+       equal to or greater than 0 and smaller than the image height; and
+
+    2. Has at least some of its pixels between the frame buffer columns whose
+       index (from left to right) is equal to or greater than 0 and smaller
+       than the image width.
+-----------------------------------------------------------------------------*/
+    int16_t start_scanline;
+        /* Index of the frame buffer scanline which contains the first visible
+           row of the current triangle, if there is any such row. If not, it
+           contains the value -1.
+        */
+
+    int16_t num_upper_rows;
+        /* The number of visible rows in the upper part of the triangle. The
+           upper part of a triangle is composed of all the rows starting from
+           the top vertex down to the middle vertex, but not including this
+           last one.
+        */
+
+    int16_t num_lower_rows;
+        /* The number of visible rows in the lower part of the triangle. The
+           lower part of a triangle is composed of all the rows from the
+           middle vertex to the bottom vertex -- all inclusive.
+        */
+
+    int16_t * buffer;
+        /* This is the "boundary buffer": a pointer to an array of int16_t's
+           where each consecutive pair of values indicates, in this order, the
+           columns of the left and right boundary pixels for a particular
+           visible triangle row. Those boundaries are inclusive on both sides
+           and may be outside the limits of the frame buffer. This field is
+           initialized and freed by the functions "init_boundary_buffer" and
+           "free_boundary_buffer", respectively.
+        */
+} boundary_info;
+
+void
+init_boundary_buffer(boundary_info * const bdi,
+                     int16_t         const height);
+
+void
+free_boundary_buffer(boundary_info *);
+
+bool
+gen_triangle_boundaries(Xy              const xy,
+                        boundary_info * const bdi,
+                        int16_t         const width,
+                        int16_t         const height);
+
+void
+get_triangle_boundaries(uint16_t              const row_index,
+                        int32_t *             const left,
+                        int32_t *             const right,
+                        const boundary_info * const bdi);
+
+#endif
diff --git a/generator/pamtris/framebuffer.c b/generator/pamtris/framebuffer.c
new file mode 100644
index 00000000..93263c91
--- /dev/null
+++ b/generator/pamtris/framebuffer.c
@@ -0,0 +1,339 @@
+/*=============================================================================
+                              framebuffer.c
+===============================================================================
+  Frame buffer functions
+
+  Every drawing operation is applied on an internal "frame buffer", which is
+  simply an "image buffer" which represents the picture currently being drawn,
+  along with a "Z-Buffer" which contains the depth values for every pixel in
+  the image buffer. Once all desired drawing operations for a particular
+  picture are effected, a function is provided to print the current contents
+  of the image buffer as a PAM image on standard output.  Another function is
+  provided to clear the contents of the frame buffer (i. e. set all image
+  samples and Z-Buffer entries to 0), with the option of only clearing either
+  the image buffer or the Z-Buffer individually.
+
+  The Z-Buffer works as follows: Every pixel in the image buffer has a
+  corresponding entry in the Z-Buffer. Initially, every entry in the Z-Buffer
+  is set to 0. Every time we desire to plot a pixel at some particular
+  position in the frame buffer, the current value of the corresponding entry
+  in the Z-Buffer is compared against the the Z component of the incoming
+  pixel. If MAX_Z minus the value of the Z component of the incoming pixel is
+  equal to or greater than the current value of the corresponding entry in the
+  Z-Buffer, the frame buffer is changed as follows:
+
+    1. All the samples but the last of the corresponding position in the
+       image buffer are set to equal those of the incoming pixel.
+
+    2. The last sample, that is, the A-component of the corresponding position
+       in the image buffer is set to equal the maxval.
+
+    3. The corresponding entry in the Z-Buffer is set to equal MAX_Z minus the
+       value of the Z component of the incoming pixel.
+
+    Otherwise, no changes are made on the frame buffer.
+=============================================================================*/
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "varying.h"
+#include "limits_pamtris.h"
+
+#include "framebuffer.h"
+
+
+
+int
+set_tupletype(const char * const str,
+              char *       const tupletype) {
+/*----------------------------------------------------------------------------
+  Set the tuple type for the output PAM images given a string ("str") of 255
+  characters or less. If the string has more than 255 characters, the function
+  returns 0. Otherwise, it returns 1. If NULL is given for the "str" argument,
+  the tuple type is set to a null string. This function is called during
+  program initialization and whenever a "r" command is executed. The second
+  argument must point to the tuple_type member of the "outpam" field in the
+  framebuffer_info struct.
+-----------------------------------------------------------------------------*/
+    if (str == NULL) {
+        memset(tupletype, 0, 256);
+    } else {
+        size_t len;
+
+        len = strlen(str);   /* initial value */
+
+        if (len > 255) {
+            return 0;
+        }
+
+        if (len > 0) {
+            memcpy(tupletype, str, len);
+        }
+
+        tupletype[len--] = '\0';
+
+        while(len > 0 && isspace(tupletype[len])) {
+            tupletype[len--] = '\0';
+        }
+    }
+
+    return 1;
+}
+
+
+
+int
+init_framebuffer(framebuffer_info * const fbi) {
+
+    uint8_t const num_planes = fbi->num_attribs + 1;
+
+    uint32_t const elements = fbi->width * fbi->height;
+
+    fbi->img.bytes = elements * (num_planes * sizeof(uint16_t));
+    fbi->z.bytes = elements * sizeof(uint32_t);
+
+    fbi->img.buffer =
+        calloc(fbi->img.bytes / sizeof(uint16_t), sizeof(uint16_t));
+    fbi->z.buffer =
+        calloc(fbi->z.bytes / sizeof(uint32_t), sizeof(uint32_t));
+
+    if(fbi->img.buffer == NULL || fbi->z.buffer == NULL) {
+        free(fbi->img.buffer);
+        free(fbi->z.buffer);
+
+        return 0;
+    }
+
+    fbi->outpam.size = sizeof(struct pam);
+    fbi->outpam.len = sizeof(struct pam);
+    fbi->outpam.file = stdout;
+    fbi->outpam.format = PAM_FORMAT;
+    fbi->outpam.plainformat = 0;
+    fbi->outpam.height = fbi->height;
+    fbi->outpam.width = fbi->width;
+    fbi->outpam.depth = num_planes;
+    fbi->outpam.maxval = fbi->maxval;
+    fbi->outpam.allocation_depth = 0;
+    fbi->outpam.comment_p = NULL;
+
+    fbi->pamrow = NULL;
+    fbi->pamrow = pnm_allocpamrow(&fbi->outpam);
+
+    if (fbi->pamrow == NULL) {
+        free(fbi->img.buffer);
+        free(fbi->z.buffer);
+
+        return 0;
+    }
+
+    return 1;
+}
+
+
+
+void
+free_framebuffer(framebuffer_info * const fbi) {
+
+    free(fbi->img.buffer);
+    free(fbi->z.buffer);
+
+    pnm_freepamrow(fbi->pamrow);
+}
+
+
+
+int
+realloc_image_buffer(int32_t            const new_maxval,
+                     int32_t            const new_num_attribs,
+                     framebuffer_info * const fbi) {
+/*----------------------------------------------------------------------------
+  Reallocate the image buffer with a new maxval and depth, given the struct
+  with information about the framebuffer. The fields variables "maxval" and
+  "num_attribs".
+
+  From the point this function is called onwards, new PAM images printed on
+  standard output will have the new maxval for the maxval and num_attribs + 1
+  for the depth.
+
+  This function does *not* check whether the new maxval and num_attribs are
+  within the proper allowed limits. That is done inside the input processing
+  function "process_next_command", which is the only function that calls this
+  one.
+
+  If the function suceeds, the image buffer is left in cleared state. The
+  Z-Buffer, however, is not touched at all.
+
+  If the new depth is equal to the previous one, no actual reallocation is
+  performed: only the global variable "maxval" is changed. But the image
+  buffer is nonetheless left in cleared state regardless.
+-----------------------------------------------------------------------------*/
+    uint8_t num_planes;
+
+    pnm_freepamrow(fbi->pamrow);
+    fbi->pamrow = NULL;
+
+    num_planes = fbi->num_attribs + 1;  /* initial value */
+
+    if (new_num_attribs != fbi->num_attribs) {
+        fbi->num_attribs = new_num_attribs;
+        num_planes = fbi->num_attribs + 1;
+
+        fbi->img.bytes =
+            fbi->width * fbi->height * (num_planes * sizeof(uint16_t));
+
+        {
+            uint16_t * const new_ptr =
+                realloc(fbi->img.buffer, fbi->img.bytes);
+
+            if (new_ptr == NULL) {
+                free(fbi->img.buffer);
+                fbi->img.buffer = NULL;
+
+                return 0;
+            }
+            fbi->img.buffer = new_ptr;
+        }
+    }
+
+    fbi->maxval = new_maxval;
+
+    fbi->outpam.size             = sizeof(struct pam);
+    fbi->outpam.len              = sizeof(struct pam);
+    fbi->outpam.file             = stdout;
+    fbi->outpam.format           = PAM_FORMAT;
+    fbi->outpam.plainformat      = 0;
+    fbi->outpam.height           = fbi->height;
+    fbi->outpam.width            = fbi->width;
+    fbi->outpam.depth            = num_planes;
+    fbi->outpam.maxval           = fbi->maxval;
+    fbi->outpam.allocation_depth = 0;
+    fbi->outpam.comment_p        = NULL;
+
+    fbi->pamrow = pnm_allocpamrow(&fbi->outpam);
+
+    if (fbi->pamrow == NULL) {
+        free(fbi->img.buffer);
+        fbi->img.buffer = NULL;
+
+        return 0;
+    }
+
+    memset(fbi->img.buffer, 0, fbi->img.bytes);
+
+    return 1;
+}
+
+
+
+void
+print_framebuffer(framebuffer_info * const fbi) {
+
+    uint8_t  const num_planes = fbi->num_attribs + 1;
+    uint32_t const end        = fbi->width * fbi->height;
+
+    uint32_t i;
+
+    pnm_writepaminit(&fbi->outpam);
+
+    for (i = 0; i != end; ) {
+        int j;
+        for (j = 0; j < fbi->width; j++) {
+            uint32_t const k = (i + j) * num_planes;
+
+            unsigned int l;
+
+            for (l = 0; l < num_planes; l++) {
+                fbi->pamrow[j][l] = fbi->img.buffer[k + l];
+            }
+        }
+
+        pnm_writepamrow(&fbi->outpam, fbi->pamrow);
+
+        i += fbi->width;
+    }
+}
+
+
+
+void
+clear_framebuffer(bool               const clear_image_buffer,
+                  bool               const clear_z_buffer,
+                  framebuffer_info * const fbi) {
+
+    if (clear_image_buffer) {
+        memset(fbi->img.buffer, 0, fbi->img.bytes);
+    }
+
+    if (clear_z_buffer) {
+        memset(fbi->z.buffer, 0, fbi->z.bytes);
+    }
+}
+
+
+
+void
+draw_span(uint32_t           const base,
+          uint16_t           const length,
+          varying *          const attribs,
+          framebuffer_info * const fbi) {
+/*----------------------------------------------------------------------------
+  Draw a horizontal span of "length" pixels into the frame buffer, performing
+  the appropriate depth tests. "base" must equal the row of the frame buffer
+  where one desires to draw the span *times* the image width, plus the column
+  of the first pixel in the span.
+
+  This function does not perform any kind of bounds checking.
+-----------------------------------------------------------------------------*/
+    static double const depth_range = MAX_Z;
+
+    uint16_t const maxval = fbi->maxval;
+    uint8_t  const z      = fbi->num_attribs;
+    uint8_t  const w      = z + 1;
+    uint8_t  const n      = w + 1;
+
+    uint8_t  const num_planes = w;
+
+    unsigned int i;
+
+    /* Process each pixel in the span: */
+
+    for (i = 0; i < length; i++) {
+        int32_t  const d      = round(depth_range * attribs[z].v);
+        uint32_t const d_mask = geq_mask64(d, fbi->z.buffer[base + i]);
+
+        uint32_t const j = base + i;
+        uint32_t const k = j * num_planes;
+
+        varying const inverse_w = inverse_varying(attribs[w]);
+
+        unsigned int l;
+
+        /* The following statements will only have any effect if the depth
+           test, performed above, has suceeded. I. e. if the depth test fails,
+           no changes will be made on the frame buffer; otherwise, the
+           frame buffer will be updated with the new values.
+        */
+        fbi->z.buffer[j] = (fbi->z.buffer[j] & ~d_mask) | (d & d_mask);
+
+        for (l = 0; l < z; l++) {
+	    varying const newval = multiply_varyings(attribs[l], inverse_w);
+
+            fbi->img.buffer[k + l] =
+                (fbi->img.buffer[k + l] & ~d_mask) |
+                (round_varying(newval) &  d_mask);
+        }
+
+        fbi->img.buffer[k + z] |= (maxval & d_mask);
+
+        /* Compute the attribute values for the next pixel: */
+
+        step_up(attribs, n);
+    }
+}
+
+
+
diff --git a/generator/pamtris/framebuffer.h b/generator/pamtris/framebuffer.h
new file mode 100644
index 00000000..b3d4f7a3
--- /dev/null
+++ b/generator/pamtris/framebuffer.h
@@ -0,0 +1,75 @@
+#ifndef FRAMEBUFFER_H_INCLUDED
+#define FRAMEBUFFER_H_INCLUDED
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "varying.h"
+#include "netpbm/pam.h"
+
+typedef struct framebuffer_info {
+/*----------------------------------------------------------------------------
+   Information about the frame buffer and PAM output
+-----------------------------------------------------------------------------*/
+    /* These fields are initialized once by reading the command line
+       arguments. "maxval" and "num_attribs" may be modified later
+       through "realloc_image_buffer"; "correct" may also be modified
+       if the eponymous command is given.
+    */
+    int32_t width;
+    int32_t height;
+    int32_t maxval;
+    int32_t num_attribs;
+
+    /* The fields below must be initialized by "init_framebuffer" and
+       freed by "free_framebuffer", except for the tuple_type field in
+       "outpam" which is initialized once by reading the command line
+       arguments and may be modified later through "set_tupletype".
+    */
+    struct {
+        uint16_t * buffer;
+        uint32_t   bytes;
+    } img; /* Image buffer */
+
+    struct {
+        uint32_t * buffer;
+        uint32_t   bytes;
+    } z;  /* Z-buffer */
+
+    struct pam outpam;
+
+    tuple * pamrow;
+} framebuffer_info;
+
+
+
+int
+set_tupletype(const char * const str,
+              char *       const tupletype);
+
+int
+init_framebuffer(framebuffer_info * const fbi);
+
+void
+free_framebuffer(framebuffer_info * const fbi);
+
+void
+print_framebuffer(framebuffer_info * const fbi);
+
+void
+clear_framebuffer(bool               const clear_image_buffer,
+                  bool               const clear_z_buffer,
+                  framebuffer_info * const fbi);
+
+int
+realloc_image_buffer(int32_t            const new_maxval,
+                     int32_t            const new_num_attribs,
+                     framebuffer_info * const fbi);
+
+void
+draw_span(uint32_t           const base,
+          uint16_t           const length,
+          varying *          const attribs,
+          framebuffer_info * const fbi);
+
+#endif
diff --git a/generator/pamtris/input.c b/generator/pamtris/input.c
new file mode 100644
index 00000000..ffb2a859
--- /dev/null
+++ b/generator/pamtris/input.c
@@ -0,0 +1,695 @@
+/*=============================================================================
+                              input.c
+===============================================================================
+   Input handling functions
+=============================================================================*/
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "netpbm/mallocvar.h"
+#include "netpbm/pm.h"
+#include "netpbm/nstring.h"
+
+#include "limits_pamtris.h"
+#include "framebuffer.h"
+#include "triangle.h"
+
+#include "input.h"
+
+#define DRAW_MODE_TRIANGLES 1
+#define DRAW_MODE_STRIP     2
+#define DRAW_MODE_FAN       3
+
+#define CMD_SET_MODE        "mode"
+#define CMD_SET_ATTRIBS     "attribs"
+#define CMD_VERTEX          "vertex"
+#define CMD_PRINT           "print"
+#define CMD_CLEAR           "clear"
+#define CMD_RESET           "reset"
+#define CMD_QUIT            "quit"
+
+#define ARG_TRIANGLES       "triangles"
+#define ARG_STRIP           "strip"
+#define ARG_FAN             "fan"
+#define ARG_IMAGE           "image"
+#define ARG_DEPTH           "depth"
+
+
+typedef struct {
+    Xy v_xy;
+        /* X- and Y-coordinates of the vertices for the current triangle.
+        */
+    Attribs v_attribs;
+        /* Vertex attributes for the current triangle. Includes the
+           Z-coordinates.
+        */
+    int32_t curr_attribs[MAX_NUM_ATTRIBS];
+        /* Attributes that will be assigned to the next vertex. Does not
+           include the Z-coordinate.
+        */
+    uint8_t next;
+        /* Index of the next vertex to be read. */
+    bool draw;
+        /* If true, draws a new triangle upon reading a new vertex. */
+
+    uint8_t mode;
+        /* Drawing mode. */
+
+    bool initialized;
+} state_info;
+
+
+
+static void
+clearAttribs(state_info * const si,
+             int32_t      const maxval,
+             int16_t      const num_attribs) {
+
+    unsigned int i;
+
+    for (i = 0; i < num_attribs; ++i) {
+        si->curr_attribs[i] = maxval;
+    }
+}
+
+
+
+void
+input_init(Input * const inputP) {
+
+    inputP->buffer = NULL;
+    inputP->length = 0;
+    inputP->number = 1;
+}
+
+
+
+void
+input_term(Input * const inputP) {
+
+    if (inputP->buffer)
+        free(inputP->buffer);
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  Indicates a whitespace-delimited input symbol. "begin" points to its first
+  character, and "end" points to one position past its last character.
+-----------------------------------------------------------------------------*/
+    char * begin;
+    char * end;
+} Token;
+
+
+
+static Token
+nextToken(char * const startPos) {
+
+    Token retval;
+    char * p;
+
+    for (p = startPos; *p && isspace(*p); ++p);
+
+    retval.begin = p;
+
+    for (; *p && !isspace(*p); ++p);
+
+    retval.end = p;
+
+    return retval;
+}
+
+
+
+static bool
+stringIsValid(const char * const target,
+              const char * const srcBegin,
+              const char * const srcEnd) {
+
+    unsigned int charsMatched;
+    const char * p;
+
+    for (p = srcBegin, charsMatched = 0;
+         p != srcEnd && target[charsMatched] != '\0'; ++p) {
+
+        if (*p == target[charsMatched])
+            ++charsMatched;
+        else
+            break;
+    }
+
+    return (*p == '\0' || isspace(*p));
+}
+
+
+
+static void
+initState(state_info * const siP) {
+
+    siP->next = 0;
+    siP->draw = false;
+    siP->mode = DRAW_MODE_TRIANGLES;
+}
+
+
+
+static void
+makeLowercase(Token const t) {
+
+    char * p;
+
+    for (p = t.begin; p != t.end; ++p)
+        *p = tolower(*p);
+}
+
+
+
+static void
+removeComments(char * const str) {
+
+    char * p;
+
+    for (p = &str[0]; *p; ++p) {
+        if (*p == '#') {
+            *p = '\0';
+
+            break;
+        }
+    }
+}
+
+
+
+static void
+processM(Token *       const ntP,
+         state_info *  const stateP,
+         bool *        const unrecognizedCmdP,
+         const char ** const errorP) {
+
+    if (!stringIsValid(CMD_SET_MODE, ntP->begin, ntP->end)) {
+        *unrecognizedCmdP = true;
+    } else {
+        *ntP = nextToken(ntP->end);
+
+        *unrecognizedCmdP = false;
+
+        if (*ntP->begin == '\0')
+            pm_asprintf(errorP, "syntax error");
+        else {
+            makeLowercase(*ntP);
+
+            switch (*ntP->begin) {
+            case 't':
+                if (!stringIsValid(ARG_TRIANGLES, ntP->begin, ntP->end))
+                    pm_asprintf(errorP, "unrecognized drawing mode");
+                else {
+                    stateP->mode = DRAW_MODE_TRIANGLES;
+                    stateP->draw = false;
+                    stateP->next = 0;
+
+                    *errorP = NULL;
+                }
+                break;
+            case 's':
+                if (!stringIsValid(ARG_STRIP, ntP->begin, ntP->end))
+                    pm_asprintf(errorP, "unrecognized drawing mode");
+                else {
+                    stateP->mode = DRAW_MODE_STRIP;
+                    stateP->draw = false;
+                    stateP->next = 0;
+
+                    *errorP = NULL;
+                }
+                break;
+            case 'f':
+                if (!stringIsValid(ARG_FAN, ntP->begin, ntP->end))
+                    pm_asprintf(errorP, "unrecognized drawing mode");
+                else {
+                    stateP->mode = DRAW_MODE_FAN;
+                    stateP->draw = false;
+                    stateP->next = 0;
+
+                    *errorP = NULL;
+                }
+                break;
+            default:
+                pm_asprintf(errorP, "unrecognized drawing mode");
+            }
+        }
+    }
+}
+
+
+
+static void
+processA(Token *            const ntP,
+         state_info *       const stateP,
+         framebuffer_info * const fbiP,
+         bool *             const unrecognizedCmdP,
+         long int *         const iArgs,
+         const char **      const errorP) {
+
+    if (!stringIsValid(CMD_SET_ATTRIBS, ntP->begin, ntP->end)) {
+        *unrecognizedCmdP = true;
+    } else {
+        unsigned int i;
+
+        *unrecognizedCmdP = false;
+
+        for (i = 0, *errorP = NULL; i < fbiP->num_attribs && !*errorP; ++i) {
+            char * strtolEnd;
+
+            *ntP = nextToken(ntP->end);
+
+            iArgs[i] = strtol(ntP->begin, &strtolEnd, 10);
+
+            if (*ntP->begin == '\0' || strtolEnd != ntP->end)
+                pm_asprintf(errorP, "syntax error");
+            else {
+                if (iArgs[i] < 0 || iArgs[i] > fbiP->maxval)
+                    pm_asprintf(errorP, "argument(s) out of bounds");
+            }
+        }
+
+        if (!*errorP) {
+            unsigned int i;
+
+            for (i = 0; i < fbiP->num_attribs; ++i)
+                stateP->curr_attribs[i] = iArgs[i];
+        }
+    }
+}
+
+
+
+static void
+processV(Token *                const ntP,
+         state_info *           const stateP,
+         struct boundary_info * const biP,
+         framebuffer_info *     const fbiP,
+         bool *                 const unrecognizedCmdP,
+         long int *             const iArgs,
+         const char **          const errorP) {
+
+    if (!stringIsValid(CMD_VERTEX, ntP->begin, ntP->end))
+        *unrecognizedCmdP = true;
+    else {
+        unsigned int i;
+
+        *unrecognizedCmdP = false;
+
+        for (i = 0, *errorP = NULL; i < 4 && !*errorP; ++i) {
+            char * strtolEnd;
+
+            *ntP = nextToken(ntP->end);
+
+            iArgs[i] = strtol(ntP->begin, &strtolEnd, 10);
+
+            if (*ntP->begin == '\0') {
+                if (i != 3)
+                    pm_asprintf(errorP, "syntax error");
+                else
+                    iArgs[i] = 1;
+            } else {
+                if (strtolEnd != ntP->end)
+                    pm_asprintf(errorP, "syntax error");
+            }
+
+            if (!*errorP) {
+                if (i < 3) {
+                    if (iArgs[i] < MIN_COORD || iArgs[i] > MAX_COORD)
+                        pm_asprintf(errorP, "coordinates out of bounds");
+                } else {
+                    if (iArgs[i] < MIN_INPUT_W || iArgs[i] > MAX_INPUT_W)
+                        pm_asprintf(errorP,
+                                    "perspective correction factor (w) "
+                                    "out of bounds");
+                }
+            }
+        }
+
+        if (!*errorP) {
+            unsigned int i;
+
+            for (i = 0; i < fbiP->num_attribs; ++i) {
+                stateP->v_attribs._[stateP->next][i] = stateP->curr_attribs[i];
+            }
+
+            stateP->v_attribs._[stateP->next][fbiP->num_attribs + 0] =
+                iArgs[2];
+            stateP->v_attribs._[stateP->next][fbiP->num_attribs + 1] =
+                iArgs[3];
+
+            stateP->v_xy._[stateP->next][0] = iArgs[0];
+            stateP->v_xy._[stateP->next][1] = iArgs[1];
+
+            ++stateP->next;
+
+            if (!stateP->draw) {
+                if (stateP->next == 3)
+                    stateP->draw = true;
+            }
+
+            if (stateP->draw)
+                draw_triangle(stateP->v_xy, stateP->v_attribs, biP, fbiP);
+
+            if (stateP->next == 3) {
+                switch(stateP->mode) {
+                case DRAW_MODE_FAN:
+                    stateP->next = 1;
+                    break;
+                case DRAW_MODE_TRIANGLES:
+                    stateP->draw = false;
+                    stateP->next = 0;
+                    break;
+                case DRAW_MODE_STRIP:
+                    stateP->next = 0;
+                    break;
+                default:
+                    stateP->next = 0;
+                }
+            }
+        }
+    }
+}
+
+
+
+static void
+processP(Token *            const ntP,
+         framebuffer_info * const fbiP,
+         bool *             const unrecognizedCmdP,
+         const char **      const errorP) {
+
+    if (!stringIsValid(CMD_PRINT, ntP->begin, ntP->end))
+        *unrecognizedCmdP = true;
+    else {
+        *unrecognizedCmdP = false;
+
+        print_framebuffer(fbiP);
+
+        *errorP = NULL;
+    }
+}
+
+
+
+
+static void
+processExcl(Token *            const ntP,
+            framebuffer_info * const fbiP,
+            bool *             const unrecognizedCmdP,
+            const char **      const errorP) {
+
+    if (ntP->end - ntP->begin > 1)
+        *unrecognizedCmdP = true;
+    else {
+        *unrecognizedCmdP = false;
+
+        print_framebuffer(fbiP);
+
+        *errorP = NULL;
+    }
+}
+
+
+
+static void
+clear(Token *            const ntP,
+      framebuffer_info * const fbiP,
+      const char **      const errorP) {
+
+    *ntP = nextToken(ntP->end);
+
+    if (*ntP->begin != '\0') {
+        makeLowercase(*ntP);
+
+        switch(*ntP->begin) {
+        case 'i':
+            if (!stringIsValid("image", ntP->begin, ntP->end))
+                pm_asprintf(errorP, "unrecognized argument");
+            else {
+                clear_framebuffer(true, false, fbiP);
+                *errorP = NULL;
+            }
+            break;
+        case 'd':
+            if (!stringIsValid("depth", ntP->begin, ntP->end))
+                pm_asprintf(errorP, "unrecognized argument");
+            else {
+                clear_framebuffer(false, true, fbiP);
+                *errorP = NULL;
+            }
+            break;
+        case 'z':
+            if (ntP->end - ntP->begin > 1)
+                pm_asprintf(errorP, "unrecognized argument");
+            else {
+                clear_framebuffer(false, true, fbiP);
+                *errorP = NULL;
+            }
+            break;
+        default:
+            pm_asprintf(errorP, "unrecognized argument");
+        }
+    } else {
+        clear_framebuffer(true, true, fbiP);
+        *errorP = NULL;
+    }
+}
+
+
+
+static void
+processC(Token *            const ntP,
+         framebuffer_info * const fbiP,
+         bool *             const unrecognizedCmdP,
+         const char **      const errorP) {
+
+    if (!stringIsValid(CMD_CLEAR, ntP->begin, ntP->end))
+        *unrecognizedCmdP = true;
+    else {
+        *unrecognizedCmdP = false;
+
+        clear(ntP, fbiP, errorP);
+    }
+}
+
+
+
+static void
+processAsterisk(Token *            const ntP,
+                framebuffer_info * const fbiP,
+                bool *             const unrecognizedCmdP,
+                const char **      const errorP) {
+
+    if (ntP->end - ntP->begin > 1)
+        *unrecognizedCmdP = true;
+    else {
+        *unrecognizedCmdP = false;
+
+        clear(ntP, fbiP, errorP);
+    }
+}
+
+
+
+static void
+processR(Token *                const ntP,
+         state_info *           const stateP,
+         framebuffer_info *     const fbiP,
+         bool *                 const unrecognizedCmdP,
+         long int *             const iArgs,
+         const char **          const errorP) {
+
+    if (!stringIsValid(CMD_RESET, ntP->begin, ntP->end))
+        *unrecognizedCmdP = true;
+    else {
+        unsigned int i;
+
+        *unrecognizedCmdP = false;
+
+        for (i = 0, *errorP = NULL; i < 2 && !*errorP; ++i) {
+            char * strtolEnd;
+
+            *ntP = nextToken(ntP->end);
+
+            iArgs[i] = strtol(ntP->begin, &strtolEnd, 10);
+
+            if (*ntP->begin == '\0' || ntP->end != strtolEnd)
+                pm_asprintf(errorP, "syntax error");
+        }
+
+        if (!*errorP) {
+            if (iArgs[0] < 1 || iArgs[0] > PAM_OVERALL_MAXVAL)
+                pm_asprintf(errorP, "invalid new maxval");
+            else {
+                if (iArgs[1] < 1 || iArgs[1] > MAX_NUM_ATTRIBS)
+                    pm_asprintf(errorP, "invalid new number of generic vertex "
+                                "attributes");
+                else {
+                    *ntP = nextToken(ntP->end);
+
+                    if (*ntP->begin != '\0') {
+                        if (!set_tupletype(ntP->begin,
+                                           fbiP->outpam.tuple_type)) {
+                            pm_message(
+                                "warning: could not set new tuple type; "
+                                "using a null string");
+                            set_tupletype(NULL, fbiP->outpam.tuple_type);
+                        }
+                    } else
+                        set_tupletype(NULL, fbiP->outpam.tuple_type);
+
+                    if (!realloc_image_buffer(iArgs[0], iArgs[1], fbiP)) {
+                        pm_error("Unable to allocate memory for "
+                                 "image buffer");
+                    }
+
+                    stateP->next = 0;
+                    stateP->draw = false;
+
+                    clearAttribs(stateP, fbiP->maxval, fbiP->num_attribs);
+                }
+            }
+        }
+    }
+}
+
+
+
+static void
+processQ(Token *                const ntP,
+         bool *                 const unrecognizedCmdP,
+         bool *                 const noMoreCommandsP,
+         const char **          const errorP) {
+
+    if (!stringIsValid(CMD_QUIT, ntP->begin, ntP->end))
+        *unrecognizedCmdP = true;
+    else {
+        *unrecognizedCmdP = false;
+
+        *noMoreCommandsP = true;
+
+        *errorP = NULL;
+    }
+}
+
+
+
+void
+input_process_next_command(Input *                const inputP,
+                           struct boundary_info * const biP,
+                           framebuffer_info *     const fbiP,
+                           bool *                 const noMoreCommandsP) {
+/*----------------------------------------------------------------------------
+  Doesn't necessarily process a command, just the next line of input, which
+  may be empty.
+
+  Return *noMoreCommandsP true iff the next command is a quit command of
+  there is no next command.
+-----------------------------------------------------------------------------*/
+    static state_info state;
+
+    Token nt;
+
+    long int iArgs[MAX_NUM_ATTRIBS];
+        /* For storing potential integer arguments. */
+    bool unrecognizedCmd;
+        /* Unrecognized command detected */
+    bool noMoreCommands;
+    const char * error;
+        /* Description of problem with the command; NULL if no problem.
+           Meaningful only when 'unrecognizedCmd' is false.
+        */
+
+    if (!state.initialized) {
+        initState(&state);
+        clearAttribs(&state, fbiP->maxval, fbiP->num_attribs);
+
+        state.initialized = true;
+    }
+
+    {
+        int eof;
+        size_t lineLen;
+
+        pm_getline(stdin, &inputP->buffer, &inputP->length, &eof, &lineLen);
+
+        if (eof) {
+            *noMoreCommandsP = true;
+            return;
+        }
+    }
+
+    removeComments(inputP->buffer);
+
+    nt = nextToken(inputP->buffer);
+
+    makeLowercase(nt);
+
+    noMoreCommands = false;  /* initial assumption */
+
+    switch (nt.begin[0]) {
+    case 'm':
+        processM(&nt, &state, &unrecognizedCmd, &error);
+        break;
+    case 'a':
+        processA(&nt, &state, fbiP, &unrecognizedCmd, iArgs, &error);
+        break;
+    case 'v':
+        processV(&nt, &state, biP, fbiP, &unrecognizedCmd, iArgs, &error);
+        break;
+    case 'p':
+        processP(&nt, fbiP, &unrecognizedCmd, &error);
+        break;
+    case '!':
+        processExcl(&nt, fbiP, &unrecognizedCmd, &error);
+        break;
+    case 'c':
+        processC(&nt, fbiP, &unrecognizedCmd, &error);
+        break;
+    case '*':
+        processAsterisk(&nt, fbiP, &unrecognizedCmd, &error);
+        break;
+    case 'r':
+        processR(&nt, &state, fbiP, &unrecognizedCmd, iArgs, &error);
+        break;
+    case 'q':
+        processQ(&nt, &unrecognizedCmd, &noMoreCommands, &error);
+        break;
+    case '\0':
+        break;
+    default:
+        unrecognizedCmd = true;
+    }
+
+    if (!noMoreCommands) {
+        char const next = *nextToken(nt.end).begin;
+
+        if (unrecognizedCmd) {
+            pm_errormsg("error: unrecognized command: line %u.",
+                        (unsigned)inputP->number);
+        } else {
+            if (error) {
+                pm_errormsg("Error in line %u: %s",
+                            (unsigned)inputP->number, error);
+                pm_strfree(error);
+            } else {
+                if (next != '\0')
+                    pm_message("warning: ignoring excess arguments: line %u",
+                               (unsigned)inputP->number);
+            }
+        }
+    }
+    ++inputP->number;
+
+    *noMoreCommandsP = noMoreCommands;
+}
+
+
diff --git a/generator/pamtris/input.h b/generator/pamtris/input.h
new file mode 100644
index 00000000..d34de3a1
--- /dev/null
+++ b/generator/pamtris/input.h
@@ -0,0 +1,27 @@
+#ifndef INPUT_H_INCLUDED
+#define INPUT_H_INCLUDED
+
+#include <stdint.h>
+
+struct boundary_info;
+struct framebuffer_info;
+
+typedef struct {
+    char *   buffer;
+    size_t   length;
+    uint64_t number;
+} Input;
+
+void
+input_init(Input * const inputP);
+
+void
+input_term(Input * const inputP);
+
+void
+input_process_next_command(Input *                   const inputP,
+                           struct boundary_info *    const bdiP,
+                           struct framebuffer_info * const fbiP,
+                           bool *                    const noMoreCommandsP);
+
+#endif
diff --git a/generator/pamtris/limits_pamtris.h b/generator/pamtris/limits_pamtris.h
new file mode 100644
index 00000000..a7ed503f
--- /dev/null
+++ b/generator/pamtris/limits_pamtris.h
@@ -0,0 +1,11 @@
+#ifndef LIMITS_H_INCLUDED
+#define LIMITS_H_INCLUDED
+
+#define MAX_NUM_ATTRIBS 20
+#define MAX_COORD       32767
+#define MIN_COORD       (-MAX_COORD)
+#define MAX_INPUT_W     1048575
+#define MIN_INPUT_W     1
+#define MAX_Z           0x3FFFFFFF
+
+#endif
diff --git a/generator/pamtris/pamtris.c b/generator/pamtris/pamtris.c
new file mode 100644
index 00000000..e0becf7a
--- /dev/null
+++ b/generator/pamtris/pamtris.c
@@ -0,0 +1,171 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "netpbm/mallocvar.h"
+#include "netpbm/shhopt.h"
+#include "netpbm/pam.h"
+
+#include "limits_pamtris.h"
+#include "framebuffer.h"
+#include "boundaries.h"
+#include "input.h"
+
+#define MAX_METRICS 8192
+
+
+
+static int
+parse_command_line(int *         const argc_ptr,
+                   const char ** const argv,
+                   int32_t *     const width,
+                   int32_t *     const height,
+                   int32_t *     const maxval,
+                   int32_t *     const num_attribs,
+                   char *        const tupletype) {
+
+    optEntry * option_def;
+    optStruct3 opt;
+        /* Instructions to pm_optParseOptions3 on how to parse our options */
+    unsigned int option_def_index;
+
+    char * tupletype_tmp;
+
+    unsigned int width_spec, height_spec, attribs_spec, tupletype_spec;
+    unsigned int rgb_spec, grayscale_spec, maxval_spec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;  /* incremented by OPTENT3 */
+    OPTENT3(0, "width",       OPT_INT,    width,          &width_spec,      0);
+    OPTENT3(0, "height",      OPT_INT,    height,         &height_spec,     0);
+    OPTENT3(0, "num_attribs", OPT_INT,    num_attribs,    &attribs_spec,    0);
+    OPTENT3(0, "tupletype",   OPT_STRING, &tupletype_tmp, &tupletype_spec,  0);
+    OPTENT3(0, "rgb",         OPT_FLAG,   NULL,           &rgb_spec,        0);
+    OPTENT3(0, "grayscale",   OPT_FLAG,   NULL,           &grayscale_spec,  0);
+    OPTENT3(0, "maxval",      OPT_INT,    maxval,         &maxval_spec,     0);
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = false;
+    opt.allowNegNum   = false;
+
+    pm_optParseOptions3(argc_ptr, (char **)argv, opt, sizeof(opt), 0);
+
+    if (!width_spec || !height_spec || (!attribs_spec && !(rgb_spec || grayscale_spec))) {
+        pm_errormsg(
+            "you must at least specify -width, -height and "
+	    "either -num_attribs, -rgb or -grayscale.");
+
+        return 0;
+    }
+
+    if (rgb_spec + grayscale_spec + attribs_spec != 1) {
+        pm_errormsg("you must provide either only -num_attribs, "
+                    "-rgb or -grayscale; not a combination of those.");
+
+        return 0;
+    }
+
+    if (*width < 1 || *width > MAX_METRICS) {
+        pm_errormsg("invalid width.");
+
+        return 0;
+    }
+
+    if (*height < 1 || *height > MAX_METRICS) {
+        pm_errormsg("invalid height.");
+
+        return 0;
+    }
+
+    if (maxval_spec) {
+        if (*maxval < 1 || *maxval > PAM_OVERALL_MAXVAL) {
+            pm_errormsg("invalid maxval.");
+
+            return 0;
+        }
+    } else {
+        *maxval = 255;
+    }
+
+    if (rgb_spec) {
+        *num_attribs = 3;
+        set_tupletype("RGB_ALPHA", tupletype);
+    }
+
+    if (grayscale_spec) {
+        *num_attribs = 1;
+        set_tupletype("GRAYSCALE_ALPHA", tupletype);
+    }
+
+    if (*num_attribs < 1 || *num_attribs > MAX_NUM_ATTRIBS) {
+        pm_errormsg("invalid number of generic attributes per vertex.");
+
+        return 0;
+    }
+
+    if (tupletype_spec) {
+        if(rgb_spec || grayscale_spec) {
+            pm_errormsg("you may not provide -tupletype together with "
+                        "-rgb or -grayscale.");
+
+            return 0;
+        }
+
+        if (!set_tupletype(tupletype_tmp, tupletype)) {
+            pm_errormsg("warning: invalid tuple type; using empty string.");
+
+            set_tupletype(NULL, tupletype);
+        }
+    }
+
+    free(option_def);
+
+    return 1;
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    framebuffer_info fbi;
+    boundary_info bi;
+    Input input;
+    bool no_more_commands;
+
+    pm_proginit(&argc, (const char**)argv);
+
+    set_tupletype(NULL, fbi.outpam.tuple_type);
+
+    if (!parse_command_line(&argc,
+                            argv,
+                            &fbi.width,
+                            &fbi.height,
+                            &fbi.maxval,
+                            &fbi.num_attribs,
+                            fbi.outpam.tuple_type)) {
+        return 1;
+    }
+
+    if (!init_framebuffer(&fbi)) {
+        pm_errormsg("out of memory.");
+
+        return 3;
+    }
+
+    init_boundary_buffer(&bi, fbi.height);
+
+    input_init(&input);
+
+    for (no_more_commands = false; !no_more_commands; )
+        input_process_next_command(&input, &bi, &fbi, &no_more_commands);
+
+    input_term(&input);
+    free_boundary_buffer(&bi);
+    free_framebuffer(&fbi);
+
+    return 0;
+}
+
+
diff --git a/generator/pamtris/triangle.c b/generator/pamtris/triangle.c
new file mode 100644
index 00000000..5143f9ee
--- /dev/null
+++ b/generator/pamtris/triangle.c
@@ -0,0 +1,327 @@
+/*=============================================================================
+                                  triangle.c
+===============================================================================
+   Triangle functions
+=============================================================================*/
+#include <stdlib.h>
+#include <string.h>
+
+#include "netpbm/mallocvar.h"
+
+#include "utils.h"
+#include "varying.h"
+#include "boundaries.h"
+#include "framebuffer.h"
+
+#include "triangle.h"
+
+static void
+draw_partial_triangle(
+    const varying *       const left_attribs_input,
+    const varying *       const rght_attribs_input,
+    bool                  const upper_part,
+    const boundary_info * const bi,
+    framebuffer_info *    const fbi) {
+
+    uint8_t const z = fbi->num_attribs;
+    uint8_t const w = z + 1;
+    uint8_t const n = w + 1;
+
+    varying * left_attribs;
+    varying * rght_attribs;
+
+    varying * attribs;
+
+    int32_t first_row_index;
+    int32_t last_row_index;
+
+    MALLOCARRAY_NOFAIL(left_attribs, n);
+    MALLOCARRAY_NOFAIL(rght_attribs, n);
+    MALLOCARRAY_NOFAIL(attribs, n);
+
+    memcpy(left_attribs, left_attribs_input, n * sizeof(varying));
+    memcpy(rght_attribs, rght_attribs_input, n * sizeof(varying));
+
+    if (upper_part) {
+        first_row_index = 0;
+        last_row_index = bi->num_upper_rows - 1;
+    } else {
+        first_row_index = bi->num_upper_rows;
+        last_row_index = bi->num_upper_rows + bi->num_lower_rows - 1;
+    }
+
+    {
+        int32_t const row_delta = last_row_index - first_row_index;
+
+        int32_t row;
+
+        int32_t left_boundary;
+        int32_t rght_boundary;
+
+        for (row = first_row_index; row <= last_row_index; row++) {
+            get_triangle_boundaries(row, &left_boundary, &rght_boundary, bi);
+            {
+                int32_t const column_delta = rght_boundary - left_boundary;
+                int32_t start_column;
+                int32_t span_length;
+
+                start_column = left_boundary;  /* initial value */
+                span_length = column_delta;    /* initial value */
+
+                prepare_for_interpolation(left_attribs, rght_attribs,
+                                          attribs, column_delta,
+                                          n);
+
+                if (left_boundary < 0) {
+                    start_column = 0;
+
+                    span_length += left_boundary;
+
+                    multi_step_up(attribs, -left_boundary, n);
+                }
+
+                if (rght_boundary >= fbi->width) {
+                    span_length -= rght_boundary - fbi->width;
+                } else {
+                    span_length++;
+                }
+
+                draw_span(
+                    (bi->start_scanline + row) * fbi->width + start_column,
+                    span_length, attribs, fbi);
+
+                if (row_delta > 0) {
+                    step_up(left_attribs, n);
+                    step_up(rght_attribs, n);
+                }
+            }
+        }
+    }
+    free(attribs);
+    free(rght_attribs);
+    free(left_attribs);
+}
+
+
+
+static void
+draw_degenerate_horizontal(Xy                 const xy,
+                           varying *          const top2mid,
+                           varying *          const top2bot,
+                           varying *          const mid2bot,
+                           framebuffer_info * const fbi) {
+
+    uint8_t const n = fbi->num_attribs + 2;
+
+    {
+        int16_t const y = xy._[0][1];
+
+        int16_t x[3];
+        int16_t x_start[3];
+        varying * attribs[3];
+        int32_t span_length[3];
+        unsigned int i;
+
+        x[0] = xy._[0][0];
+        x[1] = xy._[1][0];
+        x[2] = xy._[2][0];
+
+        x_start[0] = x[0];
+        x_start[1] = x[0];
+        x_start[2] = x[1];
+
+        attribs[0] = top2bot;
+        attribs[1] = top2mid;
+        attribs[2] = mid2bot;
+
+        span_length[0] = x[2] - x[0];
+        span_length[1] = x[1] - x[0];
+        span_length[2] = x[2] - x[1];
+
+        for (i = 0; i < 3; i++) {
+            if (x_start[i] >= fbi->width || x_start[i] + span_length[i] < 0) {
+                continue;
+            }
+
+            if (x_start[i] < 0) {
+                multi_step_up(attribs[i], -x_start[i], n);
+
+                span_length[i] += x_start[i];
+
+                x_start[i] = 0;
+            }
+
+            if (x_start[i] + span_length[i] >= fbi->width) {
+                span_length[i] -= x_start[i] + span_length[i] - fbi->width;
+            } else {
+                span_length[i]++;
+            }
+
+            draw_span(y * fbi->width + x_start[i], span_length[i],
+                      attribs[i], fbi);
+        }
+    }
+}
+
+
+
+void
+draw_triangle(Xy                 const xy_input,
+              Attribs            const attribs_input,
+              boundary_info *    const bi,
+              framebuffer_info * const fbi) {
+
+    uint8_t const z = fbi->num_attribs;
+    uint8_t const w = z + 1;
+    uint8_t const n = w + 1;
+
+    Xy xy;
+    varying * attribs[3];
+    unsigned int i;
+    uint8_t index_array[3];
+    int32_t y_array[3];
+    int32_t x_array[3];
+
+    MALLOCARRAY_NOFAIL(attribs[0], n);
+    MALLOCARRAY_NOFAIL(attribs[1], n);
+    MALLOCARRAY_NOFAIL(attribs[2], n);
+
+    xy = xy_input;
+
+    for (i = 0; i < 3; i++) {
+        int32_to_varying_array(attribs_input._[i], attribs[i], n);
+	attribs[i][z] = compute_varying_z(attribs_input._[i][z]);
+	attribs[i][w] = inverse_varying(attribs[i][w]);
+        multiply_varying_array_by_varying(attribs[i], attribs[i][w], z);
+    }
+
+    /* Argument preparations for sort3: */
+
+    index_array[0] = 0; index_array[1] = 1; index_array[2] = 2;
+    y_array[0] = xy._[0][1]; y_array[1] = xy._[1][1]; y_array[2] = xy._[2][1];
+    x_array[0] = xy._[0][0]; x_array[1] = xy._[1][0]; x_array[2] = xy._[2][0];
+
+    sort3(index_array, y_array, x_array);
+
+    {
+        uint8_t const top = index_array[0];
+        uint8_t const mid = index_array[1];
+        uint8_t const bot = index_array[2];
+
+        bool mid_is_to_the_left;
+
+        Xy xy_sorted;
+
+        xy_sorted._[0][0] = xy._[top][0];
+        xy_sorted._[0][1] = xy._[top][1];
+        xy_sorted._[1][0] = xy._[mid][0];
+        xy_sorted._[1][1] = xy._[mid][1];
+        xy_sorted._[2][0] = xy._[bot][0];
+        xy_sorted._[2][1] = xy._[bot][1];
+
+        mid_is_to_the_left =
+            gen_triangle_boundaries(xy_sorted, bi, fbi->width, fbi->height);
+
+        if (bi->start_scanline == -1) {
+            /* Triangle is completely out of the bounds of the frame buffer. */
+        } else {
+            bool const no_upper_part =
+                (xy_sorted._[1][1] == xy_sorted._[0][1]);
+
+            bool const horizontal =
+                (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]);
+                /* Tells whether we are dealing with a degenerate
+                 * horizontal triangle */
+
+            uint8_t const t = horizontal ^ 1;
+
+            int32_t top2mid_delta = xy._[mid][t] - xy._[top][t];
+            int32_t top2bot_delta = xy._[bot][t] - xy._[top][t];
+            int32_t mid2bot_delta = xy._[bot][t] - xy._[mid][t];
+
+            varying * top2mid;
+            varying * top2bot;
+            varying * mid2bot;
+
+            varying * upper_left_attribs;
+            varying * lower_left_attribs;
+            varying * upper_rght_attribs;
+            varying * lower_rght_attribs;
+
+            MALLOCARRAY_NOFAIL(top2mid, n);
+            MALLOCARRAY_NOFAIL(top2bot, n);
+            MALLOCARRAY_NOFAIL(mid2bot, n);
+
+            prepare_for_interpolation(attribs[top], attribs[mid], top2mid, top2mid_delta, n);
+            prepare_for_interpolation(attribs[top], attribs[bot], top2bot, top2bot_delta, n);
+            prepare_for_interpolation(attribs[mid], attribs[bot], mid2bot, mid2bot_delta, n);
+
+            if (mid_is_to_the_left) {
+                upper_left_attribs = top2mid;
+                lower_left_attribs = mid2bot;
+                upper_rght_attribs = top2bot;
+                lower_rght_attribs = upper_rght_attribs;
+            } else {
+                upper_rght_attribs = top2mid;
+                lower_rght_attribs = mid2bot;
+                upper_left_attribs = top2bot;
+                lower_left_attribs = upper_left_attribs;
+            }
+
+            if (!(horizontal || no_upper_part)) {
+                int32_t delta;
+
+                if (bi->num_upper_rows > 0) {
+                    if (bi->start_scanline > xy._[top][1]) {
+                        delta = bi->start_scanline - xy._[top][1];
+
+                        multi_step_up(upper_left_attribs, delta, n);
+                        multi_step_up(upper_rght_attribs, delta, n);
+                    }
+
+                    draw_partial_triangle(
+                        upper_left_attribs,
+                        upper_rght_attribs,
+                        true,
+                        bi,
+                        fbi
+                        );
+
+                    delta = xy._[mid][1] - bi->start_scanline;
+                } else {
+                    delta = top2mid_delta;
+                }
+
+                multi_step_up(upper_left_attribs, delta, n);
+                multi_step_up(upper_rght_attribs, delta, n);
+            }
+
+            if (horizontal) {
+                draw_degenerate_horizontal(
+                    xy_sorted,
+                    top2mid, top2bot, mid2bot,
+                    fbi
+                    );
+            } else {
+                if (bi->start_scanline > xy._[mid][1]) {
+                    int32_t const delta = bi->start_scanline - xy._[mid][1];
+
+                    multi_step_up(lower_left_attribs, delta, n);
+                    multi_step_up(lower_rght_attribs, delta, n);
+                }
+
+                draw_partial_triangle(
+                    lower_left_attribs,
+                    lower_rght_attribs,
+                    false,
+                    bi,
+                    fbi
+                    );
+            }
+            free(mid2bot); free(top2bot); free(top2mid);
+        }
+    }
+    free(attribs[2]); free(attribs[1]); free(attribs[0]);
+}
+
+
diff --git a/generator/pamtris/triangle.h b/generator/pamtris/triangle.h
new file mode 100644
index 00000000..e043e95c
--- /dev/null
+++ b/generator/pamtris/triangle.h
@@ -0,0 +1,25 @@
+#ifndef TRIANGLE_H_INCLUDED
+#define TRIANGLE_H_INCLUDED
+
+#include <stdint.h>
+
+#include "limits_pamtris.h"
+
+struct boundary_info;
+struct framebuffer_info;
+
+typedef struct {
+    int32_t _[3][2];
+} Xy;
+
+typedef struct {
+    int32_t _[3][MAX_NUM_ATTRIBS + 2];
+} Attribs;
+
+void
+draw_triangle(Xy                        const xy,
+              Attribs                   const attribs,
+              struct boundary_info *    const bdi,
+              struct framebuffer_info * const fbi);
+
+#endif
diff --git a/generator/pamtris/utils.c b/generator/pamtris/utils.c
new file mode 100644
index 00000000..a6b6e7d4
--- /dev/null
+++ b/generator/pamtris/utils.c
@@ -0,0 +1,266 @@
+/*=============================================================================
+                              utils.c
+===============================================================================
+   Utility functions
+=============================================================================*/
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+
+#include "limits_pamtris.h"
+#include "varying.h"
+
+#include "utils.h"
+
+
+
+void
+prepare_for_interpolation(const varying * const begin,
+                          const varying * const end,
+                          varying *       const out,
+                          int32_t               num_steps,
+                          uint8_t         const elements) {
+
+    double inverse_num_steps;
+    unsigned int i;
+
+    if (num_steps < 1) {
+        num_steps = 1;
+    }
+
+    inverse_num_steps = 1.0 / num_steps;
+
+    for (i = 0; i < elements; i++) {
+        out[i].v = begin[i].v;
+        out[i].s = (end[i].v - begin[i].v) * inverse_num_steps;
+    }
+}
+
+
+
+varying
+compute_varying_z(int32_t const input_z) {
+
+    varying retval;
+
+    retval.v = 1.0 / (1 + input_z - MIN_COORD);
+    retval.s = 0.0;
+
+    return retval;
+}
+
+
+
+void
+multiply_varying_array_by_varying(varying * const vars,
+                                  varying   const multiplier,
+                                  uint8_t   const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        vars[i].v *= multiplier.v;
+	vars[i].s  = 0.0;
+    }
+}
+
+
+void
+divide_varying_array_by_varying(varying * const vars,
+                                varying   const divisor,
+                                uint8_t   const elements) {
+
+    double const inverse_divisor = 1.0 / divisor.v;
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        vars[i].v *= inverse_divisor;
+	vars[i].s  = 0.0;
+    }
+}
+
+
+
+varying
+inverse_varying(varying const var) {
+
+    varying retval;
+
+    retval.v = 1.0 / var.v;
+    retval.s = 0.0;
+
+    return retval;
+}
+
+
+
+varying
+multiply_varyings(varying const a,
+                  varying const b) {
+
+    varying retval;
+
+    retval.v = a.v * b.v;
+    retval.s = 0.0;
+
+    return retval;
+}
+
+
+
+void
+step_up(varying * const vars,
+        uint8_t   const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        vars[i].v += vars[i].s;
+    }
+}
+
+
+
+void
+multi_step_up(varying * const vars,
+              int32_t   const times,
+              uint8_t   const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        vars[i].v += times * vars[i].s;
+    }
+}
+
+
+
+void
+int32_to_varying_array(const int32_t * const in,
+                       varying *       const out,
+                       uint8_t         const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        out[i].v = in[i];
+        out[i].s = 0.0;
+    }
+}
+
+
+
+/* static int64_t
+abs64(int64_t x)
+{
+
+    int64_t const nm = ~geq_mask64(x, 0);
+
+    return (-x & nm) | (x & ~nm);
+} */
+
+
+
+int32_t
+round_varying(varying const var) {
+
+    return round(var.v);
+}
+
+
+
+int64_t
+geq_mask64(int64_t a, int64_t b) {
+
+    uint64_t const diff = a - b;
+
+    return -((~diff) >> 63);
+}
+
+
+
+static void
+swap(uint8_t * const a,
+     uint8_t * const b) {
+/*----------------------------------------------------------------------------
+  Swap the contents pointed to by a and b.
+-----------------------------------------------------------------------------*/
+    uint8_t const temp = *a;
+
+    *a = *b;
+    *b = temp;
+}
+
+
+
+void
+sort3(uint8_t *       const index_array,
+      const int32_t * const y_array,
+      const int32_t * const x_array) {
+/*----------------------------------------------------------------------------
+  Sort an index array of 3 elements. This function is used to sort vertices
+  with regard to relative row from top to bottom, but instead of sorting
+  an array of vertices with all their coordinates, we simply sort their
+  indices. Each element in the array pointed to by "index_array" should
+  contain one of the numbers 0, 1 or 2, and each one of them should be
+  different. "y_array" should point to an array containing the corresponding
+  Y coordinates (row) of each vertex and "x_array" should point to an array
+  containing the corresponding X coordinates (column) of each vertex.
+
+  If the Y coordinates are all equal, the indices are sorted with regard to
+  relative X coordinate from left to right. If only the top two vertex have
+  the same Y coordinate, the array is sorted normally with regard to relative
+  Y coordinate, but the first two indices are then sorted with regard to
+  relative X coordinate. Finally, If only the bottom two vertex have the same
+  Y coordinate, the array is sorted normally with regard to relative Y
+  coordinate, but the last two indices are then sorted with regard to relative
+  X coordinate.
+-----------------------------------------------------------------------------*/
+    uint8_t * const ia = index_array;
+
+    const int32_t * ya;
+    const int32_t * xa;
+
+    ya = y_array;  /* initial value */
+    xa = x_array;  /* initial value */
+
+    if (ya[0] == ya[1] && ya[1] == ya[2]) {
+        /* In case the vertices represent a degenerate horizontal triangle, we
+           sort according to relative X coordinate, as opposed to Y.
+        */
+        ya = xa;
+    }
+
+    if (ya[ia[2]] < ya[ia[1]]) {
+        swap(ia, ia + 2);
+        if (ya[ia[2]] < ya[ia[1]]) {
+            swap(ia + 1, ia + 2);
+            if (ya[ia[1]] < ya[ia[0]]) {
+                swap(ia, ia + 1);
+            }
+        }
+    } else if (ya[ia[1]] < ya[ia[0]]) {
+        swap(ia, ia + 1);
+        if (ya[ia[2]] < ya[ia[1]]) {
+            swap(ia + 1, ia + 2);
+        }
+    }
+
+    if (ya == xa) {
+        return;
+    }
+
+    if (ya[ia[0]] == ya[ia[1]]) {
+        if (xa[ia[1]] < xa[ia[0]]) {
+            swap(ia, ia + 1);
+        }
+    } else if (ya[ia[1]] == ya[ia[2]]) {
+        if (xa[ia[2]] < xa[ia[1]]) {
+            swap(ia + 1, ia + 2);
+        }
+    }
+}
+
+
diff --git a/generator/pamtris/utils.h b/generator/pamtris/utils.h
new file mode 100644
index 00000000..bd9dcdbe
--- /dev/null
+++ b/generator/pamtris/utils.h
@@ -0,0 +1,58 @@
+#ifndef UTIL_H_INCLUDED
+#define UTIL_H_INCLUDED
+
+#include "varying.h"
+
+void
+prepare_for_interpolation(const varying * const begin,
+                          const varying * const end,
+                          varying *       const out,
+                          int32_t               num_steps,
+                          uint8_t         const elements);
+
+varying
+compute_varying_z(int32_t const input_z);
+
+void
+multiply_varying_array_by_varying(varying * const vars,
+                                  varying   const divisor,
+                                  uint8_t   const elements);
+
+void
+divide_varying_array_by_varying(varying * const vars,
+                                varying   const divisor,
+                                uint8_t   const elements);
+
+varying
+inverse_varying(varying const var);
+
+varying
+multiply_varyings(varying const a,
+                  varying const b);
+
+void
+step_up(varying * const vars,
+       uint8_t    const elements);
+
+void
+multi_step_up(varying * const vars,
+             int32_t    const times,
+             uint8_t    const elements);
+
+void
+int32_to_varying_array(const int32_t * const in,
+                       varying *       const out,
+                       uint8_t         const elements);
+
+int32_t
+round_varying(varying const var);
+
+int64_t
+geq_mask64(int64_t a, int64_t b);
+
+void
+sort3(uint8_t *       const index_array,
+      const int32_t * const y_array,
+      const int32_t * const x_array);
+
+#endif
diff --git a/generator/pamtris/varying.h b/generator/pamtris/varying.h
new file mode 100644
index 00000000..6605f02d
--- /dev/null
+++ b/generator/pamtris/varying.h
@@ -0,0 +1,12 @@
+#ifndef VARYING_H_INCLUDED
+#define VARYING_H_INCLUDED
+
+#include <stdint.h>
+
+
+typedef struct {
+    double v; /* Value */
+    double s; /* Step */
+} varying;
+
+#endif
diff --git a/generator/pbmmake.c b/generator/pbmmake.c
index 600440f0..0352d007 100644
--- a/generator/pbmmake.c
+++ b/generator/pbmmake.c
@@ -61,7 +61,7 @@ parseCommandLine(int argc, char ** argv,
 
     if (blackOpt + whiteOpt + grayOpt > 1)
         pm_error("You can specify only one of -black, -white, and -gray");
-    
+
     if (blackOpt)
         cmdlineP->color = COLOR_BLACK;
     else if (whiteOpt)
@@ -83,8 +83,8 @@ parseCommandLine(int argc, char ** argv,
 
 
 
-static void 
-writeGrayRaster(unsigned int const cols, 
+static void
+writeGrayRaster(unsigned int const cols,
                 unsigned int const rows,
                 FILE *       const ofP) {
 
@@ -96,7 +96,7 @@ writeGrayRaster(unsigned int const cols,
     bitrow0 = pbm_allocrow_packed(cols);
     bitrow1 = pbm_allocrow_packed(cols);
 
-    for (i=0; i <= lastCol; ++i) { 
+    for (i=0; i <= lastCol; ++i) {
         bitrow0[i] = (PBM_WHITE*0xaa) | (PBM_BLACK*0x55);
         bitrow1[i] = (PBM_WHITE*0x55) | (PBM_BLACK*0xaa);
         /* 0xaa = 10101010 ; 0x55 = 01010101 */
@@ -119,7 +119,7 @@ writeGrayRaster(unsigned int const cols,
     pbm_freerow(bitrow1);
 }
 
-    
+
 
 static void
 writeSingleColorRaster(unsigned int const cols,
@@ -134,7 +134,7 @@ writeSingleColorRaster(unsigned int const cols,
 
     bitrow0 = pbm_allocrow_packed(cols);
 
-    for (i = 0; i <= lastCol; ++i) 
+    for (i = 0; i <= lastCol; ++i)
         bitrow0[i] = color*0xff;
 
     if (color != 0)
@@ -161,7 +161,7 @@ main(int argc, char * argv[]) {
     parseCommandLine(argc, argv, &cmdline);
 
     pbm_writepbminit(stdout, cmdline.width, cmdline.height, 0);
-    
+
     if (cmdline.color == COLOR_GRAY)
         writeGrayRaster(cmdline.width, cmdline.height, stdout);
     else {
@@ -169,6 +169,6 @@ main(int argc, char * argv[]) {
         writeSingleColorRaster(cmdline.width, cmdline.height, color, stdout);
     }
     pm_close(stdout);
-    
+
     return 0;
 }
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index 9f4366d4..e6f27865 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -10,41 +10,93 @@
 ** implied warranty.
 */
 
+#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 <string.h>
+#include <math.h>
 #include <limits.h>
 #include <assert.h>
+#include <setjmp.h>
+#include <locale.h>
+#include <wchar.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
+#include "pm.h"
 #include "pbm.h"
 #include "pbmfont.h"
 
-struct cmdlineInfo {
+#define  MAXLINECHARS 5000
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char * text;    /* text from command line or NULL if none */
+    const PM_WCHAR * text; /* text from command line or NULL if none */
     const char * font;    /* -font option value or NULL if none */
     const char * builtin; /* -builtin option value or NULL if none */
-    unsigned int dump;   
-        /* undocumented dump option for installing a new built-in font */
-    float space;   /* -space option value or default */
-    unsigned int width;     /* -width option value or zero */
-    int lspace;    /* lspace option value or default */
-    unsigned int nomargins;     /* -nomargins */
-    unsigned int verbose;
+    float space;          /* -space option value or default */
+    int lspace;           /* -lspace option value or default */
+    unsigned int width;   /* -width option value or zero */
+    unsigned int wchar;   /* -wchar option specified  */
+    unsigned int nomargins;  /* -nomargins option specified  */
+    unsigned int dryrun;     /* -dry-run option specified */
+    unsigned int textdump;   /* -text-dump option specified */
+    unsigned int verbose;    /* -verbose option specified */
+        /* undocumented option */
+    unsigned int dumpsheet; /* font data sheet in PBM format for -font */
 };
 
 
 
+static const PM_WCHAR *
+textFmCmdLine(int argc, const char ** argv) {
+
+    char * text;
+    PM_WCHAR * wtext;
+    unsigned int i;
+    unsigned int totaltextsize;
+
+    MALLOCARRAY(text, MAXLINECHARS+1);
+
+    if (!text)
+        pm_error("Unable to allocate memory for a buffer of up to %u "
+                 "characters of text", MAXLINECHARS);
+
+    text[0] = '\0';
+
+    for (i = 1, totaltextsize = 1; i < argc; ++i) {
+        if (i > 1) {
+            strcat(text, " ");
+        }
+        totaltextsize += strlen(argv[i]) + 1;
+        if (totaltextsize > MAXLINECHARS)
+            pm_error("input text too long");
+        strcat(text, argv[i]);
+    }
+    MALLOCARRAY(wtext, totaltextsize * sizeof(PM_WCHAR));
+
+    if (!wtext)
+        pm_error("Unable to allocate memory for a buffer of up to %u "
+                 "wide characters of text", totaltextsize);
+
+    for (i = 0; i < totaltextsize; ++i)
+        wtext[i] = (PM_WCHAR) text[i];
+
+    free(text);
+
+    return wtext;
+}
+
+
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -59,110 +111,151 @@ parseCommandLine(int argc, const char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "font",      OPT_STRING, &cmdlineP->font, NULL,        0);
-    OPTENT3(0, "builtin",   OPT_STRING, &cmdlineP->builtin, NULL,     0);
-    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdlineP->dump,        0);
-    OPTENT3(0, "space",     OPT_FLOAT,  &cmdlineP->space, NULL,       0);
-    OPTENT3(0, "width",     OPT_UINT,   &cmdlineP->width, NULL,       0);
-    OPTENT3(0, "lspace",    OPT_INT,    &cmdlineP->lspace, NULL,      0);
-    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdlineP->nomargins,   0);
-    OPTENT3(0, "verbose",   OPT_FLAG,   NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,    NULL,   0);
+    OPTENT3(0, "builtin",    OPT_STRING, &cmdlineP->builtin, NULL,   0);
+    OPTENT3(0, "space",      OPT_FLOAT,  &cmdlineP->space,   NULL,   0);
+    OPTENT3(0, "lspace",     OPT_INT,    &cmdlineP->lspace,  NULL,   0);
+    OPTENT3(0, "width",      OPT_UINT,   &cmdlineP->width,   NULL,   0);
+    OPTENT3(0, "nomargins",  OPT_FLAG,   NULL, &cmdlineP->nomargins, 0);
+    OPTENT3(0, "wchar",      OPT_FLAG,   NULL, &cmdlineP->wchar,     0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,   0);
+    OPTENT3(0, "dry-run",    OPT_FLAG,   NULL, &cmdlineP->dryrun,    0);
+    OPTENT3(0, "text-dump",  OPT_FLAG,   NULL, &cmdlineP->textdump,  0);
+    OPTENT3(0, "dump-sheet", OPT_FLAG,   NULL, &cmdlineP->dumpsheet, 0);
 
     /* Set the defaults */
-    cmdlineP->font = NULL;
+    cmdlineP->font    = NULL;
     cmdlineP->builtin = NULL;
-    cmdlineP->space = 0.0;
-    cmdlineP->width = 0;
-    cmdlineP->lspace = 0;
+    cmdlineP->space   = 0.0;
+    cmdlineP->width   = 0;
+    cmdlineP->lspace  = 0;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->width > 0 && cmdlineP->nomargins) {
+        pm_message("-nomargins has no effect when -width is specified");
+        cmdlineP->nomargins = FALSE;
+    } else if (cmdlineP->width > INT_MAX-10)
+        pm_error("-width value too large");
+
+    if (cmdlineP->space > pbm_maxfontwidth())
+        pm_error("-space value too large");
+    else if (cmdlineP->space < -pbm_maxfontwidth())
+        pm_error("negative -space value too large");
+
+    if (cmdlineP->lspace > pbm_maxfontheight())
+        pm_error("-lspace value too large");
+    else if (cmdlineP->lspace < -pbm_maxfontheight())
+        pm_error("negative -lspace value too large");
+
+    if (cmdlineP->textdump) {
+        if (cmdlineP->dryrun)
+            pm_error("You cannot specify both -dry-run and -text-dump");
+        else if (cmdlineP->dumpsheet)
+            pm_error("You cannot specify both -dump-sheet and -text-dump");
+    }
+
+    if (cmdlineP->dryrun && cmdlineP->dumpsheet)
+        pm_error("You cannot specify both -dry-run and -dump-sheet");
 
     if (argc-1 == 0)
         cmdlineP->text = NULL;
-    else {
-        char *text;
-        int i;
-        int totaltextsize;
-
-        totaltextsize = 1;  /* initial value */
-        
-        text = malloc(totaltextsize);  /* initial allocation */
-        text[0] = '\0';
-        
-        for (i = 1; i < argc; ++i) {
-            if (i > 1) {
-                totaltextsize += 1;
-                text = realloc(text, totaltextsize);
-                if (text == NULL)
-                    pm_error("out of memory allocating space for input text");
-                strcat(text, " ");
-            } 
-            totaltextsize += strlen(argv[i]);
-            text = realloc(text, totaltextsize);
-            if (text == NULL)
-                pm_error("out of memory allocating space for input text");
-            strcat(text, argv[i]);
-        }
-        cmdlineP->text = text;
+    else {  /* Text to render is part of command line */
+        if (cmdlineP->wchar)
+            pm_error("-wchar is not valid when text is from command line");
+
+        cmdlineP->text = textFmCmdLine(argc, argv);
+
+
     }
+    free(option_def);
 }
 
 
 
 static void
-reportFont(struct font * const fontP) {
-
-    unsigned int n;
-    unsigned int c;
+reportFont(const struct font2 * const fontP) {
 
     pm_message("FONT:");
-    pm_message("  character dimensions: %uw x %uh",
+    pm_message("  Name: %s", fontP->name);
+    pm_message("  Encoding: %s", fontP->charset_string);
+    pm_message("  Origin: %s", pbmFontOrigin[fontP->load_fn]);
+    pm_message("  Character dimensions: %uw x %uh",
                fontP->maxwidth, fontP->maxheight);
     pm_message("  Additional vert white space: %d pixels", fontP->y);
+    pm_message("  # characters loaded: %u", fontP->chars);
+}
+
+
+
+
+
+
+static struct font2 *
+font2FromFile(const char * const fileName,
+              PM_WCHAR     const maxmaxglyph) {
+
+    struct font2 * font2P;
+
+    jmp_buf jmpbuf;
+    int rc;
 
-    for (c = 0, n = 0; c < ARRAY_SIZE(fontP->glyph); ++c)
-        if (fontP->glyph[c])
-            ++n;
+    rc = setjmp(jmpbuf);
 
-    pm_message("  # characters: %u", n);
+    if (rc == 0) {
+        /* This is the normal program flow */
+        pm_setjmpbuf(&jmpbuf);
+
+        font2P = pbm_loadfont2(fileName, maxmaxglyph);
+
+        pm_setjmpbuf(NULL);
+    } else {
+        /* This is the second pass, after pbm_loadbdffont2 does a longjmp
+           because it fails.
+        */
+        pm_setjmpbuf(NULL);
+
+        pm_error("Failed to load font from file '%s'", fileName);
+    }
+
+    return font2P;
 }
 
 
 
 static void
-computeFont(struct cmdlineInfo const cmdline,
-            struct font **     const fontPP) {
+computeFont(struct CmdlineInfo const cmdline,
+            struct font2 **    const fontPP) {
 
-    struct font * fontP;
+    struct font2 * font2P;
 
     if (cmdline.font)
-        fontP = pbm_loadfont(cmdline.font);
-    else {
-        if (cmdline.builtin)
-            fontP = pbm_defaultfont(cmdline.builtin);
-        else
-            fontP = pbm_defaultfont("bdf");
-    }
+        font2P = font2FromFile(cmdline.font,
+                               cmdline.wchar ? PM_FONT2_MAXGLYPH :
+                                               PM_FONT_MAXGLYPH);
+    else if (cmdline.builtin)
+        font2P = pbm_defaultfont2(cmdline.builtin);
+    else
+        font2P = pbm_defaultfont2(cmdline.wchar ? "bdf" : "bdf");
 
     if (cmdline.verbose)
-        reportFont(fontP);
+        reportFont(font2P);
 
-    if (cmdline.dump) {
-        pbm_dumpfont(fontP);
-        exit(0);
-    }
-    *fontPP = fontP;
+    *fontPP = font2P;
 }
 
 
 
-struct text {
-    char **      textArray;  /* malloc'ed */
+struct Text {
+    PM_WCHAR **  textArray;  /* malloc'ed */
+        /* This is strictly characters that are in user's font - no control
+           characters, no undefined code points.
+        */
     unsigned int allocatedLineCount;
     unsigned int lineCount;
 };
@@ -170,41 +263,49 @@ struct text {
 
 
 static void
-allocTextArray(struct text * const textP,
+allocTextArray(struct Text * const textP,
                unsigned int  const maxLineCount,
                unsigned int  const maxColumnCount) {
 
     unsigned int line;
 
-    textP->allocatedLineCount = maxLineCount;
-
+    textP->allocatedLineCount = maxColumnCount > 0 ? maxLineCount : 0;
     MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
-    
-    for (line = 0; line < maxLineCount; ++line)
-        MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
 
+    for (line = 0; line < maxLineCount; ++line) {
+        if (maxColumnCount > 0)
+            MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
+    else
+        textP->textArray[line] = NULL;
+    }
     textP->lineCount = 0;
 }
 
 
 
 static void
-freeTextArray(struct text const text) {
+freeTextArray(struct Text const text) {
 
     unsigned int line;
 
     for (line = 0; line < text.allocatedLineCount; ++line)
-        free((char **)text.textArray[line]);
+        free((PM_WCHAR **)text.textArray[line]);
 
     free(text.textArray);
 }
 
 
 
+enum FixMode {SILENT, /* convert silently */
+              WARN,   /* output message to stderr */
+              QUIT    /* abort */ };
+
+
 static void
-fixControlChars(const char *  const input,
-                struct font * const fontP,
-                const char ** const outputP) {
+fixControlChars(const PM_WCHAR  * const input,
+                struct font2    * const fontP,
+                const PM_WCHAR ** const outputP,
+                enum FixMode      const fixMode) {
 /*----------------------------------------------------------------------------
    Return a translation of input[] that can be rendered as glyphs in
    the font 'fontP'.  Return it as newly malloced *outputP.
@@ -214,8 +315,9 @@ fixControlChars(const char *  const input,
    Remove any trailing newline.  (But leave intermediate ones as line
    delimiters).
 
-   Turn anything that isn't a code point in the font to a single space
-   (which isn't guaranteed to be in the font either, of course).
+   Depending on value of fixMode, turn anything that isn't a code point
+   in the font to a single space (which isn't guaranteed to be in the
+   font either, of course).
 -----------------------------------------------------------------------------*/
     /* We don't know in advance how big the output will be because of the
        tab expansions.  So we make sure before processing each input
@@ -228,10 +330,10 @@ fixControlChars(const char *  const input,
     unsigned int const tabSize = 8;
 
     unsigned int inCursor, outCursor;
-    char * output;      /* Output buffer.  Malloced */
+    PM_WCHAR * output;      /* Output buffer.  Malloced */
     size_t outputSize;  /* Currently allocated size of 'output' */
 
-    outputSize = strlen(input) + 1 + tabSize;
+    outputSize = wcslen(input) + 1 + tabSize;
         /* Leave room for one worst case tab expansion and NUL terminator */
     MALLOCARRAY(output, outputSize);
 
@@ -239,7 +341,8 @@ fixControlChars(const char *  const input,
         pm_error("Couldn't allocate %u bytes for a line of text.",
                  (unsigned)outputSize);
 
-    for (inCursor = 0, outCursor = 0; input[inCursor] != '\0'; ++inCursor) {
+    for (inCursor = 0, outCursor = 0; input[inCursor] != L'\0'; ++inCursor) {
+        PM_WCHAR const currentChar = input[inCursor];
         if (outCursor + 1 + tabSize > outputSize) {
             outputSize = outCursor + 1 + 4 * tabSize;
             REALLOCARRAY(output, outputSize);
@@ -247,24 +350,43 @@ fixControlChars(const char *  const input,
                 pm_error("Couldn't allocate %u bytes for a line of text.",
                          (unsigned)outputSize);
         }
-        if (input[inCursor] == '\n' && input[inCursor+1] == '\0') {
+        if (currentChar == L'\n' && input[inCursor+1] == L'\0') {
             /* This is a terminating newline.  We don't do those. */
-        } else if (input[inCursor] == '\t') { 
+        } else if (currentChar == L'\t') {
             /* Expand this tab into the right number of spaces. */
             unsigned int const nextTabStop =
                 (outCursor + tabSize) / tabSize * tabSize;
 
+            if (fontP->glyph[L' '] == NULL)
+                pm_error("space character not defined in font");
+
             while (outCursor < nextTabStop)
-                output[outCursor++] = ' ';
-        } else if (!fontP->glyph[(unsigned char)input[inCursor]]) {
+                output[outCursor++] = L' ';
+        } else if (currentChar > fontP->maxglyph ||
+                   !fontP->glyph[currentChar]) {
+            if (currentChar > PM_FONT2_MAXGLYPH)
+                pm_message("code point %X is beyond what this program "
+                           "can handle.  Max=%X",
+                           (unsigned int)currentChar, PM_FONT2_MAXGLYPH);
+
             /* Turn this unknown char into a single space. */
-            output[outCursor++] = ' ';
+            if (fontP->glyph[L' '] == NULL)
+                pm_error("space character not defined in font");
+            else if (fixMode == QUIT)
+                pm_error("code point %X not defined in font",
+                         (unsigned int) currentChar );
+            else {
+                if (fixMode == WARN)
+                    pm_message("converting code point %X to space",
+                               (unsigned int) currentChar );
+                output[outCursor++] = ' ';
+            }
         } else
             output[outCursor++] = input[inCursor];
 
         assert(outCursor <= outputSize);
     }
-    output[outCursor++] = '\0';
+    output[outCursor++] = L'\0';
 
     assert(outCursor <= outputSize);
 
@@ -274,478 +396,702 @@ fixControlChars(const char *  const input,
 
 
 static void
-fill_rect(bit** const bits, 
-          int   const row0, 
-          int   const col0, 
-          int   const height, 
-          int   const width, 
-          bit   const color) {
-
-    int row;
-
-    for (row = row0; row < row0 + height; ++row) {
-        int col;
-        for (col = col0; col < col0 + width; ++col)
-            bits[row][col] = color;
+clearBackground(bit ** const bits,
+                int    const cols,
+                int    const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int colChar;
+        for (colChar = 0; colChar < pbm_packed_bytes(cols); ++colChar)
+            bits[row][colChar] = 0x00;
     }
 }
 
 
 
 static void
-get_line_dimensions(const char line[], const struct font * const font_p, 
-                    const float intercharacter_space,
-                    double * const bwidP, int * const backup_space_needed_p) {
+getEdges(double               const currentPosition,
+         PM_WCHAR             const currentChar,
+         const struct glyph * const glyphP,
+         int                  const currLeftEdge,
+         double               const currRightEdge,
+         int                * const newLeftEdgeP,
+         double             * const newRightEdgeP) {
+
+    int leftEdge;
+    double rightEdge;
+
+    if (glyphP == NULL)
+        pm_error("Unrenderable char: %04X", (unsigned int) currentChar);
+    else {
+        leftEdge  =  (int) MIN(currentPosition + glyphP->x, currLeftEdge);
+        rightEdge =  MAX(currentPosition + glyphP->x + glyphP->width,
+                         currRightEdge);
+    }
+    *newLeftEdgeP  = leftEdge;
+    *newRightEdgeP = rightEdge;
+}
+
+
+
+static void
+advancePosition(double               const currentPosition,
+                PM_WCHAR             const currentChar,
+                const struct glyph * const glyphP,
+                float                const space,
+                double               const accumulatedSpace,
+                double             * const newPositionP,
+                double             * const newAccumulatedSpaceP) {
+/*----------------------------------------------------------------------------
+  Advance position according to value for glyph.
+  Add extra intercharacter space if -space option was used.
+
+  The advance value must be zero or positive.
+----------------------------------------------------------------------------*/
+
+    /* Start position of next character */
+    /* Must not move left from current position */
+    int const fullPixels = (int) (accumulatedSpace + space);
+        /* round toward 0 */
+    int const advance    = (int) glyphP->xadd + fullPixels;
+
+    if (advance < 0) {
+        if (space < 0)
+            pm_error("Negative -space value too large");
+        else
+            pm_error("Abnormal horizontal advance value %d "
+                     "for code point 0x%lx.",
+                     glyphP->xadd, (unsigned long int) currentChar);
+    }
+    else if (currentPosition + advance > INT_MAX)
+        pm_error("Image is too wide");
+    else {
+        *newPositionP = currentPosition + advance;
+        *newAccumulatedSpaceP = accumulatedSpace + space
+            - (double) fullPixels;
+    }
+}
+
+
+
+static void
+getLineDimensions(PM_WCHAR             const line[],
+                  const struct font2 * const fontP,
+                  float                const intercharacterSpace,
+                  double *             const rightEdgeP,
+                  int    *             const leftEdgeP) {
 /*----------------------------------------------------------------------------
-   Determine the width in pixels of the line of text line[] in the font
-   *font_p, and return it as *bwidP.  Also determine how much of this
-   width goes to the left of the nominal starting point of the line because
-   the first character in the line has a "backup" distance.  Return that
-   as *backup_space_needed_p.
+   Determine the left edge and right edge in pixels of the line of text
+   line[] in the font *fontP, and return them as *leftEdgeP and *rightEdgeP.
+   *leftEdgeP will be negative if the leftmost character in the line has a
+   "backup" distance.
+
+   Note that the right (left) edge may not belong to the last (first)
+   character in the text line.  This happens when the font is slanted
+   (xadd is smaller than width) and/or intercharacter space is negative.
+   This is illustrated by the following:
+
+     pbmtext -nomargin "ART." | pnmshear -30 -noantialias
+
+   Also note that there may be no black pixels on what is reported as an edge.
+   This often happens with fixed-width font in which the white areas on the
+   sides are not trimmed.
 -----------------------------------------------------------------------------*/
-    int cursor;  /* cursor into the line of text */
-    double accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we are 
-           stepping through.  Because the intercharacter space might not be
-           an integer, we accumulate it here and realize full pixels whenever
-           we have more than one pixel.  Note that this can be negative
-           (which means were crowding, rather than spreading, text).
+    unsigned int cursor;  /* cursor into the line of text */
+    double currentPosition;
+        /* sum of xadd values and intercharacter space so far in line.  this
+           is never negative.
         */
-    double bwid;
-    bool no_chars_yet; 
-        /* We haven't seen any renderable characters yet in the line. */
-    struct glyph * lastGlyphP;
-        /* Glyph of last character processed so far.  Undefined if
-           'no_chars_yet'.
+    double accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we are stepping
+           through.  Because the intercharacter space might not be an integer,
+           we accumulate it here and realize full pixels whenever we have more
+           than one pixel.  Note that this can be negative (which means were
+           crowding, rather than spreading, text).
         */
+    int leftEdge;
+    double rightEdge;
 
-    no_chars_yet = TRUE;   /* initial value */
-    accumulatedIcs = 0.0;  /* initial value */
-    bwid = 0.0;  /* initial value */
-    
-    for (cursor = 0; line[cursor] != '\0'; cursor++) {
-        struct glyph * const glyphP = 
-            font_p->glyph[(unsigned char)line[cursor]];
-
-        if (glyphP) {
-            if (no_chars_yet) {
-                no_chars_yet = FALSE;
-                if (glyphP->x < 0) 
-                    *backup_space_needed_p = -glyphP->x;
-                else {
-                    *backup_space_needed_p = 0;
-                    bwid += glyphP->x;
-                }
-            } else {
-                /* handle extra intercharacter space (-space option) */
-                accumulatedIcs += intercharacter_space;
-                if (accumulatedIcs >= INT_MAX)
-                    pm_error("Image width too large.");
-                if (accumulatedIcs <= INT_MIN)
-                    pm_error("Absurdly large negative -space value.");
-                {
-                    int const fullPixels = (int) accumulatedIcs;
-                    bwid           += fullPixels;
-                    accumulatedIcs -= fullPixels;
-                }
-            }
-            lastGlyphP = glyphP;
-            bwid += glyphP->xadd;
-        }
+    currentPosition = 0;  /* initial value */
+    accumulatedIcs  = 0.0;  /* initial value */
+
+    leftEdge  = INT_MAX;  /* initial value */
+    rightEdge = INT_MIN;  /* initial value */
+
+    for (cursor = 0; line[cursor] != L'\0'; ++cursor) {
+        PM_WCHAR          const currentChar = line[cursor];
+        unsigned long int const glyphIndex  = (unsigned long int) currentChar;
+        struct glyph *    const glyphP      = fontP->glyph[glyphIndex];
+
+        getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
+                 &leftEdge, &rightEdge);
+
+        advancePosition(currentPosition, currentChar, glyphP,
+                        intercharacterSpace, accumulatedIcs,
+                        &currentPosition, &accumulatedIcs);
     }
-    if (no_chars_yet)
-        /* Line has no renderable characters */
-        *backup_space_needed_p = 0;
-    else {
-        /* Line has at least one renderable character.
-           Recalculate width of last character in line so it ends
-           right at the right edge of the glyph (no extra space to
-           anticipate another character).
-        */
-        bwid -= lastGlyphP->xadd;
-        bwid += lastGlyphP->width + lastGlyphP->x;
+
+    if (line[0] == L'\0') {     /* Empty line */
+        leftEdge  = 0;
+        rightEdge = 0.0;
     }
-    if (bwid > INT_MAX)
-        pm_error("Image width too large.");
-    else
-        *bwidP = bwid; 
+
+    *leftEdgeP  = leftEdge;
+    *rightEdgeP = rightEdge;
 }
 
 
 
 static void
-insert_character(const struct glyph * const glyph, 
-                 int                  const toprow, 
-                 int                  const leftcol,
-                 bit **               const bits) {
+getCharsWithinWidth(PM_WCHAR             const line[],
+                    const struct font2 * const fontP,
+                    float                const intercharacter_space,
+                    unsigned int         const targetWidth,
+                    unsigned int       * const charCountP,
+                    int                * const leftEdgeP) {
+/*----------------------------------------------------------------------------
+   Determine how many characters of text line[] fit into an image of target
+   width targetWidth.
+
+   *leftEdgeP will be negative if the leftmost character in the line has a
+   "backup" distance and zero if it does not.
+-----------------------------------------------------------------------------*/
+    if (line[0] == L'\0') {
+        /* Empty line */
+        *leftEdgeP = 0;
+        *charCountP = 0;
+    } else {
+        unsigned int cursor;  /* cursor into the line of text */
+        double currentPosition;
+        double accumulatedIcs;
+        int leftEdge;
+        double rightEdge;
+        unsigned int currentWidth;
+
+        currentPosition = 0;    /* initial value */
+        accumulatedIcs  = 0.0;  /* initial value */
+
+        leftEdge     = INT_MAX;  /* initial value */
+        rightEdge    = INT_MIN;  /* initial value */
+
+        for (cursor = 0, currentWidth = 0;
+             currentWidth <= targetWidth && line[cursor] != L'\0';
+             ++cursor) {
+            PM_WCHAR const currentChar = line[cursor];
+            unsigned long int const glyphIndex =
+              (unsigned long int) currentChar;
+            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+
+            getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
+                     &leftEdge, &rightEdge);
+
+            advancePosition(currentPosition, currentChar, glyphP,
+                            intercharacter_space, accumulatedIcs,
+                            &currentPosition, &accumulatedIcs);
+
+            currentWidth = rightEdge - ((leftEdge > 0 ) ? 0 : leftEdge);
+        }
+
+        if (currentWidth > targetWidth) {
+            if (cursor == 1)
+                pm_error("-width value too small "
+                         "to accomodate single character");
+            else
+                *charCountP = cursor - 1;
+        } else
+            *charCountP = cursor;
+
+        *leftEdgeP  = leftEdge;
+    }
+}
+
+
+
+static void
+insertCharacter(const struct glyph * const glyphP,
+                int                  const toprow,
+                int                  const leftcol,
+                unsigned int         const cols,
+                unsigned int         const rows,
+                bit **               const bits) {
 /*----------------------------------------------------------------------------
    Insert one character (whose glyph is 'glyph') into the image bits[].
    Its top left corner shall be row 'toprow', column 'leftcol'.
 -----------------------------------------------------------------------------*/
+    if (glyphP->width == 0 && glyphP->height == 0) {
+        /* No bitmap data.  Some BDF files code space this way */
+    } else {
+        unsigned int glyph_y;  /* Y position within the glyph */
+
+        if (leftcol + glyphP->x < 0 ||
+            leftcol + glyphP->x + glyphP->width > cols ||
+            toprow < 0 ||
+            toprow + glyphP->height >rows )
+            pm_error("internal error.  Rendering out of bounds");
 
-    int glyph_y, glyph_x;  /* position within the glyph */
+        for (glyph_y = 0; glyph_y < glyphP->height; ++glyph_y) {
+            unsigned int glyph_x;  /* position within the glyph */
 
-    for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) {
-        for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) {
-            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
-                bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = 
-                    PBM_BLACK;
+            for (glyph_x = 0; glyph_x < glyphP->width; ++glyph_x) {
+                if (glyphP->bmap[glyph_y * glyphP->width + glyph_x]) {
+                    unsigned int const col = leftcol + glyphP->x + glyph_x;
+                    bits[toprow+glyph_y][col/8] |= PBM_BLACK << (7-col%8);
+                }
+            }
         }
     }
-}    
+}
 
 
 
 static void
-insert_characters(bit **        const bits, 
-                  struct text   const lp,
-                  struct font * const fontP, 
-                  int           const topmargin, 
-                  int           const leftmargin,
-                  float         const intercharacter_space,
-                  int           const lspace) {
+insertCharacters(bit **         const bits,
+                 struct Text    const lp,
+                 struct font2 * const fontP,
+                 int            const topmargin,
+                 int            const leftmargin,
+                 float          const intercharacter_space,
+                 unsigned int   const cols,
+                 unsigned int   const rows,
+                 int            const lspace,
+                 bool           const fixedAdvance) {
 /*----------------------------------------------------------------------------
    Render the text 'lp' into the image 'bits' using font *fontP and
    putting 'intercharacter_space' pixels between characters and
    'lspace' pixels between the lines.
 -----------------------------------------------------------------------------*/
-    int line;  /* Line number in input text */
+    unsigned int line;  /* Line number in input text */
 
     for (line = 0; line < lp.lineCount; ++line) {
-        int row;  /* row in image of top of current typeline */
-        int leftcol;  /* Column in image of left edge of current glyph */
-        int cursor;  /* cursor into a line of input text */
-        float accumulated_ics;
+        unsigned int row;  /* row in image of top of current typeline */
+        double leftcol;  /* Column in image of left edge of current glyph */
+        unsigned int cursor;  /* cursor into a line of input text */
+        double accumulatedIcs;
             /* accumulated intercharacter space so far in the line we
                are building.  Because the intercharacter space might
                not be an integer, we accumulate it here and realize
-               full pixels whenever we have more than one pixel. 
+               full pixels whenever we have more than one pixel.
             */
 
         row = topmargin + line * (fontP->maxheight + lspace);
         leftcol = leftmargin;
-        accumulated_ics = 0.0;  /* initial value */
-    
+        accumulatedIcs = 0.0;  /* initial value */
+
         for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
-            unsigned int const glyphIndex = 
-                (unsigned char)lp.textArray[line][cursor];
-            struct glyph* glyph;   /* the glyph for this character */
-
-            glyph = fontP->glyph[glyphIndex];
-            if (glyph != NULL) {
-                const int toprow = row + fontP->maxheight + fontP->y 
-                    - glyph->height - glyph->y;
-                    /* row number in image of top row in glyph */
-                
-                insert_character(glyph, toprow, leftcol, bits);
-
-                leftcol += glyph->xadd;
-                {
-                    /* handle extra intercharacter space (-space option) */
-                    int full_pixels;  /* integer part of accumulated_ics */
-                    accumulated_ics += intercharacter_space;
-                    full_pixels = (int) accumulated_ics;
-                    if (full_pixels > 0) {
-                        leftcol += full_pixels;
-                        accumulated_ics -= full_pixels;
-                    }
-                }
-            }
+            PM_WCHAR const currentChar = lp.textArray[line][cursor];
+            unsigned long int const glyphIndex =
+                (unsigned long int)currentChar;
+            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+            int const toprow =
+                row + fontP->maxheight + fontP->y - glyphP->height - glyphP->y;
+                /* row number in image of top row in glyph */
+
+            assert(glyphP != NULL);
+
+            insertCharacter(glyphP, toprow, leftcol, cols, rows, bits);
+
+        if (fixedAdvance)
+            leftcol += fontP->maxwidth;
+        else
+            advancePosition(leftcol, currentChar, glyphP,
+                            intercharacter_space, accumulatedIcs,
+                            &leftcol, &accumulatedIcs);
         }
     }
 }
 
 
-struct outputTextCursor {
-    struct text text;
-        /* The output text.  The lineCount field of this represents
-           the number of lines we have completed.  The line after that
-           is the one we are currently filling.
-        */
-    unsigned int maxWidth;
-        /* A line of output can't be wider than this many pixels */
-    float intercharacterSpace;
-        /* The amount of extra space, in characters, that should be added
-           between every two characters (Pbmtext -space option)
-        */
-    unsigned int columnNo;
-        /* The column Number (starting at 0) in the current line that we are
-           filling where the next character goes.
-        */
-    bool noCharsYet;
-        /* We haven't put any renderable characters yet in the
-           output line. 
-        */
-    unsigned int widthSoFar;
-        /* The accumulated width, in pixels, of all the characters now
-           in the current output line 
-        */
-    float accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we
-           are stepping through.  Because the intercharacter space
-           might not be an integer, we accumulate it here and
-           realize full pixels whenever we have more than one
-           pixel.  Note that this is negative if we're crowding, rather
-           than spreading, characters.
-        */
-};
 
+static void
+flowText(struct Text    const inputText,
+         int            const targetWidth,
+         struct font2 * const fontP,
+         float          const intercharacterSpace,
+         struct Text  * const outputTextP,
+         unsigned int * const maxleftbP) {
 
+    unsigned int outputLineNum;
+    unsigned int incursor;   /* cursor into the line we are reading */
+    unsigned int const maxLineCount = 50; /* max output lines */
+    int leftEdge;
+    int leftExtreme = 0;
+    unsigned int charCount;
 
-static void
-initializeFlowedOutputLine(struct outputTextCursor * const cursorP) {
+    allocTextArray(outputTextP, maxLineCount, 0);
 
-    cursorP->columnNo = 0;
-    cursorP->noCharsYet = TRUE;
-    cursorP->widthSoFar = 0.0;
-    cursorP->accumulatedIcs = 0.0;
-}
+    for (incursor = 0, outputLineNum = 0;
+         inputText.textArray[0][incursor] != L'\0'; ) {
 
+        unsigned int outcursor;
 
+        getCharsWithinWidth(&inputText.textArray[0][incursor], fontP,
+                            intercharacterSpace, targetWidth,
+                            &charCount, &leftEdge);
 
-static void
-initializeFlowedOutput(struct outputTextCursor * const cursorP,
-                       unsigned int              const maxLines,
-                       unsigned int              const maxWidth,
-                       float                     const intercharacterSpace) {
-    
-    allocTextArray(&cursorP->text, maxLines, maxWidth);
-    cursorP->maxWidth = maxWidth;
-    cursorP->intercharacterSpace = intercharacterSpace;
-    initializeFlowedOutputLine(cursorP);
-}
+        MALLOCARRAY(outputTextP->textArray[outputLineNum], charCount+1);
 
+        if (!outputTextP->textArray[outputLineNum])
+            pm_error("Unable to allocate memory for the text of line %u, "
+                     "%u characters long", outputLineNum, charCount);
 
+        ++outputTextP->allocatedLineCount;
 
-static void
-finishOutputLine(struct outputTextCursor * const cursorP) {
+        for (outcursor = 0; outcursor < charCount; ++outcursor, ++incursor)
+            outputTextP->textArray[outputLineNum][outcursor] =
+                inputText.textArray[0][incursor];
+
+        outputTextP->textArray[outputLineNum][charCount] = L'\0';
+        ++outputLineNum;
+        if (outputLineNum >= maxLineCount)
+            pm_error("-width too small.  too many output lines");
 
-    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
-        char * const currentLine = 
-            cursorP->text.textArray[cursorP->text.lineCount];
-        currentLine[cursorP->columnNo++] = '\0';
-        ++cursorP->text.lineCount;
+        leftExtreme = MIN(leftEdge, leftExtreme);
     }
+    outputTextP->lineCount = outputLineNum;
+    *maxleftbP = (unsigned int) -leftExtreme;
 }
 
 
 
 static void
-placeCharacterInOutput(char                      const lastch,
-                       struct font *             const fontP, 
-                       struct outputTextCursor * const cursorP) {
-/*----------------------------------------------------------------------------
-   Place a character of text in the text array at the position indicated
-   by *cursorP, keeping track of what space this character will occupy
-   when this text array is ultimately rendered using font *fontP.
+truncateText(struct Text    const inputText,
+             unsigned int   const targetWidth,
+             struct font2 * const fontP,
+             float          const intercharacterSpace,
+             unsigned int * const maxleftbP) {
 
-   Note that while we compute how much space the character will take when
-   rendered, we don't render it.
------------------------------------------------------------------------------*/
-    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
-        unsigned int const glyphIndex = (unsigned char)lastch;
-        if (fontP->glyph[glyphIndex]) {
-            if (cursorP->noCharsYet) {
-                cursorP->noCharsYet = FALSE;
-                if (fontP->glyph[glyphIndex]->x > 0) 
-                    cursorP->widthSoFar += fontP->glyph[glyphIndex]->x;
-            } else {
-                /* handle extra intercharacter space (-space option) */
-                cursorP->accumulatedIcs += cursorP->intercharacterSpace;
-                {
-                    int const fullPixels = (int)cursorP->accumulatedIcs;
-                    cursorP->widthSoFar     += fullPixels;
-                    cursorP->accumulatedIcs -= fullPixels;
-                }
-            }
-            cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd;
-        }
-        if (cursorP->widthSoFar < cursorP->maxWidth) {
-            char * const currentLine = 
-                cursorP->text.textArray[cursorP->text.lineCount];
-            currentLine[cursorP->columnNo++] = lastch;
-        } else {
-            /* Line is full; finish it off, start the next one, and
-               place the character there.
-            */
-            /* TODO: We really should back up to the previous white space
-               character and move the rest of the line to the next line
-            */
-            finishOutputLine(cursorP);
-            initializeFlowedOutputLine(cursorP);
-            placeCharacterInOutput(lastch, fontP, cursorP);
+    unsigned int lineNum;  /* Line number on which we are currently working */
+    int leftEdge;
+    int leftExtreme = 0;
+
+    for (lineNum = 0; lineNum < inputText.lineCount; ++lineNum) {
+        PM_WCHAR * const currentLine = inputText.textArray[lineNum];
+
+        unsigned int charCount;
+
+        getCharsWithinWidth(currentLine, fontP,
+                            intercharacterSpace, targetWidth,
+                            &charCount, &leftEdge);
+
+        if (currentLine[charCount] != L'\0') {
+            pm_message("truncating line %u from %u to %u characters",
+                       lineNum, (unsigned) wcslen(currentLine), charCount);
+            currentLine[charCount] = L'\0';
         }
+
+        leftExtreme = MIN(leftEdge, leftExtreme);
     }
+    *maxleftbP = (unsigned int) - leftExtreme;
 }
 
 
 
 static void
-flowText(struct text    const inputText,
-         int            const width, 
-         struct font *  const fontP, 
-         float          const intercharacterSpace,
-         struct text *  const outputTextP) {
-    
-    unsigned int const maxLineCount = 50;
-
-    unsigned int inputLine;  
-        /* Input line number on which we are currently working */
-    struct outputTextCursor outputCursor;
-
-    for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) {
-        unsigned int incursor;   /* cursor into the line we are reading */
-
-        initializeFlowedOutput(&outputCursor, maxLineCount,
-                               width, intercharacterSpace);
-        
-        for (incursor = 0; 
-             inputText.textArray[inputLine][incursor] != '\0'; 
-             ++incursor)
-            placeCharacterInOutput(inputText.textArray[inputLine][incursor],
-                                   fontP, &outputCursor);
-        finishOutputLine(&outputCursor);
+fgetWideString(PM_WCHAR *    const widestring,
+               unsigned int  const size,
+               FILE *        const ifP,
+               bool *        const eofP,
+               const char ** const errorP) {
+
+    wchar_t * rc;
+
+    assert(widestring);
+    assert(size > 0);
+
+    rc = fgetws(widestring, size, ifP);
+
+    if (rc == NULL) {
+        if (feof(ifP)) {
+            *eofP   = true;
+            *errorP = NULL;
+        } else if (ferror(ifP) && errno == EILSEQ)
+            pm_asprintf(errorP,
+                        "fgetws(): conversion error: sequence is "
+                        "invalid for locale '%s'",
+                        setlocale(LC_CTYPE, NULL));
+        else
+            pm_asprintf(errorP,
+                        "fgetws() of max %u bytes failed",
+                        size);
+    } else {
+        *eofP   = false;
+        *errorP = NULL;
     }
-    *outputTextP = outputCursor.text;
 }
 
 
 
 static void
-truncateText(struct text   const inputText, 
-             unsigned int  const width, 
-             struct font * const fontP, 
-             float         const intercharacterSpace,
-             struct text * const outputTextP) {
-
-    struct text truncatedText;
-    int line;  /* Line number on which we are currently working */
-
-    allocTextArray(&truncatedText, inputText.lineCount, width);
-
-    for (line = 0; line < inputText.lineCount; ++line){
-        int cursor;  /* cursor into the line of text */
-        unsigned char lastch;  /* line[cursor] */
-        int widthSoFar;
-            /* How long the line we've built, in pixels, is so far */
-        float accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we are 
-           stepping through.  Because the intercharacter space might not be
-           an integer, we accumulate it here and realize full pixels whenever
-           we have more than one pixel.  Note that this is negative if we're
-           crowding, not spreading, characters.
-        */
+fgetNarrowString(PM_WCHAR *    const widestring,
+                 unsigned int  const size,
+                 FILE *        const ifP,
+                 bool *        const eofP,
+                 const char ** const errorP) {
 
-        int noCharsYet; 
-        /* logical: we haven't seen any renderable characters yet in 
-           the line.
-        */
-        noCharsYet = TRUE;   /* initial value */
-        widthSoFar = 0;  /* initial value */
-        accumulatedIcs = 0.0;  /* initial value */
+    char * bufNarrow;
+    char * rc;
 
-        truncatedText.textArray[line][0] = '\0';  /* Start with empty line */
-
-        for (cursor = 0; 
-             inputText.textArray[line][cursor] != '\0' && widthSoFar < width; 
-             cursor++) {
-            lastch = inputText.textArray[line][cursor];
-            if (fontP->glyph[(unsigned char)lastch]) {
-                if (noCharsYet) {
-                    noCharsYet = FALSE;
-                    if (fontP->glyph[lastch]->x > 0) 
-                        widthSoFar += fontP->glyph[lastch]->x;
-                } else {
-                    /* handle extra intercharacter space (-space option) */
-                    accumulatedIcs += intercharacterSpace;
-                    {
-                        int const fullPixels = (int) intercharacterSpace;
-                        widthSoFar     += fullPixels;
-                        accumulatedIcs -= fullPixels;
-                    }
-                }
-                widthSoFar += fontP->glyph[lastch]->xadd;
-            }
-            if (widthSoFar < width) {
-                truncatedText.textArray[line][cursor] = 
-                    inputText.textArray[line][cursor];
-                truncatedText.textArray[line][cursor+1] = '\0';
-            }
-        }
+    assert(widestring);
+    assert(size > 0);
+
+    MALLOCARRAY_NOFAIL(bufNarrow, MAXLINECHARS+1);
+
+    rc = fgets(bufNarrow, size, ifP);
+
+    if (rc == NULL) {
+        if (feof(ifP)) {
+            *eofP   = true;
+            *errorP = NULL;
+        } else
+            pm_asprintf(errorP, "Error reading file");
+    } else {
+        size_t cnt;
+
+        for (cnt = 0; cnt < size && bufNarrow[cnt] != '\0'; ++cnt)
+            widestring[cnt] = (PM_WCHAR)(unsigned char) bufNarrow[cnt];
+
+        widestring[cnt] = L'\0';
+
+        *eofP   = false;
+        *errorP = NULL;
     }
-    truncatedText.lineCount = inputText.lineCount;
-    *outputTextP = truncatedText;
+    free(bufNarrow);
 }
 
 
 
 static void
-getText(const char          cmdline_text[], 
-        struct font * const fontP,
-        struct text * const input_textP) {
-
-    struct text input_text;
-
-    if (cmdline_text) {
-        MALLOCARRAY_NOFAIL(input_text.textArray, 1);
-        input_text.allocatedLineCount = 1;
-        input_text.lineCount = 1;
-        fixControlChars(cmdline_text, fontP,
-                        (const char**)&input_text.textArray[0]);
+fgetNarrowWideString(PM_WCHAR *    const widestring,
+                     unsigned int  const size,
+                     FILE *        const ifP,
+                     bool *        const eofP,
+                     const char ** const errorP) {
+/*----------------------------------------------------------------------------
+  Return the next line from file *ifP, as *widestring.
+
+  Lines are delimited by newline characters and EOF.
+
+  'size' is the size in characters of the buffer at *widestring.  If the line
+  to which the file is positioned is longer than that minus 1, we consider it
+  to be only that long and consider the next character of the actual line to
+  be the first character of the next line.  We leave the file positioned
+  to that character.
+
+  Return *eofP == true iff we encounter end of file (and therefore don't read
+  a line).
+
+  If we can't read the file (or sense EOF), return as *errorP a text
+  explanation of why; otherwise, return *errorP = NULL.
+
+  The line we return is null-terminated.  But it also includes any embedded
+  null characters that are within the line in the file.  It is not strictly
+  possible for Caller to tell whether a null character in *widestring comes
+  from the file or is the one we put there, so Caller should just ignore any
+  null character and anything after it.  It is also not possible for Caller to
+  tell if we trunctaed the actual line because of 'size' if there is a null
+  character in the line.  This means there just isn't any way to get
+  reasonable behavior from this function if the input file contains null
+  characters (but at least the damage is limited to presenting arbitrary text
+  as the contents of the file - the program won't crash).
+
+  Null characters never appear within normal text (including wide-character
+  text).  If there is one in the input file, it is probably because the input
+  is corrupted.
+
+  The line we return may or may not end in a newline character.  It ends in a
+  newline character unless it doesn't fit in 'size' characters or it is the
+  last line in the file and doesn't end in newline.
+-----------------------------------------------------------------------------*/
+    /* The limitations described above with respect to null characters in
+       *ifP are derived from the same limitations in POSIX 'fgets' and
+       'fgetws'.  To avoid them, we would have to read *ifP one character
+       at a time with 'fgetc' and 'fgetwc'.
+    */
+
+    int const wideCode = fwide(ifP, 0);
+        /* Width orientation for *ifP: positive means wide, negative means
+           byte, zero means undecided.
+        */
+
+    assert(widestring);
+    assert(size > 0);
+
+    if (wideCode > 0)
+        /* *ifP is wide-oriented */
+        fgetWideString(widestring, size, ifP, eofP, errorP);
+    else
+        fgetNarrowString(widestring, size, ifP, eofP, errorP);
+}
+
+
+
+
+static void
+getText(PM_WCHAR       const cmdlineText[],
+        struct font2 * const fontP,
+        struct Text  * const inputTextP,
+        enum FixMode   const fixMode) {
+/*----------------------------------------------------------------------------
+   Get as *inputTextP the text to format, given that the text on the
+   command line (one word per command line argument, separated by spaces),
+   is 'cmdlineText'.
+
+   If 'cmdlineText' is null, that means to get the text from Standard Input.
+   Otherwise, 'cmdlineText' is that text.
+
+   But we return text as only renderable characters - characters in *fontP -
+   with control characters interpreted or otherwise fixed, according to
+   'fixMode'.
+
+   If *inputTextP indicates Standard Input and Standard Input contains null
+   characters, we will truncate lines or consider a single line to be multiple
+   lines.
+-----------------------------------------------------------------------------*/
+    struct Text inputText;
+
+    if (cmdlineText) {
+        MALLOCARRAY_NOFAIL(inputText.textArray, 1);
+        inputText.allocatedLineCount = 1;
+        inputText.lineCount = 1;
+        fixControlChars(cmdlineText, fontP,
+                        (const PM_WCHAR**)&inputText.textArray[0], fixMode);
+        free((void *) cmdlineText);
     } else {
         /* Read text from stdin. */
 
-        unsigned int maxlines;  
-            /* Maximum number of lines for which we presently have space
-               in the text array 
+        unsigned int maxlines;
+            /* Maximum number of lines for which we presently have space in
+               the text array
             */
-        char buf[5000];
-        char ** text_array;
+        PM_WCHAR *   buf;
+        PM_WCHAR **  textArray;
         unsigned int lineCount;
+        bool         eof;
+
+        MALLOCARRAY(buf, MAXLINECHARS+1);
+
+        if (!buf)
+            pm_error("Unable to allocate memory for up to %u characters of "
+                     "text", MAXLINECHARS);
 
         maxlines = 50;  /* initial value */
-        MALLOCARRAY_NOFAIL(text_array, maxlines);
-        
-        lineCount = 0;  /* initial value */
-        while (fgets(buf, sizeof(buf), stdin) != NULL) {
-            if (strlen(buf) + 1 >= sizeof(buf))
-                pm_error("A line of input text is longer than %u characters."
-                         "Cannot process.", (unsigned)sizeof(buf)-1);
-            if (lineCount >= maxlines) {
-                maxlines *= 2;
-                REALLOCARRAY(text_array, maxlines);
-                if (text_array == NULL)
-                    pm_error("out of memory");
+        MALLOCARRAY(textArray, maxlines);
+
+        if (!textArray)
+            pm_error("Unable to allocate memory for a buffer for up to %u "
+                     "lines of text", maxlines);
+
+        for (lineCount = 0, eof = false; !eof; ) {
+            const char * error;
+            fgetNarrowWideString(buf, MAXLINECHARS, stdin, &eof, &error);
+            if (error)
+                pm_error("Unable to read line %u from file.  %s",
+                         lineCount, error);
+            else {
+                if (!eof) {
+                    if (wcslen(buf) + 1 >= MAXLINECHARS)
+                        pm_error(
+                            "Line %u (starting at zero) of input text "
+                            "is longer than %u characters."
+                            "Cannot process",
+                            lineCount, (unsigned int) MAXLINECHARS-1);
+                    if (lineCount >= maxlines) {
+                        maxlines *= 2;
+                        REALLOCARRAY(textArray, maxlines);
+                        if (textArray == NULL)
+                            pm_error("out of memory");
+                    }
+                    fixControlChars(buf, fontP,
+                                    (const PM_WCHAR **)&textArray[lineCount],
+                                    fixMode);
+                    if (textArray[lineCount] == NULL)
+                        pm_error("out of memory");
+                    ++lineCount;
+                }
             }
-            fixControlChars(buf, fontP, (const char **)&text_array[lineCount]);
-            if (text_array[lineCount] == NULL)
-                pm_error("out of memory");
-            ++lineCount;
         }
-        input_text.textArray = text_array;
-        input_text.lineCount = lineCount;
-        input_text.allocatedLineCount = lineCount;
+        inputText.textArray = textArray;
+        inputText.lineCount = lineCount;
+        inputText.allocatedLineCount = lineCount;
+        free(buf);
+    }
+    *inputTextP = inputText;
+}
+
+
+
+static void
+computeMargins(struct CmdlineInfo const cmdline,
+               struct Text        const inputText,
+               struct font2 *     const fontP,
+               unsigned int *     const vmarginP,
+               unsigned int *     const hmarginP) {
+
+    if (cmdline.nomargins) {
+        *vmarginP = 0;
+        *hmarginP = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            *vmarginP = fontP->maxheight / 2;
+            *hmarginP = fontP->maxwidth;
+        } else {
+            *vmarginP = fontP->maxheight;
+            *hmarginP = 2 * fontP->maxwidth;
+        }
     }
-    *input_textP = input_text;
 }
 
 
 
 static void
-computeImageHeight(struct text         const formattedText, 
-                   const struct font * const fontP,
-                   int                 const interlineSpace,
-                   unsigned int        const vmargin,
-                   unsigned int *      const rowsP) {
+formatText(struct CmdlineInfo const cmdline,
+           struct Text        const inputText,
+           struct font2 *     const fontP,
+           unsigned int       const hmargin,
+           struct Text *      const formattedTextP,
+           unsigned int *     const maxleftb0P) {
+/*----------------------------------------------------------------------------
+  Flow or truncate lines to meet user's width request.
+-----------------------------------------------------------------------------*/
+    if (cmdline.width > 0) {
+        unsigned int const fontMargin = fontP->x < 0 ? -fontP->x : 0;
+
+        if (cmdline.width > INT_MAX -10)
+            pm_error("-width value too large: %u", cmdline.width);
+        else if (cmdline.width < 2 * hmargin)
+            pm_error("-width value too small: %u", cmdline.width);
+        else if (inputText.lineCount == 1) {
+            flowText(inputText, cmdline.width - fontMargin,
+                     fontP, cmdline.space, formattedTextP, maxleftb0P);
+            freeTextArray(inputText);
+        } else {
+            truncateText(inputText, cmdline.width - fontMargin,
+                         fontP, cmdline.space, maxleftb0P);
+            *formattedTextP = inputText;
+        }
+    } else
+        *formattedTextP = inputText;
+}
+
+
+
+static void
+computeImageHeight(struct Text          const formattedText,
+                   const struct font2 * const fontP,
+                   int                  const interlineSpace,
+                   unsigned int         const vmargin,
+                   unsigned int       * const rowsP) {
 
     if (interlineSpace < 0 && fontP->maxheight < -interlineSpace)
         pm_error("-lspace value (%d) negative and exceeds font height.",
-                 interlineSpace);     
+                 interlineSpace);
     else {
-        double const rowsD = 2 * (double) vmargin + 
-            (double) formattedText.lineCount * fontP->maxheight + 
+        double const rowsD = 2 * (double) vmargin +
+            (double) formattedText.lineCount * fontP->maxheight +
             (double) (formattedText.lineCount-1) * interlineSpace;
-        
+
         if (rowsD > INT_MAX-10)
             pm_error("Image height too large.");
         else
@@ -756,128 +1102,286 @@ computeImageHeight(struct text         const formattedText,
 
 
 static void
-computeImageWidth(struct text         const formattedText, 
-                  const struct font * const fontP,
-                  float               const intercharacterSpace,
-                  unsigned int        const hmargin,
-                  unsigned int *      const colsP,
-                  int *               const maxleftbP) {
+computeImageWidth(struct Text          const formattedText,
+                  const struct font2 * const fontP,
+                  float                const intercharacterSpace,
+                  unsigned int         const hmargin,
+                  unsigned int *       const colsP,
+                  unsigned int *       const maxleftbP) {
 
     if (intercharacterSpace < 0 && fontP->maxwidth < -intercharacterSpace)
-        pm_error("-space value (%f) negative; exceeds font width.",
-                 intercharacterSpace);     
+        pm_error("negative -space value %.2f exceeds font width",
+                 intercharacterSpace);
     else {
         /* Find the widest line, and the one that backs up the most past
            the nominal start of the line.
         */
-    
-        unsigned int line;
-        double maxwidth;
-        int maxleftb;
+
+        unsigned int lineNum;
+        double rightExtreme;
+        int leftExtreme;
         double colsD;
 
-        for (line = 0, maxwidth = 0.0, maxleftb = 0;
-             line < formattedText.lineCount;
-             ++line) {
-
-            double bwid;
-            int backupSpaceNeeded;
-            
-            get_line_dimensions(formattedText.textArray[line], fontP,
-                                intercharacterSpace,
-                                &bwid, &backupSpaceNeeded);
-            
-            maxwidth = MAX(maxwidth, bwid);
-            maxleftb = MAX(maxleftb, backupSpaceNeeded);
+        rightExtreme = 0.0;  /* initial value */
+        leftExtreme = 0;     /* initial value */
+
+        for (lineNum = 0; lineNum < formattedText.lineCount;  ++lineNum) {
+            double rightEdge;
+            int leftEdge;
+
+            getLineDimensions(formattedText.textArray[lineNum], fontP,
+                              intercharacterSpace,
+                              &rightEdge, &leftEdge);
+            rightExtreme = MAX(rightExtreme, rightEdge);
+            leftExtreme  = MIN(leftExtreme,  leftEdge);
         }
-        colsD = 2 * (double) hmargin + (double) maxwidth;
-    
+        leftExtreme = MIN(leftExtreme, 0);
+
+        colsD = (double) (-leftExtreme) + rightExtreme + 2 * hmargin;
+
         if (colsD > INT_MAX-10)
             pm_error("Image width too large.");
         else
             *colsP = (unsigned int) colsD;
-    
-        *maxleftbP = maxleftb;
+
+        *maxleftbP = (unsigned int) - leftExtreme;
     }
 }
 
 
 
-int
-main(int argc, const char *argv[]) {
+static void
+renderText(unsigned int   const cols,
+           unsigned int   const rows,
+           struct font2 * const fontP,
+           unsigned int   const hmargin,
+           unsigned int   const vmargin,
+           struct Text    const formattedText,
+           unsigned int   const maxleftb,
+           float          const space,
+           int            const lspace,
+           bool           const fixedAdvance,
+           FILE *         const ofP) {
+
+    bit ** const bits = pbm_allocarray(pbm_packed_bytes(cols), rows);
 
-    struct cmdlineInfo cmdline;
-    bit ** bits;
-    unsigned int rows, cols;
-    struct font * fontP;
-    unsigned int vmargin, hmargin;
-    struct text inputText;
-    struct text formattedText;
-    int maxleftb;
+    /* Fill background with white */
+    clearBackground(bits, cols, rows);
 
-    pm_proginit(&argc, argv);
+    /* Put the text in  */
+    insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb,
+                     space, cols, rows, lspace, fixedAdvance);
 
-    parseCommandLine(argc, argv, &cmdline);
-    
-    computeFont(cmdline, &fontP);
+    /* Free all font data */
+    pbm_destroybdffont2(fontP); 
 
-    getText(cmdline.text, fontP, &inputText);
-       
-    if (cmdline.nomargins) {
-        vmargin = 0;
-        hmargin = 0;
+    {
+        unsigned int row;
+
+        pbm_writepbminit(ofP, cols, rows, 0);
+
+        for (row = 0; row < rows; ++row)
+            pbm_writepbmrow_packed(ofP, bits[row], cols, 0);
+    }
+
+    pbm_freearray(bits, rows);
+}
+
+
+
+static PM_WCHAR const * sheetTextArray[] = {
+L"M \",/^_[`jpqy| M",
+L"                ",
+L"/  !\"#$%&'()*+ /",
+L"< ,-./01234567 <",
+L"> 89:;<=>?@ABC >",
+L"@ DEFGHIJKLMNO @",
+L"_ PQRSTUVWXYZ[ _",
+L"{ \\]^_`abcdefg {",
+L"} hijklmnopqrs }",
+L"~ tuvwxyz{|}~  ~",
+L"                ",
+L"M \",/^_[`jpqy| M" };
+
+
+
+static void
+validateText(const PM_WCHAR ** const textArray,
+             struct font2    * const fontP) {
+/*----------------------------------------------------------------------------
+   Abort the program if there are characters in 'textArray' which cannot be
+   rendered in font *fontP.
+-----------------------------------------------------------------------------*/
+    const PM_WCHAR * output;
+    unsigned int textRow;
+
+    for (textRow = 0; textRow < 12; ++textRow)
+        fixControlChars(textArray[textRow], fontP, &output, QUIT);
+
+    free((PM_WCHAR *)output);
+}
+
+
+
+static void
+renderSheet(struct font2 * const fontP,
+            FILE *         const ofP) {
+
+    int const cols  = fontP->maxwidth  * 16;
+    int const rows  = fontP->maxheight * 12;
+    struct Text const sheetText =
+        { (PM_WCHAR ** const) sheetTextArray, 12, 12};
+
+    validateText(sheetTextArray, fontP);
+
+    renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0),
+               0.0, 0, TRUE, ofP);
+}
+
+
+
+static void
+dryrunOutput(unsigned int const cols,
+             unsigned int const rows,
+             FILE *       const ofP) {
+
+    fprintf(ofP, "%u %u\n", cols, rows);
+}
+
+
+
+static void
+textDumpOutput(struct Text   const lp,
+               FILE *        const ofP) {
+/*----------------------------------------------------------------------------
+   Output the text 'lp' as characters.  (Do not render.)
+
+   Note that the output stream is wide-oriented; it cannot be mixed with
+   narrow-oriented output.  The libnetpbm library functions are
+   narrow-oriented.  Thus, when this output is specified, it must not be mixed
+   with any output from the library; it should be the sole output.
+-----------------------------------------------------------------------------*/
+    int rc;
+
+    rc = fwide(ofP, 1);
+    if (rc != 1) {
+        /* This occurs when narrow-oriented output to ofP happens before we
+           get here.
+        */
+        pm_error("Failed to set output stream to wide "
+                 "(fwide() returned %d.  Maybe the output file "
+                 "was written in narrow mode before this program was invoked?",
+                 rc);
     } else {
-        if (inputText.lineCount == 1) {
-            vmargin = fontP->maxheight / 2;
-            hmargin = fontP->maxwidth;
-        } else {
-            vmargin = fontP->maxheight;
-            hmargin = 2 * fontP->maxwidth;
+        unsigned int line;  /* Line number in input text */
+
+        for (line = 0; line < lp.lineCount; ++line) {
+            fputws(lp.textArray[line], ofP);
+            fputwc(L'\n', ofP);
         }
     }
-    
-    if (cmdline.width > 0) {
-        if (cmdline.width > INT_MAX -10)
-            pm_error("-width value too large: %u", cmdline.width);
-            
-        /* Flow or truncate lines to meet user's width request */
-        if (inputText.lineCount == 1) 
-            flowText(inputText, cmdline.width, fontP, cmdline.space,
-                     &formattedText);
-        else
-            truncateText(inputText, cmdline.width, fontP, cmdline.space,
-                         &formattedText);
-        freeTextArray(inputText);
-    } else
-        formattedText = inputText;
-        
+}
+
+
+
+static void
+pbmtext(struct CmdlineInfo const cmdline,
+        struct font2 *     const fontP,
+        FILE *             const ofP) {
+
+    unsigned int rows, cols;
+        /* Dimensions in pixels of the output image */
+    unsigned int cols0;
+    unsigned int vmargin, hmargin;
+        /* Margins in pixels we add to the output image */
+    unsigned int hmargin0;
+    struct Text inputText;
+    struct Text formattedText;
+    unsigned int maxleftb, maxleftb0;
+
+    getText(cmdline.text, fontP, &inputText,
+            cmdline.verbose ? WARN : SILENT);
+
+    computeMargins(cmdline, inputText, fontP, &vmargin, &hmargin0);
+
+    formatText(cmdline, inputText, fontP, hmargin0,
+               &formattedText, &maxleftb0);
+
     if (formattedText.lineCount == 0)
-        pm_error("No input text.");
-    
-    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin,
-                       &rows);
+        pm_error("No input text");
+
+    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, &rows);
 
-    computeImageWidth(formattedText, fontP, cmdline.space, hmargin,
-                      &cols, &maxleftb);
+    computeImageWidth(formattedText, fontP, cmdline.space,
+                      cmdline.width > 0 ? 0 : hmargin0, &cols0, &maxleftb);
 
-    if (cols == 0 || rows == 0)
+    if (cols0 == 0 || rows == 0)
         pm_error("Input is all whitespace and/or non-renderable characters.");
 
-    bits = pbm_allocarray(cols, rows);
+    if (cmdline.width == 0) {
+        cols    = cols0;
+        hmargin = hmargin0;
+    } else {
+        if (cmdline.width < cols0)
+            pm_error("internal error: calculated image width (%u) exceeds "
+                     "specified -width value: %u",
+                     cols0, cmdline.width);
+        else if (maxleftb0 != maxleftb)
+            pm_error("internal error: contradicting backup values");
+        else {
+            hmargin = MIN(hmargin0, (cmdline.width - cols0) / 2);
+            cols = cmdline.width;
+        }
+    }
 
-    /* Fill background with white */
-    fill_rect(bits, 0, 0, rows, cols, PBM_WHITE);
+    if (cmdline.dryrun)
+        dryrunOutput(cols, rows, ofP);
+    else if (cmdline.textdump)
+        textDumpOutput(formattedText, ofP);
+    else
+        renderText(cols, rows, fontP, hmargin, vmargin, formattedText,
+                   maxleftb, cmdline.space, cmdline.lspace, FALSE, ofP);
 
-    /* Put the text in  */
-    insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
-                      cmdline.space, cmdline.lspace);
+    freeTextArray(formattedText);
+}
 
-    pbm_writepbm(stdout, bits, cols, rows, 0);
 
-    pbm_freearray(bits, rows);
 
-    freeTextArray(formattedText);
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+    struct font2 * fontP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.wchar) {
+        char * newLocale;
+        newLocale = setlocale(LC_ALL, "");
+        if (!newLocale)
+            pm_error("Failed to set locale (LC_ALL) from environment");
+
+        /* Orient standard input stream to wide */
+        fwide(stdin,  1);
+    } else
+        fwide(stdin, -1);
+
+    if (cmdline.verbose)
+        pm_message("LC_CTYPE is set to '%s'", setlocale(LC_CTYPE, NULL) );
+
+    computeFont(cmdline, &fontP);
+
+    if (cmdline.dumpsheet)
+        renderSheet(fontP, stdout);
+    else
+        pbmtext(cmdline, fontP, stdout);
+
     pm_close(stdout);
 
     return 0;
 }
+
+
+
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index e6367530..f543618d 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -1,4 +1,4 @@
-/*
+ /*
  * pbmtextps.c -  render text into a bitmap using a postscript interpreter
  *
  * Copyright (C) 2002 by James McCann.
@@ -14,68 +14,33 @@
  *
  * Additions by Bryan Henderson contributed to public domain by author.
  *
+ * PostScript(R) Language Reference, Third Edition  (a.k.a. "Red Book")
+ * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
+ * ISBN 0-201-37922-8
+ *
+ * Postscript Font Naming Issues:
+ * https://partners.adobe.com/public/developer/en/font/5088.FontNames.pdf
+ *
+ * Other resources:
+ * http://partners.adobe.com/public/developer/ps/index_specs.html
  */
-#define _XOPEN_SOURCE   /* Make sure popen() is in stdio.h */
-#define _BSD_SOURCE     /* Make sure stdrup() is in string.h */
+
+#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>
 #include <string.h>
 #include <errno.h>
+#include <assert.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
 #include "shhopt.h"
+#include "pm_system.h"
 #include "pbm.h"
 
-
-#define BUFFER_SIZE 2048
-
-struct cmdlineInfo {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    unsigned int res;         /* resolution, DPI */
-    unsigned int fontsize;    /* Size of font in points */
-    const char * font;      /* Name of postscript font */
-    float        stroke;
-        /* Width of stroke in points (only for outline font) */
-    unsigned int verbose;
-    const char * text;
-};
-
-
-
-static void
-writeFileToStdout(const char * const fileName){
-    /* simple pbmtopbm */
-
-    FILE * ifP;
-    int format;
-    int cols, rows, row ;
-    unsigned char * bitrow; 
-    
-    ifP = pm_openr(fileName);
-    pbm_readpbminit(ifP, &cols, &rows, &format);
-
-    if (cols==0 || rows==0 || cols>INT_MAX-10 || rows>INT_MAX-10)
-      pm_error("Abnormal output from gs program.  "
-               "width x height = %u x %u", cols, rows);
-               
-    pbm_writepbminit(stdout, cols, rows, 0);           
-               
-    bitrow = pbm_allocrow_packed(cols);
-    
-    for (row = 0; row < rows; ++row) {
-        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
-        pbm_writepbmrow_packed(stdout, bitrow, cols, 0);
-    }
-    pbm_freerow_packed(bitrow);
-}
-
-
-
 static void
 validateFontName(const char * const name) {
 /*-----------------------------------------------------------------------------
@@ -88,11 +53,11 @@ validateFontName(const char * const name) {
     unsigned int idx;
 
     for (idx = 0; name[idx] != '\0'; ++idx) {
-        char const c = name[idx]; 
+        char const c = name[idx];
 
         if (c < 32 || c > 125)
             pm_error("Invalid character in font name");
-        else 
+        else
             switch (c) {
               case '[':   case ']':   case '(':   case ')':
               case '{':   case '}':   case '/':   case '\\':
@@ -119,7 +84,7 @@ asciiHexEncode(char *          const inbuff,
     unsigned int idx;
 
     for (idx = 0; inbuff[idx] != '\0'; ++idx) {
-        unsigned int const item = (unsigned char) inbuff[idx]; 
+        unsigned int const item = (unsigned char) inbuff[idx];
 
         outbuff[idx*2]   = hexits[item >> 4];
         outbuff[idx*2+1] = hexits[item & 0xF];
@@ -132,7 +97,7 @@ asciiHexEncode(char *          const inbuff,
 
 static void
 buildTextFromArgs(int           const argc,
-                  char **       const argv,
+                  const char ** const argv,
                   const char ** const asciiHexTextP) {
 /*----------------------------------------------------------------------------
    Build the array of text to be included in the Postscript program to
@@ -161,7 +126,7 @@ buildTextFromArgs(int           const argc,
             if (text == NULL)
                 pm_error("out of memory");
             strcat(text, " ");
-        } 
+        }
         totalTextSize += strlen(argv[i]);
         text = realloc(text, totalTextSize);
         if (text == NULL)
@@ -169,7 +134,7 @@ buildTextFromArgs(int           const argc,
         strcat(text, argv[i]);
     }
 
-    { 
+    {
         char * asciiHexText;
 
         MALLOCARRAY(asciiHexText, totalTextSize * 2);
@@ -186,9 +151,31 @@ buildTextFromArgs(int           const argc,
 
 
 
+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;
+};
+
+
+
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*---------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
@@ -199,37 +186,104 @@ parseCommandLine(int argc, char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    unsigned int cropSpec, ascentSpec, descentSpec;
+    unsigned int leftmarginSpec, rightmarginSpec;
+    unsigned int topmarginSpec, bottommarginSpec;
 
     MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "resolution", OPT_UINT,   &cmdlineP->res,            NULL,  0);
-    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,           NULL,  0);
-    OPTENT3(0, "fontsize",   OPT_UINT,   &cmdlineP->fontsize,       NULL,  0);
-    OPTENT3(0, "stroke",     OPT_FLOAT,  &cmdlineP->stroke,         NULL,  0);
-    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,         0);
+    OPTENT3(0, "resolution",    OPT_UINT,
+            &cmdlineP->res,          NULL,                      0);
+    OPTENT3(0, "font",          OPT_STRING,
+            &cmdlineP->font,         NULL,                      0);
+    OPTENT3(0, "fontsize",      OPT_FLOAT,
+            &cmdlineP->fontsize,     NULL,                      0);
+    OPTENT3(0, "stroke",        OPT_FLOAT,
+            &cmdlineP->stroke,       NULL,                      0);
+    OPTENT3(0, "ascent",        OPT_FLOAT,
+            &cmdlineP->ascent,       &ascentSpec,               0);
+    OPTENT3(0, "descent",       OPT_FLOAT,
+            &cmdlineP->descent,      &descentSpec,              0);
+    OPTENT3(0, "leftmargin",    OPT_FLOAT,
+            &cmdlineP->leftmargin,   &leftmarginSpec,           0);
+    OPTENT3(0, "rightmargin",   OPT_FLOAT,
+            &cmdlineP->rightmargin,  &rightmarginSpec,          0);
+    OPTENT3(0, "topmargin",     OPT_FLOAT,
+            &cmdlineP->topmargin,    &topmarginSpec,            0);
+    OPTENT3(0, "bottommargin",  OPT_FLOAT,
+            &cmdlineP->bottommargin, &bottommarginSpec,         0);
+    OPTENT3(0, "crop",          OPT_FLAG,
+            NULL,                    &cropSpec,                 0);
+    OPTENT3(0, "pad",           OPT_FLAG,
+            NULL,                    &cmdlineP->pad,            0);
+    OPTENT3(0, "verbose",       OPT_FLAG,
+            NULL,                    &cmdlineP->verbose,        0);
+    OPTENT3(0, "dump-ps",       OPT_FLAG,
+            NULL,                    &cmdlineP->dump,           0);
 
     /* Set the defaults */
     cmdlineP->res = 150;
     cmdlineP->fontsize = 24;
     cmdlineP->font = "Times-Roman";
-    cmdlineP->stroke = -1;
+    cmdlineP->stroke  = -1;
+    cmdlineP->ascent  = 0;
+    cmdlineP->descent = 0;
+    cmdlineP->rightmargin = 0;
+    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 */
     opt.allowNegNum = FALSE;
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
 
     validateFontName(cmdlineP->font);
 
+    if (cmdlineP->fontsize <= 0)
+        pm_error("-fontsize must be positive");
+    if (cmdlineP->ascent < 0)
+        pm_error("-ascent must not be negative");
+    if (cmdlineP->descent < 0)
+        pm_error("-descent must not be negative");
+    if (cmdlineP->leftmargin <0)
+        pm_error("-leftmargin must not be negative");
+    if (cmdlineP->rightmargin <0)
+        pm_error("-rightmargin must not be negative");
+    if (cmdlineP->topmargin <0)
+        pm_error("-topmargin must not be negative");
+    if (cmdlineP->bottommargin <0)
+        pm_error("-bottommargin must not be negative");
+
+    if (cropSpec == TRUE) {
+        if (ascentSpec || descentSpec ||
+            leftmarginSpec || rightmarginSpec ||
+            topmarginSpec || bottommarginSpec ||
+            cmdlineP->pad)
+              pm_error("-crop cannot be specified with -ascent, -descent, "
+                       "-leftmargin, -rightmargin, "
+                       "-topmargin, -bottommargin or -pad");
+    } else {
+        if (!descentSpec && !bottommarginSpec && !cmdlineP->pad)
+            cmdlineP->descent = cmdlineP->fontsize * 1.5;
+
+        if (!leftmarginSpec)
+             cmdlineP->leftmargin = cmdlineP->fontsize / 2;
+    }
+
     buildTextFromArgs(argc, argv, &cmdlineP->text);
+
+    free(option_def);
 }
 
 
 
 static void
-termCmdline(struct cmdlineInfo const cmdline) {
+termCmdline(struct CmdlineInfo const cmdline) {
 
     pm_strfree(cmdline.text);
 }
@@ -237,305 +291,431 @@ termCmdline(struct cmdlineInfo const cmdline) {
 
 
 static const char *
-construct_postscript(struct cmdlineInfo const cmdline) {
+postscriptProgram(struct CmdlineInfo const cmdline) {
+/*-----------------------------------------------------------------------------
+  In Postscript, the bottom of the page is row zero.  Postscript allows
+  negative values but negative regions are clipped from the output image.
+  We make adjustments to ensure that nothing is lost.
+
+  Postscript also allow fonts to have negative values in the bounding box
+  coordinates.  The bottom edge of "L" is row zero: this row is called the
+  "baseline".  The feet of "g" "p" "y" extend into negative region.  In a
+  similar manner the left edge of the bounding box may be negative.  We add
+  margins on the left and the bottom with "xorigin" and "yorigin" to
+  provide for such characters.
+
+  The sequence "textstring false charpath flattenpath pathbbox" determines
+  the bounding box of the entire text when rendered.
+-----------------------------------------------------------------------------*/
+
+    /* C89 limits the size of a string constant, so we have to build the
+       Postscript command in pieces.
+
+       psVariable, psTemplate: Set variables.
+       psFixed1: Scale font.  Calculate pad metrics.
+       psFixed2: Determine width, height, xorigin, yorigin.
+       psFixed3: Render.
+       psFixed4: Verbose mode: Report font name, metrics.
+
+       We could add code to psFixed2 for handling right-to-left writing
+       (Hebrew, Arabic) and vertical writing (Chinese, Korean, Japanese).
+    */
+
+    const char * const psTemplate =
+        "/FindFont {/%s findfont} def\n"
+        "/fontsize %f def\n"
+        "/pensize %f def\n"
+        "/textstring <%s> def\n"
+        "/ascent %f def\n"
+        "/descent %f def\n"
+        "/leftmargin %f def\n"
+        "/rightmargin %f def\n"
+        "/topmargin %f def\n"
+        "/bottommargin %f def\n"
+        "/pad %s def\n"
+        "/verbose %s def\n";
+
+    const char * const psFixed1 =
+        "FindFont fontsize scalefont\n"
+        "pad { dup dup\n"
+        "  /FontMatrix get 3 get /yscale exch def\n"
+        "  /FontBBox get dup\n"
+        "  1 get yscale mul neg /padbottom exch def\n"
+        "  3 get yscale mul /padtop exch def}\n"
+        "  {/padbottom 0 def /padtop 0 def}\n"
+        "  ifelse\n"
+        "setfont\n";
+
+    const char * const psFixed2 =
+        "0 0 moveto\n"
+        "textstring false charpath flattenpath pathbbox\n"
+        "/BBtop    exch def\n"
+        "/BBright  exch def\n"
+        "/BBbottom exch neg def\n"
+        "/BBleft   exch neg def\n"
+        "/max { 2 copy lt { exch } if pop } bind def\n"
+        "/yorigin descent padbottom max BBbottom max bottommargin add def\n"
+        "/xorigin leftmargin BBleft max def\n"
+        "/width xorigin BBright add rightmargin add def\n"
+        "/height ascent BBtop max padtop max topmargin add yorigin add def\n";
+
+    const char * const psFixed3 =
+        "<</PageSize [width height]>> setpagedevice\n"
+        "xorigin yorigin moveto\n"
+        "pensize 0 lt\n"
+        "  {textstring show}\n"
+        "  {pensize setlinewidth 0 setgray\n"
+        "  textstring true charpath stroke}\n"
+        "  ifelse\n"
+        "showpage\n";
+
+    const char * const psFixed4 =
+        "verbose\n"
+        "  {xorigin yorigin moveto\n"
+        "   [(width height) width height] ==\n"
+        "   [(ascent descent) height yorigin sub yorigin] ==\n"
+        "   [(bounding box) \n"
+        "     textstring false charpath flattenpath pathbbox] ==\n"
+        "   [(Fontname) FindFont dup /FontName\n"
+        "     known\n"
+        "       {/FontName get}\n"
+        "       {pop (anonymous)}\n"
+        "       ifelse]  ==}\n"
+        "  if";
 
     const char * retval;
-    const char * template;
-
-    if (cmdline.stroke < 0) 
-        template =
-            "/%s findfont\n"
-            "%d scalefont\n"
-            "setfont\n"
-            "12 36 moveto\n"
-            "<%s> show\n"
-            "showpage\n";
-    else 
-        template =
-            "/%s findfont\n"
-            "%d scalefont\n"
-            "setfont\n"
-            "12 36 moveto\n"
-            "%f setlinewidth\n"
-            "0 setgray\n"
-            "<%s> true charpath\n"
-            "stroke\n"
-            "showpage\n";
-
-    if (cmdline.stroke < 0)
-        pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, 
-                    cmdline.text);
-    else
-        pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, 
-                    cmdline.stroke, cmdline.text);
+    const char * psVariable;
+
+    pm_asprintf(&psVariable, psTemplate, cmdline.font,
+                cmdline.fontsize, cmdline.stroke, cmdline.text,
+                cmdline.ascent, cmdline.descent,
+                cmdline.leftmargin, cmdline.rightmargin,
+                cmdline.topmargin,  cmdline.bottommargin,
+                cmdline.pad ? "true" : "false",
+                cmdline.verbose ? "true" : "false" );
+
+    pm_asprintf(&retval, "%s%s%s%s%s", psVariable,
+                psFixed1, psFixed2, psFixed3, psFixed4);
+
+    pm_strfree(psVariable);
 
     return retval;
 }
 
 
 
-static const char *
-gsExecutableName() {
+static const char **
+gsArgList(const char *       const outputFilename,
+          struct CmdlineInfo const cmdline) {
+
+    unsigned int const maxArgCt = 50;
+
+    const char ** retval;
+    unsigned int argCt;  /* Number of arguments in 'retval' so far */
+
+    if (cmdline.res <= 0)
+         pm_error("Resolution (dpi) must be positive.");
 
-    const char * const which = "which gs";
+    if (cmdline.fontsize <= 0)
+         pm_error("Font size must be positive.");
 
-    static char buffer[BUFFER_SIZE];
+    MALLOCARRAY_NOFAIL(retval, maxArgCt+2);
 
-    FILE * f;
+    argCt = 0;  /* initial value */
 
-    memset(buffer, 0, BUFFER_SIZE);
+    pm_asprintf(&retval[argCt++], "ghostscript");
+    pm_asprintf(&retval[argCt++], "-r%d", cmdline.res);
+    pm_asprintf(&retval[argCt++], "-sDEVICE=pbmraw");
+    pm_asprintf(&retval[argCt++], "-sOutputFile=%s", outputFilename);
+    pm_asprintf(&retval[argCt++], "-q");
+    pm_asprintf(&retval[argCt++], "-dBATCH");
+    pm_asprintf(&retval[argCt++], "-dSAFER");
+    pm_asprintf(&retval[argCt++], "-dNOPAUSE");
+    pm_asprintf(&retval[argCt++], "-");
 
-    f = popen(which, "r");
-    if (!f)
-        pm_error("Can't find ghostscript");
+    retval[argCt++] = NULL;
 
-    fread(buffer, 1, BUFFER_SIZE, f);
-    if (buffer[strlen(buffer) - 1] == '\n')
-        buffer[strlen(buffer) - 1] = '\0';
-    pclose(f);
-    
-    if (buffer[0] != '/' && buffer[0] != '.')
-        pm_error("Can't find ghostscript");
+    assert(argCt < maxArgCt);
 
-    return buffer;
+    return retval;
 }
 
 
 
-static const char *
-cropExecutableName(void) {
+static void
+reportGhostScript(const char *  const executableNm,
+                  const char ** const argList) {
 
-    const char * const which = "which pnmcrop";
+    unsigned int i;
 
-    static char buffer[BUFFER_SIZE];
-    const char * retval;
+    pm_message("Running Ghostscript interpreter '%s'", executableNm);
 
-    FILE * f;
-
-    memset(buffer, 0, BUFFER_SIZE);
-
-    f = popen(which, "r");
-    if (!f)
-        retval = NULL;
-    else {
-        fread(buffer, 1, BUFFER_SIZE, f);
-        if (buffer[strlen(buffer) - 1] == '\n')
-            buffer[strlen(buffer) - 1] = 0;
-        pclose(f);
-            
-        if (buffer[0] != '/' && buffer[0] != '.') {
-            retval = NULL;
-            pm_message("Can't find pnmcrop");
-        } else
-            retval = buffer;
-    }
-    return retval;
+    pm_message("Program arguments:");
+
+    for (i = 0; argList[i]; ++i)
+        pm_message("  '%s'", argList[i]);
 }
 
 
 
-static const char *
-gsCommand(const char *       const psFname,
-          const char *       const outputFilename, 
-          struct cmdlineInfo const cmdline) {
+static void
+freeArgList(const char ** const argList) {
 
-    const char * retval;
-    double const x = (double) cmdline.res * 11;
-    double const y = (double) cmdline.res * 
-                     ((double) cmdline.fontsize * 2 + 72)  / 72;
-    
-    if (cmdline.res <= 0)
-         pm_error("Resolution (dpi) must be positive.");
-    
-    if (cmdline.fontsize <= 0)
-         pm_error("Font size must be positive.");
-    
-    /* The following checks are for guarding against overflows in this 
-       function.  Huge x,y values that pass these checks may be
-       rejected by the 'gs' program.
-    */
-    
-    if (x > (double) INT_MAX-10)
-         pm_error("Absurdly fine resolution: %u. Output width too large.",
-                   cmdline.res );
-    if (y > (double) INT_MAX-10)
-         pm_error("Absurdly fine resolution (%u) and/or huge font size (%u). "
-                  "Output height too large.", cmdline.res, cmdline.fontsize);
-         
-    pm_asprintf(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm "
-                "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s "
-                "</dev/null >/dev/null", 
-                gsExecutableName(), (int) x, (int) y, cmdline.res, 
-                outputFilename, psFname);
+    unsigned int i;
 
-    return retval;
+    for (i = 0; argList[i]; ++i)
+        pm_strfree(argList[i]);
+
+    free(argList);
 }
 
 
 
-static const char *
-cropCommand(const char * const inputFileName) {
+static void
+reportFontName(const char * const fontname) {
 
-    const char * retval;
-    const char * plainOpt = pm_plain_output ? "-plain" : "" ;
-    
-    if (cropExecutableName()) {
-        pm_asprintf(&retval, "%s -top -right %s %s", 
-                    cropExecutableName(), plainOpt, inputFileName);
-        if (retval == pm_strsol)
-            pm_error("Unable to allocate memory");
-    } else
-        retval = NULL;
+    pm_message("Font: '%s'", fontname);
 
-    return retval;
 }
 
 
 
 static void
-writeProgram(const char *       const psFname,
-             struct cmdlineInfo const cmdline) {
+reportMetrics(float const  width,
+              float const  height,
+              float const  ascent,
+              float const  descent,
+              float const  BBoxleft,
+              float const  BBoxbottom,
+              float const  BBoxright,
+              float const  BBoxtop) {
+
+    pm_message("-- Metrics in points.  Bottom left is (0,0) --");
+    pm_message("Width:   %f", width);
+    pm_message("Height:  %f", height);
+    pm_message("Ascent:  %f", ascent);
+    pm_message("Descent: %f", descent);
+    pm_message("BoundingBox_Left:   %f", BBoxleft);
+    pm_message("BoundingBox_Right:  %f", BBoxright);
+    pm_message("BoundingBox_Top:    %f", BBoxtop);
+    pm_message("BoundingBox_Bottom: %f", BBoxbottom);
 
-    const char * ps;
-    FILE * psfile;
+}
 
-    psfile = fopen(psFname, "w");
-    if (psfile == NULL)
-        pm_error("Can't open temp file '%s'.  Errno=%d (%s)",
-                 psFname, errno, strerror(errno));
 
-    ps = construct_postscript(cmdline);
 
-    if (cmdline.verbose)
-        pm_message("Postscript program = '%s'", ps);
-        
-    if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps))
-        pm_error("Can't write postscript to temp file");
+static void
+acceptGSoutput(int             const pipetosuckFd,
+               void *          const nullParams ) {
+/*-----------------------------------------------------------------------------
+  Accept text written to stdout by the PostScript program.
+
+  There are two kinds of output:
+    (1) Metrics and fontname reported, when verbose is on.
+    (2) Error messages from ghostscript.
+
+  We read one line at a time.
+
+  We cannot predict how long one line can be in case (2).  In practice
+  the "execute stack" report gets long.  We provide by setting lineBuffSize
+  to a large number.
+-----------------------------------------------------------------------------*/
+    unsigned int const lineBuffSize = 1024*32;
+    FILE *       const inFileP = fdopen(pipetosuckFd, "r");
+
+    float width, height, ascent, descent;
+    float BBoxleft, BBoxbottom, BBoxright, BBoxtop;
+    char * lineBuff;  /* malloc'd */
+    char fontname [2048];
+    bool fontnameReported, widthHeightReported;
+    bool ascentDescentReported, BBoxReported;
+
+    assert(nullParams == NULL);
+
+    fontnameReported      = FALSE; /* Initial value */
+    widthHeightReported   = FALSE; /* Initial value */
+    ascentDescentReported = FALSE; /* Initial value */
+    BBoxReported          = FALSE; /* Initial value */
 
-    fclose(psfile);
+    MALLOCARRAY_NOFAIL(lineBuff, lineBuffSize);
 
-    pm_strfree(ps);
+    while (fgets(lineBuff, lineBuffSize, inFileP) != NULL) {
+        unsigned int rWidthHeight, rAscentDescent, rBBox, rFontname;
+
+        rWidthHeight = sscanf(lineBuff, "[(width height) %f %f]",
+                              &width, &height);
+
+        rAscentDescent = sscanf(lineBuff, "[(ascent descent) %f %f]",
+                                &ascent, &descent);
+
+        rBBox =  sscanf(lineBuff, "[(bounding box) %f %f %f %f]",
+                        &BBoxleft, &BBoxbottom, &BBoxright, &BBoxtop);
+
+        rFontname = sscanf(lineBuff, "[(Fontname) /%2047s", fontname);
+
+        if (rFontname == 1)
+            fontnameReported = TRUE;
+        else if (rWidthHeight == 2)
+            widthHeightReported = TRUE;
+        else if (rAscentDescent == 2)
+            ascentDescentReported = TRUE;
+        else if (rBBox == 4)
+            BBoxReported = TRUE;
+        else
+            pm_message("[gs] %s", lineBuff);
+    }
+
+    if (fontnameReported) {
+        fontname[strlen(fontname)-1] = 0;
+        reportFontName(fontname);
+
+        if (widthHeightReported && ascentDescentReported && BBoxReported)
+            reportMetrics(width, height, ascent, descent,
+                          BBoxleft, BBoxbottom, BBoxright, BBoxtop);
+    }
+    fclose(inFileP);
+    pm_strfree(lineBuff);
 }
 
 
 
 static void
-executeProgram(const char *       const psFname, 
+executeProgram(const char *       const psProgram,
                const char *       const outputFname,
-               struct cmdlineInfo const cmdline) {
+               struct CmdlineInfo const cmdline) {
 
-    const char * com;
-    int rc;
+    const char *  const executableNm = "gs";
+    const char ** const argList = gsArgList(outputFname, cmdline);
+
+    struct bufferDesc feedBuffer;
+    int               termStatus;
+    unsigned int      bytesFed;
+
+    bytesFed = 0;  /* Initial value */
+
+    feedBuffer.buffer            = (unsigned char *) psProgram;
+    feedBuffer.size              = strlen(psProgram);
+    feedBuffer.bytesTransferredP = &bytesFed;
 
-    com = gsCommand(psFname, outputFname, cmdline);
-    if (com == NULL)
-        pm_error("Can't allocate memory for a 'ghostscript' command");
-    
     if (cmdline.verbose)
-        pm_message("Running Postscript interpreter '%s'", com);
+        reportGhostScript(executableNm, argList);
+
+    pm_system2_vp(executableNm,
+                  argList,
+                  &pm_feed_from_memory, &feedBuffer,
+                  cmdline.verbose ? &acceptGSoutput : &pm_accept_null, NULL,
+                  &termStatus);
+
+    if (termStatus != 0) {
+        const char * const msg = pm_termStatusDesc(termStatus);
 
-    rc = system(com);
-    if (rc != 0)
-        pm_error("Failed to run Ghostscript process.  rc=%d", rc);
+        pm_error("Failed to run Ghostscript process.  %s", msg);
 
-    pm_strfree(com);
+        pm_strfree(msg);
+    }
+    freeArgList(argList);
 }
 
 
 
 static void
-cropToStdout(const char * const inputFileName,
-             bool         const verbose) {
+writePbm(const char * const fileName,
+         FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+  Write the PBM image that is in the file named 'fileName" to file *ofP.
+  I.e. pbmtopbm.
+
+  It's not a byte-for-byte copy because PBM allows the same image to be
+  represented many ways (all of which we can accept as our input), but we use
+  libnetpbm to write our output in its specific way.
+----------------------------------------------------------------------------*/
+    FILE * ifP;
+    int format;
+    int cols, rows, row ;
+    unsigned char * bitrow;
 
-    const char * com;
+    ifP = pm_openr(fileName);
+    pbm_readpbminit(ifP, &cols, &rows, &format);
 
-    com = cropCommand(inputFileName);
+    if (cols == 0 || rows == 0 || cols > INT_MAX - 10 || rows > INT_MAX - 10)
+        pm_error("Abnormal output from gs program.  "
+                 "width x height = %u x %u", cols, rows);
 
-    if (com == NULL) {
-        /* No pnmcrop.  So don't crop. */
-        pm_message("Can't find pnmcrop command, image will be large");
-        writeFileToStdout(inputFileName);
-    } else {
-        FILE * pnmcrop;
-
-        if (verbose)
-            pm_message("Running crop command '%s'", com);
-        
-        pnmcrop = popen(com, "r");
-        if (pnmcrop == NULL)
-            pm_error("Can't run pnmcrop process");
-        else {
-            char buf[2048];
-            bool eof;
-
-            eof = FALSE;
-            
-            while (!eof) {
-                int bytesRead;
-
-                bytesRead = fread(buf, 1, sizeof(buf), pnmcrop);
-                if (bytesRead > 0) {
-                    int rc;
-                    rc = fwrite(buf, 1, bytesRead, stdout);
-                    if (rc != bytesRead)
-                        pm_error("Can't write to stdout");
-                } else if (bytesRead == 0)
-                    eof = TRUE;
-                else
-                    pm_error("Failed to read output of Pnmcrop process.  "
-                             "Errno=%d (%s)", errno, strerror(errno));
-            }
-            fclose(pnmcrop);
-        }
-        pm_strfree(com);
+    pbm_writepbminit(ofP, cols, rows, 0);
+
+    bitrow = pbm_allocrow_packed(cols);
+
+    for (row = 0; row < rows; ++row) {
+        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+        pbm_writepbmrow_packed(ofP, bitrow, cols, 0);
     }
+    pbm_freerow_packed(bitrow);
+    pm_close(ifP);
 }
 
 
 
 static void
-createOutputFile(struct cmdlineInfo const cmdline) {
+generatePbm(struct CmdlineInfo const cmdline,
+            FILE *             const ofP) {
+
+    const char * const psProgram = postscriptProgram(cmdline);
+
+    const char * tempPbmFname;
+    FILE * pbmFileP;
+
+    pm_make_tmpfile(&pbmFileP, &tempPbmFname);
+    assert(pbmFileP != NULL && tempPbmFname != NULL);
+    fclose(pbmFileP);
+
+    executeProgram(psProgram, tempPbmFname, cmdline);
+
+    /* Although Ghostscript created a legal PBM file, it uses a different
+       implementation of the format from libnetpbm's canonical output format,
+       so instead of copying the content of 'tempPbmFname' to *ofP byte for
+       byte, we copy it as a PBM image.
+    */
+    writePbm(tempPbmFname, ofP);
+
+    unlink(tempPbmFname);
+    pm_strfree(tempPbmFname);
+    pm_strfree(psProgram);
+}
 
-    const char * const template = "./pstextpbm.%d.tmp.%s";
-    
-    const char * psFname;
-    const char * uncroppedPbmFname;
 
-    pm_asprintf(&psFname, template, getpid(), "ps");
-    if (psFname == NULL)
-        pm_error("Unable to allocate memory");
- 
-    writeProgram(psFname, cmdline);
 
-    pm_asprintf(&uncroppedPbmFname, template, getpid(), "pbm");
-    if (uncroppedPbmFname == NULL)
-        pm_error("Unable to allocate memory");
- 
-    executeProgram(psFname, uncroppedPbmFname, cmdline);
+static void
+dumpPsProgram(struct CmdlineInfo const cmdline) {
+
+    const char * psProgram;
 
-    unlink(psFname);
-    pm_strfree(psFname);
+    psProgram = postscriptProgram(cmdline);
 
-    cropToStdout(uncroppedPbmFname, cmdline.verbose);
+    puts(psProgram);
 
-    unlink(uncroppedPbmFname);
-    pm_strfree(uncroppedPbmFname);
+    pm_strfree(psProgram);
 }
 
 
 
-int 
-main(int argc, char *argv[]) {
+int
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
-    pbm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    createOutputFile(cmdline);
+    if (cmdline.dump)
+        dumpPsProgram(cmdline);
+    else
+        generatePbm(cmdline, stdout);
 
     termCmdline(cmdline);
 
     return 0;
 }
+
+
+
diff --git a/generator/pgmcrater b/generator/pgmcrater
index 1c22ed70..c66f5576 100755
--- a/generator/pgmcrater
+++ b/generator/pgmcrater
@@ -37,6 +37,26 @@ use strict;
 
 use Getopt::Long;
 
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pamcrater', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+##############################################################################
+#
+#  MAINLINE
+#
+##############################################################################
+
+doVersionHack(\@ARGV);
+
 my @pgmcraterArgv = @ARGV;
 
 my $validOptions = GetOptions(
diff --git a/generator/pgmkernel.c b/generator/pgmkernel.c
index ec634c16..37072c38 100644
--- a/generator/pgmkernel.c
+++ b/generator/pgmkernel.c
@@ -223,11 +223,13 @@ main(int argc, const char * argv[]) {
 
         unsigned int col;
         for (col = 0; col < (cmdline.cols +1) / 2; ++col) {
+            double const epsilon = 1e-15;
             double const dx2 = SQR(col - xcenter);
 
             double const normalized = t(dx2, dy2, cmdline.weight) / 2 / tMax;
 
-            gray const grayval = ROUNDU(cmdline.maxval * (0.5 + normalized));
+            gray const grayval =
+                ROUNDU(cmdline.maxval * (0.5 + normalized + epsilon));
 
             halfKernel[arow][col                   ] = grayval;
             halfKernel[arow][cmdline.cols - col - 1] = grayval;
diff --git a/generator/pgmmake.c b/generator/pgmmake.c
index f8f8b09c..ae706639 100644
--- a/generator/pgmmake.c
+++ b/generator/pgmmake.c
@@ -1,13 +1,18 @@
+#include <stdlib.h>
+#include <string.h>
+
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "pgm.h"
 
-struct cmdlineInfo {
+
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    gray grayLevel;
+    double grayLevel;
     unsigned int cols;
     unsigned int rows;
     gray maxval;
@@ -15,9 +20,37 @@ struct cmdlineInfo {
 
 
 
+static double
+grayLevelFromArg(const char * const arg) {
+
+    double retval;
+
+    if (strlen(arg) < 1)
+        pm_error("Gray level argument is a null string");
+    else {
+        char * endPtr;
+
+        retval = strtod(arg, &endPtr);
+
+        if (*endPtr != '\0')
+            pm_error("Gray level argument '%s' is not a floating point number",
+                     arg);
+
+        if (retval < 0.0)
+            pm_error("You can't have a negative gray level (%f)", retval);
+        if (retval > 1.0)
+            pm_error("Gray level must be in the range [0.0, 1.0].  "
+                     "You specified %f", retval);
+
+    }
+    return retval;
+}
+
+
+
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
   Convert program invocation arguments (argc,argv) into a format the 
   program can use easily, struct cmdlineInfo.  Validate arguments along
@@ -34,7 +67,7 @@ parseCommandLine(int argc, char ** argv,
     unsigned int maxvalSpec;
     unsigned int option_def_index;
 
-    MALLOCARRAY(option_def, 100);
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
@@ -43,11 +76,9 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = false;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    free (option_def);
-
     if (!maxvalSpec)
         cmdlineP->maxval = PGM_MAXMAXVAL;
     else {
@@ -65,37 +96,36 @@ parseCommandLine(int argc, char ** argv,
         pm_error("Only 3 arguments allowed: gray level, width, height.  "
                  "You specified %d", argc-1);
     else {
-        double const grayLevel = atof(argv[1]);
-        if (grayLevel < 0.0)
-            pm_error("You can't have a negative gray level (%f)", grayLevel);
-        if (grayLevel > 1.0)
-            pm_error("Gray level must be in the range [0.0, 1.0].  "
-                     "You specified %f", grayLevel);
-        cmdlineP->grayLevel = ROUNDU(grayLevel * cmdlineP->maxval);
+        cmdlineP->grayLevel = grayLevelFromArg(argv[1]);
         cmdlineP->cols = pm_parse_width(argv[2]);
         cmdlineP->rows = pm_parse_height(argv[3]);
     }
+    free(option_def);
 }
 
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char ** const argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     gray * grayrow;
     unsigned int col, row;
+    gray grayLevel;
 
-    pgm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
+    grayLevel = pgm_unnormalize(cmdline.grayLevel, cmdline.maxval);
+
     pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+
     grayrow = pgm_allocrow(cmdline.cols);
 
     /* All rows are identical.  Fill once. */
     for (col = 0; col < cmdline.cols; ++col)
-        grayrow[col] = cmdline.grayLevel;
+        grayrow[col] = grayLevel;
 
     for (row = 0; row < cmdline.rows; ++row)
         pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0);
@@ -105,3 +135,5 @@ main(int argc, char *argv[]) {
 
     return 0;
 }
+
+
diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c
index 442edc59..40d0e189 100644
--- a/generator/pgmnoise.c
+++ b/generator/pgmnoise.c
@@ -56,7 +56,7 @@ parseCommandLine(int argc, const char ** const argv,
 
     if (maxvalSpec) {
         if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
-            pm_error("Maxval too large: %u.  Maximu is %u", 
+            pm_error("Maxval too large: %u.  Maximu is %u",
                      cmdlineP->maxval, PGM_OVERALLMAXVAL);
         else if (cmdlineP->maxval == 0)
             pm_error("Maxval must not be zero");
@@ -70,7 +70,7 @@ parseCommandLine(int argc, const char ** const argv,
     else {
         int const width  = atoi(argv[1]);
         int const height = atoi(argv[2]);
-        
+
         if (width <= 0)
             pm_error("Width must be positive, not %d", width);
         else
@@ -90,12 +90,12 @@ randPool(unsigned int const digits) {
 /*----------------------------------------------------------------------------
   Draw 'digits' bits from pool of random bits.  If the number of random bits
   in pool is insufficient, call rand() and add 31 bits to it.
-  
+
   'digits' must be at most 16.
 
   We assume that each call to rand() generates 31 bits, or RAND_MAX ==
   2147483647.
-  
+
   The underlying logic is flexible and endian-free.  The above conditions
   can be relaxed.
 -----------------------------------------------------------------------------*/
@@ -114,7 +114,7 @@ randPool(unsigned int const digits) {
         hold >>= digits;
         len   -= digits;
     } else {              /* Load another 31 bits into hold */
-        hold    = rand(); 
+        hold    = rand();
         retval |= (hold << len);
         hold >>=  (digits - len);
         len = 31 - digits + len;
@@ -164,11 +164,11 @@ pgmnoise(FILE *       const ofP,
             unsigned int col;
             for (col = 0; col < cols; ++col)
                 destrow[col] = randPool(bitLen);
-        } 
-        else { 
+        }
+        else {
             unsigned int col;
             for (col = 0; col < cols; ++col)
-                destrow[col] = rand() % (maxval + 1); 
+                destrow[col] = rand() % (maxval + 1);
         }
         pgm_writepgmrow(ofP, destrow, cols, maxval, 0);
     }
@@ -181,7 +181,7 @@ pgmnoise(FILE *       const ofP,
 int
 main(int          argc,
      const char * argv[]) {
-    
+
     struct cmdlineInfo cmdline;
 
     pm_proginit(&argc, argv);
@@ -194,3 +194,6 @@ main(int          argc,
 
     return 0;
 }
+
+
+
diff --git a/generator/pgmramp.c b/generator/pgmramp.c
index 225542fe..db32b9f0 100644
--- a/generator/pgmramp.c
+++ b/generator/pgmramp.c
@@ -35,7 +35,7 @@ static void
 parseCommandLine(int argc, char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
-  Convert program invocation arguments (argc,argv) into a format the 
+  Convert program invocation arguments (argc,argv) into a format the
   program can use easily, struct cmdlineInfo.  Validate arguments along
   the way and exit program with message if invalid.
 
@@ -111,12 +111,23 @@ parseCommandLine(int argc, char ** argv,
 
 
 
+static int
+diffu(unsigned int const subtrahend,
+      unsigned int const subtractor) {
+
+    return (int)subtrahend - (int)subtractor;
+
+    /* (Not the conventional terminology, but better) */
+}
+
+
+
 int
 main(int argc, char *argv[]) {
 
     struct cmdlineInfo cmdline;
     gray *grayrow;
-    int rowso2, colso2;
+    unsigned int rowso2, colso2;
     unsigned int row;
 
     pgm_init( &argc, argv );
@@ -149,20 +160,17 @@ main(int argc, char *argv[]) {
                         MAX((float) cmdline.cols + cmdline.rows-2, 1);
                 break;
             case RT_RECT: {
-                float const r = fabs((int)(rowso2 - row)) / rowso2;
-                float const c = fabs((int)(colso2 - col)) / colso2;
+                float const r = fabs((float)diffu(rowso2, row)) / rowso2;
+                float const c = fabs((float)diffu(colso2, col)) / colso2;
                 grayrow[col] =
                     cmdline.maxval - (r + c) / 2.0 * cmdline.maxval;
             } break;
 
             case RT_ELLIP: {
-                float const r = fabs((int)(rowso2 - row)) / rowso2;
-                float const c = fabs((int)(colso2 - col)) / colso2;
-                float v;
+                float const r = fabs((float)diffu(rowso2, row)) / rowso2;
+                float const c = fabs((float)diffu(colso2, col)) / colso2;
+                float const v = MAX(0.0f, MIN(1.0f, SQR(r) + SQR(c)));
 
-                v = r * r + c * c;
-                if ( v < 0.0 ) v = 0.0;
-                else if ( v > 1.0 ) v = 1.0;
                 grayrow[col] = cmdline.maxval - v * cmdline.maxval;
             } break;
             }
@@ -174,3 +182,6 @@ main(int argc, char *argv[]) {
     pm_close(stdout);
     return 0;
 }
+
+
+
diff --git a/generator/ppmcie.c b/generator/ppmcie.c
index 717ed13b..86325ba6 100644
--- a/generator/ppmcie.c
+++ b/generator/ppmcie.c
@@ -10,7 +10,7 @@
     granted,  without any conditions or restrictions.  This software is
     provided "as is" without express or implied warranty.
 
-    This program was called cietoppm in Walker's original work.  
+    This program was called cietoppm in Walker's original work.
     Because "cie" is not a graphics format, Bryan changed the name
     when he integrated it into the Netpbm package in March 2000.
 */
@@ -294,10 +294,10 @@ static struct colorSystem const
 */
 static struct colorSystem Customsystem = {
     "Custom",
-    0.64,  0.33,  0.30,  0.60,  0.15,  0.06,  
+    0.64,  0.33,  0.30,  0.60,  0.15,  0.06,
     IlluminantD65, GAMMA_REC709
 };
-    
+
 
 
 static void
@@ -344,11 +344,11 @@ xyz_to_rgb(const struct colorSystem * const cs,
     which   sums  to  the  desired  chromaticity.   If  the  requested
     chromaticity falls outside the  Maxwell  triangle  (color  gamut)
     formed  by the three primaries, one of the r, g, or b weights will
-    be negative.  
+    be negative.
 
     Caller can use constrain_rgb() to desaturate an outside-gamut
     color to the closest representation within the available
-    gamut. 
+    gamut.
 -----------------------------------------------------------------------------*/
     double xr, yr, zr, xg, yg, zg, xb, yb, zb;
     double xw, yw, zw;
@@ -542,7 +542,7 @@ drawTongueOutline(pixel ** const pixels,
 
         computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp,
                                        &icx, &icy);
-        
+
         if (wavelength > 380)
             ppmd_line(pixels, pixcols, pixrows, maxval,
                       B(lx, ly), B(icx, icy),
@@ -589,7 +589,7 @@ findTongue(pixel ** const pixels,
         int const leftEdge = i;
 
         *presentP = true;
-        
+
         for (j = pxcols - 1;
              j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0;
              --j);
@@ -651,12 +651,12 @@ fillInTongue(pixel **                   const pixels,
                 xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb);
 
                 mx = maxval;
-        
+
                 /* Check whether the requested color  is  within  the
                    gamut  achievable with the given color system.  If
                    not, draw it in a reduced  intensity,  interpolated
                    by desaturation to the closest within-gamut color. */
-        
+
                 if (constrain_rgb(&jr, &jg, &jb))
                     mx = highlightGamut ? maxval : ((maxval + 1) * 3) / 4;
 
@@ -688,7 +688,7 @@ drawYAxis(pixel **     const pixels,
           unsigned int const xBias,
           unsigned int const yBias,
           pixel        const axisColor) {
-              
+
     unsigned int const pxrows = pixrows - yBias;
 
     ppmd_line(pixels, pixcols, pixrows, maxval,
@@ -706,7 +706,7 @@ drawXAxis(pixel **     const pixels,
           unsigned int const xBias,
           unsigned int const yBias,
           pixel        const axisColor) {
-              
+
     unsigned int const pxcols = pixcols - xBias;
     unsigned int const pxrows = pixrows - yBias;
 
@@ -778,7 +778,7 @@ tickY(pixel **     const pixels,
         /* Pixel row where the top of the tick goes */
     unsigned int const tickThickness = Sz(3);
         /* Thickness of the tick in pixels */
-    
+
     char s[20];
 
     assert(tenth < 10);
@@ -970,7 +970,7 @@ plotBlackBodyCurve(pixel **                   const pixels,
 
                 /* Label selected tick marks with decreasing density. */
 
-                if (t <= 5000.1 || (t > 5000.0 && 
+                if (t <= 5000.1 || (t > 5000.0 &&
                                     ((((int) t) % 5000) == 0) &&
                                     t != 20000.0)) {
                     char bb[20];
@@ -980,7 +980,7 @@ plotBlackBodyCurve(pixel **                   const pixels,
                               B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb,
                               PPMD_NULLDRAWPROC, (char *) &rgbcolor);
                 }
-  
+
             }
         }
         lx = xb;
@@ -998,7 +998,7 @@ overlappingLegend(bool const upvp,
 
     if (upvp)
         retval = (waveLength == 430 || waveLength == 640);
-    else 
+    else
         retval = (waveLength == 460 || waveLength == 630 || waveLength == 640);
     return retval;
 }
@@ -1054,7 +1054,7 @@ plotMonochromeWavelengths(
             /* Draw the tick mark */
             PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
             tx = icx + ((x < 520) ? Sz(-2) : ((x >= 535) ? Sz(2) : 0));
-            ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); 
+            ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2)));
             ppmd_line(pixels, pixcols, pixrows, maxval,
                       B(icx, icy), B(tx, ty),
                       PPMD_NULLDRAWPROC, (char *) &rgbcolor);
@@ -1113,7 +1113,7 @@ writeLabel(pixel **                   const pixels,
     char sysdesc[256];
 
     PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
-    
+
     pm_snprintf(sysdesc, sizeof(sysdesc),
                 "System: %s\n"
                 "Primary illuminants (X, Y)\n"
@@ -1198,9 +1198,9 @@ main(int          argc,
         } else if (pm_keymatch(argv[argn], "-smpte", 2)) {
             cs = &SMPTEsystem;
         } else if (pm_keymatch(argv[argn], "-hdtv", 2)) {
-            cs = &HDTVsystem;                 
+            cs = &HDTVsystem;
         } else if (pm_keymatch(argv[argn], "-cie", 1)) {
-            cs = &CIEsystem;                 
+            cs = &CIEsystem;
         } else if (pm_keymatch(argv[argn], "-black", 3)) {
             showBlack = true;         /* Show black body curve */
         } else if (pm_keymatch(argv[argn], "-wpoint", 2)) {
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
index 8ea86429..390180e3 100644
--- a/generator/ppmforge.c
+++ b/generator/ppmforge.c
@@ -31,7 +31,7 @@
 
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <math.h>
 #include <assert.h>
diff --git a/generator/ppmpat.c b/generator/ppmpat.c
index fe1a1d27..908c200f 100644
--- a/generator/ppmpat.c
+++ b/generator/ppmpat.c
@@ -10,15 +10,21 @@
 ** implied warranty.
 */
 
-#define _XOPEN_SOURCE  /* get M_PI in math.h */
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+                           /* get M_PI in math.h */
+#define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
+#define SPIROGRAPHS 0   /* Spirograph to be added soon */
 
 #include <assert.h>
 #include <math.h>
 #include <limits.h>
+#include <string.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
+#include "nstring.h"
 #include "ppm.h"
 #include "ppmdraw.h"
 
@@ -28,28 +34,138 @@ typedef enum {
     PAT_GINGHAM3,
     PAT_MADRAS,
     PAT_TARTAN,
+    PAT_ARGYLE1,
+    PAT_ARGYLE2,
     PAT_POLES,
     PAT_SQUIG,
     PAT_CAMO,
-    PAT_ANTICAMO
-} pattern;
+    PAT_ANTICAMO,
+    PAT_SPIRO1,
+    PAT_SPIRO2,
+    PAT_SPIRO3
+} Pattern;
 
-struct cmdlineInfo {
+typedef struct {
+/*----------------------------------------------------------------------------
+   An ordered list of colors with a cursor.
+-----------------------------------------------------------------------------*/
+    unsigned int count;
+    unsigned int index;
+        /* Current position in the list */
+    pixel *      color;
+        /* Malloced array 'count' in size. */
+} ColorTable;
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    pattern basePattern;
+    Pattern      basePattern;
     unsigned int width;
     unsigned int height;
+    unsigned int colorSpec;
+    ColorTable   colorTable;
     unsigned int randomseed;
     unsigned int randomseedSpec;
 };
 
 
+static void
+validateColorCount(Pattern      const basePattern,
+                   unsigned int const colorCount) {
+
+    if (colorCount == 0)
+        pm_error("-color: no colors specified");
+
+    switch (basePattern) {
+    case PAT_GINGHAM2:
+    case PAT_ARGYLE1:
+    case PAT_SPIRO1:
+        if (colorCount != 2)
+            pm_error("Wrong number of colors: %u. "
+                     "2 colors are required for the specified pattern.",
+                     colorCount);
+        break;
+    case PAT_GINGHAM3:
+    case PAT_MADRAS:
+    case PAT_TARTAN:
+    case PAT_ARGYLE2:
+        if (colorCount != 3)
+            pm_error("Wrong number of colors: %u. "
+                     "3 colors are required for the specified pattern.",
+                     colorCount);
+        break;
+    case PAT_POLES:
+        if (colorCount < 2)
+            pm_error("Too few colors: %u. "
+                     "At least 2 colors are required "
+                     "for the specified pattern.",
+                     colorCount);
+        break;
+    case PAT_SQUIG:
+    case PAT_CAMO:
+    case PAT_ANTICAMO:
+        if (colorCount < 3)
+            pm_error("Wrong number of colors: %u. "
+                     "At least 3 colors are required "
+                     "for the specified pattern.",
+                     colorCount);
+        break;
+
+    case PAT_SPIRO2:
+    case PAT_SPIRO3:
+    default:
+        pm_error("INTERNAL ERROR.");
+    }
+}
+
+
+
+static void
+parseColorOpt(const char ** const colorText,
+              ColorTable  * const colorTableP,
+              Pattern       const basePattern) {
+/*----------------------------------------------------------------------------
+    String-list argument to -color is a comma-separated array of
+    color names or values, e.g.:
+    "-color=red,white,blue"
+    "-color=rgb:ff/ff/ff,rgb:00/00/00,rgb:80/80/ff"
+
+    Input:
+      Color name/value string-list: colorText[]
+
+    Output values:
+      Color array: colorTableP->color[]
+      Number of colors found: colorTableP->colors
+----------------------------------------------------------------------------*/
+    unsigned int colorCount;
+    unsigned int i;
+    pixel * inColor;
+
+    for (colorCount = 0; colorText[colorCount] != NULL; ++colorCount)
+        ;
+
+    MALLOCARRAY(inColor, colorCount);
+
+    if (!inColor)
+        pm_error("Failed to allocate table space for %u colors "
+                 "specified by -color", colorCount);
+
+    for (i = 0; i < colorCount; ++i)
+        inColor[i] = ppm_parsecolor(colorText[i], PPM_MAXMAXVAL);
+
+    validateColorCount(basePattern, colorCount);
+
+    colorTableP->count = colorCount;
+    colorTableP->index = 0;  /* initial value */
+    colorTableP->color = inColor;
+}
+
+
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -60,15 +176,21 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    const char ** colorText;
     unsigned int basePatternCount;
     unsigned int gingham2;
     unsigned int gingham3;
     unsigned int madras;
     unsigned int tartan;
+    unsigned int argyle1;
+    unsigned int argyle2;
     unsigned int poles;
     unsigned int squig;
     unsigned int camo;
     unsigned int anticamo;
+    unsigned int spiro1;
+    unsigned int spiro2;
+    unsigned int spiro3;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -85,6 +207,10 @@ parseCommandLine(int argc, const char ** argv,
             &madras,     0);
     OPTENT3(0, "tartan",        OPT_FLAG,   NULL,
             &tartan,     0);
+    OPTENT3(0, "argyle1",       OPT_FLAG,   NULL,
+            &argyle1,     0);
+    OPTENT3(0, "argyle2",       OPT_FLAG,   NULL,
+            &argyle2,     0);
     OPTENT3(0, "poles",         OPT_FLAG,   NULL,
             &poles,      0);
     OPTENT3(0, "squig",         OPT_FLAG,   NULL,
@@ -93,7 +219,19 @@ parseCommandLine(int argc, const char ** argv,
             &camo,       0);
     OPTENT3(0, "anticamo",      OPT_FLAG,   NULL,
             &anticamo,   0);
-    OPTENT3(0, "randomseed",    OPT_UINT,   &cmdlineP->randomseed,
+#if SPIROGRAPHS != 0
+    OPTENT3(0, "spiro1",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+    OPTENT3(0, "spiro2",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+    OPTENT3(0, "spiro3",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+#else
+    spiro1 = spiro2 = spiro3 = 0;
+#endif
+    OPTENT3(0, "color",         OPT_STRINGLIST, &colorText,
+            &cmdlineP->colorSpec,           0);
+    OPTENT3(0, "randomseed",    OPT_UINT,       &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec,      0);
 
     opt.opt_table = option_def;
@@ -102,16 +240,14 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    free(option_def);
 
     basePatternCount =
-        gingham2 +
-        gingham3 +
-        madras +
-        tartan +
+        gingham2 + gingham3 + madras + tartan + argyle1 + argyle2 +
         poles +
         squig +
-        camo +
-        anticamo;
+        camo + anticamo +
+        spiro1 + spiro2 + spiro3;
 
     if (basePatternCount < 1)
         pm_error("You must specify a base pattern option such as -gingham2");
@@ -127,6 +263,10 @@ parseCommandLine(int argc, const char ** argv,
             cmdlineP->basePattern = PAT_MADRAS;
         else if (tartan)
             cmdlineP->basePattern = PAT_TARTAN;
+        else if (argyle1)
+            cmdlineP->basePattern = PAT_ARGYLE1;
+        else if (argyle2)
+            cmdlineP->basePattern = PAT_ARGYLE2;
         else if (poles)
             cmdlineP->basePattern = PAT_POLES;
         else if (squig)
@@ -135,9 +275,22 @@ parseCommandLine(int argc, const char ** argv,
             cmdlineP->basePattern = PAT_CAMO;
         else if (anticamo)
             cmdlineP->basePattern = PAT_ANTICAMO;
+        else if (spiro1)
+            cmdlineP->basePattern = PAT_SPIRO1;
+        else if (spiro2)
+            cmdlineP->basePattern = PAT_SPIRO2;
+        else if (spiro3)
+            cmdlineP->basePattern = PAT_SPIRO3;
         else
             assert(false);  /* Every possibility is accounted for */
     }
+
+    if (cmdlineP->colorSpec) {
+        parseColorOpt(colorText, &cmdlineP->colorTable, cmdlineP->basePattern);
+        free(colorText);
+    } else
+        cmdlineP->colorTable.count = 0;
+
     if (argc-1 != 2)
         pm_error("You must specify 2 non-option arguments: width and height "
                  "in pixels.  You specified %u", argc-1);
@@ -150,7 +303,15 @@ parseCommandLine(int argc, const char ** argv,
         if (cmdlineP->height < 1)
             pm_error("Height must be at least 1 pixel");
     }
-    free(option_def);
+}
+
+
+
+static void
+freeCmdline(struct CmdlineInfo const cmdline) {
+
+    if (cmdline.colorSpec)
+        free(cmdline.colorTable.color);
 }
 
 
@@ -169,7 +330,7 @@ validateComputableDimensions(unsigned int const cols,
       PPMD functions use signed integers for pixel positions
       (because they allow you to specify points off the canvas).
     */
-      
+
     if (cols > INT_MAX/4 || rows > INT_MAX/4 || rows > INT_MAX/cols)
         pm_error("Width and/or height are way too large: %u x %u",
                  cols, rows);
@@ -187,7 +348,7 @@ randomColor(pixval const maxval) {
                rand() % (maxval + 1),
                rand() % (maxval + 1)
         );
-    
+
     return p;
 }
 
@@ -242,12 +403,12 @@ averageTwoColors(pixel const p1,
 static ppmd_drawproc average_drawproc;
 
 static void
-average_drawproc(pixel **     const pixels, 
-                 int          const cols, 
-                 int          const rows, 
-                 pixval       const maxval, 
-                 int          const col, 
-                 int          const row, 
+average_drawproc(pixel **     const pixels,
+                 int          const cols,
+                 int          const rows,
+                 pixval       const maxval,
+                 int          const col,
+                 int          const row,
                  const void * const clientdata) {
 
     if (col >= 0 && col < cols && row >= 0 && row < rows)
@@ -257,6 +418,29 @@ average_drawproc(pixel **     const pixels,
 
 
 
+static void
+nextColor(ColorTable * const colorTableP) {
+/*----------------------------------------------------------------------------
+  Increment index, return it to 0 if we have used all the colors
+-----------------------------------------------------------------------------*/
+    colorTableP->index = (colorTableP->index + 1) % colorTableP->count;
+}
+
+
+
+static void
+nextColorBg(ColorTable * const colorTableP) {
+/*----------------------------------------------------------------------------
+  Increment index, return it to 1 if we have used all the colors (color[0] is
+  the background color, it's outside the cycle)
+-----------------------------------------------------------------------------*/
+    colorTableP->index = colorTableP->index % (colorTableP->count - 1) + 1;
+        /* Works when index == 0, but no callers rely on this. */
+
+}
+
+
+
 /*----------------------------------------------------------------------------
    Camouflage stuff
 -----------------------------------------------------------------------------*/
@@ -282,7 +466,7 @@ randomAnticamoColor(pixval const maxval) {
     case 3:
         PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2);
         break;
-        
+
     case 4:
     case 5:
         PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3);
@@ -306,7 +490,7 @@ randomAnticamoColor(pixval const maxval) {
         PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2);
         break;
     }
-    
+
     return p;
 }
 
@@ -335,7 +519,7 @@ randomCamoColor(pixval const maxval) {
         /* dark green */
         PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2);
         break;
-    
+
     case 6:
     case 7:
         /* brown */
@@ -362,15 +546,18 @@ rnduni(void) {
 
 
 static void
-clearBackground(pixel **     const pixels,
-                unsigned int const cols,
-                unsigned int const rows,
-                pixval       const maxval,
-                bool         const antiflag) {
+clearBackgroundCamo(pixel **     const pixels,
+                    unsigned int const cols,
+                    unsigned int const rows,
+                    pixval       const maxval,
+                    ColorTable * const colorTableP,
+                    bool         const antiflag) {
 
     pixel color;
 
-    if (antiflag)
+    if (colorTableP->count > 0) {
+        color = colorTableP->color[0];
+    } else if (antiflag)
         color = randomAnticamoColor(maxval);
     else
         color = randomCamoColor(maxval);
@@ -381,23 +568,29 @@ clearBackground(pixel **     const pixels,
 }
 
 
+
 static void
 camoFill(pixel **         const pixels,
          unsigned int     const cols,
          unsigned int     const rows,
          pixval           const maxval,
          struct fillobj * const fh,
+         ColorTable     * const colorTableP,
          bool             const antiflag) {
-         
+
     pixel color;
 
-    if (antiflag)
+    if (colorTableP->count > 0) {
+        assert(colorTableP->index < colorTableP->count);
+        color = colorTableP->color[colorTableP->index];
+        nextColorBg(colorTableP);
+    } else if (antiflag)
         color = randomAnticamoColor(maxval);
     else
         color = randomCamoColor(maxval);
 
     ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
-}        
+}
 
 
 
@@ -428,9 +621,9 @@ computeXsYs(int *        const xs,
     double const b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
         MIN_ELLIPSE_FACTOR;
     double const theta = rnduni() * 2.0 * M_PI;
-    
+
     unsigned int p;
-        
+
     for (p = 0; p < pointCt; ++p) {
         double const c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
             MIN_POINT_FACTOR;
@@ -448,14 +641,20 @@ static void
 camo(pixel **     const pixels,
      unsigned int const cols,
      unsigned int const rows,
+     ColorTable * const colorTableP,
      pixval       const maxval,
      bool         const antiflag) {
 
-    unsigned int const n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5;
+    unsigned int const n = (rows * cols) / SQR(BLOBRAD) * 5;
 
     unsigned int i;
 
-    clearBackground(pixels, cols, rows, maxval, antiflag);
+    clearBackgroundCamo(pixels, cols, rows, maxval, colorTableP, antiflag);
+
+    if (colorTableP->count > 0) {
+        assert(colorTableP->count > 1);
+        colorTableP->index = 1;  /* Foreground colors start at 1 */
+    }
 
     for (i = 0; i < n; ++i) {
         unsigned int const pointCt =
@@ -475,9 +674,9 @@ camo(pixel **     const pixels,
         ppmd_polyspline(
             pixels, cols, rows, maxval, x0, y0, pointCt, xs, ys, x0, y0,
             ppmd_fill_drawproc, fh);
-        
-        camoFill(pixels, cols, rows, maxval, fh, antiflag);
-        
+
+        camoFill(pixels, cols, rows, maxval, fh, colorTableP, antiflag);
+
         ppmd_fill_destroy(fh);
     }
 }
@@ -485,19 +684,21 @@ camo(pixel **     const pixels,
 
 
 /*----------------------------------------------------------------------------
-   Gingham stuff
+   Plaid patterns
 -----------------------------------------------------------------------------*/
 
-
-
 static void
 gingham2(pixel **     const pixels,
          unsigned int const cols,
          unsigned int const rows,
+         ColorTable   const colorTable,
          pixval       const maxval) {
 
-    pixel const backcolor = randomDarkColor(maxval);
-    pixel const forecolor = randomBrightColor(maxval);
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const forecolor = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
     unsigned int const colso2 = cols / 2;
     unsigned int const rowso2 = rows / 2;
 
@@ -524,15 +725,19 @@ static void
 gingham3(pixel **     const pixels,
          unsigned int const cols,
          unsigned int const rows,
+         ColorTable   const colorTable,
          pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const colso4 = cols / 4;
     unsigned int const rowso4 = rows / 4;
 
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
-
     /* Warp. */
     ppmd_filledrectangle(
         pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC,
@@ -568,8 +773,16 @@ static void
 madras(pixel **     const pixels,
        unsigned int const cols,
        unsigned int const rows,
+       ColorTable   const colorTable,
        pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const cols2  = cols * 2 / 44;
     unsigned int const rows2  = rows * 2 / 44;
     unsigned int const cols3  = cols * 3 / 44;
@@ -580,9 +793,6 @@ madras(pixel **     const pixels,
     unsigned int const rows6a = rows12 / 2;
     unsigned int const cols6b = cols12 - cols6a;
     unsigned int const rows6b = rows12 - rows6a;
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
 
     /* Warp. */
     ppmd_filledrectangle(
@@ -631,7 +841,7 @@ madras(pixel **     const pixels,
         pixels, cols, rows, maxval, 9 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
         cols2, rows, PPMD_NULLDRAWPROC, &backcolor);
     ppmd_filledrectangle(
-        pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b, 
+        pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b,
         0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color);
 
     /* Woof. */
@@ -681,7 +891,7 @@ madras(pixel **     const pixels,
         pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b,
         cols, rows2, average_drawproc, &backcolor);
     ppmd_filledrectangle(
-        pixels, cols, rows, maxval, 0, 
+        pixels, cols, rows, maxval, 0,
         10 * rows2 + 3 * rows3 + rows6a + rows6b,
         cols, rows3, average_drawproc, &fore2color);
 }
@@ -692,8 +902,16 @@ static void
 tartan(pixel **     const pixels,
        unsigned int const cols,
        unsigned int const rows,
+       ColorTable   const colorTable,
        pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const cols1  = cols / 22;
     unsigned int const rows1  = rows / 22;
     unsigned int const cols3  = cols * 3 / 22;
@@ -704,9 +922,6 @@ tartan(pixel **     const pixels,
     unsigned int const rows5a = rows10 / 2;
     unsigned int const cols5b = cols10 - cols5a;
     unsigned int const rows5b = rows10 - rows5a;
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
 
     /* Warp. */
     ppmd_filledrectangle(
@@ -763,6 +978,71 @@ tartan(pixel **     const pixels,
 
 
 
+static void
+drawAndFillDiamond(pixel **     const pixels,
+                   unsigned int const cols,
+                   unsigned int const rows,
+                   pixval       const maxval,
+                   pixel        const forecolor) {
+
+    unsigned int const colso2 = cols / 2;
+    unsigned int const rowso2 = rows / 2;
+
+    ppmd_pathbuilder * const pathBuilderP = ppmd_pathbuilder_create();
+
+    ppmd_pathbuilder_setBegPoint(pathBuilderP,
+                 ppmd_makePoint (colso2, 0));
+
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(cols-1, rowso2)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(colso2, rows-1)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(0,      rowso2)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(colso2, 0)));
+
+    ppmd_fill_path(pixels, cols, rows, maxval,
+                   ppmd_pathbuilder_pathP(pathBuilderP), forecolor);
+}
+
+
+
+static void
+argyle(pixel **     const pixels,
+       unsigned int const cols,
+       unsigned int const rows,
+       ColorTable   const colorTable,
+       pixval       const maxval,
+       bool         const stripes) {
+
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+        colorTable.color[0] : randomDarkColor(maxval);
+    pixel const forecolor = colorSpec ?
+        colorTable.color[1] : randomBrightColor(maxval);
+
+    /* Fill canvas with background to start */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &backcolor);
+
+    drawAndFillDiamond(pixels, cols, rows, maxval, forecolor);
+
+    if (stripes) {
+         /* Connect corners with thin stripes */
+         pixel const stripecolor =
+             colorSpec ? colorTable.color[2] : randomBrightColor(maxval);
+
+         ppmd_line(pixels, cols, rows, maxval, 0, 0, cols-1, rows-1,
+              PPMD_NULLDRAWPROC, (char *) &stripecolor);
+         ppmd_line(pixels, cols, rows, maxval, cols-1, 0, 0, rows-1,
+              PPMD_NULLDRAWPROC, (char *) &stripecolor);
+    }
+}
+
+
+
 /*----------------------------------------------------------------------------
    Poles stuff
 -----------------------------------------------------------------------------*/
@@ -780,14 +1060,21 @@ placeAndColorPolesRandomly(int *        const xs,
                            unsigned int const cols,
                            unsigned int const rows,
                            pixval       const maxval,
+                           ColorTable * const colorTableP,
                            unsigned int const poleCt) {
 
     unsigned int i;
 
     for (i = 0; i < poleCt; ++i) {
+
         xs[i] = rand() % cols;
         ys[i] = rand() % rows;
-        colors[i] = randomBrightColor(maxval);
+
+        if (colorTableP->count > 0) {
+            colors[i] = colorTableP->color[colorTableP->index];
+            nextColor(colorTableP);
+        } else
+            colors[i] = randomBrightColor(maxval);
     }
 }
 
@@ -799,8 +1086,8 @@ assignInterpolatedColor(pixel * const resultP,
                         double  const dist1,
                         pixel   const color2,
                         double  const dist2) {
-    
-    if (dist1 == 0) 
+
+    if (dist1 == 0)
         /* pixel is a pole */
         *resultP = color1;
     else {
@@ -809,7 +1096,7 @@ assignInterpolatedColor(pixel * const resultP,
         pixval const r = (PPM_GETR(color1)*dist2 + PPM_GETR(color2)*dist1)/sum;
         pixval const g = (PPM_GETG(color1)*dist2 + PPM_GETG(color2)*dist1)/sum;
         pixval const b = (PPM_GETB(color1)*dist2 + PPM_GETB(color2)*dist1)/sum;
-        
+
         PPM_ASSIGN(*resultP, r, g, b);
     }
 }
@@ -820,15 +1107,17 @@ static void
 poles(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
+      ColorTable * const colorTableP,
       pixval       const maxval) {
 
     unsigned int const poleCt = MAX(2, MIN(MAXPOLES, cols * rows / 30000));
-    
+
     int xs[MAXPOLES], ys[MAXPOLES];
     pixel colors[MAXPOLES];
     unsigned int row;
 
-    placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval, poleCt);
+    placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval,
+                               colorTableP, poleCt);
 
     /* Interpolate points */
 
@@ -871,11 +1160,15 @@ poles(pixel **     const pixels,
 #define SQ_POINTS 7
 #define SQ_MAXCIRCLE_POINTS 5000
 
-static int sq_circlecount;
-static pixel sq_colors[SQ_MAXCIRCLE_POINTS];
-static ppmd_point sq_offs[SQ_MAXCIRCLE_POINTS];
-
+struct Squig {
+    unsigned int circleCt;
+    pixel        color[SQ_MAXCIRCLE_POINTS];
+    ppmd_point   off[SQ_MAXCIRCLE_POINTS];
+};
 
+typedef struct {
+    struct Squig * squigP;
+} SqClientData;
 
 static void
 validateSquigAspect(unsigned int const cols,
@@ -884,7 +1177,7 @@ validateSquigAspect(unsigned int const cols,
     if (cols / rows >= 25 || rows / cols >= 25)
         pm_error("Image too narrow.  Aspect ratio: %u/%u=%f "
                  "is outside accepted range: 0.04 - 25.0",
-                 cols, rows, (float)cols/rows ); 
+                 cols, rows, (float)cols/rows );
 
 }
 
@@ -902,14 +1195,18 @@ vectorSum(ppmd_point const a,
 static ppmd_drawprocp sqMeasureCircleDrawproc;
 
 static void
-sqMeasureCircleDrawproc(pixel**      const pixels, 
-                        unsigned int const cols, 
-                        unsigned int const rows, 
-                        pixval       const maxval, 
+sqMeasureCircleDrawproc(pixel**      const pixels,
+                        unsigned int const cols,
+                        unsigned int const rows,
+                        pixval       const maxval,
                         ppmd_point   const p,
                         const void * const clientdata) {
 
-    sq_offs[sq_circlecount++] = p;
+    const SqClientData * const sqClientDataP = clientdata;
+
+    struct Squig * const squigP = sqClientDataP->squigP;
+
+    squigP->off[squigP->circleCt++] = p;
 }
 
 
@@ -917,19 +1214,46 @@ sqMeasureCircleDrawproc(pixel**      const pixels,
 static ppmd_drawprocp sqRainbowCircleDrawproc;
 
 static void
-sqRainbowCircleDrawproc(pixel **     const pixels, 
-                        unsigned int const cols, 
-                        unsigned int const rows, 
-                        pixval       const maxval, 
+sqRainbowCircleDrawproc(pixel **     const pixels,
+                        unsigned int const cols,
+                        unsigned int const rows,
+                        pixval       const maxval,
                         ppmd_point   const p,
                         const void * const clientdata) {
 
+    const SqClientData * const sqClientDataP = clientdata;
+
+    struct Squig * const squigP = sqClientDataP->squigP;
+
     unsigned int i;
 
-    for (i = 0; i < sq_circlecount; ++i)
+    for (i = 0; i < squigP->circleCt; ++i)
         ppmd_point_drawprocp(
-            pixels, cols, rows, maxval, vectorSum(p, sq_offs[i]),
-            &sq_colors[i]);
+            pixels, cols, rows, maxval, vectorSum(p, squigP->off[i]),
+            &squigP->color[i]);
+}
+
+
+
+static void
+chooseSqPoleColors(ColorTable * const colorTableP,
+                   pixval       const maxval,
+                   pixel *      const color1P,
+                   pixel *      const color2P,
+                   pixel *      const color3P) {
+
+    if (colorTableP->count > 0) {
+        *color1P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+        *color2P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+        *color3P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+    } else {
+        *color1P = randomBrightColor(maxval);
+        *color2P = randomBrightColor(maxval);
+        *color3P = randomBrightColor(maxval);
+    }
 }
 
 
@@ -937,16 +1261,19 @@ sqRainbowCircleDrawproc(pixel **     const pixels,
 static void
 sqAssignColors(unsigned int const circlecount,
                pixval       const maxval,
+               ColorTable * const colorTableP,
                pixel *      const colors) {
 
-    pixel const rc1 = randomBrightColor(maxval);
-    pixel const rc2 = randomBrightColor(maxval);
-    pixel const rc3 = randomBrightColor(maxval);
     float const cco3 = (circlecount - 1) / 3.0;
 
+    pixel rc1;
+    pixel rc2;
+    pixel rc3;
     unsigned int i;
 
-    for (i = 0; i < circlecount ; ++i) {
+    chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3);
+
+    for (i = 0; i < circlecount; ++i) {
         if (i < cco3) {
             float const frac = (float)i/cco3;
             PPM_ASSIGN(colors[i],
@@ -984,14 +1311,19 @@ sqAssignColors(unsigned int const circlecount,
 
 
 static void
-clearImageToBlack(pixel **     const pixels,
-                  unsigned int const cols,
-                  unsigned int const rows,
-                  pixval       const maxval) {
+clearBackgroundSquig(pixel **     const pixels,
+                     unsigned int const cols,
+                     unsigned int const rows,
+                     ColorTable * const colorTableP,
+                     pixval       const maxval) {
 
     pixel color;
 
-    PPM_ASSIGN(color, 0, 0, 0);
+    if (colorTableP->count > 0) {
+        color = colorTableP->color[0];
+        colorTableP->index = 1;
+    } else
+        PPM_ASSIGN(color, 0, 0, 0);
 
     ppmd_filledrectangle(
         pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
@@ -1009,7 +1341,7 @@ chooseWrapAroundPoint(unsigned int const cols,
                       ppmd_point * const p1P,
                       ppmd_point * const p2P,
                       ppmd_point * const p3P) {
-                      
+
     switch (rand() % 4) {
     case 0:
         p1P->x = rand() % cols;
@@ -1091,27 +1423,37 @@ static void
 squig(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
+      ColorTable * const colorTableP,
       pixval       const maxval) {
 
     int i;
 
     validateSquigAspect(cols, rows);
-    
-    clearImageToBlack(pixels, cols, rows, maxval);
+
+    clearBackgroundSquig(pixels, cols, rows, colorTableP, maxval);
 
     /* Draw the squigs. */
     ppmd_setlinetype(PPMD_LINETYPE_NODIAGS);
     ppmd_setlineclip(0);
+
     for (i = SQUIGS; i > 0; --i) {
         unsigned int const radius = (cols + rows) / 2 / (25 + i * 2);
 
+        struct Squig squig;
+
+        SqClientData sqClientData;
+
         ppmd_point c[SQ_POINTS];
         ppmd_point p0, p1, p2, p3;
-        sq_circlecount = 0;
+
+        squig.circleCt = 0;
+
+        sqClientData.squigP = &squig;
+
         ppmd_circlep(pixels, cols, rows, maxval,
                      ppmd_makePoint(0, 0), radius,
-                     sqMeasureCircleDrawproc, NULL);
-        sqAssignColors(sq_circlecount, maxval, sq_colors);
+                     sqMeasureCircleDrawproc, &sqClientData);
+        sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color);
 
         chooseWrapAroundPoint(cols, rows, &c[0], &c[SQ_POINTS-1],
                               &p0, &p1, &p2, &p3);
@@ -1131,13 +1473,13 @@ squig(pixel **     const pixels,
 
         ppmd_linep(
             pixels, cols, rows, maxval, p0, p1,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
         ppmd_polysplinep(
             pixels, cols, rows, maxval, p1, SQ_POINTS, c, p2,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
         ppmd_linep(
             pixels, cols, rows, maxval, p2, p3,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
     }
 }
 
@@ -1146,50 +1488,68 @@ squig(pixel **     const pixels,
 int
 main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     pixel ** pixels;
 
     pm_proginit(&argc, argv);
-    
+
     parseCommandLine(argc, argv, &cmdline);
 
     validateComputableDimensions(cmdline.width, cmdline.height);
-    
+
     srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
 
     pixels = ppm_allocarray(cmdline.width, cmdline.height);
 
     switch (cmdline.basePattern) {
     case PAT_GINGHAM2:
-        gingham2(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        gingham2(pixels, cmdline.width, cmdline.height,
+                 cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_GINGHAM3:
-        gingham3(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        gingham3(pixels, cmdline.width, cmdline.height,
+                 cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_MADRAS:
-        madras(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        madras(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_TARTAN:
-        tartan(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        tartan(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL);
+        break;
+
+    case PAT_ARGYLE1:
+        argyle(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL, FALSE);
+        break;
+
+    case PAT_ARGYLE2:
+        argyle(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL, TRUE);
         break;
 
     case PAT_POLES:
-        poles(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        poles(pixels, cmdline.width, cmdline.height,
+              &cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_SQUIG:
-        squig(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        squig(pixels, cmdline.width, cmdline.height,
+              &cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_CAMO:
-        camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 0);
+        camo(pixels, cmdline.width, cmdline.height,
+             &cmdline.colorTable, PPM_MAXMAXVAL, 0);
         break;
 
     case PAT_ANTICAMO:
-        camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 1);
+        camo(pixels, cmdline.width, cmdline.height,
+             &cmdline.colorTable, PPM_MAXMAXVAL, 1);
         break;
 
     default:
@@ -1201,6 +1561,8 @@ main(int argc, const char ** argv) {
 
     ppm_freearray(pixels, cmdline.height);
 
+    freeCmdline(cmdline);
+
     return 0;
 }
 
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
index c0568d9b..e8a329ff 100755
--- a/generator/ppmrainbow
+++ b/generator/ppmrainbow
@@ -25,31 +25,57 @@ exec perl -w -x -S -- "$0" "$@"
 #!/usr/bin/perl
 use strict;
 use Getopt::Long;
+use File::Temp;
 
 my ($FALSE, $TRUE) = (0,1);
 
 (my $myname = $0) =~ s#\A.*/##;
 
+
+
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pgmramp', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
 sub fatal($) {
     my ($msg) = @_;
 
-    print(STDERR "$msg\n");
+    print(STDERR "ppmrainbow: $msg\n");
     exit(1);
 }
 
-my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose);
+
+
+##############################################################################
+#
+#                                 MAINLINE
+#
+##############################################################################
+
+doVersionHack(\@ARGV);
+
+my ($Twid, $Thgt, $tmpdir, $repeat, $verbose);
 
 # set defaults
 $Twid = 600;
 $Thgt = 8;
 $tmpdir = $ENV{"TMPDIR"} || "/tmp";
-$norepeat = $FALSE;
+$repeat = $TRUE;
 $verbose = $FALSE;
 
 GetOptions("width=i"   => \$Twid,
            "height=i"  => \$Thgt,
            "tmpdir=s"  => \$tmpdir,
-           "norepeat!" => \$norepeat,
+           "repeat!"   => \$repeat,
            "verbose!"  => \$verbose);
 
 if ($Twid < 1 || $Thgt < 1) {
@@ -59,7 +85,7 @@ my $verboseCommand = $verbose ? "set -x;" : "";
 
 if (@ARGV < 1) {
     fatal("You must specify at least one color as an argument");
-} elsif (@ARGV < 2 && $norepeat) {
+} elsif (@ARGV < 2 && ! $repeat) {
     fatal("With the -norepeat option, you must specify at least two colors " .
           "as arguments.");
 }
@@ -67,14 +93,11 @@ if (@ARGV < 1) {
 my @colorlist;
 
 @colorlist = @ARGV;
-if (!$norepeat) {
+if ($repeat) {
     push @colorlist, $ARGV[0];
 }
 
-my $ourtmp = "$tmpdir/ppmrainbow$$";
-mkdir($ourtmp, 0777) or
-    die("Unable to create directory for temporary files '$ourtmp");
-
+my $ourtmp = File::Temp::tempdir("$tmpdir/ppmrainbowXXXX", UNLINK=>1);
 
 my $widthRemaining;
 my $n;
@@ -92,7 +115,7 @@ while (@colorlist >= 2) {
     my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " .
                     "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile");
     if ($rc != 0) {
-        fatal("pgmramp|pgmtoppm failed.");
+        fatal("pgmramp|pgmtoppm pipe failed.");
     }
     $widthRemaining -= $w;
     $n++;
diff --git a/generator/ppmwheel.c b/generator/ppmwheel.c
index ef5021f9..29e4730c 100644
--- a/generator/ppmwheel.c
+++ b/generator/ppmwheel.c
@@ -16,142 +16,245 @@
 #include <string.h>
 #include <math.h>
 
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
 #include "ppm.h"
 
 #ifndef PI
 #define PI  3.14159265358979323846
 #endif
 
-#ifndef ABS
-#define ABS(a) ((a) < 0 ? -(a) : (a))
-#endif
 
-static void 
-hsv_rgb(double const in_h, double const in_s, double const in_v, 
-        double * const r, double * const g, double * const b) {
+
+typedef enum {WT_HUE_VAL, WT_HUE_SAT, WT_PPMCIRC} WheelType;
+
+
+struct CmdlineInfo {
+    unsigned int diameter;
+    WheelType    wheelType;
+    pixval       maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char **argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
-   This is a stripped down hsv->rgb converter that works only for
-   Saturation of zero.
+  Convert program invocation arguments (argc,argv) into a format the
+  program can use easily, struct CmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage
+  argv[] points to.
 -----------------------------------------------------------------------------*/
-    double h, s, v;
-
-    h = in_h < 0.0 ? 0.0 : in_h > 360.0 ? 360.0 : in_h;
-
-    v = in_v < 0.0 ? 0.0 : in_v > 1.0 ? 1.0 : in_v;
-
-    s = in_s < 0.0 ? 0.0 : in_s > 1.0 ? 1.0 : in_s;
-
-    if (s != 0.0)
-        pm_error("Internal error: non-zero saturation");
-
-    if (h <= 60.0) {          /* from red to yellow */
-        *r = 1.0;
-        *g = h / 60.0;
-        *b = 0.0;
-    } else if ( h <= 120.0 ) {   /* from yellow to green */
-        *r = 1.0 - (h - 60.0) / 60.0;
-        *g = 1.0;
-        *b = 0.0;
-    } else if ( h <= 180.0 ) {   /* from green to cyan */
-        *r = 0.0;
-        *g = 1.0;
-        *b = (h - 120.0) / 60.0;
-    } else if ( h <= 240.0 ) {    /* from cyan to blue */
-        *r = 0.0;
-        *g = 1.0 - (h - 180.0) / 60.0;
-        *b = 1.0;
-    } else if ( h <= 300.0) {    /* from blue to magenta */
-        *r = (h - 240.0) / 60.0;
-        *g = 0.0;
-        *b = 1.0;
-    } else {                      /* from magenta to red */
-        *r = 1.0;
-        *g = 0.0;
-        *b = 1.0 - (h - 300.0) / 60.0;
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec, huevalueSpec, huesaturationSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;
+    OPTENT3(0, "maxval",         OPT_UINT,
+            &cmdlineP->maxval, &maxvalSpec,        0);
+    OPTENT3(0, "huevalue",       OPT_FLAG,
+            NULL,              &huevalueSpec,      0);
+    OPTENT3(0, "huesaturation",  OPT_FLAG,
+            NULL,              &huesaturationSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PPM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PPM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval,
+                     PPM_OVERALLMAXVAL);
+
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
     }
 
-    if ( v >= 0.5) {
-        v = 2.0 - 2.0 * v;
-        v = sqrt (v);
-        *r = 1.0 + v * (*r - 1.0);
-        *g = 1.0 + v * (*g - 1.0);
-        *b = 1.0 + v * (*b - 1.0);
+    if (huevalueSpec + huesaturationSpec > 1)
+        pm_error("You may specify at most one of "
+                 "-huevalue and -huesaturation");
+
+    cmdlineP->wheelType =
+        huevalueSpec      ? WT_HUE_VAL :
+        huesaturationSpec ? WT_HUE_SAT :
+        WT_PPMCIRC;
+
+    if (argc-1 != 1) {
+        pm_error("Need 1 argument diameter of the wheel in pixels");
     } else {
-        v *= 2.0;
-        v = sqrt (sqrt ( sqrt (v)));
-        *r *= v;
-        *g *= v;
-        *b *= v;
+        const char * const diameterArg = argv[1];
+
+        if (strlen(diameterArg) == 0)
+            pm_error("Diameter argument is a null string");
+        else {
+            long argNumber;
+            char * tailptr;
+            argNumber = strtol(diameterArg, &tailptr, 10);
+
+            if (*tailptr != '\0')
+                pm_error("You specified an invalid number as diameter: '%s'",
+                         diameterArg);
+            if (argNumber <= 0)
+                pm_error("Diameter must be positive.  You specified %ld.",
+                         argNumber);
+            if (argNumber < 4)
+                pm_error("Diameter must be at least 4.  You specified %ld",
+                         argNumber);
+
+            cmdlineP->diameter = argNumber;
+        }
     }
+    free(option_def);
 }
 
 
-int
-main(int argc, char *argv[]) {
-    pixel *orow;
-    int rows, cols;
-    pixval maxval;
-    unsigned int row;
-    unsigned int xcenter, ycenter, radius;
-    long diameter;
-    char * tailptr;
 
-    ppm_init( &argc, argv );
 
-    if (argc-1 != 1)
-        pm_error("Program takes one argument:  diameter of color wheel");
 
-    diameter = strtol(argv[1], &tailptr, 10);
-    if (strlen(argv[1]) == 0 || *tailptr != '\0')
-        pm_error("You specified an invalid diameter: '%s'", argv[1]);
-    if (diameter <= 0)
-        pm_error("Diameter must be positive.  You specified %ld.", diameter);
-    if (diameter < 4)
-        pm_error("Diameter must be at least 4.  You specified %ld", diameter);
 
-    cols = rows = diameter;
-    
-    orow = ppm_allocrow(cols);
 
-    maxval = PPM_MAXMAXVAL;
-    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+static pixel
+ppmcircColor(pixel  const normalColor,
+             pixval const maxval,
+             double const d) {
+/*----------------------------------------------------------------------------
+   The color that Ppmcirc (by Peter Kirchgessner, not part of Netpbm) puts at
+   'd' units from the center where the normal color in a hue-value color wheel
+   is 'normalColor'.
+
+   We have no idea what the point of this is.
+-----------------------------------------------------------------------------*/
+    pixel retval;
+
+    if (d >= 0.5) {
+        double const scale = sqrt(2.0 - 2.0 * d);
+
+        PPM_ASSIGN(retval,
+                   maxval - scale * (maxval - normalColor.r/d),
+                   maxval - scale * (maxval - normalColor.g/d),
+                   maxval - scale * (maxval - normalColor.b/d));
+    } else if (d == 0.0) {
+        PPM_ASSIGN(retval, 0, 0, 0);
+    } else {
+        double const scale = sqrt(sqrt(sqrt(2.0 * d)))/d;
+        PPM_ASSIGN(retval,
+                   normalColor.r * scale,
+                   normalColor.g * scale,
+                   normalColor.b * scale);
+    }
+    return retval;
+}
+
 
-    radius = diameter/2 - 1;
 
-    xcenter = cols / 2;
-    ycenter = rows / 2;
+static pixel
+wheelColor(WheelType const wheelType,
+           double    const dx,
+           double    const dy,
+           double    const radius,
+           pixval    const maxval) {
+
+    double const dist = sqrt(SQR(dx) + SQR(dy));
+
+    pixel retval;
+
+    if (dist > radius) {
+        retval = ppm_whitepixel(maxval);
+    } else {
+        double const hue90 = atan2(dx, dy) / PI * 180.0;
+        struct hsv hsv;
+
+        hsv.h = hue90 < 0.0 ? 360.0 + hue90 : hue90;
+
+        switch (wheelType) {
+        case WT_HUE_SAT:
+            hsv.v = 1.0;
+            hsv.s = dist / radius;
+            retval = ppm_color_from_hsv(hsv, maxval);
+            break;
+        case WT_HUE_VAL:
+            hsv.s = 1.0;
+            hsv.v = dist / radius;
+            retval = ppm_color_from_hsv(hsv, maxval);
+            break;
+        case WT_PPMCIRC:
+            hsv.s = 1.0;
+            hsv.v = dist / radius;
+            {
+                pixel const hvColor = ppm_color_from_hsv(hsv, maxval);
+                retval = ppmcircColor(hvColor, maxval, dist/radius);
+            }
+            break;
+        }
+    }
+    return retval;
+}
+
+
+
+static void
+ppmwheel(WheelType    const wheelType,
+         unsigned int const diameter,
+         pixval       const maxval,
+         FILE *       const ofP) {
+
+    unsigned int const cols   = diameter;
+    unsigned int const rows   = diameter;
+    unsigned int const radius = diameter/2 - 1;
+    unsigned int const xcenter = cols / 2;
+    unsigned int const ycenter = rows / 2;
+
+    unsigned int row;
+    pixel * orow;
+
+    orow = ppm_allocrow(cols);
+
+    ppm_writeppminit(ofP, cols, rows, maxval, 0);
 
     for (row = 0; row < rows; ++row) {
         unsigned int col;
         for (col = 0; col < cols; ++col) {
             double const dx = (int)col - (int)xcenter;
             double const dy = (int)row - (int)ycenter;
-            double const dist = sqrt(dx*dx + dy*dy);
 
-            pixval r, g, b;
+            orow[col] = wheelColor(wheelType, dx, dy, radius, maxval);
+        }
+        ppm_writeppmrow(ofP, orow, cols, maxval, 0);
+    }
+    ppm_freerow(orow);
+}
 
-            if (dist > radius) {
-                r = g = b = maxval;
-            } else {
-                double hue, sat, val;
-                double dr, dg, db;
 
-                hue = atan2(dx, dy) / PI * 180.0;
-                if (hue < 0.0) 
-                    hue = 360.0 + hue;
-                sat = 0.0;
-                val = dist / radius;
 
-                hsv_rgb(hue, sat, val, &dr, &dg, &db);
+int
+main(int argc, const char ** argv) {
+
+    struct CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ppmwheel(cmdline.wheelType, cmdline.diameter, cmdline.maxval, stdout);
 
-                r = (pixval)(maxval * dr);
-                g = (pixval)(maxval * dg);
-                b = (pixval)(maxval * db);
-            }
-            PPM_ASSIGN (orow[col], r, g, b );
-        }
-        ppm_writeppmrow(stdout, orow, cols, maxval, 0);
-    }
     pm_close(stdout);
-    exit(0);
+    return 0;
 }
+
+