about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile2
-rw-r--r--analyzer/pamgetcolor.c409
-rw-r--r--analyzer/pamsumm.c26
-rw-r--r--analyzer/ppmhist.c2
-rw-r--r--converter/other/fiasco/lib/error.c256
-rw-r--r--doc/HISTORY25
-rw-r--r--editor/Makefile2
-rw-r--r--editor/pamaltsat.c535
-rw-r--r--editor/pamscale.c58
-rw-r--r--editor/ppmbrighten.c35
-rw-r--r--editor/ppmcolormask.c10
-rw-r--r--editor/ppmdraw.c68
-rw-r--r--generator/Makefile4
-rw-r--r--generator/pamtris/Makefile27
-rw-r--r--generator/pamtris/boundaries.c334
-rw-r--r--generator/pamtris/boundaries.h71
-rw-r--r--generator/pamtris/fract.h54
-rw-r--r--generator/pamtris/framebuffer.c334
-rw-r--r--generator/pamtris/framebuffer.h75
-rw-r--r--generator/pamtris/input.c641
-rw-r--r--generator/pamtris/input.h31
-rw-r--r--generator/pamtris/limits_pamtris.h7
-rw-r--r--generator/pamtris/pamtris.c140
-rw-r--r--generator/pamtris/triangle.c427
-rw-r--r--generator/pamtris/triangle.h25
-rw-r--r--generator/pamtris/utils.c256
-rw-r--r--generator/pamtris/utils.h41
-rw-r--r--generator/pbmtext.c85
-rw-r--r--generator/ppmcie.c38
-rw-r--r--lib/Makefile5
-rw-r--r--lib/libpamn.c60
-rw-r--r--lib/libpbmfont.c1185
-rw-r--r--lib/libpbmfont0.c335
-rw-r--r--lib/libpbmfont1.c359
-rw-r--r--lib/libpbmfont2.c1041
-rw-r--r--lib/libpbmfontdump.c96
-rw-r--r--lib/pam.h12
-rw-r--r--lib/pbmfont.h229
-rw-r--r--lib/pbmfontdata.h7
-rw-r--r--lib/pbmfontdata0.c9
-rw-r--r--lib/pbmfontdata1.c24
-rw-r--r--lib/pbmfontdata2.c27
-rw-r--r--test/Makefile2
-rw-r--r--test/all-in-place.ok4
-rwxr-xr-xtest/all-in-place.test4
-rw-r--r--test/pbmtext-bdf.ok13
-rwxr-xr-xtest/pbmtext-bdf.test78
-rw-r--r--test/pbmtext-iso88591.ok8
-rwxr-xr-xtest/pbmtext-iso88591.test12
-rw-r--r--test/pbmtext-utf8.ok12
-rwxr-xr-xtest/pbmtext-utf8.test28
-rw-r--r--test/pbmtext.ok4
-rwxr-xr-xtest/pbmtext.test4
-rw-r--r--test/pgmnoise.ok1
-rw-r--r--test/ppmforge.ok1
-rw-r--r--test/ppmpat-random.ok3
-rw-r--r--test/ppmrough.ok1
-rw-r--r--version.mk4
58 files changed, 5847 insertions, 1739 deletions
diff --git a/GNUmakefile b/GNUmakefile
index c2139d2a..3bf3623d 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -460,6 +460,7 @@ CHECK_VARS = \
 	ZLIB="$(ZLIB)" \
 
 # Test files in source tree.
+# BUILDBINDIRS is a list of directories which contain target binaries
 
 check-tree : BUILDBINDIRS :=./analyzer \
 ./converter/other \
@@ -479,6 +480,7 @@ check-tree : BUILDBINDIRS :=./analyzer \
 ./editor/pamflip \
 ./editor/specialty \
 ./generator \
+./generator/pamtris \
 ./other \
 ./other/pamx
 
diff --git a/analyzer/pamgetcolor.c b/analyzer/pamgetcolor.c
index 993dd79d..cd9d2028 100644
--- a/analyzer/pamgetcolor.c
+++ b/analyzer/pamgetcolor.c
@@ -3,98 +3,115 @@
 #include <pm_gamma.h>
 #include <pam.h>
 
+#include "pm_c_util.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 
 typedef unsigned int  uint;
 
-/* specification of a cirtular "region" over which to measre the avg. color: */
 typedef struct {
+/*----------------------------------------------------------------------------
+  Specification of a circular "region" over which to measure the average color
+-----------------------------------------------------------------------------*/
     uint         x;        /* coordinates of the center                      */
     uint         y;        /* of the region;                                 */
-    char const * label;    /* optional label supplied on the command line    */
+    const char * label;    /* optional label supplied on the command line    */
 } RegSpec;
 
-/* represents a single color measurement over a "region": */
 typedef struct {
+/*----------------------------------------------------------------------------
+  Represents a single color measurement over a "region"
+-----------------------------------------------------------------------------*/
     uint         area;     /* area in pixels over which to average the color */
     /* cumulative normalised intensity-proportiunal value of the region:     */
     double       color[3];
 } RegData;
 
-/*command-line parameters: */
 typedef struct {
+/*----------------------------------------------------------------------------
+  All the information the user supplied in the command line, in a form easy
+  for the program to use.
+-----------------------------------------------------------------------------*/
     uint         linear;
     uint         radius;
     uint         regN;      /* number of regions                             */
     uint         maxLbLen;  /* maximum label length                          */
     RegSpec *    regSpecs;
         /* list of points to sample, dymamically allocated*/
-    char const * formatStr; /* output color format as string                 */
+    const char * formatStr; /* output color format as string                 */
     uint         formatId;  /* the Id of the selected color format           */
     uint         formatArg; /* the argument to the color formatting function */
-    char const * infile;
-} CmdlineInfo;
+    const char * infile;
+} CmdLineInfo;
 
 /* Generic pointer to a color-formatting function. Returns the textual
    representation of the color <tuple> in terms of the image pointed-to
    by <pamP>. <param> is a generic integer parameter that depends on the
    specific funcion and may denote precison or maxval.
 */
-typedef char const *
+typedef const char *
 (*FormatColor)(struct pam * const pamP,
                tuple        const color,
                uint         const param);
 
-/* The color format specificaiton: */
 typedef struct ColorFormat {
-    /* format id (compared against the -format command-line argument):       */
+/*----------------------------------------------------------------------------
+  The color format specification
+-----------------------------------------------------------------------------*/
     char        const * id;
-    /* function that returns converts a color into this format:              */
+        /* format id (compared against the -format command-line argument) */
     FormatColor const   formatColor;
-    /* meaning of the <param> argument of <formatColor>():                   */
+        /* function that returns converts a color into this format */
     char        const * argName;
-    uint        const   defParam;   /* default value of that argument        */
-    uint        const   maxParam;   /* maximum value of that argument        */
+        /* meaning of the <param> argument of <formatColor>() */
+    uint        const   defParam;
+        /* default value of that argument        */
+    uint        const   maxParam;
+        /* maximum value of that argument        */
 } ColorFormat;
 
 
 
-static char const *
+static const char *
 fcInt(struct pam * const pamP,
       tuple        const color,
       uint         const param) {
-/* format <color> as an integer tuple with maxval <param> */
+/*----------------------------------------------------------------------------
+  Format 'color' as an integer tuple with maxval 'param'
+-----------------------------------------------------------------------------*/
     return pnm_colorspec_rgb_integer(pamP, color, param);
 }
 
 
 
-static char const *
+static const char *
 fcNorm(struct pam * const pamP,
        tuple        const color,
        uint         const param) {
-/* format <color> as normalised tuple with precision <param> */
+/*----------------------------------------------------------------------------
+  Format 'color' as normalized tuple with precision 'param'
+-----------------------------------------------------------------------------*/
     return pnm_colorspec_rgb_norm(pamP, color, param);
 }
 
 
 
-static char const *
+static const char *
 fcX11(struct pam * const pamP,
       tuple        const color,
       uint         const param) {
-/* format <color> as hexadecimal tuple with <param> digits*/
+/*----------------------------------------------------------------------------
+  Format 'color' as hexadecimal tuple with 'param' digits
+-----------------------------------------------------------------------------*/
     return pnm_colorspec_rgb_x11(pamP, color, param);
 }
 
 
 
-#define FormatsN 3
+static int const defaultFormat = 0;
 
-static int const DefaultFormat = 0;
 /* Table with the full information about color formats */
-ColorFormat formats[ FormatsN ] = {
+ColorFormat const formats[ 3 ] = {
     /*   Id     Function  Argument name  Default  Max   */
     {   "int",  &fcInt,   "maxval",      255,     65535  },
     {   "norm", &fcNorm,  "digit count",   3,         6  },
@@ -112,12 +129,14 @@ sqri(int const v) {
 
 
 static RegSpec
-parseRegSpec(char const * const s) {
+parsedRegSpec(const char * const s) {
 /*----------------------------------------------------------------------------
-  Parse region specification <s> from the command line and return its
-  structured representation.  A specification is of the format <x,y[:label].
+  The region specification represented by command line argument 's'.
+
+  's' is of the format x,y[:label].
 -----------------------------------------------------------------------------*/
-    char* end, *start;
+    char * end;
+    char *start;
     RegSpec res;
 
     start = (char *)s;
@@ -146,8 +165,7 @@ parseRegSpec(char const * const s) {
                 break; /* empty label */
             return res;
         }
-    }
-    while (1 == 0);
+    } while (false);
 
     pm_error("Wrong region specification: %s", s);
 
@@ -157,126 +175,157 @@ parseRegSpec(char const * const s) {
 
 
 static void
-parseColorFmt(CmdlineInfo * const cmdLineP) {
+parseColorFmt(const char * const formatStr,
+              uint *       const formatIdP,
+              uint *       const formatArgP) {
 /*----------------------------------------------------------------------------
-  Parse the color format specificaction from the command line stored in the
-  <formatStr> member of <cmdLineP> and save it into members <formatId> and
-  <formatArg>.  A format specification is <format>[:<arg>].
+  Parse the color format specification string 'formatStr' as
+  *formatIdP and *formatArgP.
+
+  A format specification string is of format format[:arg].
 -----------------------------------------------------------------------------*/
-    const int     FmtNotFound = -1;
-    const char *  ErrSpec = "Wrong color format specification: ";
-    const char *  formatStr;
-          char *  colonLoc; /* location of the colon in the specification */
+    int           const FmtNotFound = -1;
+    const char *  const errSpec = "Wrong color format specification: ";
+
+    const char *  colonLoc; /* location of the colon in the specification */
     uint          n, f;
-    ColorFormat * formatP;
+    const ColorFormat * formatP;
+    uint formatId;
 
-    formatStr = cmdLineP->formatStr;
     colonLoc  = strchr(formatStr, ':');
     if (colonLoc != NULL) n = colonLoc - formatStr;
     else                  n = strlen(formatStr);
 
-    cmdLineP->formatId = FmtNotFound;
-
-    for (f = 0; f < FormatsN; f++) {
-        if (strncmp(formatStr, formats[f].id, n) == 0) {
-            cmdLineP->formatId = f;
-            break;
-        }
+    for (f = 0, formatId = FmtNotFound;
+         f < ARRAY_SIZE(formats) && formatId == FmtNotFound; ++f) {
+        if (strncmp(formatStr, formats[f].id, n) == 0)
+            formatId = f;
     }
-    if (cmdLineP->formatId == FmtNotFound) {
+    if (formatId == FmtNotFound)
         pm_error("Color format not recognised.");
-    }
-    formatP = &formats[cmdLineP->formatId];
-    if (colonLoc != NULL) {
+
+    *formatIdP = formatId;
+
+    formatP = &formats[formatId];
+
+    if (colonLoc) {
         long int arg;
-        char *argStart, *argEnd;
+        const char * argStart;
+        char * argEnd;
 
         argStart = colonLoc + 1;
+
         if (*argStart == '\0')
             pm_error("%sthe colon should be followed by %s.",
-                ErrSpec, formatP->argName);
+                errSpec, formatP->argName);
 
         arg = strtol(argStart, &argEnd, 10);
+
         if (*argEnd != '\0')
             pm_error("%sfailed to parse the %s: %s.",
-                ErrSpec, formatP->argName, argStart);
+                errSpec, formatP->argName, argStart);
 
         if (arg < 1)
             pm_error("%s%s must be greater than zero.",
-                ErrSpec, formatP->argName);
+                errSpec, formatP->argName);
 
         if (arg > formatP->maxParam)
             pm_error("%s%s cannot exceed %i.",
-                ErrSpec, formatP->argName, formatP->maxParam);
-        cmdLineP->formatArg = arg;
-    }
-    else
-        cmdLineP->formatArg = formatP->defParam;
+                errSpec, formatP->argName, formatP->maxParam);
+
+        *formatArgP = arg;
+    } else
+        *formatArgP = formatP->defParam;
 }
 
 
 
-static CmdlineInfo
-parseCommandLine(int argc, char const ** argv) {
-/*----------------------------------------------------------------------------
-  Parse the command-line arguments and store them in a form convenient for the
-  program.
------------------------------------------------------------------------------*/
-    int         r;
-    uint        formatSet;
-    CmdlineInfo cmdLine;
-    optStruct3  opt;
-    uint        option_def_index = 0;
+static CmdLineInfo
+parsedCommandLine(int                 argc,
+                  const char ** const argv) {
 
     optEntry * option_def;
-    MALLOCARRAY_NOFAIL(option_def, 100);
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
 
-    cmdLine.radius    = 0;
+    CmdLineInfo cmdLine;
 
-    opt.opt_table     = option_def;
-    opt.short_allowed = 0;
-    opt.allowNegNum   = 0;
+    uint infileSpec, radiusSpec, formatSpec, linearSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
-    OPTENT3(0, "infile",    OPT_STRING, &cmdLine.infile,    NULL,       0);
-    OPTENT3(0, "radius",    OPT_INT,    &cmdLine.radius,    NULL,       0);
-    OPTENT3(0, "format",    OPT_STRING, &cmdLine.formatStr, &formatSet, 0);
-    OPTENT3(0, "linear",    OPT_FLAG,   &cmdLine.linear,    NULL,       0);
-    OPTENT3(0,  0,          OPT_END,    NULL,               NULL,       0);
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "infile",    OPT_STRING, &cmdLine.infile,    &infileSpec, 0);
+    OPTENT3(0, "radius",    OPT_INT,    &cmdLine.radius,    &radiusSpec, 0);
+    OPTENT3(0, "format",    OPT_STRING, &cmdLine.formatStr, &formatSpec, 0);
+    OPTENT3(0, "linear",    OPT_FLAG,   &cmdLine.linear,    &linearSpec, 0);
+    OPTENT3(0,  0,          OPT_END,    NULL,               NULL,        0);
 
-    cmdLine.radius = 0;
-    cmdLine.linear = 0;
-    cmdLine.infile = "-";
+    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);
 
-    if (formatSet) {
-        parseColorFmt(&cmdLine);
+    if (!infileSpec)
+        cmdLine.infile = "-";
+
+    if (!radiusSpec)
+        cmdLine.radius = 0;
+
+    if (formatSpec) {
+        parseColorFmt(cmdLine.formatStr,
+                      &cmdLine.formatId, &cmdLine.formatArg);
     } else {
-        cmdLine.formatId  = DefaultFormat;
-        cmdLine.formatArg = formats[DefaultFormat].defParam;
+        cmdLine.formatId  = defaultFormat;
+        cmdLine.formatArg = formats[defaultFormat].defParam;
     }
 
-    cmdLine.regN    = argc - 1;
-    MALLOCARRAY_NOFAIL(cmdLine.regSpecs, cmdLine.regN);
+    if (!linearSpec)
+        cmdLine.radius = 0;
 
-    cmdLine.maxLbLen = 0;
-    if (argc < 2)
+    if (argc-1 < 1)
         pm_error("No regions specified.");
 
-    for (r = 0; r < argc - 1; r++) {
-        size_t lbLen;
-        cmdLine.regSpecs[r] = parseRegSpec(argv[r+1]);
-        lbLen = strlen(cmdLine.regSpecs[r].label);
-        if (lbLen > cmdLine.maxLbLen)
-            cmdLine.maxLbLen = lbLen;
+    cmdLine.regN = argc - 1;
+
+    MALLOCARRAY(cmdLine.regSpecs, cmdLine.regN);
+
+    if (!cmdLine.regSpecs)
+        pm_error("Could not get memory for %u region specifications",
+                 cmdLine.regN);
+
+    {
+        uint r;
+        uint maxLbLen;
+
+        for (r = 0, maxLbLen = 0; r < argc - 1; ++r) {
+            size_t lbLen;
+            cmdLine.regSpecs[r] = parsedRegSpec(argv[r+1]);
+            lbLen = strlen(cmdLine.regSpecs[r].label);
+            maxLbLen = MAX(maxLbLen, lbLen);
+        }
+        cmdLine.maxLbLen = maxLbLen;
     }
 
     free(option_def);
+
     return cmdLine;
 }
 
 
 
+static void
+freeCommandLine(CmdLineInfo const cmdLine) {
+
+    free(cmdLine.regSpecs);
+}
+
+
+
 static RegData * allocRegSamples(uint n) {
 /*----------------------------------------------------------------------------
   Allocate an array of <n> initialised region samles.  The array should be
@@ -299,7 +348,7 @@ static RegData * allocRegSamples(uint n) {
 
 
 static uint getYmax(struct pam * const pamP,
-                    CmdlineInfo  const cmdLine) {
+                    CmdLineInfo  const cmdLine) {
 /*----------------------------------------------------------------------------
   Find the maximum row in the image that contains a pixel from a region.
 -----------------------------------------------------------------------------*/
@@ -330,16 +379,17 @@ readChord(RegData *    const dataP,
           uint         const x0,
           uint         const x1) {
 /*----------------------------------------------------------------------------
-  Update region sample <dataP> with the data from horisontal chord lying in
-  row <row> and going from <x0> to <x1>. <linear> denotes whether <pamP> is
-  true PPM or the linear variation.
+  Update region sample *dataP with the data from horizontal chord lying in row
+  'row' and going from 'x0' to 'x1'. 'linear' means tuples in 'row' are the
+  intensity-linear values as opposed to normal libnetpbm gamma-adjusted
+  values.
 -----------------------------------------------------------------------------*/
     uint x;
 
-    for (x = x0; x <= x1; x++) {
+    for (x = x0; x <= x1; ++x) {
         uint l;
 
-        for (l = 0; l < 3; l++) {
+        for (l = 0; l < 3; ++l) {
             double val;
 
             val = (double)row[x][l] / pamP->maxval;
@@ -348,7 +398,7 @@ readChord(RegData *    const dataP,
                 val = pm_ungamma709(val);
             dataP->color[l] += val;
         }
-        dataP->area++;
+        ++dataP->area;
     }
 }
 
@@ -358,86 +408,95 @@ static void
 processRow(tuple *      const   row,
            uint         const   y,
            struct pam * const   pamP,
-           CmdlineInfo  const * cmdLineP,
+           CmdLineInfo  const * cmdLineP,
            RegData *    const   regSamples) {
 /*----------------------------------------------------------------------------
-  Reads a row from image <pamP> into allocated tuple array <row>, and updates
-  region samples <regSamples[]> from it.  <y> is the position of the row.
+  Read a row from image described by *pamP into 'row', and update region
+  samples regSamples[] from it.  'y' is the position of the row.
 -----------------------------------------------------------------------------*/
     uint r;
 
     pnm_readpamrow(pamP, row);
-    for (r = 0; r < cmdLineP->regN; r++) {
-        RegSpec   spec;
-        RegData * dataP;
-        uint      xd, xd2;
-        int       yd;
-        int       x0, x1;
-
-        spec  = cmdLineP->regSpecs[r];
-        dataP = &regSamples[r];
-        yd    = (int)spec.y - (int)y;
-        if (abs(yd) > cmdLineP->radius)
-            continue; /* to avoid the slow root operation when possible */
-        xd2 = sqri(cmdLineP->radius) - sqri(yd);
-        xd = (int)(sqrt((double)xd2) + 0.5);
-        x0 = spec.x - xd;
-        x1 = spec.x + xd;
-
-        /* clip horisontal chord to image boundaries: */
-        if (x0 < 0)
-            x0 = 0;
-        if (x1 >= pamP->width)
-            x1 = pamP->width - 1;
-
-        readChord(dataP, cmdLineP->linear, pamP, row, x0, x1);
+
+    for (r = 0; r < cmdLineP->regN; ++r) {
+        RegSpec   const spec = cmdLineP->regSpecs[r];
+        RegData * const dataP = &regSamples[r];
+        int       const yd = (int)spec.y - (int)y;
+
+        if (abs(yd) > cmdLineP->radius) {
+            /* Row is entirely above or below the region; Avoid the slow root
+               operation
+            */
+        } else {
+            uint const xd2 = sqri(cmdLineP->radius) - sqri(yd);
+            uint const xd  = ROUNDU(sqrt((double)xd2));
+
+            int x0, x1;
+
+            x0 = spec.x - xd;  /* initial value */
+            x1 = spec.x + xd;  /* initial value */
+
+            /* clip horizontal chord to image boundaries: */
+            if (x0 < 0)
+                x0 = 0;
+            if (x1 >= pamP->width)
+                x1 = pamP->width - 1;
+
+            readChord(dataP, cmdLineP->linear, pamP, row, x0, x1);
+        }
     }
 }
 
 
 
 static RegData *
-getColors(struct pam * const pamP,
-          CmdlineInfo  const cmdLine) {
+colorsFmImage(struct pam * const pamP,
+              CmdLineInfo  const cmdLine) {
 /*----------------------------------------------------------------------------
-  Scans image <pamP> and collects color data for the regions.
+  Color data for the regions requested by 'cmdLine' in the image described by
+  *pamP.
 -----------------------------------------------------------------------------*/
     uint      y, ymax;
-    RegData * samples;
+    RegData * samplesP;
     tuple *   row;
-    FILE *    inFile;
+    FILE *    ifP;
+
+    ifP = pm_openr(cmdLine.infile);
 
-    inFile = pm_openr(cmdLine.infile);
-    pnm_readpaminit(inFile, pamP, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
 
-    ymax = getYmax( pamP, cmdLine );
+    ymax = getYmax(pamP, cmdLine);
 
-    samples = allocRegSamples( cmdLine.regN );
-    row     = pnm_allocpamrow(pamP);
-    y       = 0;
-    for (y = 0; y <= ymax; y++)
-        processRow( row, y, pamP, &cmdLine, samples );
+    samplesP = allocRegSamples(cmdLine.regN);
+    row      = pnm_allocpamrow(pamP);
+
+    for (y = 0; y <= ymax; ++y)
+        processRow(row, y, pamP, &cmdLine, samplesP);
 
     pnm_freepamrow(row);
-    pm_close(inFile);
-    return samples;
+    pm_close(ifP);
+
+    return samplesP;
 }
 
 
 
-static char const *
-formatColor(RegData      const data,
-            CmdlineInfo  const cmdLine,
-            struct pam * const pamP,
-            tuple        const tup) {
+static const char *
+outputColorSpec(RegData      const data,
+                CmdLineInfo  const cmdLine,
+                struct pam * const pamP,
+                tuple        const tup) {
 /*----------------------------------------------------------------------------
-  Format the color of region sample <data> according to the format specified
-  in <cmdLine>.  The image <pamP> and tuple <tup> are required by the Netpbm
-  formatting functions.
+  Color of region sample 'data' formatted for output as requested by
+  'cmdLine'.
+
+  *pamP tells how to interpret 'data'.
+
+  'tup' is working space for internal use.
 -----------------------------------------------------------------------------*/
     uint l;
 
-    for (l = 0; l < 3; l++)
+    for (l = 0; l < 3; ++l)
         tup[l] = pm_gamma709(data.color[l]/data.area) * pamP->maxval;
 
     return formats[cmdLine.formatId].
@@ -447,29 +506,37 @@ formatColor(RegData      const data,
 
 
 static void
-printColors(struct pam * const pamP,
-            CmdlineInfo  const cmdLine,
-            FILE *       const outChan,
-            RegData      const regSamples[]) {
+printColors(struct pam *    const pamP,
+            CmdLineInfo     const cmdLine,
+            FILE *          const ofP,
+            const RegData * const regSamples) {
 /*----------------------------------------------------------------------------
-  Prints the colors or <regSamples> to channel <outChan> in the format
-  specified in <cmdLine>. <pamP> is required by the formatting function.
+  Print the colors regSamples[] to *ofP in the format
+  requested by 'cmdLine'. 
+
+  *pamP tells how to interpret regSamples[]
 -----------------------------------------------------------------------------*/
     char  fmt[20];
     uint  r;
     tuple tup;
 
     tup = pnm_allocpamtuple(pamP);
-    sprintf(fmt, "%%%is: %%s\n", cmdLine.maxLbLen);
-    for (r = 0; r < cmdLine.regN; r++) {
+
+    pm_snprintf(fmt, sizeof(fmt), "%%%is: %%s\n", cmdLine.maxLbLen);
+
+    for (r = 0; r < cmdLine.regN; ++r) {
         RegSpec      spec;
         RegData      data;
-        char const * color;
+        const char * color;
 
         spec  = cmdLine.regSpecs[r];
+
         data  = regSamples[r];
-        color = formatColor( data, cmdLine, pamP, tup );
-        fprintf(outChan, fmt, spec.label, color);
+
+        color = outputColorSpec(data, cmdLine, pamP, tup);
+
+        fprintf(ofP, fmt, spec.label, color);
+
         pm_strfree(color);
     }
     pnm_freepamtuple(tup);
@@ -478,21 +545,21 @@ printColors(struct pam * const pamP,
 
 
 int
-main(int argc, char const *argv[]) {
+main(int argc, const char *argv[]) {
 
     RegData *   regSamples;
-    CmdlineInfo cmdLine;
+    CmdLineInfo cmdLine;
     struct pam  pam;
 
     pm_proginit(&argc, argv);
 
-    cmdLine    = parseCommandLine(argc, argv);
+    cmdLine = parsedCommandLine(argc, argv);
 
-    regSamples = getColors(&pam, cmdLine);
+    regSamples = colorsFmImage(&pam, cmdLine);
 
     printColors(&pam, cmdLine, stdout, regSamples);
 
-    free(cmdLine.regSpecs); /* Asymmetrical: maybe write freeCommandLine() ? */
+    freeCommandLine(cmdLine);
     free(regSamples);
 
     return 0;
diff --git a/analyzer/pamsumm.c b/analyzer/pamsumm.c
index 7d2c000a..9b74e789 100644
--- a/analyzer/pamsumm.c
+++ b/analyzer/pamsumm.c
@@ -69,18 +69,18 @@ parseCommandLine(int argc, const char ** const argv,
         cmdlineP->function = FN_MIN;
     } else if (maxSpec) {
         cmdlineP->function = FN_MAX;
-    } else 
+    } else
         pm_error("You must specify one of -sum, -min, -max, or -mean");
-        
+
     if (argc-1 > 1)
         pm_error("Too many arguments (%d).  File name is the only argument.",
                  argc-1);
 
     if (argc-1 < 1)
         cmdlineP->inputFileName = "-";
-    else 
+    else
         cmdlineP->inputFileName = argv[1];
-    
+
     free(option_def);
 }
 
@@ -122,11 +122,11 @@ aggregate(struct pam *   const inpamP,
         unsigned int plane;
         for (plane = 0; plane < inpamP->depth; ++plane) {
             switch(function) {
-            case FN_ADD:  
-            case FN_MEAN: 
+            case FN_ADD:
+            case FN_MEAN:
                 accumulatorP->u.sum += tupleRow[col][plane];
             break;
-            case FN_MIN:  
+            case FN_MIN:
                 if (tupleRow[col][plane] < accumulatorP->u.min)
                     accumulatorP->u.min = tupleRow[col][plane];
                 break;
@@ -134,7 +134,7 @@ aggregate(struct pam *   const inpamP,
                 if (tupleRow[col][plane] > accumulatorP->u.min)
                     accumulatorP->u.min = tupleRow[col][plane];
                 break;
-            } 
+            }
         }
     }
 }
@@ -150,7 +150,7 @@ printSummary(struct Accum  const accumulator,
              bool          const brief) {
 
     switch (function) {
-    case FN_ADD: {  
+    case FN_ADD: {
         const char * const intro = brief ? "" : "the sum of all samples is ";
 
         if (mustNormalize)
@@ -169,7 +169,7 @@ printSummary(struct Accum  const accumulator,
     }
     break;
     case FN_MIN: {
-        const char * const intro = 
+        const char * const intro =
             brief ? "" : "the minimum of all samples is ";
 
         if (mustNormalize)
@@ -179,7 +179,7 @@ printSummary(struct Accum  const accumulator,
     }
     break;
     case FN_MAX: {
-        const char * const intro = 
+        const char * const intro =
             brief ? "" : "the maximum of all samples is ";
 
         if (mustNormalize)
@@ -221,11 +221,11 @@ main(int argc, const char *argv[]) {
         aggregate(&inpam, inputRow, cmdline.function, &accumulator);
     }
     printSummary(accumulator, (unsigned)inpam.maxval,
-                 inpam.height * inpam.width * inpam.depth, 
+                 inpam.height * inpam.width * inpam.depth,
                  cmdline.function, cmdline.normalize, cmdline.brief);
 
     pnm_freepamrow(inputRow);
     pm_close(inpam.file);
-    
+
     return 0;
 }
diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c
index 299ab6ca..c4ab3581 100644
--- a/analyzer/ppmhist.c
+++ b/analyzer/ppmhist.c
@@ -172,7 +172,7 @@ universalMaxval(pixval const maxval,
                 int    const format) {
 /*----------------------------------------------------------------------------
   A maxval that makes it impossible for a pixel to be invalid in an image that
-  states it maxval as 'maxval' and has format 'format'.
+  states its maxval as 'maxval' and has format 'format'.
 
   E.g. in a one-byte-per-sample image, it's not possible to read a sample
   value greater than 255, so a maxval of 255 makes it impossible for a sample
diff --git a/converter/other/fiasco/lib/error.c b/converter/other/fiasco/lib/error.c
index 08291ce0..394f896f 100644
--- a/converter/other/fiasco/lib/error.c
+++ b/converter/other/fiasco/lib/error.c
@@ -3,29 +3,19 @@
  *
  *  Written by:		Stefan Frank
  *			Ullrich Hafner
- *  
+ *
  *  Credits:	Modelled after variable argument routines from Jef
- *		Poskanzer's pbmplus package. 
+ *		Poskanzer's pbmplus package.
  *
  *  This file is part of FIASCO (Fractal Image And Sequence COdec)
  *  Copyright (C) 1994-2000 Ullrich Hafner
-
-    "int dummy = " change to int dummy; dummy =" for Netpbm to avoid 
-    unused variable warning.
-
- */
-
-/*
- *  $Date: 2000/06/14 20:49:37 $
- *  $Author: hafner $
- *  $Revision: 5.1 $
- *  $State: Exp $
  */
 
 #define _ERROR_C
 
 #include "config.h"
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <errno.h>
 
@@ -47,7 +37,7 @@
 /*****************************************************************************
 
 			     local variables
-  
+
 *****************************************************************************/
 
 static fiasco_verbosity_e  verboselevel  = FIASCO_SOME_VERBOSITY;
@@ -60,63 +50,71 @@ jmp_buf env;
 /*****************************************************************************
 
 			       public code
-  
+
 *****************************************************************************/
 
 void
-set_error(const char *format, ...) {
+set_error(const char * const format, ...) {
 /*----------------------------------------------------------------------------
   Set error text to given string.
 -----------------------------------------------------------------------------*/
     va_list      args;
     unsigned     len;
+    bool         error;
     const char * str;
 
-    len = 0;  /* initial value */
-    str = format;  /* initial value */
-
     VA_START (args, format);
 
-    len = strlen (format);
-    while ((str = strchr (str, '%'))) {
-        ++str;
-        if (*str == 's') {
-            char * const vstring = va_arg (args, char *);
-            len += strlen(vstring);
-        } else if (*str == 'd') {
-            (void)va_arg(args, int);
-            len += 10;
-        } else if (*str == 'c') {
-            (void)va_arg(args, int);
-            len += 1;
-        } else
-            return;
-        ++str;
+    /* Compute how long the error text will be: 'len' */
+
+    for (len = strlen(format), str = &format[0], error = false;
+         *str && !error; ) {
+
+        str = strchr(str, '%');
+
+        if (*str) {
+            ++str; /* Move past % */
+            if (*str == 's') {
+                char * const vstring = va_arg (args, char *);
+                len += strlen(vstring);
+            } else if (*str == 'd') {
+                (void)va_arg(args, int);
+                len += 10;
+            } else if (*str == 'c') {
+                (void)va_arg(args, int);
+                len += 1;
+            } else
+                error = true;
+            if (!error)
+                ++str;
+        }
     }
     va_end(args);
 
-    VA_START(args, format);
+    if (!error) {
+        VA_START(args, format);
 
-    if (error_message)
-        Free(error_message);
-    error_message = Calloc(len, sizeof (char));
-   
-    vsprintf(error_message, format, args);
+        if (error_message)
+            Free(error_message);
+        error_message = Calloc(len, sizeof (char));
 
-    va_end(args);
+        vsprintf(error_message, format, args);
+
+        va_end(args);
+    }
 }
 
 
 
 void
-error(const char *format, ...) {
+error(const char * const format, ...) {
 /*----------------------------------------------------------------------------
   Set error text to given string.
-  -----------------------------------------------------------------------------*/
+-----------------------------------------------------------------------------*/
     va_list      args;
     unsigned     len;
     const char * str;
-   
+
     len = 0; /* initial value */
     str = &format[0];  /* initial value */
 
@@ -141,7 +139,7 @@ error(const char *format, ...) {
             exit(1);
 #endif
         };
-      
+
         ++str;
     }
     va_end(args);
@@ -151,11 +149,11 @@ error(const char *format, ...) {
     if (error_message)
         Free(error_message);
     error_message = Calloc(len, sizeof (char));
-   
+
     vsprintf(error_message, format, args);
 
     va_end(args);
-   
+
 #if HAVE_SETJMP_H
     longjmp(env, 1);
 #else
@@ -166,117 +164,123 @@ error(const char *format, ...) {
 
 
 const char *
-fiasco_get_error_message (void)
-/*
- *  Return value:
- *	Last error message of FIASCO library.
- */
-{
-   return error_message ? error_message : "";
+fiasco_get_error_message(void) {
+/*----------------------------------------------------------------------------
+  Last error message of FIASCO library.
+-----------------------------------------------------------------------------*/
+    return error_message ? error_message : "";
 }
 
+
+
 const char *
-get_system_error (void)
-{
-   return strerror (errno);
+get_system_error(void) {
+    return strerror(errno);
 }
 
+
+
 void
-file_error (const char *filename)
-/*
- *  Print file error message and exit.
- *
- *  No return value.
- */
-{
-   error ("File `%s': I/O Error - %s.", filename, get_system_error ());
+file_error(const char * const filename) {
+/*----------------------------------------------------------------------------
+   Print file error message and exit.
+-----------------------------------------------------------------------------*/
+    error("File `%s': I/O Error - %s.", filename, get_system_error ());
 }
 
-void 
-warning (const char *format, ...)
-/*
- *  Issue a warning and continue execution.
- *
- *  No return value.
- */
-{
-   va_list	args;
 
-   VA_START (args, format);
 
-   if (verboselevel == FIASCO_NO_VERBOSITY)
-      return;
-	
-   fprintf (stderr, "Warning: ");
-   vfprintf (stderr, format, args);
-   fputc ('\n', stderr);
+void
+warning(const char * const format, ...) {
+/*----------------------------------------------------------------------------
+  Issue a warning.
+-----------------------------------------------------------------------------*/
+    va_list	args;
+
+    VA_START (args, format);
 
-   va_end (args);
+    if (verboselevel == FIASCO_NO_VERBOSITY) {
+        /* User doesn't want warnings */
+    } else {
+        fprintf (stderr, "Warning: ");
+        vfprintf (stderr, format, args);
+        fputc ('\n', stderr);
+    }
+    va_end (args);
 }
 
-void 
-message (const char *format, ...)
-/*
- *  Print a message to stderr.
- */
-{
-   va_list args;
 
-   VA_START (args, format);
 
-   if (verboselevel == FIASCO_NO_VERBOSITY)
-      return;
+void
+message(const char * const format, ...) {
+/*----------------------------------------------------------------------------
+   Print a message to Standard Error
+-----------------------------------------------------------------------------*/
+    va_list args;
 
-   vfprintf (stderr, format, args);
-   fputc ('\n', stderr);
-   va_end (args);
+    VA_START (args, format);
+
+    if (verboselevel == FIASCO_NO_VERBOSITY) {
+        /* User doesn't want messages */
+    } else {
+        vfprintf (stderr, format, args);
+        fputc ('\n', stderr);
+    }
+    va_end (args);
 }
 
-void 
-debug_message (const char *format, ...)
-/*
- *  Print a message to stderr.
- */
-{
-   va_list args;
 
-   VA_START (args, format);
 
-   if (verboselevel < FIASCO_ULTIMATE_VERBOSITY)
-      return;
+void
+debug_message(const char * const format, ...) {
+/*----------------------------------------------------------------------------
+   Print a message to Standard Error if debug messages are enabled.
+-----------------------------------------------------------------------------*/
+    va_list args;
 
-   fprintf (stderr, "*** ");
-   vfprintf (stderr, format, args);
-   fputc ('\n', stderr);
-   va_end (args);
+    VA_START (args, format);
+
+    if (verboselevel >= FIASCO_ULTIMATE_VERBOSITY) {
+        fprintf (stderr, "*** ");
+        vfprintf (stderr, format, args);
+        fputc ('\n', stderr);
+    }
+    va_end (args);
 }
 
-void
-info (const char *format, ...)
-/*
- *  Print a message to stderr. Do not append a newline.
- */
-{
-   va_list args;
 
-   VA_START (args, format);
 
-   if (verboselevel == FIASCO_NO_VERBOSITY)
-      return;
+void
+info(const char * const format, ...) {
+/*----------------------------------------------------------------------------
+   Print a message to stderr. Do not append a newline.
+-----------------------------------------------------------------------------*/
+    va_list args;
+
+    VA_START (args, format);
 
-   vfprintf (stderr, format, args);
-   fflush (stderr);
-   va_end (args);
+    if (verboselevel == FIASCO_NO_VERBOSITY) {
+        /* User doesn't want informational messages */
+    } else {
+        vfprintf (stderr, format, args);
+        fflush (stderr);
+    }
+    va_end (args);
 }
 
+
+
 void
-fiasco_set_verbosity (fiasco_verbosity_e level)
-{
+fiasco_set_verbosity(fiasco_verbosity_e const level) {
    verboselevel = level;
 }
 
+
+
 fiasco_verbosity_e
-fiasco_get_verbosity (void)
-{
+fiasco_get_verbosity(void) {
    return verboselevel;
 }
+
+
+
diff --git a/doc/HISTORY b/doc/HISTORY
index cf618983..7076a357 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,17 +4,34 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-18.09.08 BJH  Release 10.83.02
+18.09.29 BJH  Release 10.84.00
 
-              pamgetcolor: fix bug: gets color of only the top half of a
-              region.
+              Add pamaltsat.  Thanks Anton Shepelev <anton.txt@gmail.com>.
+
+              Add pamtris.  Thanks Lucas Brunno Luna
+              <lucaslunar32@hotmail.com>.
+
+              libpbmfont, pbmtext: fix bugs with BDF file lines with
+              insufficient number of fields.  Unknown effect.
+
+              pbmtext: -wchar works with built-in fonts.
 
-18.07.07 BJH  Release 10.83.01
+              pbmtext: improved -verbose information about BDF fonts:
+              include CHARSET_REGISTRY, CHARSET_ENCODING.
+
+              libnetpbm font facilities: built-in fonts work with wide
+              characters.
 
               pbmtext; libnetpbm BDF font processing: fix invalid memory
               reference when BDF font file has invalid syntax.  Broken
               in primordial Netpbm, ca 1993.
 
+              pamgetcolor: fix bug: gets color of only the top half of a
+              region.
+
+              pnmfiasco, fiascotopnm: Fix trivial memory leak.  Always broken
+              (programs were new in Netpbm 9.6, July 2000).
+
 18.06.30 BJH  Release 10.83.00
 
               Add pamlevels.  Thanks Anton Shepelev <anton.txt@gmail.com>.
diff --git a/editor/Makefile b/editor/Makefile
index d7d71bf6..159f8ea7 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -16,7 +16,7 @@ SUBDIRS = pamflip specialty
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamaddnoise pambackground pamcomp pamcut \
+PORTBINARIES = pamaddnoise pamaltsat pambackground pamcomp pamcut \
 	       pamdice pamditherbw pamedge \
 	       pamenlarge \
 	       pamfunc pamlevels pammasksharpen \
diff --git a/editor/pamaltsat.c b/editor/pamaltsat.c
new file mode 100644
index 00000000..6d9b91e0
--- /dev/null
+++ b/editor/pamaltsat.c
@@ -0,0 +1,535 @@
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+#include <pam.h>
+#include <pm_gamma.h>
+#include <nstring.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef unsigned int  uint;
+typedef unsigned char uchar;
+
+typedef enum {MLog,  MSpectrum } Method; /* method identifiers */
+
+typedef struct {
+    Method       method;
+    const char * name;
+} MethodTableEntry;
+
+MethodTableEntry methodTable[] = {
+    {MLog,      "log"},
+    {MSpectrum, "spectrum"}
+};
+
+/* Command-line arguments parsed: */
+typedef struct {
+    const char * inputFileName;
+        /* name of the input file. "-" for stdin */
+    float        strength;
+    uint         linear;
+    Method       method;
+} CmdlineInfo;
+
+
+
+
+static Method
+methodFmNm(const char * const methodNm) {
+/*----------------------------------------------------------------------------
+   The method of saturation whose name is 'methodNm'
+-----------------------------------------------------------------------------*/
+    uint  i;
+    bool found;
+    Method method;
+
+    for (i = 0, found = false; i < ARRAY_SIZE(methodTable) && !found; ++i) {
+        if (streq(methodNm, methodTable[i].name)) {
+            found = true;
+            method = methodTable[i].method;
+        }
+    }
+
+    if (!found) {
+        /* Issue error message and abort */
+        char * methodList;
+        uint   methodListLen;
+        uint   i;
+
+        /* Allocate a buffer to store the list of known saturation methods: */
+        for (i = 0, methodListLen = 0; i < ARRAY_SIZE(methodTable); ++i)
+            methodListLen += strlen(methodTable[i].name) + 2;
+
+        MALLOCARRAY(methodList, methodListLen);
+
+        if (!methodList)
+            pm_error("Failed to allocate memory for %lu saturation "
+                     "method names", (unsigned long)ARRAY_SIZE(methodTable));
+
+        /* Fill the list of methods: */
+        for (i = 0, methodList[0] = '\0'; i < ARRAY_SIZE(methodTable); ++i) {
+            if (i > 0)
+                strcat(methodList, ", ");
+            strcat(methodList, methodTable[i].name);
+        }
+
+        pm_error("Unknown saturation method: '%s'. Known methods are: %s",
+                 methodNm, methodList);
+
+        free(methodList);
+    }
+    return method;
+}
+
+
+
+static CmdlineInfo
+parsedCommandLine(int argc, const char ** argv) {
+
+    CmdlineInfo cmdline;
+    optStruct3 opt;
+
+    uint option_def_index;
+    uint methodSpec, strengthSpec, linearSpec;
+    const char * method;
+
+    optEntry * option_def;
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "method",   OPT_STRING, &method,            &methodSpec,   0);
+    OPTENT3(0, "strength", OPT_FLOAT,  &cmdline.strength,  &strengthSpec, 0);
+    OPTENT3(0, "linear",   OPT_FLAG,   &cmdline.linear,    &linearSpec,   0);
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum   = 0;
+
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (methodSpec)
+        cmdline.method = methodFmNm(method);
+    else
+        cmdline.method = MSpectrum;
+
+    if (!strengthSpec)
+        pm_error("You must specify -strength");
+
+    if (!linearSpec)
+        cmdline.linear = 0;
+
+    if (argc-1 < 1)
+        cmdline.inputFileName = "-";
+    else {
+        cmdline.inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("Program takes at most one argument:  file name");
+    }
+
+    free(option_def);
+
+    return cmdline;
+}
+
+
+
+typedef struct {
+    double _[3];
+} TupleD;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  Information about a color sample in linear format
+-----------------------------------------------------------------------------*/
+    TupleD sample;    /* layer intensities                        */
+    double maxval;    /* the highest layer intensity              */
+    uint   maxl;      /* index of that layer                      */
+    uint   minl;      /* index of the layer with lowest intensity */
+    double intensity; /* total sample intensity                   */
+} LinSampleInfo;
+
+/* ---------------------------- Binary search ------------------------------ */
+/*                    ( a minimal drop-in implementation )                   */
+
+/* Function to search, where <data> is an arbitrary user-supplied parameter */
+typedef double (binsearchFunc)(double       const x,
+                               const void * const data);
+
+/* The binary-search function. Returns such <x> from [<min>, <max>] that
+   monotonically increasing function func(x, data) equals <value> within
+   precision <prec>. <dataP> is an arbitary parameter to <func>. */
+static double
+binsearch(binsearchFunc       func,
+          const void  * const dataP,
+          double        const prec,
+          double        const minArg,
+          double        const maxArg,
+          double        const value
+         ) {
+    double x;
+    double min, max;
+    bool found;
+
+    for (min = minArg, max = maxArg, found = false; !found;) {
+
+        x = (min + max) / 2;
+        {
+            double const f = func(x, dataP);
+
+            if ((fabs(f - value)) < prec)
+                found = true;
+            else {
+                assert(f != value);
+
+                if (f > value) max = x;
+                else           min = x;
+            }
+        }
+    }
+    return x;
+}
+
+/* ------------- Utilities not specific to saturation methods -------------- */
+
+/* Y chromaticities in Rec.709: R       G       B  */
+static double const yCoeffs[3]  = {0.3333, 0.6061, 0.0606};
+
+static void
+applyRatio(TupleD * const tupP,
+           double   const ratio) {
+/*----------------------------------------------------------------------------
+  Multiply the components of tuple *tupP by coefficient 'ratio'.
+-----------------------------------------------------------------------------*/
+    uint c;
+
+    for (c = 0; c < 3; ++c)
+        tupP->_[c] = tupP->_[c] * ratio;
+}
+
+
+
+static void
+getTupInfo(tuplen          const tup,
+           bool            const linear,
+           LinSampleInfo * const siP) {
+/*----------------------------------------------------------------------------
+  Convert PBM tuple <tup> into linear form with double precision siP->sample
+  and obtain also additional information required for further processing.
+  Return the result as *siP.
+-----------------------------------------------------------------------------*/
+    uint i;
+    double minval;
+
+    minval         = 1.1;
+    siP->intensity = 0;
+    siP->maxval    = 0.0;
+    siP->maxl      = 0;
+
+    for (i = 0; i < 3; ++i) {
+        double linval;
+        if (!linear)
+            linval = pm_ungamma709(tup[i]);
+        else
+            linval = tup[i];
+
+        siP->sample._[i] = linval;
+
+        if (linval > siP->maxval) {
+            siP->maxval = linval;
+            siP->maxl   = i;
+        }
+        if (linval < minval) {
+           siP->minl = i;
+           minval    = linval;
+        }
+        siP->intensity += linval * yCoeffs[i];
+    }
+}
+
+/* ------------------------ Logarithmic saturation ------------------------- */
+
+/* Method and algorithm by Anton Shepelev.  */
+
+static void
+tryLogSat(double          const sat,
+          LinSampleInfo * const siP,
+          TupleD *        const tupsatP,
+          double *        const intRatioP,
+          double *        const highestP) {
+/*----------------------------------------------------------------------------
+  Try to increase the saturation of siP->sample by a factor 'sat' and return
+  the result as *tupsatP.
+
+  Also return as *intRatioP the ratio of intensities of 'tupin' and
+  siP->sample.
+
+  Return as *highestP the highest component the saturated color would have if
+  normalized to intensity siP->intensity.
+
+  If the return value exceeds unity saturation cannot be properly increased by
+  the required factor.
+-----------------------------------------------------------------------------*/
+    uint   c;
+    double intSat;
+
+    for (c = 0, intSat = 0.0; c < 3; ++c) {
+        tupsatP->_[c] = pow(siP->sample._[c], sat);
+        intSat       = intSat + tupsatP->_[c] * yCoeffs[c];
+    }
+
+    {
+        double const intRatio = siP->intensity / intSat;
+
+        double const maxComp = tupsatP->_[siP->maxl] * intRatio;
+
+        *intRatioP = intRatio;
+        *highestP = maxComp;
+    }
+}
+
+
+
+/* Structure for the binary search of maximum saturation: */
+typedef struct {
+    LinSampleInfo * siP;
+        /* original color with precalculated information  */
+    TupleD *        tupsatP;
+        /* saturated color                            */
+    double *        intRatioP;
+        /* ratio of orignal and saturated intensities */
+} MaxLogSatInfo;
+
+
+
+static binsearchFunc binsearchMaxLogSat;
+
+static double
+binsearchMaxLogSat(double       const x,
+                   const void * const dataP) {
+/*----------------------------------------------------------------------------
+  Target function for the generic binary search routine, for the finding
+  of the maximum possible saturation of a given color. 'dataP' shall point
+  to a MaxSatInfo structure.
+-----------------------------------------------------------------------------*/
+    const MaxLogSatInfo * const infoP = dataP;
+
+    double highest;
+
+    tryLogSat(x, infoP->siP, infoP->tupsatP, infoP->intRatioP, &highest);
+
+    return highest;
+}
+
+
+
+static void
+getMaxLogSat(LinSampleInfo * const siP,
+             TupleD        * const tupsatP,
+             double        * const intRatioP,
+             double          const upperLimit
+          ) {
+/*  Saturates the color <siP->sample> as much as possible and stores the result
+    in <tupsatP>, which must be multiplied by <*intRatioP> in order to restore
+    the intensity of the original color. The range of saturation search is
+    [1.0..<upperlimit>]. */
+    const double PREC = 0.00001; /* precision of binary search */
+
+    MaxLogSatInfo info;
+
+    info.siP       = siP;
+    info.tupsatP   = tupsatP;
+    info.intRatioP = intRatioP;
+
+/*  Discarding return value (maximum saturation) because upon completion of
+    binsearch() info.tupsatP will contain the saturated color. The target value
+    of maximum channel intensity is decreased by PREC in order to avoid
+    overlow. */
+    binsearch(binsearchMaxLogSat, &info, PREC, 1.0, upperLimit, 1.0 - PREC);
+}
+
+
+
+static void
+saturateLog(LinSampleInfo* const siP,
+            double         const sat,
+            TupleD*        const tupsatP) {
+/*----------------------------------------------------------------------------
+  Saturate linear tuple *siP using the logarithmic saturation method.
+-----------------------------------------------------------------------------*/
+    double intRatio;
+        /* ratio of original and saturated intensities */
+    double maxlValSat;
+        /* maximum component intensity in the saturated sample */
+
+    tryLogSat(sat, siP, tupsatP, &intRatio, &maxlValSat);
+
+    /* if we cannot saturate siP->sample by 'sat', use the maximum possible
+       saturation
+    */
+    if (maxlValSat > 1.0)
+        getMaxLogSat(siP, tupsatP, &intRatio, sat);
+
+    /* restore the original intensity: */
+    applyRatio(tupsatP, intRatio);
+}
+
+
+
+/* ------------------------- Spectrum saturation --------------------------- */
+
+/* Method and algorithm by Anton Shepelev.  */
+
+static void
+saturateSpectrum(LinSampleInfo * const siP,
+                 double          const sat,
+                 TupleD *        const tupsatP) {
+/*----------------------------------------------------------------------------
+  Saturate linear tuple *siP using the Spectrum saturation method.
+-----------------------------------------------------------------------------*/
+    double k;
+    double * sample;
+
+    sample = siP->sample._; /* short-cut to the input sample data */
+
+    if (sample[siP->minl] == sample[siP->maxl])
+        k = 1.0; /* Cannot saturate a neutral sample */
+    else {
+        double const km1 =
+            (1.0 - siP->intensity)/(siP->maxval - siP->intensity);
+            /* Maximum saturation factor that keeps maximum layer intesity
+               within range
+            */
+        double const km2 = siP->intensity/(siP->intensity - sample[siP->minl]);
+            /* Maximum saturation factor  that keeps minimum layer intesity
+               within range
+            */
+
+        /* To satisfy both constraints, choose the strictest: */
+        double const km = km1 > km2 ? km2 : km1;
+
+        /* Ensure the saturation factor does not exceed the maximum
+           possible value:
+        */
+        k = sat < km ? sat : km;
+    }
+
+    {
+        /* Initialize the resulting sample with the input value */
+        uint i;
+        for (i = 0; i < 3; ++i)
+            tupsatP->_[i] = sample[i];
+    }
+
+    applyRatio(tupsatP, k); /* apply the saturation factor */
+
+    {
+         /* restore the original intensity */
+        uint i;
+        for (i = 0; i < 3; ++i)
+            tupsatP->_[i] = tupsatP->_[i] - siP->intensity * (k - 1.0);
+    }
+}
+
+
+
+/* --------------------- General saturation algorithm ---------------------- */
+
+static void
+saturateTup(Method const method,
+            double const sat,
+            bool   const linear,
+            tuplen const tup) {
+/*----------------------------------------------------------------------------
+  Saturate black and white tuple 'tup'
+-----------------------------------------------------------------------------*/
+    LinSampleInfo si;
+
+    getTupInfo(tup, linear, &si);
+
+    if (sat < 1.0 ||  /* saturation can always be decresed */
+        si.maxval < 1.0 ) { /* there is room for increase        */
+
+        TupleD tupsat;
+
+        /* Dispatch saturation methods:
+           (There seems too little benefit in using a table of
+           function pointers, so a manual switch should suffice)
+        */
+        switch (method) {
+            case MLog:      saturateLog     (&si, sat, &tupsat); break;
+            case MSpectrum: saturateSpectrum(&si, sat, &tupsat); break;
+        }
+
+        /* Put the processed tuple back in the tuple row, gamma-adjusting it
+           if required.
+        */
+        {
+            uint i;
+
+            for (i = 0; i < 3; ++i)
+                tup[i] = linear ? tupsat._[i] : pm_gamma709(tupsat._[i]);
+        }
+    }
+}
+
+
+
+static void
+pamaltsat(CmdlineInfo const cmdline,
+          FILE *      const ofP) {
+
+    struct pam inPam, outPam;
+    tuplen *   tuplerown;
+    FILE *     ifP;
+    uint       row;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    outPam = inPam;
+    outPam.file = ofP;
+
+    tuplerown = pnm_allocpamrown(&inPam);
+
+    pnm_writepaminit(&outPam);
+
+    for (row = 0; row < inPam.height; ++row) {
+        pnm_readpamrown(&inPam, tuplerown);
+
+        if (inPam.depth >= 3) {
+            uint col;
+
+            for (col = 0; col < inPam.width; ++col)
+                saturateTup(cmdline.method, cmdline.strength, cmdline.linear,
+                            tuplerown[col]);
+        }
+
+        pnm_writepamrown(&outPam, tuplerown);
+    }
+
+    pnm_freepamrown(tuplerown);
+    pm_close(ifP);
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
+
+    cmdline = parsedCommandLine(argc, argv);
+
+    pamaltsat(cmdline, stdout);
+
+    return 0;
+}
+
+
+
diff --git a/editor/pamscale.c b/editor/pamscale.c
index 433d5cee..27902dbf 100644
--- a/editor/pamscale.c
+++ b/editor/pamscale.c
@@ -24,6 +24,7 @@
 
 #define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
+#include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <math.h>
@@ -359,23 +360,23 @@ typedef struct {
 
 
 static filter Filters[] = {
-    { "point",     filter_box,       radius_point,     FALSE },
-    { "box",       filter_box,       radius_box,       FALSE },
-    { "triangle",  filter_triangle,  radius_triangle,  FALSE },
-    { "quadratic", filter_quadratic, radius_quadratic, FALSE },
-    { "cubic",     filter_cubic,     radius_cubic,     FALSE },
-    { "catrom",    filter_catrom,    radius_catrom,    FALSE },
-    { "mitchell",  filter_mitchell,  radius_mitchell,  FALSE },
-    { "gauss",     filter_gauss,     radius_gauss,     FALSE },
-    { "sinc",      filter_sinc,      radius_sinc,      TRUE  },
-    { "bessel",    filter_bessel,    radius_bessel,    TRUE  },
-    { "hanning",   filter_hanning,   radius_hanning,   FALSE },
-    { "hamming",   filter_hamming,   radius_hamming,   FALSE },
-    { "blackman",  filter_blackman,  radius_blackman,  FALSE },
-    { "kaiser",    filter_kaiser,    radius_kaiser,    FALSE },
-    { "normal",    filter_normal,    radius_normal,    FALSE },
-    { "hermite",   filter_hermite,   radius_hermite,   FALSE },
-    { "lanczos",   filter_lanczos,   radius_lanczos,   FALSE },
+    { "point",     filter_box,       radius_point,     false },
+    { "box",       filter_box,       radius_box,       false },
+    { "triangle",  filter_triangle,  radius_triangle,  false },
+    { "quadratic", filter_quadratic, radius_quadratic, false },
+    { "cubic",     filter_cubic,     radius_cubic,     false },
+    { "catrom",    filter_catrom,    radius_catrom,    false },
+    { "mitchell",  filter_mitchell,  radius_mitchell,  false },
+    { "gauss",     filter_gauss,     radius_gauss,     false },
+    { "sinc",      filter_sinc,      radius_sinc,      true  },
+    { "bessel",    filter_bessel,    radius_bessel,    true  },
+    { "hanning",   filter_hanning,   radius_hanning,   false },
+    { "hamming",   filter_hamming,   radius_hamming,   false },
+    { "blackman",  filter_blackman,  radius_blackman,  false },
+    { "kaiser",    filter_kaiser,    radius_kaiser,    false },
+    { "normal",    filter_normal,    radius_normal,    false },
+    { "hermite",   filter_hermite,   radius_hermite,   false },
+    { "lanczos",   filter_lanczos,   radius_lanczos,   false },
    { NULL },
 };
 
@@ -452,12 +453,12 @@ lookupFilterByName(const char * const filtername,
     unsigned int i;
     bool found;
 
-    found = FALSE;  /* initial assumption */
+    found = false;  /* initial assumption */
 
     for (i=0; Filters[i].name; ++i) {
         if (strcmp(filtername, Filters[i].name) == 0) {
             *filterP = Filters[i];
-            found = TRUE;
+            found = true;
         }
     }
     if (!found) {
@@ -641,7 +642,8 @@ parseCommandLine(int argc,
     int xsize, ysize, pixels;
     int reduce;
     float xscale, yscale;
-    const char *filterOpt, *window;
+    const char * filterOpt;
+    const char * window;
     unsigned int filterSpec, windowSpec;
     unsigned int xscaleSpec, yscaleSpec, xsizeSpec, ysizeSpec;
     unsigned int pixelsSpec, reduceSpec;
@@ -667,8 +669,8 @@ parseCommandLine(int argc,
     OPTENT3(0, "linear",    OPT_FLAG,    NULL,       &cmdlineP->linear,    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 */
+    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. */
@@ -1440,7 +1442,7 @@ static bool
 scanbufContainsTheRows(SCAN  const scanbuf,
                        WLIST const rowWeights) {
 /*----------------------------------------------------------------------------
-   Return TRUE iff scanbuf 'scanbuf' contains every row mentioned in
+   Return true iff scanbuf 'scanbuf' contains every row mentioned in
    'rowWeights'.
 
    It might contain additional rows besides.
@@ -1448,7 +1450,7 @@ scanbufContainsTheRows(SCAN  const scanbuf,
     bool missingRow;
     unsigned int i;
 
-    for (i = 0, missingRow = FALSE;
+    for (i = 0, missingRow = false;
          i < rowWeights.nWeight && !missingRow;
         ++i) {
         unsigned int const inputRow = rowWeights.Weight[i].position;
@@ -1461,7 +1463,7 @@ scanbufContainsTheRows(SCAN  const scanbuf,
             /* Nope, this slot has some other row or no row at all.
                So the row we're looking for isn't in the scanbuf.
             */
-            missingRow = TRUE;
+            missingRow = true;
         }
     }
     return !missingRow;
@@ -1569,7 +1571,7 @@ resample(struct pam *     const inpamP,
         /* Output all the rows we can make out of the current contents of
            the scanbuf.  Might be none.
         */
-        needMoreInput = FALSE;  /* initial assumption */
+        needMoreInput = false;  /* initial assumption */
         while (outputRow < outpamP->height && !needMoreInput) {
             WLIST const rowWeights = vertWeight[outputRow];
                 /* The description of what makes up our current output row;
@@ -1583,7 +1585,7 @@ resample(struct pam *     const inpamP,
                                       horizWeight, line, weight);
                 ++outputRow;
             } else
-                needMoreInput = TRUE;
+                needMoreInput = true;
         }
     }
 
@@ -2213,7 +2215,7 @@ main(int argc, const char **argv ) {
 
     ifP = pm_openr(cmdline.inputFileName);
 
-    eof = FALSE;
+    eof = false;
     while (!eof) {
         pamscale(ifP, stdout, cmdline);
         pnm_nextimage(ifP, &eof);
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
index a7aba2e7..6ee6897b 100644
--- a/editor/ppmbrighten.c
+++ b/editor/ppmbrighten.c
@@ -31,11 +31,11 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -68,9 +68,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. */
-    
+
     if (saturationSpec) {
         if (saturationOpt < -100)
             pm_error("Saturation reduction cannot be more than 100%%.  "
@@ -106,17 +106,17 @@ mod(int const dividend, unsigned int const divisor) {
 
     if (remainder < 0)
         return divisor + remainder;
-    else 
+    else
         return (unsigned int) remainder;
 }
 
 
 
-static void 
+static void
 RGBtoHSV(pixel          const color,
          pixval         const maxval,
-         unsigned int * const hP, 
-         unsigned int * const sP, 
+         unsigned int * const hP,
+         unsigned int * const sP,
          unsigned int * const vP) {
 
     unsigned int const R = (MULTI * PPM_GETR(color) + maxval - 1) / maxval;
@@ -161,12 +161,12 @@ RGBtoHSV(pixel          const color,
 
 
 static void
-HSVtoRGB(unsigned int   const h, 
-         unsigned int   const s, 
-         unsigned int   const v, 
+HSVtoRGB(unsigned int   const h,
+         unsigned int   const s,
+         unsigned int   const v,
          pixval         const maxval,
          pixel *        const colorP) {
-    
+
     unsigned int R, G, B;
 
     if (s == 0) {
@@ -222,7 +222,7 @@ HSVtoRGB(unsigned int   const h,
             pm_error("Invalid H value passed to HSVtoRGB: %u/%u", h, MULTI);
         }
     }
-    PPM_ASSIGN(*colorP, 
+    PPM_ASSIGN(*colorP,
                (R * maxval) / MULTI,
                (G * maxval) / MULTI,
                (B * maxval) / MULTI);
@@ -267,7 +267,7 @@ getMinMax(FILE *         const ifP,
 
 
 int
-main(int argc, char * argv[]) {
+main(int argc, const char ** argv) {
 
     struct cmdlineInfo cmdline;
     FILE * ifP;
@@ -276,7 +276,7 @@ main(int argc, char * argv[]) {
     pixval maxval;
     int rows, cols, format, row;
 
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -311,7 +311,7 @@ main(int argc, char * argv[]) {
             unsigned int H, S, V;
 
             RGBtoHSV(pixelrow[col], maxval, &H, &S, &V);
-            
+
             if (cmdline.normalize) {
                 V -= minValue;
                 V = (V * MULTI) /
@@ -335,3 +335,6 @@ main(int argc, char * argv[]) {
     */
     return 0;
 }
+
+
+
diff --git a/editor/ppmcolormask.c b/editor/ppmcolormask.c
index d9f68b68..3812ac86 100644
--- a/editor/ppmcolormask.c
+++ b/editor/ppmcolormask.c
@@ -66,10 +66,10 @@ parseColorOpt(const char *         const colorOpt,
     char * colorOptWork;
     char * cursor;
     bool eol;
-    
+
     colorOptWork = strdup(colorOpt);
     cursor = &colorOptWork[0];
-    
+
     eol = FALSE;    /* initial value */
     colorCt = 0;    /* initial value */
     while (!eol && colorCt < ARRAY_SIZE(cmdlineP->maskColor)) {
@@ -150,7 +150,7 @@ parseCommandLine(int argc, const char ** argv,
                 cmdlineP->inputFilename = "-";  /* he wants stdin */
             else if (argc-1 == 2)
                 cmdlineP->inputFilename = argv[2];
-            else 
+            else
                 pm_error("Too many arguments.  The only arguments accepted "
                          "are the mask color and optional input file name");
         }
@@ -191,7 +191,7 @@ isBkColor(tuple        const comparator,
     /* TODO: keep a cache of the bk color for each color in
        a colorhash_table.
     */
-    
+
     assert(pamP->depth >= 3);
 
     PPM_ASSIGN(comparatorPixel,
@@ -281,7 +281,7 @@ main(int argc, const char *argv[]) {
                 if (colorIsInSet(inputRow[col], &inPam, cmdline)) {
                     maskRow[col][0] = PAM_BLACK;
                     ++numPixelsMasked;
-                } else 
+                } else
                     maskRow[col][0] = PAM_BW_WHITE;
             }
             pnm_writepamrow(&outPam, maskRow);
diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c
index b2ed39ca..c76489c9 100644
--- a/editor/ppmdraw.c
+++ b/editor/ppmdraw.c
@@ -1,5 +1,5 @@
 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
-#define _XOPEN_SOURCE 500 
+#define _XOPEN_SOURCE 500
    /* Make sure M_PI is in math.h, strdup is in string.h */
 #define _BSD_SOURCE      /* Make sure strdup is in string.h (alternate) */
 
@@ -50,7 +50,7 @@ parseCommandLine (int argc, const char ** argv,
                   struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -84,7 +84,7 @@ 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. */
-    
+
     if (!scriptSpec && !scriptfileSpec)
         pm_error("You must specify either -script or -scriptfile");
 
@@ -282,7 +282,7 @@ struct drawCommand {
 
 static void
 freeDrawCommand(const struct drawCommand * const commandP) {
-    
+
     switch (commandP->verb) {
     case VERB_SETPOS:
         break;
@@ -313,7 +313,7 @@ freeDrawCommand(const struct drawCommand * const commandP) {
         pm_strfree(commandP->u.textArg.text);
         break;
     }
-    
+
 
     free((void *) commandP);
 }
@@ -361,21 +361,21 @@ doFilledCircle(pixel **                   const pixels,
     struct fillobj * fhP;
 
     fhP = ppmd_fill_create();
-            
+
     ppmd_circle(pixels, cols, rows, maxval,
                 commandP->u.circleArg.cx,
                 commandP->u.circleArg.cy,
                 commandP->u.circleArg.radius,
                 ppmd_fill_drawproc,
                 fhP);
-            
+
     ppmd_fill(pixels, cols, rows, maxval,
               fhP,
               PPMD_NULLDRAWPROC,
               &drawStateP->color);
 
     ppmd_fill_destroy(fhP);
-} 
+}
 
 
 
@@ -386,7 +386,7 @@ doTextHere(pixel **                   const pixels,
            pixval                     const maxval,
            const struct drawCommand * const commandP,
            struct drawState *         const drawStateP) {
-    
+
     ppmd_text(pixels, cols, rows, maxval,
               drawStateP->currentPos.x,
               drawStateP->currentPos.y,
@@ -395,14 +395,14 @@ doTextHere(pixel **                   const pixels,
               commandP->u.textArg.text,
               PPMD_NULLDRAWPROC,
               &drawStateP->color);
-    
+
     {
         int left, top, right, bottom;
-        
+
         ppmd_text_box(commandP->u.textArg.height, 0,
                       commandP->u.textArg.text,
                       &left, &top, &right, &bottom);
-        
+
 
         drawStateP->currentPos.x +=
             ROUND((right-left) * cosdeg(commandP->u.textArg.angle));
@@ -529,10 +529,10 @@ executeScript(struct script * const scriptP,
 
 
 struct tokenSet {
-    
+
     const char * token[10];
     unsigned int count;
-    
+
 };
 
 
@@ -571,7 +571,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
                 else if (streq(typeArg, "normal"))
-                    drawCommandP->u.setlinetypeArg.type = 
+                    drawCommandP->u.setlinetypeArg.type =
                         PPMD_LINETYPE_NODIAGS;
                 else
                     pm_error("Invalid type");
@@ -610,7 +610,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.lineArg.y0 = atoi(commandTokens.token[2]);
                 drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
                 drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
-            } 
+            }
         } else if (streq(verb, "line_here")) {
             drawCommandP->verb = VERB_LINE_HERE;
             if (commandTokens.count < 3)
@@ -621,7 +621,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                     &drawCommandP->u.lineHereArg;
                 argP->right = atoi(commandTokens.token[1]);
                 argP->down = atoi(commandTokens.token[2]);
-            } 
+            }
        } else if (streq(verb, "spline3")) {
             drawCommandP->verb = VERB_SPLINE3;
             if (commandTokens.count < 7)
@@ -636,7 +636,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->y1 = atoi(commandTokens.token[4]);
                 argP->x2 = atoi(commandTokens.token[5]);
                 argP->y2 = atoi(commandTokens.token[6]);
-            } 
+            }
         } else if (streq(verb, "circle")) {
             drawCommandP->verb = VERB_CIRCLE;
             if (commandTokens.count < 4)
@@ -647,7 +647,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cx     = atoi(commandTokens.token[1]);
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
-            } 
+            }
         } else if (streq(verb, "filledcircle")) {
             drawCommandP->verb = VERB_FILLEDCIRCLE;
             if (commandTokens.count < 4)
@@ -658,7 +658,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cx     = atoi(commandTokens.token[1]);
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
-            } 
+            }
         } else if (streq(verb, "filledrectangle")) {
             drawCommandP->verb = VERB_FILLEDRECTANGLE;
             if (commandTokens.count < 5)
@@ -671,7 +671,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->y      = atoi(commandTokens.token[2]);
                 argP->width  = atoi(commandTokens.token[3]);
                 argP->height = atoi(commandTokens.token[4]);
-            } 
+            }
         } else if (streq(verb, "text")) {
             drawCommandP->verb = VERB_TEXT;
             if (commandTokens.count < 6)
@@ -713,9 +713,9 @@ disposeOfCommandTokens(struct tokenSet * const tokenSetP,
     /* We've got a whole command in 'tokenSet'.  Parse it into *scriptP
        and reset tokenSet to empty.
     */
-    
+
     struct commandListElt * commandListEltP;
-    
+
     MALLOCVAR(commandListEltP);
     if (commandListEltP == NULL)
         pm_error("Out of memory allocating command list element frame");
@@ -747,12 +747,14 @@ processToken(const char *      const scriptText,
              struct script *   const scriptP,
              struct tokenSet * const tokenSetP) {
 
-    char * token;
     unsigned int const tokenLength = cursor - tokenStart;
+
+    char * token;
+
     MALLOCARRAY_NOFAIL(token, tokenLength + 1);
     memcpy(token, &scriptText[tokenStart], tokenLength);
     token[tokenLength] = '\0';
-    
+
     if (streq(token, ";")) {
         disposeOfCommandTokens(tokenSetP, scriptP);
         free(token);
@@ -779,7 +781,7 @@ parseScript(const char *     const scriptText,
         */
     bool quotedToken;
         /* Current token is a quoted string.  Meaningless if 'intoken'
-           is false 
+           is false
         */
     struct tokenSet tokenSet;
 
@@ -803,7 +805,7 @@ parseScript(const char *     const scriptText,
 
     while (scriptText[cursor] != '\0') {
         char const scriptChar = scriptText[cursor];
-        
+
         if (intoken) {
             if ((quotedToken && scriptChar == '"') ||
                 (!quotedToken && (isspace(scriptChar) || scriptChar == ';'))) {
@@ -835,7 +837,7 @@ parseScript(const char *     const scriptText,
                 }
             }
             ++cursor;
-        }            
+        }
     }
 
     if (intoken) {
@@ -879,7 +881,7 @@ getScript(struct cmdlineInfo const cmdline,
     pm_strfree(scriptText);
 }
 
-          
+
 
 static void
 doOneImage(FILE *          const ifP,
@@ -888,13 +890,13 @@ doOneImage(FILE *          const ifP,
     pixel ** pixels;
     pixval maxval;
     int rows, cols;
-    
+
     pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
-    
+
     executeScript(scriptP, pixels, cols, rows, maxval);
-    
+
     ppm_writeppm(stdout, pixels, cols, rows, maxval, 0);
-    
+
     ppm_freearray(pixels, rows);
 }
 
diff --git a/generator/Makefile b/generator/Makefile
index 5120008c..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
@@ -39,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/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..8ea28682
--- /dev/null
+++ b/generator/pamtris/boundaries.c
@@ -0,0 +1,334 @@
+/*=============================================================================
+                                 boundaries.c
+===============================================================================
+   Boundary buffer functions
+
+   New triangles are drawn one scanline at a time, and for every such scanline
+   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 "utils.h"
+#include "fract.h"
+
+
+#include "boundaries.h"
+
+
+
+static fract
+make_pos_fract(int32_t const quotient,
+               int32_t const remainder) {
+
+    fract retval;
+
+    retval.q = quotient;
+    retval.r = remainder;
+    retval.negative_flag = 0;
+
+    return retval;
+}
+
+
+
+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_row is accordingly set to -1. The argument is a 3-element array
+  of pairs of int16_t's 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;
+    fract left_x;
+    fract right_x;
+    bool no_upper_part;
+    int32_t top2mid_delta;
+    int32_t top2bot_delta;
+    int32_t mid2bot_delta;
+    fract top2mid_step;
+    fract top2bot_step;
+    fract mid2bot_step;
+    fract* upper_left_step;
+    fract* lower_left_step;
+    fract* upper_right_step;
+    fract* lower_right_step;
+    int32_t upper_left_delta;
+    int32_t lower_left_delta;
+    int32_t upper_right_delta;
+    int32_t lower_right_delta;
+    fract* left_step[2];
+    fract* right_step[2];
+    int32_t left_delta[2];
+    int32_t right_delta[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 topmost scanline or
+           completely below the bottom 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;
+
+    left_x  = make_pos_fract(xy._[0][0], 0);
+    right_x = make_pos_fract(xy._[0][0], 0);
+
+    if (xy._[0][1] == xy._[1][1]) {
+        /* Triangle has only a lower part. */
+
+        mid_is_to_the_left = 0;
+
+        right_x.q = xy._[1][0];
+    } else 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;
+    }
+
+    no_upper_part = (xy._[1][1] == xy._[0][1]);
+
+    top2mid_delta = xy._[1][1] - xy._[0][1] + !no_upper_part;
+    top2bot_delta = xy._[2][1] - xy._[0][1] + 1;
+    mid2bot_delta = xy._[2][1] - xy._[1][1] + no_upper_part;
+
+    gen_steps(&xy._[0][0], &xy._[1][0], &top2mid_step, 1, top2mid_delta);
+    gen_steps(&xy._[0][0], &xy._[2][0], &top2bot_step, 1, top2bot_delta);
+    gen_steps(&xy._[1][0], &xy._[2][0], &mid2bot_step, 1, mid2bot_delta);
+
+    if (mid_is_to_the_left == 2) {
+        if (top2bot_step.negative_flag) {
+            if (top2mid_step.negative_flag) {
+                if (top2mid_step.q == top2bot_step.q) {
+                    mid_is_to_the_left =
+                        top2mid_step.r * top2bot_delta >
+                        top2bot_step.r * top2mid_delta;
+                } else {
+                    mid_is_to_the_left = top2mid_step.q < top2bot_step.q;
+                }
+            } else {
+                mid_is_to_the_left = 0;
+            }
+        } else {
+            if (!top2mid_step.negative_flag) {
+                if (top2mid_step.q == top2bot_step.q) {
+                    mid_is_to_the_left =
+                        top2mid_step.r * top2bot_delta <
+                        top2bot_step.r * top2mid_delta;
+                } else {
+                    mid_is_to_the_left = top2mid_step.q < top2bot_step.q;
+                }
+            } else {
+                mid_is_to_the_left = 1;
+            }
+        }
+    }
+    if (mid_is_to_the_left) {
+        upper_left_step     = &top2mid_step;
+        lower_left_step     = &mid2bot_step;
+        upper_right_step    = &top2bot_step;
+        lower_right_step    = upper_right_step;
+
+        upper_left_delta    = top2mid_delta;
+        lower_left_delta    = mid2bot_delta;
+        upper_right_delta   = top2bot_delta;
+        lower_right_delta   = upper_right_delta;
+    } else {
+        upper_right_step    = &top2mid_step;
+        lower_right_step    = &mid2bot_step;
+        upper_left_step     = &top2bot_step;
+        lower_left_step     = upper_left_step;
+
+        upper_right_delta   = top2mid_delta;
+        lower_right_delta   = mid2bot_delta;
+        upper_left_delta    = top2bot_delta;
+        lower_left_delta    = upper_left_delta;
+    }
+
+    left_step[0] = upper_left_step;
+    left_step[1] = lower_left_step;
+    right_step[0] = upper_right_step;
+    right_step[1] = lower_right_step;
+    left_delta[0] = upper_left_delta;
+    left_delta[1] = lower_left_delta;
+    right_delta[0] = upper_right_delta;
+    right_delta[1] = lower_right_delta;
+    num_rows_ptr[0] = &bi->num_upper_rows;
+    num_rows_ptr[1] = &bi->num_lower_rows;
+
+    y = xy._[0][1];
+
+    i = 0;
+    k = 0;
+
+    if (no_upper_part) {
+        k = 1;
+
+        right_x.q = xy._[1][0];
+    }
+
+    step_up(&left_x, left_step[k], 1, left_delta[k]);
+    step_up(&right_x, right_step[k], 1, right_delta[k]);
+
+    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_x, left_step[k], 1, delta, left_delta[k]);
+            multi_step_up(&right_x, right_step[k], 1, delta, right_delta[k]);
+
+            if (y < 0) {
+                k++;
+                continue;
+            }
+        } else if(y >= height) {
+            return mid_is_to_the_left;
+        }
+
+        if (end > height) {
+            end = height;
+        }
+
+        while (y < end) {
+            if (left_x.q >= width || right_x.q < 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++] = left_x.q;
+                bi->buffer[i++] = right_x.q;
+
+                (*(num_rows_ptr[k]))++;
+            }
+
+            step_up(&left_x, left_step[k], 1, left_delta[k]);
+            step_up(&right_x, right_step[k], 1, right_delta[k]);
+
+            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..d41719cb
--- /dev/null
+++ b/generator/pamtris/boundaries.h
@@ -0,0 +1,71 @@
+#ifndef BOUNDARIES_H_INCLUDED
+#define BOUNDARIES_H_INCLUDED
+
+#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/fract.h b/generator/pamtris/fract.h
new file mode 100644
index 00000000..ff3c4402
--- /dev/null
+++ b/generator/pamtris/fract.h
@@ -0,0 +1,54 @@
+#ifndef FRACT_H_INCLUDED
+#define FRACT_H_INCLUDED
+
+#include <stdbool.h>
+#include <stdint.h>
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+    This struct and the functions that manipulate variables of this type act
+    as a substitute for floating point computations. Here, whenever we need a
+    value with a fractional component, we represent it using two parts: 1. An
+    integer part, called the "quotient", and 2. A fractional part, which is
+    itself composed of a "remainder" (or "numerator") and a "divisor" (or
+    "denominator"). The fract struct provides storage for the quotient and the
+    remainder, but the divisor must be given separately (because it often
+    happens in this program that whenever we are dealing with one variable of
+    type fract, we are dealing with more of them at the same time, and they
+    all have the same divisor).
+
+    To be more precise, the way we actually use variables of this type works
+    like this: We read integer values through standard input; When drawing
+    triangles, we need need to calculate differences between some pairs of
+    these input values and divide such differences by some other integer,
+    which is the above mentioned divisor. That result is then used to compute
+    successive interpolations between the two values for which we had
+    originally calculated the difference, and is therefore called the
+    "interpolation step". The values between which we wish to take successive
+    interpolations are called the "initial value" and the "final value". The
+    interpolation procedure works like this: First, we transform the initial
+    value into a fract variable by equating the quotient of that variable to
+    the initial value and assigning 0 to its remainder. Then, we successivelly
+    apply the interpolation step to that variable through successive calls to
+    step_up and/or multi_step_up until the quotient of the variable equals the
+    final value. Each application of step_up or multi_step_up yields a
+    particular linear interpolation between the initial and final values.
+
+    If and only if a particular fract variable represents an interpolation
+    step, the "negative_flag" field indicates whether the step is negative
+    (i. e. negative_flag == true) or not (negative_flag == false). This is
+    necessary in order to make sure that variables are "stepped up" in the
+    appropriate direction, so to speak, as the field which stores the
+    remainder in any fract variable, "r", is always equal to or above 0, and
+    the quotient of a step may be 0, so the actual sign of the step value is
+    not always discoverable through a simple examination of the sign of the
+    quotient. On the other hand, if the variable does not represent an
+    interpolation step, the negative_flag is meaningless.
+-----------------------------------------------------------------------------*/
+    int32_t q;     /* Quotient */
+    int32_t r: 31; /* Remainder */
+    bool    negative_flag: 1;
+} fract;
+
+#endif
diff --git a/generator/pamtris/framebuffer.c b/generator/pamtris/framebuffer.c
new file mode 100644
index 00000000..03cd720c
--- /dev/null
+++ b/generator/pamtris/framebuffer.c
@@ -0,0 +1,334 @@
+/*=============================================================================
+                              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 "utils.h"
+#include "fract.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);
+
+        return 1;
+    } 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,
+          fract *            const attribs_start,
+          const fract *      const attribs_steps,
+          int32_t            const div,
+          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.
+-----------------------------------------------------------------------------*/
+    uint8_t const num_planes = fbi->num_attribs + 1;
+
+    unsigned int i;
+
+    /* Process each pixel in the span: */
+
+    for (i = 0; i < length; i++) {
+        int32_t  const z        = MAX_Z - attribs_start[fbi->num_attribs].q;
+        uint32_t const z_mask   = -(~(z - fbi->z.buffer[base + i]) >> 31);
+        uint32_t const n_z_mask = ~z_mask;
+
+        uint32_t const j = base + i;
+        uint32_t const k = j * num_planes;
+
+        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 framebuffer; otherwise, the
+           framebuffer will be updated with the new values.
+        */
+        fbi->z.buffer[j] = (fbi->z.buffer[j] & n_z_mask) | (z & z_mask);
+
+        for (l = 0; l < fbi->num_attribs; l++) {
+            fbi->img.buffer[k + l] =
+                (fbi->img.buffer[k + l] & n_z_mask) |
+                (attribs_start[l].q & z_mask);
+        }
+
+        fbi->img.buffer[k + fbi->num_attribs] =
+            (fbi->img.buffer[k + fbi->num_attribs] & n_z_mask) |
+            (fbi->maxval & z_mask);
+
+        /* Compute the attribute values for the next pixel: */
+
+        step_up(attribs_start, attribs_steps, num_planes, div);
+    }
+}
+
+
+
diff --git a/generator/pamtris/framebuffer.h b/generator/pamtris/framebuffer.h
new file mode 100644
index 00000000..73ec96be
--- /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 "fract.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".
+    */
+    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,
+          fract *            const attribs_start,
+          const fract *      const attribs_steps,
+          int32_t            const divisor,
+          framebuffer_info * const fbi);
+
+#endif
diff --git a/generator/pamtris/input.c b/generator/pamtris/input.c
new file mode 100644
index 00000000..166d6db5
--- /dev/null
+++ b/generator/pamtris/input.c
@@ -0,0 +1,641 @@
+/*=============================================================================
+                              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 "limits_pamtris.h"
+#include "framebuffer.h"
+#include "triangle.h"
+
+#include "input.h"
+
+#define MAX_COORD       32767
+#define MIN_COORD       -MAX_COORD
+
+#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"
+
+#define WARNING_EXCESS_ARGS "warning: ignoring excess arguments: line %lu."
+#define SYNTAX_ERROR        "syntax error: line %lu."
+
+typedef struct {
+    Xy v_xy;
+        /* X- and Y-coordinates of the vertices for the current triangle.
+           int32_t v_attribs[3][MAX_NUM_ATTRIBS + 1]; // Vertex attributes for
+           the current triangle. Includes the Z-coordinates.
+        */
+	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
+clear_attribs(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
+init_input_processor(input_info * const ii) {
+
+    MALLOCARRAY_NOFAIL(ii->buffer, 128);
+
+    ii->length = 128;
+    ii->number = 1;
+}
+
+
+
+void
+free_input_processor(input_info * const ii) {
+    free(ii->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
+next_token(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
+string_is_valid(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
+init_state(state_info * const si) {
+
+    si->next = 0;
+    si->draw = false;
+    si->mode = DRAW_MODE_TRIANGLES;
+}
+
+
+
+static void
+make_lowercase(token const t) {
+
+    char * p;
+
+    for (p = t.begin; p != t.end; ++p)
+        *p = tolower(*p);
+}
+
+
+
+static void
+remove_comments(char * const str) {
+
+    char * p;
+
+    for (p = &str[0]; *p; ++p) {
+        if (*p == '#') {
+            *p = '\0';
+
+            break;
+        }
+    }
+}
+
+
+
+int
+process_next_command(input_info           * const line,
+                     struct boundary_info * const bi,
+                     framebuffer_info     * const fbi) {
+/*----------------------------------------------------------------------------
+  Doesn't necessarily process a command, just the next line of input, which
+  may be empty. Always returns 1, except when it cannot read any more lines of
+  input, an image buffer reallocation fails, or a "q" command is found in the
+  input -- in such cases it returns 0.
+-----------------------------------------------------------------------------*/
+    static state_info state;
+
+    token nt;
+
+    long int i_args[MAX_NUM_ATTRIBS];
+        /* For storing potential integer arguments. */
+    char * strtol_end;
+        /* To compare against nt.end when checking for errors with strtol */
+    bool unrecognized_cmd;
+        /* To print out an error message in case an unrecognized command was
+           given.
+        */
+    bool unrecognized_arg;
+        /* To print out an error message in case an unrecognized argument was
+           given.
+        */
+    bool must_break_out;
+        /* To break out of the below switch statement when an invalid argument
+           is found.
+        */
+    bool ok;
+        /* Indicates whether the input line was OK so that we can print out a
+           warning in case of excess arguments.
+        */
+
+    /* initial values */
+    strtol_end = NULL;
+    unrecognized_cmd = false;
+    unrecognized_arg = false;
+    must_break_out = false;
+    ok = false;
+
+    if (!state.initialized) {
+        init_state(&state);
+        clear_attribs(&state, fbi->maxval, fbi->num_attribs);
+
+        state.initialized = true;
+    }
+
+    if (getline(&line->buffer, &line->length, stdin) == -1) {
+        return 0;
+    }
+
+    remove_comments(line->buffer);
+
+    nt = next_token(line->buffer);
+
+    make_lowercase(nt);
+
+    switch (*nt.begin) {
+    case 'm':
+        if (!string_is_valid(CMD_SET_MODE, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+
+        nt = next_token(nt.end);
+
+        if (*nt.begin == '\0') {
+            pm_errormsg(SYNTAX_ERROR, line->number);
+
+            break;
+        }
+
+        make_lowercase(nt);
+
+        switch(*nt.begin) {
+        case 't':
+            if (!string_is_valid(ARG_TRIANGLES, nt.begin, nt.end)) {
+                unrecognized_arg = true;
+
+                break;
+            }
+
+            state.mode = DRAW_MODE_TRIANGLES;
+            state.draw = false;
+            state.next = 0;
+
+            ok = true;
+
+            break;
+        case 's':
+            if (!string_is_valid(ARG_STRIP, nt.begin, nt.end)) {
+                unrecognized_arg = true;
+
+                break;
+            }
+
+            state.mode = DRAW_MODE_STRIP;
+            state.draw = false;
+            state.next = 0;
+
+            ok = true;
+
+            break;
+        case 'f':
+            if (!string_is_valid(ARG_FAN, nt.begin, nt.end)) {
+                unrecognized_arg = true;
+
+                break;
+            }
+
+            state.mode = DRAW_MODE_FAN;
+            state.draw = false;
+            state.next = 0;
+
+            ok = true;
+
+            break;
+        default:
+            unrecognized_arg = true;
+        }
+
+        if (unrecognized_arg) {
+            pm_errormsg("error: unrecognized drawing mode in line %lu.",
+                        line->number);
+        }
+
+        break;
+    case 'a': {
+        uint8_t i;
+        if (!string_is_valid(CMD_SET_ATTRIBS, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+
+        for (i = 0; i < fbi->num_attribs; i++) {
+            nt = next_token(nt.end);
+
+            i_args[i] = strtol(nt.begin, &strtol_end, 10);
+
+            if (*nt.begin == '\0' || strtol_end != nt.end) {
+                pm_errormsg(SYNTAX_ERROR, line->number);
+
+                must_break_out = true;
+
+                break;
+            }
+
+            if (i_args[i] < 0 || i_args[i] > fbi->maxval) {
+                pm_errormsg("error: argument(s) out of bounds: line %lu.",
+                            line->number);
+
+                must_break_out = true;
+
+                break;
+            }
+        }
+
+        if (must_break_out)
+        {
+            break;
+        }
+
+        for (i = 0; i < fbi->num_attribs; i++) {
+            state.curr_attribs[i] = i_args[i];
+        }
+
+        ok = true;
+
+    } break;
+    case 'v': {
+        uint8_t i;
+
+        if (!string_is_valid(CMD_VERTEX, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+
+        for (i = 0; i < 3; i++) {
+            nt = next_token(nt.end);
+
+            i_args[i] = strtol(nt.begin, &strtol_end, 10);
+
+            if (*nt.begin == '\0' || strtol_end != nt.end) {
+                pm_errormsg(SYNTAX_ERROR, line->number);
+
+                must_break_out = true;
+
+                break;
+            }
+
+            if (i < 2) {
+                if (i_args[i] < MIN_COORD || i_args[i] > MAX_COORD) {
+                    pm_errormsg(
+                        "error: coordinates out of bounds: line %lu.",
+                        line->number);
+
+                    must_break_out = true;
+
+                    break;
+                }
+            } else {
+                if (i_args[i] < 0 || i_args[i] > MAX_Z) {
+                    pm_errormsg(
+                        "error: Z component out of bounds: line %lu.",
+                        line->number);
+
+                    must_break_out = true;
+
+                    break;
+                }
+            }
+        }
+
+        if (must_break_out)
+        {
+            break;
+        }
+
+        for (i = 0; i < fbi->num_attribs; i++) {
+            state.v_attribs._[state.next][i] = state.curr_attribs[i];
+        }
+
+        state.v_attribs._[state.next][fbi->num_attribs] = i_args[2];
+
+        state.v_xy._[state.next][0] = i_args[0];
+        state.v_xy._[state.next][1] = i_args[1];
+
+        state.next++;
+
+        if (!state.draw) {
+            if (state.next == 3) {
+                state.draw = true;
+            }
+        }
+
+        if (state.draw) {
+            draw_triangle(state.v_xy, state.v_attribs, bi, fbi);
+        }
+
+        if (state.next == 3) {
+            switch(state.mode) {
+            case DRAW_MODE_FAN:
+                state.next = 1;
+                break;
+            case DRAW_MODE_TRIANGLES:
+                state.draw = false;
+            case DRAW_MODE_STRIP:
+            default:
+                state.next = 0;
+            }
+        }
+
+        ok = true;
+
+    } break;
+    case 'p':
+        if (!string_is_valid(CMD_PRINT, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+    case '!':
+        if (*nt.begin == '!') {
+            if (nt.end - nt.begin > 1) {
+                unrecognized_cmd = true;
+
+                break;
+            }
+        }
+
+        print_framebuffer(fbi);
+
+        ok = true;
+
+        break;
+    case 'c':
+        if (!string_is_valid(CMD_CLEAR, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+    case '*':
+        if (*nt.begin == '*') {
+            if(nt.end - nt.begin > 1) {
+                unrecognized_cmd = true;
+
+                break;
+            }
+        }
+
+        nt = next_token(nt.end);
+
+        if (*nt.begin != '\0') {
+            make_lowercase(nt);
+
+            switch(*nt.begin) {
+            case 'i':
+                if (!string_is_valid("image", nt.begin, nt.end)) {
+                    unrecognized_arg = true;
+
+                    break;
+                }
+
+                clear_framebuffer(true, false, fbi);
+
+                break;
+            case 'd':
+                if (!string_is_valid("depth", nt.begin, nt.end)) {
+                    unrecognized_arg = true;
+
+                    break;
+                }
+            case 'z':
+                if (*nt.begin == 'z') {
+                    if (nt.end - nt.begin > 1) {
+                        unrecognized_arg = true;
+
+                        break;
+                    }
+                }
+
+                clear_framebuffer(false, true, fbi);
+
+                break;
+            default:
+                unrecognized_arg = true;
+            }
+
+            if (unrecognized_arg) {
+                pm_errormsg("error: unrecognized argument: line %lu.",
+                            line->number);
+
+                break;
+            }
+        } else {
+            clear_framebuffer(true, true, fbi);
+        }
+
+        ok = true;
+
+        break;
+    case 'r': {
+        uint8_t i;
+
+        if (!string_is_valid(CMD_RESET, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+
+        for (i = 0; i < 2; i++) {
+            nt = next_token(nt.end);
+
+            i_args[i] = strtol(nt.begin, &strtol_end, 10);
+
+            if (*nt.begin == '\0' || nt.end != strtol_end) {
+                pm_errormsg(SYNTAX_ERROR, line->number);
+
+                must_break_out = true;
+
+                break;
+            }
+        }
+
+        if (must_break_out) {
+            break;
+        }
+
+        if (i_args[0] < 1 || i_args[0] > PAM_OVERALL_MAXVAL) {
+            pm_errormsg("error: invalid new maxval: line %lu.",
+                        line->number);
+
+            break;
+        }
+
+        if (i_args[1] < 1 || i_args[1] > MAX_NUM_ATTRIBS) {
+            pm_errormsg("error: invalid new number of generic vertex "
+                        "attributes: line %lu.", line->number);
+
+            break;
+        }
+
+        nt = next_token(nt.end);
+
+        if (*nt.begin != '\0') {
+            if (!set_tupletype(nt.begin, fbi->outpam.tuple_type)) {
+                pm_message("warning: could not set new tuple type; "
+                           "using a null string: line %lu.",
+                           line->number);
+
+                set_tupletype(NULL, fbi->outpam.tuple_type);
+            }
+        } else {
+            set_tupletype(NULL, fbi->outpam.tuple_type);
+        }
+
+        if (!realloc_image_buffer(i_args[0], i_args[1], fbi)) {
+            pm_errormsg
+                (
+                    "fatal error upon reading line %lu: "
+                    "could not reallocate image buffer -- "
+                    "terminating pamtris.",
+                    line->number
+                    );
+
+            return 0;
+        }
+
+        state.next = 0;
+        state.draw = false;
+
+        clear_attribs(&state, fbi->maxval, fbi->num_attribs);
+
+    } break;
+    case 'q':
+        if (!string_is_valid(CMD_QUIT, nt.begin, nt.end)) {
+            unrecognized_cmd = true;
+
+            break;
+        }
+
+        return 0;
+    case '\0':
+        break;
+    default:
+        unrecognized_cmd = true;
+    }
+
+    {
+        char const next = *next_token(nt.end).begin;
+
+        if (unrecognized_cmd) {
+            pm_errormsg("error: unrecognized command: line %lu.",
+                        line->number);
+        }
+        else if (ok && next != '\0') {
+            pm_message(WARNING_EXCESS_ARGS, line->number);
+        }
+    }
+    line->number++;
+
+    return 1;
+}
+
+
diff --git a/generator/pamtris/input.h b/generator/pamtris/input.h
new file mode 100644
index 00000000..66969bb2
--- /dev/null
+++ b/generator/pamtris/input.h
@@ -0,0 +1,31 @@
+#ifndef INPUT_H_INCLUDED
+#define INPUT_H_INCLUDED
+
+#include <stdint.h>
+
+struct boundary_info;
+struct framebuffer_info;
+
+typedef struct input_info {
+/*----------------------------------------------------------------------------
+  Information necessary for the "process_next_command" function.  It must be
+  initialized through "init_input_processor" and freed by
+  "free_input_processor".
+-----------------------------------------------------------------------------*/
+    char *   buffer;
+    size_t   length;
+    uint64_t number;
+} input_info;
+
+void
+init_input_processor(input_info * const ii);
+
+void
+free_input_processor(input_info * const ii);
+
+int
+process_next_command(input_info *              const ii,
+                     struct boundary_info *    const bdi,
+                     struct framebuffer_info * const fbi);
+
+#endif
diff --git a/generator/pamtris/limits_pamtris.h b/generator/pamtris/limits_pamtris.h
new file mode 100644
index 00000000..dcf1f1e6
--- /dev/null
+++ b/generator/pamtris/limits_pamtris.h
@@ -0,0 +1,7 @@
+#ifndef LIMITS_H_INCLUDED
+#define LIMITS_H_INCLUDED
+
+#define MAX_NUM_ATTRIBS     20
+#define MAX_Z           ((1 << 30) - 1)
+
+#endif
diff --git a/generator/pamtris/pamtris.c b/generator/pamtris/pamtris.c
new file mode 100644
index 00000000..74663531
--- /dev/null
+++ b/generator/pamtris/pamtris.c
@@ -0,0 +1,140 @@
+#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_ptr;
+
+    unsigned int width_spec, height_spec, maxval_spec, attribs_spec;
+    unsigned int tupletype_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, "maxval",      OPT_INT,    maxval,         &maxval_spec,     0);
+    OPTENT3(0, "num_attribs", OPT_INT,    num_attribs,    &attribs_spec,    0);
+    OPTENT3(0, "tupletype",   OPT_STRING, &tupletype_ptr, &tupletype_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) {
+        pm_errormsg(
+            "you must at least specify -width, -height and -num_attribs.");
+
+        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 (*num_attribs < 1 || *num_attribs > MAX_NUM_ATTRIBS) {
+        pm_errormsg("invalid number of generic attributes per vertex.");
+
+        return 0;
+    }
+
+    if (tupletype_spec) {
+        if (!set_tupletype(tupletype_ptr, tupletype)) {
+            pm_errormsg("warning: invalid tuple type; using the null string.");
+
+            set_tupletype(NULL, tupletype);
+        }
+    }
+
+    free(option_def);
+
+    return 1;
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    framebuffer_info fbi;
+    boundary_info bi;
+    input_info ii;
+
+    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);
+
+    init_input_processor(&ii);
+
+    while (process_next_command(&ii, &bi, &fbi));
+
+    free_input_processor(&ii);
+    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..09d821e0
--- /dev/null
+++ b/generator/pamtris/triangle.c
@@ -0,0 +1,427 @@
+/*=============================================================================
+                                  triangle.c
+===============================================================================
+   Triangle functions
+=============================================================================*/
+#include <stdlib.h>
+#include <string.h>
+
+#include "netpbm/mallocvar.h"
+
+#include "utils.h"
+#include "fract.h"
+#include "boundaries.h"
+#include "framebuffer.h"
+
+#include "triangle.h"
+
+static void
+draw_partial_triangle(
+    const fract *         const left_attribs_input,
+    const fract *         const left_attribs_steps,
+    const fract *         const rght_attribs_input,
+    const fract *         const rght_attribs_steps,
+    int32_t               const left_div,
+    int32_t               const rght_div,
+    bool                  const upper_part,
+    const boundary_info * const bi,
+    framebuffer_info *    const fbi) {
+
+    uint8_t const num_planes = fbi->num_attribs + 1;
+
+    fract * left_attribs;
+    fract * rght_attribs;
+
+    int32_t first_row_index;
+    int32_t last_row_index;
+
+    MALLOCARRAY_NOFAIL(left_attribs, num_planes);
+    MALLOCARRAY_NOFAIL(rght_attribs, num_planes);
+
+    memcpy(left_attribs, left_attribs_input, num_planes * sizeof(fract));
+    memcpy(rght_attribs, rght_attribs_input, num_planes * sizeof(fract));
+
+    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; ) {
+            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;
+
+                fract   * attribs_start;
+                int32_t * attribs_begin;
+                int32_t * attribs_end;
+                fract   * attribs_steps;
+
+                MALLOCARRAY_NOFAIL(attribs_start, num_planes);
+                MALLOCARRAY_NOFAIL(attribs_begin, num_planes);
+                MALLOCARRAY_NOFAIL(attribs_end,   num_planes);
+                MALLOCARRAY_NOFAIL(attribs_steps, num_planes);
+
+                start_column = left_boundary;  /* initial value */
+                span_length = column_delta;    /* initial value */
+
+                fract_to_int32_array(left_attribs, attribs_begin, num_planes);
+                fract_to_int32_array(rght_attribs, attribs_end, num_planes);
+
+                int32_to_fract_array(attribs_begin, attribs_start, num_planes);
+
+                gen_steps(attribs_begin, attribs_end, attribs_steps,
+                          num_planes, column_delta);
+
+                if (left_boundary < 0) {
+                    start_column = 0;
+
+                    span_length += left_boundary;
+
+                    multi_step_up(attribs_start, attribs_steps, num_planes,
+                                  -left_boundary, column_delta);
+                }
+
+                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_start, attribs_steps, column_delta,
+                    fbi);
+
+                if (row_delta > 0) {
+                    step_up(left_attribs, left_attribs_steps, num_planes,
+                            left_div);
+                    step_up(rght_attribs, rght_attribs_steps, num_planes,
+                            rght_div);
+                }
+                row++;
+                free(attribs_steps);
+                free(attribs_end);
+                free(attribs_begin);
+                free(attribs_start);
+            }
+        }
+    }
+    free(rght_attribs);
+    free(left_attribs);
+}
+
+
+
+static void
+draw_degenerate_horizontal(Xy                 const xy,
+                           fract *            const attribs_left,
+                           fract *            const attribs_mid,
+                           const fract *      const top2mid_steps,
+                           const fract *      const top2bot_steps,
+                           const fract *      const mid2bot_steps,
+                           int32_t            const top2mid_delta,
+                           int32_t            const top2bot_delta,
+                           int32_t            const mid2bot_delta,
+                           framebuffer_info * const fbi) {
+
+    uint8_t const num_planes = fbi->num_attribs + 1;
+
+    fract * attribs_left_bkup;
+
+    MALLOCARRAY_NOFAIL(attribs_left_bkup, num_planes);
+
+    memcpy(attribs_left_bkup, attribs_left, num_planes * sizeof(fract));
+
+    {
+        int16_t const y = xy._[0][1];
+
+        int16_t x[3];
+        int16_t x_start[3];
+        fract * attribs[3];
+        const fract * steps[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] = attribs_left;
+        attribs[1] = attribs_left_bkup;
+        attribs[2] = attribs_mid;
+
+        steps[0] = top2bot_steps;
+        steps[1] = top2mid_steps;
+        steps[2] = mid2bot_steps;
+
+        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++) {
+            int32_t const column_delta = span_length[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], steps[i], num_planes, -x_start[i],
+                              column_delta);
+
+                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], steps[i], column_delta, fbi);
+        }
+    }
+    free(attribs_left_bkup);
+}
+
+
+
+void
+draw_triangle(Xy                 const xy_input,
+              Attribs            const attribs_input,
+              boundary_info *    const bi,
+              framebuffer_info * const fbi) {
+
+    uint8_t const num_planes = fbi->num_attribs + 1;
+
+    Xy xy;
+    int32_t * attribs[3];
+    unsigned int i;
+    uint8_t index_array[3];
+    int32_t y_array[3];
+    int32_t x_array[3];
+
+    MALLOCARRAY_NOFAIL(attribs[0], num_planes);
+    MALLOCARRAY_NOFAIL(attribs[1], num_planes);
+    MALLOCARRAY_NOFAIL(attribs[2], num_planes);
+
+    xy = xy_input;
+
+    for (i = 0; i < 3; i++) {
+        memcpy(attribs[i], attribs_input._[i], num_planes * sizeof(int32_t));
+    }
+
+    /* 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 framebuffer. */
+        } 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]);
+                /* We are dealing with a degenerate horizontal triangle */
+
+            uint8_t 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];
+
+            fract * top2mid_steps;
+            fract * top2bot_steps;
+            fract * mid2bot_steps;
+
+            fract * upper_left_attribs_steps;
+            fract * lower_left_attribs_steps;
+            fract * upper_rght_attribs_steps;
+            fract * lower_rght_attribs_steps;
+
+            int32_t upper_left_delta;
+            int32_t lower_left_delta;
+            int32_t upper_rght_delta;
+            int32_t lower_rght_delta;
+
+            fract * left_attribs;
+            fract * rght_attribs;
+
+            bool degenerate_horizontal;
+
+            MALLOCARRAY_NOFAIL(top2mid_steps, num_planes);
+            MALLOCARRAY_NOFAIL(top2bot_steps, num_planes);
+            MALLOCARRAY_NOFAIL(mid2bot_steps, num_planes);
+            MALLOCARRAY_NOFAIL(left_attribs, num_planes);
+            MALLOCARRAY_NOFAIL(rght_attribs, num_planes);
+
+            if (!horizontal) {
+                top2mid_delta += !no_upper_part;
+                top2bot_delta += 1;
+                mid2bot_delta += no_upper_part;
+            }
+
+            gen_steps(attribs[top], attribs[mid], top2mid_steps, num_planes,
+                      top2mid_delta);
+            gen_steps(attribs[top], attribs[bot], top2bot_steps, num_planes,
+                      top2bot_delta);
+            gen_steps(attribs[mid], attribs[bot], mid2bot_steps, num_planes,
+                      mid2bot_delta);
+
+            int32_to_fract_array(attribs[top], left_attribs, num_planes);
+            int32_to_fract_array(attribs[top], rght_attribs, num_planes);
+
+            if (mid_is_to_the_left) {
+                upper_left_attribs_steps = top2mid_steps;
+                lower_left_attribs_steps = mid2bot_steps;
+                upper_rght_attribs_steps = top2bot_steps;
+                lower_rght_attribs_steps = upper_rght_attribs_steps;
+
+                upper_left_delta = top2mid_delta;
+                lower_left_delta = mid2bot_delta;
+                upper_rght_delta = top2bot_delta;
+                lower_rght_delta = upper_rght_delta;
+            } else {
+                upper_rght_attribs_steps = top2mid_steps;
+                lower_rght_attribs_steps = mid2bot_steps;
+                upper_left_attribs_steps = top2bot_steps;
+                lower_left_attribs_steps = upper_left_attribs_steps;
+
+                upper_rght_delta = top2mid_delta;
+                lower_rght_delta = mid2bot_delta;
+                upper_left_delta = top2bot_delta;
+                lower_left_delta = upper_left_delta;
+            }
+
+            if (no_upper_part) {
+                int32_to_fract_array(attribs[mid], rght_attribs, num_planes);
+
+                if (horizontal) {
+                    degenerate_horizontal = true;
+                } else {
+                    degenerate_horizontal = false;
+
+                    step_up(left_attribs, lower_left_attribs_steps, num_planes,
+                            lower_left_delta);
+                    step_up(rght_attribs, lower_rght_attribs_steps, num_planes,
+                            lower_rght_delta);
+                }
+            } else {
+                int32_t delta;
+
+                degenerate_horizontal = false;
+
+                step_up(left_attribs, upper_left_attribs_steps, num_planes,
+                        upper_left_delta);
+                step_up(rght_attribs, upper_rght_attribs_steps, num_planes,
+                        upper_rght_delta);
+
+                if (bi->num_upper_rows > 0) {
+
+                    if (bi->start_scanline > xy._[top][1]) {
+                        delta = bi->start_scanline - xy._[top][1];
+
+                        multi_step_up(left_attribs, upper_left_attribs_steps,
+                                      num_planes, delta, upper_left_delta);
+                        multi_step_up(rght_attribs, upper_rght_attribs_steps,
+                                      num_planes, delta, upper_rght_delta);
+                    }
+
+                    draw_partial_triangle(
+                        left_attribs, upper_left_attribs_steps,
+                        rght_attribs, upper_rght_attribs_steps,
+                        upper_left_delta, upper_rght_delta,
+                        true,
+                        bi,
+                        fbi
+                        );
+
+                    delta = xy._[mid][1] - bi->start_scanline;
+                } else {
+                    delta = top2mid_delta;
+                }
+
+                multi_step_up(left_attribs, upper_left_attribs_steps,
+                              num_planes, delta, upper_left_delta);
+                multi_step_up(rght_attribs, upper_rght_attribs_steps,
+                              num_planes, delta, upper_rght_delta);
+            }
+            if (degenerate_horizontal) {
+                draw_degenerate_horizontal(
+                    xy_sorted,
+                    left_attribs, rght_attribs,
+                    top2mid_steps, top2bot_steps, mid2bot_steps,
+                    top2mid_delta, top2bot_delta, mid2bot_delta,
+                    fbi
+                    );
+            } else {
+                if (bi->start_scanline > xy._[mid][1]) {
+                    int32_t const delta = bi->start_scanline - xy._[mid][1];
+
+                    multi_step_up(left_attribs, lower_left_attribs_steps,
+                                  num_planes, delta, lower_left_delta);
+                    multi_step_up(rght_attribs, lower_rght_attribs_steps,
+                                  num_planes, delta, lower_rght_delta);
+                }
+
+                draw_partial_triangle(
+                    left_attribs, lower_left_attribs_steps,
+                    rght_attribs, lower_rght_attribs_steps,
+                    lower_left_delta, lower_rght_delta,
+                    false,
+                    bi,
+                    fbi
+                    );
+            }
+            free(rght_attribs); free(left_attribs);
+            free(mid2bot_steps); free(top2bot_steps); free(top2mid_steps);
+        }
+    }
+    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..79178ad0
--- /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 + 1];
+} 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..09c9b4d0
--- /dev/null
+++ b/generator/pamtris/utils.c
@@ -0,0 +1,256 @@
+/*=============================================================================
+                              utils.c
+===============================================================================
+   Utility functions
+=============================================================================*/
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "fract.h"
+
+#include "utils.h"
+
+
+
+void
+step_up(fract *       const vars,
+        const fract * const steps,
+        uint8_t       const element_ct,
+        int32_t       const divisor) {
+/*----------------------------------------------------------------------------
+  Apply interpolation steps steps[] to a collection of fract variables vars[]
+  once.  I.e. add each steps[i] to vars[i].
+
+  'element_ct' is the number of elements in 'vars' and 'steps'.
+
+  'divisor' is the divisor used to interpret the fractions.
+
+  It *is* safe to pass a 0 divisor to this function.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < element_ct; ++i) {
+        /* To add the fraction steps[i] to the fraction vars[i]: add the
+           quotient of step steps[i] to the quotient of variable vars[i] and
+           the remainder of the step to the remainder of the variable. If this
+           makes the agumented remainder equal to or larger than the divisor,
+           increment the quotient of the variable if the step is positive or
+           decrement it if the step is negative, and subtract the divisor from
+           the remainder of the variable (in either case).
+        */
+
+        vars[i].q += steps[i].q;
+        vars[i].r += steps[i].r;
+
+        {
+            uint32_t const negative_mask = -steps[i].negative_flag;
+                /* (-1 if the step is negative; 1 otherwise) */
+
+            uint32_t const overdiv_mask =
+                -(((uint32_t)~(vars[i].r - divisor)) >> 31);
+                /*  = ~0 if var->r >= div; 0 otherwise. */
+
+            vars[i].q += (negative_mask | 1) & overdiv_mask;
+            vars[i].r -= divisor & overdiv_mask;
+        }
+    }
+}
+
+
+
+void
+multi_step_up(fract *       const vars,
+              const fract * const steps,
+              uint8_t       const elements,
+              int32_t       const times,
+              int32_t       const div) {
+/*----------------------------------------------------------------------------
+  Similar to step_up, but apply the interpolation step an arbitrary number
+  of times, instead of just once.
+
+  It *is* also safe to pass a 0 divisor to this function.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        uint32_t const negative_mask = -steps[i].negative_flag;
+
+        vars[i].q += times * steps[i].q;
+        vars[i].r += times * steps[i].r;
+
+        if(vars[i].r >= div && div != 0) {
+            int32_t const r_q = vars[i].r / div;
+            int32_t const r_r = vars[i].r % div;
+
+            vars[i].q += (-r_q & negative_mask) | (r_q & ~negative_mask);
+                /* = -r_q if the step is negative; r_q, otherwise. */
+            vars[i].r = r_r;
+        }
+    }
+}
+
+
+
+void
+gen_steps(const int32_t * const begin,
+          const int32_t * const end,
+          fract         * const out,
+          uint8_t         const elements,
+          int32_t         const div) {
+/*----------------------------------------------------------------------------
+  Generate the interpolation steps for a collection of initial and final
+  values. "begin" points to an array of initial values, "end" points to the
+  array of corresponding final values; each interpolation step is stored in
+  the appropriate position in the array pointed by "out"; "elements" indicates
+  the number of elements in each of the previously mentioned arrays and
+  "divisor" is the common value by which we want to divide the difference
+  between each element in the array pointed to by "end" and the corresponding
+  element in the array pointed to by "begin".  After an execution of this
+  function, for each out[i], with 0 <= i < elements, the following will hold:
+
+    1. If divisor > 1:
+      out[i].q = (end[i] - begin[i]) / divisor
+      out[i].r = abs((end[i] - begin[i]) % divisor)
+
+    2. If divisor == 1 || divisor == 0:
+      out[i].q = end[i] - begin[i]
+      out[i].r = 0
+-----------------------------------------------------------------------------*/
+    if (div > 1) {
+        unsigned int i;
+
+        for (i = 0; i < elements; i++) {
+            int32_t const delta = end[i] - begin[i];
+
+            out[i].q = delta / div;
+            out[i].r = abs(delta % div);
+            out[i].negative_flag = ((uint32_t)delta) >> 31;
+        }
+    } else {
+        unsigned int i;
+
+        for (i = 0; i < elements; i++) {
+            int32_t const delta = end[i] - begin[i];
+
+            out[i].q = delta;
+            out[i].r = 0;
+            out[i].negative_flag = ((uint32_t)delta) >> 31;
+        }
+    }
+}
+
+
+
+void
+fract_to_int32_array(const fract * const in,
+                     int32_t *     const out,
+                     uint8_t       const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        out[i] = in[i].q;
+    }
+}
+
+
+
+void
+int32_to_fract_array(const int32_t * const in,
+                     fract *         const out,
+                     uint8_t         const elements) {
+
+    unsigned int i;
+
+    for (i = 0; i < elements; i++) {
+        out[i].q = in[i];
+        out[i].r = 0;
+    }
+}
+
+
+
+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..3b7cfbe4
--- /dev/null
+++ b/generator/pamtris/utils.h
@@ -0,0 +1,41 @@
+#ifndef UTIL_H_INCLUDED
+#define UTIL_H_INCLUDED
+
+#include "fract.h"
+
+void
+gen_steps(const int32_t * const begin,
+          const int32_t * const end,
+          fract *         const out,
+          uint8_t         const elements,
+          int32_t         const divisor);
+
+void
+step_up(fract *       const vars,
+        const fract * const steps,
+        uint8_t       const elements,
+        int32_t       const divisor);
+
+void
+multi_step_up(fract *       const vars,
+              const fract * const steps,
+              uint8_t       const elements,
+              int32_t       const times,
+              int32_t       const divisor);
+
+void
+fract_to_int32_array(const fract * const in,
+                     int32_t     * const out,
+                     uint8_t       const elements);
+
+void
+int32_to_fract_array(const int32_t * const in,
+                     fract *         const out,
+                     uint8_t         const elements);
+
+void
+sort3(uint8_t *       const index_array,
+      const int32_t * const y_array,
+      const int32_t * const x_array);
+
+#endif
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index 7d9f7cf7..e52d5199 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -179,59 +179,26 @@ parseCommandLine(int argc, const char ** argv,
 
 
 static void
-reportFont(struct font2 * 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);
-
-    for (c = 0, n = 0; c <= fontP->maxglyph; ++c) {
-        if (fontP->glyph[c])
-            ++n;
-    }
-
-    pm_message("  # characters: %u", n);
+    pm_message("  # characters loaded: %u", fontP->chars);
 }
 
 
 
-static struct font *
-fontFromFile(const char * const fileName) {
-
-    struct font  * retval;
-
-    jmp_buf jmpbuf;
-    int rc;
-
-    rc = setjmp(jmpbuf);
-
-    if (rc == 0) {
-        /* This is the normal program flow */
-        pm_setjmpbuf(&jmpbuf);
-
-        retval = pbm_loadfont(fileName);
-
-        pm_setjmpbuf(NULL);
-    } else {
-        /* This is the second pass, after pbm_loadfont does a longjmp
-           because it fails.
-        */
-        pm_setjmpbuf(NULL);
-
-        pm_error("Failed to load font from file '%s'", fileName);
-    }
-
-    return retval;
-}
 
 
 
 static struct font2 *
-font2FromFile(const char * const fileName) {
+font2FromFile(const char * const fileName,
+              PM_WCHAR     const maxmaxglyph) {
 
     struct font2 * font2P;
 
@@ -244,7 +211,7 @@ font2FromFile(const char * const fileName) {
         /* This is the normal program flow */
         pm_setjmpbuf(&jmpbuf);
 
-        font2P = pbm_loadbdffont2(fileName, PM_FONT2_MAXGLYPH);
+        font2P = pbm_loadfont2(fileName, maxmaxglyph);
 
         pm_setjmpbuf(NULL);
     } else {
@@ -267,21 +234,14 @@ computeFont(struct CmdlineInfo const cmdline,
 
     struct font2 * font2P;
 
-    if (cmdline.wchar && cmdline.font)
-        font2P = font2FromFile(cmdline.font);
-    else {
-        struct font  * fontP;
-
-        if (cmdline.font)
-            fontP = fontFromFile(cmdline.font);
-        else {
-            if (cmdline.builtin)
-                fontP = pbm_defaultfont(cmdline.builtin);
-            else
-                fontP = pbm_defaultfont("bdf");
-        }
-        font2P = pbm_expandbdffont(fontP);
-    }
+    if (cmdline.font)
+        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(font2P);
@@ -292,7 +252,7 @@ computeFont(struct CmdlineInfo const cmdline,
 
 
 struct Text {
-    PM_WCHAR **     textArray;  /* malloc'ed */
+    PM_WCHAR **  textArray;  /* malloc'ed */
         /* This is strictly characters that are in user's font - no control
            characters, no undefined code points.
         */
@@ -996,6 +956,7 @@ getText(PM_WCHAR       const cmdlineText[],
         inputText.lineCount = 1;
         fixControlChars(cmdlineText, fontP,
                         (const PM_WCHAR**)&inputText.textArray[0], fixMode);
+        free((void *) cmdlineText);
     } else {
         /* Read text from stdin. */
 
@@ -1053,6 +1014,7 @@ getText(PM_WCHAR       const cmdlineText[],
         inputText.textArray = textArray;
         inputText.lineCount = lineCount;
         inputText.allocatedLineCount = lineCount;
+        free(buf);
     }
     *inputTextP = inputText;
 }
@@ -1209,6 +1171,9 @@ renderText(unsigned int   const cols,
     insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb,
                      space, cols, rows, lspace, fixedAdvance);
 
+    /* Free all font data */
+    pbm_destroybdffont2(fontP); 
+
     {
         unsigned int row;
 
@@ -1412,10 +1377,6 @@ main(int argc, const char *argv[]) {
     else
         pbmtext(cmdline, fontP, stdout);
 
-    /* Note that *fontP is unfreeable.  See pbm_loadbdffont2,
-       pbm_expandbdffont
-    */
-
     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/lib/Makefile b/lib/Makefile
index a0f33745..65177758 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -28,7 +28,8 @@ endif
 LIBOBJECTS = libpm.o pmfileio.o fileio.o colorname.o \
 	libpamd.o \
 	libpbm1.o libpbm2.o libpbm3.o \
-	libpbmfont.o pbmfontdata1.o pbmfontdata2.o \
+	libpbmfont0.o libpbmfont1.o libpbmfont2.o \
+	pbmfontdata0.o pbmfontdata1.o pbmfontdata2.o \
 	libpgm1.o libpgm2.o \
 	libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \
 	libppmd.o ppmdfont.o standardppmdfont.o path.o \
@@ -56,7 +57,7 @@ MANUALS3 = libnetpbm
 MANUALS5 = pbm pgm ppm pnm pam
 
 INTERFACE_HEADERS = colorname.h \
-	pam.h pamdraw.h pammap.h pbm.h pbmfont.h \
+	pam.h pamdraw.h pammap.h pbm.h pbmfont.h pbmfontdata.h \
 	pgm.h pm.h pm_gamma.h pm_system.h pnm.h \
 	ppm.h ppmcmap.h ppmdfont.h ppmdraw.h ppmfloyd.h \
 	util/mallocvar.h util/runlength.h util/shhopt.h \
diff --git a/lib/libpamn.c b/lib/libpamn.c
index 65c8979f..8ae57037 100644
--- a/lib/libpamn.c
+++ b/lib/libpamn.c
@@ -69,7 +69,8 @@ allocpamrown(const struct pam * const pamP,
    overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
    ensures this assumption is valid.
 -----------------------------------------------------------------------------*/
-    int const bytes_per_tuple = allocationDepth(pamP) * sizeof(samplen);
+    unsigned int const bytes_per_tuple =
+        allocationDepth(pamP) * sizeof(samplen);
 
     tuplen * tuplerown;
     const char * error;
@@ -643,6 +644,63 @@ pnm_unapplyopacityrown(struct pam * const pamP,
 
 
 
+void
+pnm_maketuplergbn(const struct pam * const pamP,
+                  tuplen             const tuple) {
+
+    if (allocationDepth(pamP) < 3)
+        pm_error("allocation depth %u passed to pnm_maketuplergb().  "
+                 "Must be at least 3.", allocationDepth(pamP));
+
+    if (pamP->depth < 3)
+        tuple[2] = tuple[1] = tuple[0];
+}
+
+
+
+void
+pnm_makerowrgbn(const struct pam * const pamP,
+                tuplen *           const tuplerow) {
+
+    if (pamP->depth < 3) {
+        unsigned int col;
+
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makerowrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+
+        for (col = 0; col < pamP->width; ++col) {
+            tuplen const thisTuple = tuplerow[col];
+            thisTuple[2] = thisTuple[1] = thisTuple[0];
+        }
+    }
+}
+
+
+
+void
+pnm_makearrayrgbn(const struct pam * const pamP,
+                  tuplen **          const tuples) {
+
+    if (pamP->depth < 3) {
+        unsigned int row;
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makearrayrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+
+        for (row = 0; row < pamP->height; ++row) {
+            tuplen * const tuplerow = tuples[row];
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                tuplen const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
 static void
 fillInMap(pnm_transformMap const ungammaTransformMap,
           sample           const maxval,
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
deleted file mode 100644
index 24858b04..00000000
--- a/lib/libpbmfont.c
+++ /dev/null
@@ -1,1185 +0,0 @@
-/*
-**
-** Font routines.
-**
-** BDF font code Copyright 1993 by George Phillips.
-**
-** Copyright (C) 1991 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-**
-** BDF font specs available from:
-** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
-** Glyph Bitmap Distribution Format (BDF) Specification
-** Version 2.2
-** 22 March 1993
-** Adobe Developer Support
-*/
-
-#include <assert.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "netpbm/pm_c_util.h"
-#include "netpbm/mallocvar.h"
-#include "netpbm/nstring.h"
-
-#include "pbmfont.h"
-#include "pbm.h"
-
-static unsigned int const firstCodePoint = 32;
-    /* This is the code point of the first character in a pbmfont font.
-       In ASCII, it is a space.
-    */
-
-static unsigned int const nCharsInFont = 96;
-    /* The number of characters in a pbmfont font.  A pbmfont font defines
-       characters at position 32 (ASCII space) through 127, so that's 96.
-    */
-
-
-struct font *
-pbm_defaultfont(const char * const name) {
-/*----------------------------------------------------------------------------
-   Generate the built-in font with name 'name'.
------------------------------------------------------------------------------*/
-    struct font * retval;
-
-    if (streq(name, "bdf"))
-        retval = &pbm_defaultBdffont;
-    else if (streq(name, "fixed"))
-        retval = &pbm_defaultFixedfont;
-    else
-        pm_error( "built-in font name unknown, try 'bdf' or 'fixed'");
-
-    return retval;
-}
-
-
-
-static void
-findFirstBlankRow(const bit **   const font,
-                  unsigned int   const fcols,
-                  unsigned int   const frows,
-                  unsigned int * const browP) {
-
-    unsigned int row;
-    bool foundBlank;
-
-    for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) {
-        unsigned int col;
-        bit col0Value = font[row][0];
-        bool rowIsBlank;
-        rowIsBlank = true;  /* initial assumption */
-        for (col = 1; col < fcols; ++col)
-            if (font[row][col] != col0Value)
-                rowIsBlank = false;
-
-        if (rowIsBlank) {
-            foundBlank = true;
-            *browP = row;
-        }
-    }
-
-    if (!foundBlank)
-        pm_error("couldn't find blank pixel row in font");
-}
-
-
-
-static void
-findFirstBlankCol(const bit **   const font,
-                  unsigned int   const fcols,
-                  unsigned int   const frows,
-                  unsigned int * const bcolP) {
-
-    unsigned int col;
-    bool foundBlank;
-
-    for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) {
-        unsigned int row;
-        bit row0Value = font[0][col];
-        bool colIsBlank;
-        colIsBlank = true;  /* initial assumption */
-        for (row = 1; row < frows; ++row)
-            if (font[row][col] != row0Value)
-                colIsBlank = false;
-
-        if (colIsBlank) {
-            foundBlank = true;
-            *bcolP = col;
-        }
-    }
-
-    if (!foundBlank)
-        pm_error("couldn't find blank pixel column in font");
-}
-
-
-
-static void
-computeCharacterSize(const bit **   const font,
-                     unsigned int   const fcols,
-                     unsigned int   const frows,
-                     unsigned int * const cellWidthP,
-                     unsigned int * const cellHeightP,
-                     unsigned int * const charWidthP,
-                     unsigned int * const charHeightP) {
-
-    unsigned int firstBlankRow;
-    unsigned int firstBlankCol;
-    unsigned int heightLast11Rows;
-
-    findFirstBlankRow(font, fcols, frows, &firstBlankRow);
-
-    findFirstBlankCol(font, fcols, frows, &firstBlankCol);
-
-    heightLast11Rows = frows - firstBlankRow;
-
-    if (heightLast11Rows % 11 != 0)
-        pm_error("The rows of characters in the font do not appear to "
-                 "be all the same height.  The last 11 rows are %u pixel "
-                 "rows high (from pixel row %u up to %u), "
-                 "which is not a multiple of 11.",
-                 heightLast11Rows, firstBlankRow, frows);
-    else {
-        unsigned int widthLast15Cols;
-
-        *cellHeightP = heightLast11Rows / 11;
-
-        widthLast15Cols = fcols - firstBlankCol;
-
-        if (widthLast15Cols % 15 != 0)
-            pm_error("The columns of characters in the font do not appear to "
-                     "be all the same width.  "
-                     "The last 15 columns are %u pixel "
-                     "columns wide (from pixel col %u up to %u), "
-                     "which is not a multiple of 15.",
-                     widthLast15Cols, firstBlankCol, fcols);
-        else {
-            *cellWidthP = widthLast15Cols / 15;
-
-            *charWidthP = firstBlankCol;
-            *charHeightP = firstBlankRow;
-        }
-    }
-}
-
-
-
-struct font*
-pbm_dissectfont(const bit ** const font,
-                unsigned int const frows,
-                unsigned int const fcols) {
-    /*
-       This routine expects a font bitmap representing the following text:
-      
-       (0,0)
-          M ",/^_[`jpqy| M
-      
-          /  !"#$%&'()*+ /
-          < ,-./01234567 <
-          > 89:;<=>?@ABC >
-          @ DEFGHIJKLMNO @
-          _ PQRSTUVWXYZ[ _
-          { \]^_`abcdefg {
-          } hijklmnopqrs }
-          ~ tuvwxyz{|}~  ~
-      
-          M ",/^_[`jpqy| M
-      
-       The bitmap must be cropped exactly to the edges.
-      
-       The characters in the border you see are irrelevant except for
-       character size compuations.  The 12 x 8 array in the center is
-       the font.  The top left character there belongs to code point
-       0, and the code points increase in standard reading order, so
-       the bottom right character is code point 127.  You can't define
-       code points < 32 or > 127 with this font format.
-
-       The characters in the top and bottom border rows must include a
-       character with the lowest reach of any in the font (e.g. "y",
-       "_") and one with the highest reach (e.g. '"').  The characters
-       in the left and right border columns must include characters
-       with the rightmost and leftmost reach of any in the font
-       (e.g. "M" for both).
-
-       The border must be separated from the font by one blank text
-       row or text column.
-      
-       The dissection works by finding the first blank row and column;
-       i.e the lower right corner of the "M" in the upper left corner
-       of the matrix.  That gives the height and width of the
-       maximum-sized character, which is not too useful.  But the
-       distance from there to the opposite side is an integral
-       multiple of the cell size, and that's what we need.  Then it's
-       just a matter of filling in all the coordinates.  */
-    
-    unsigned int cellWidth, cellHeight;
-        /* Dimensions in pixels of each cell of the font -- that
-           includes the glyph and the white space above and to the
-           right of it.  Each cell is a tile of the font image.  The
-           top character row and left character row don't count --
-           those cells are smaller because they are missing the white
-           space.
-        */
-    unsigned int charWidth, charHeight;
-        /* Maximum dimensions of glyph itself, inside its cell */
-
-    int row, col, ch, r, c, i;
-    struct font * fn;
-    struct glyph * glyph;
-    char* bmap;
-
-    computeCharacterSize(font, fcols, frows,
-                         &cellWidth, &cellHeight, &charWidth, &charHeight);
-
-    /* Now convert to a general font */
-
-    MALLOCVAR(fn);
-    if (fn == NULL)
-        pm_error("out of memory allocating font structure");
-
-    fn->maxwidth  = charWidth;
-    fn->maxheight = charHeight;
-    fn->x = fn->y = 0;
-
-    fn->oldfont = font;
-    fn->frows = frows;
-    fn->fcols = fcols;
-    
-    /* Initialize all character positions to "undefined."  Those that
-       are defined in the font will be filled in below.
-    */
-    for (i = 0; i < PM_FONT_MAXGLYPH + 1; i++)
-        fn->glyph[i] = NULL;
-
-    MALLOCARRAY(glyph, nCharsInFont);
-    if ( glyph == NULL )
-        pm_error( "out of memory allocating glyphs" );
-    
-    bmap = (char*) malloc( fn->maxwidth * fn->maxheight * nCharsInFont );
-    if ( bmap == (char*) 0)
-        pm_error( "out of memory allocating glyph data" );
-
-    /* Now fill in the 0,0 coords. */
-    row = cellHeight * 2;
-    col = cellWidth * 2;
-    for (i = 0; i < firstCodePoint; ++i)
-        fn->glyph[i] = NULL;
-
-    for ( ch = 0; ch < nCharsInFont; ++ch ) {
-        glyph[ch].width = fn->maxwidth;
-        glyph[ch].height = fn->maxheight;
-        glyph[ch].x = glyph[ch].y = 0;
-        glyph[ch].xadd = cellWidth;
-
-        for ( r = 0; r < glyph[ch].height; ++r )
-            for ( c = 0; c < glyph[ch].width; ++c )
-                bmap[r * glyph[ch].width + c] = font[row + r][col + c];
-    
-        glyph[ch].bmap = bmap;
-        bmap += glyph[ch].width * glyph[ch].height;
-
-        fn->glyph[firstCodePoint + ch] = &glyph[ch];
-
-        col += cellWidth;
-        if ( col >= cellWidth * 14 ) {
-            col = cellWidth * 2;
-            row += cellHeight;
-        }
-    }
-    for (i = firstCodePoint + nCharsInFont; i < PM_FONT_MAXGLYPH + 1; ++i)
-        fn->glyph[i] = NULL;
-    
-    return fn;
-}
-
-
-
-struct font *
-pbm_loadfont(const char * const filename) {
-
-    FILE * fileP;
-    struct font * fontP;
-    char line[10] = "\0\0\0\0\0\0\0\0\0\0";
-        /* Initialize to suppress Valgrind error which is reported when file
-           is empty or very small.
-        */
-
-    fileP = pm_openr(filename);
-    fgets(line, 10, fileP);
-    pm_close(fileP);
-
-    if (line[0] == PBM_MAGIC1 && 
-        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
-        fontP = pbm_loadpbmfont(filename);
-    } else if (!strncmp(line, "STARTFONT", 9)) {
-        fontP = pbm_loadbdffont(filename);
-        if (!fontP)
-            pm_error("could not load BDF font file");
-    } else {
-        pm_error("font file not in a recognized format.  Does not start "
-                 "with the signature of a PBM file or BDF font file");
-        assert(false);
-        fontP = NULL;  /* defeat compiler warning */
-    }
-    return fontP;
-}
-
-
-
-struct font *
-pbm_loadpbmfont(const char * const filename) {
-
-    FILE * ifP;
-    bit ** font;
-    int fcols, frows;
-
-    ifP = pm_openr(filename);
-
-    font = pbm_readpbm(ifP, &fcols, &frows);
-
-    if ((fcols - 1) / 16 >= pbm_maxfontwidth() ||
-       (frows - 1) / 12 >= pbm_maxfontheight())
-        pm_error("Absurdly large PBM font file: %s", filename);
-    else if (fcols < 31 || frows < 23) {
-        /* Need at least one pixel per character, and this is too small to
-           have that.
-        */
-        pm_error("PBM font file '%s' too small to be a font file: %u x %u.  "
-                 "Minimum sensible size is 31 x 23",
-                 filename, fcols, frows);
-    }
-
-    pm_close(ifP);
-
-    return pbm_dissectfont((const bit **)font, frows, fcols);
-}
-
-
-
-void
-pbm_dumpfont(struct font * const fontP,
-             FILE *        const ofP) {
-/*----------------------------------------------------------------------------
-  Dump out font as C source code.
------------------------------------------------------------------------------*/
-    unsigned int i;
-    unsigned int ng;
-
-    if (fontP->oldfont)
-        pm_message("Netpbm no longer has the capability to generate "
-                   "a font in long hexadecimal data format");
-
-    for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) {
-        if (fontP->glyph[i])
-            ++ng;
-    }
-
-    printf("static struct glyph _g[%d] = {\n", ng);
-
-    for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
-        struct glyph * const glyphP = fontP->glyph[i];
-        if (glyphP) {
-            unsigned int j;
-            printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height,
-                   glyphP->x, glyphP->y, glyphP->xadd);
-            
-            for (j = 0; j < glyphP->width * glyphP->height; ++j) {
-                if (glyphP->bmap[j])
-                    printf("\\1");
-                else
-                    printf("\\0");
-            }    
-            --ng;
-            printf("\" }%s\n", ng ? "," : "");
-        }
-    }
-    printf("};\n");
-
-    printf("struct font XXX_font = { %d, %d, %d, %d, {\n",
-           fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y);
-
-    {
-        unsigned int i;
-
-        for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
-            if (fontP->glyph[i])
-                printf(" _g + %d", ng++);
-            else
-                printf(" NULL");
-        
-            if (i != PM_FONT_MAXGLYPH) printf(",");
-            printf("\n");
-        }
-    }
-
-    printf(" }\n};\n");
-}
-
-
-/*----------------------------------------------------------------------------
-  Routines for loading a BDF font file
------------------------------------------------------------------------------*/
-
-
-/* The following are not recognized in individual glyph data; library
-   routines do a pm_error if they see one:
-
-   Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET,
-   CONTENTVERSION.
-
-   The following is not recognized and is thus ignored at the global level:
-   DWIDTH
-*/
-
-
-#define MAXBDFLINE 1024 
-
-/* Official Adobe document says max length of string is 65535 characters.
-   However the value 1024 is sufficient for practical uses.
-*/
-
-typedef struct {
-/*----------------------------------------------------------------------------
-   This is an object for reading lines of a font file.  It reads and tokenizes
-   them into words.
------------------------------------------------------------------------------*/
-    FILE * ifP;
-
-    char line[MAXBDFLINE+1];
-        /* This is the storage space for the words of the line.  The
-           words go in here, one after another, separated by NULs.
-
-           It also functions as a work area for readline_read().
-        */
-    const char * arg[6];
-        /* These are the words; each entry is a pointer into line[] (above) */
-} Readline;
-
-
-
-static void
-readline_init(Readline * const readlineP,
-              FILE *     const ifP) {
-
-    readlineP->ifP = ifP;
-
-    readlineP->arg[0] = NULL;
-}
-
-
-
-static void
-tokenize(char *         const s,
-         const char **  const words,
-         unsigned int   const wordsSz) {
-/*----------------------------------------------------------------------------
-   Chop up 's' into words by changing space characters to NUL.  Return as
-   'words' an array of pointers to the beginnings of those words in 's'.
-   Terminate the words[] list with a null pointer.
-
-   'wordsSz' is the number of elements of space in 'words'.  If there are more
-   words in 's' than will fit in that space (including the terminating null
-   pointer), ignore the excess on the right.
------------------------------------------------------------------------------*/
-    unsigned int n;  /* Number of words in words[] so far */
-    char * p;
-
-    p = &s[0];
-    n = 0;
-
-    while (*p) {
-        if (ISSPACE(*p))
-            *p++ = '\0';
-        else {
-            words[n++] = p;
-            if (n >= wordsSz - 1)
-                break;
-            while (*p && !ISSPACE(*p))
-                ++p;
-        }
-    }
-    assert(n <= wordsSz - 1);
-    words[n] = NULL;
-}
-
-
-
-static void
-readline_read(Readline * const readlineP,
-              bool *     const eofP) {
-/*----------------------------------------------------------------------------
-   Read a nonblank line from the file.  Make its contents available
-   as readlineP->arg[].
-
-   Return *eofP == true iff there is no nonblank line before EOF or we
-   are unable to read the file.
------------------------------------------------------------------------------*/
-    bool gotLine;
-    bool error;
-
-    for (gotLine = false, error = false; !gotLine && !error; ) {
-        char * rc;
-
-        rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP);
-        if (rc == NULL)
-            error = true;
-        else {
-            tokenize(readlineP->line,
-                     readlineP->arg, ARRAY_SIZE(readlineP->arg));
-            if (readlineP->arg[0] != NULL)
-                gotLine = true;
-        }
-    }
-    *eofP = error;
-}
-
-
-
-static void
-parseBitmapRow(const char *    const hex,
-               unsigned int    const glyphWidth,
-               unsigned char * const bmap,
-               unsigned int    const origBmapIndex,
-               unsigned int *  const newBmapIndexP,
-               const char **   const errorP) {
-/*----------------------------------------------------------------------------
-   Parse one row of the bitmap for a glyph, from the hexadecimal string
-   for that row in the font file, 'hex'.  The glyph is 'glyphWidth'
-   pixels wide.
-
-   We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP.
------------------------------------------------------------------------------*/
-    unsigned int bmapIndex;
-    int i;  /* dot counter */
-    const char * p;
-
-    bmapIndex = origBmapIndex;
-
-    for (i = glyphWidth, p = &hex[0], *errorP = NULL;
-         i > 0 && !*errorP;
-         i -= 4) {
-
-        if (*p == '\0')
-            pm_asprintf(errorP, "Not enough hexadecimal digits for glyph "
-                        "of width %u in '%s'",
-                        glyphWidth, hex);
-        else {
-            char const hdig = *p++;
-            unsigned int hdigValue;
-
-            if (hdig >= '0' && hdig <= '9')
-                hdigValue = hdig - '0';
-            else if (hdig >= 'a' && hdig <= 'f')
-                hdigValue = 10 + (hdig - 'a');
-            else if (hdig >= 'A' && hdig <= 'F')
-                hdigValue = 10 + (hdig - 'A');
-            else 
-                pm_asprintf(errorP,
-                            "Invalid hex digit x%02x (%c) in bitmap data '%s'",
-                            (unsigned int)(unsigned char)hdig, 
-                            isprint(hdig) ? hdig : '.',
-                            hex);
-
-            if (!*errorP) {
-                if (i > 0)
-                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
-                if (i > 1)
-                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
-                if (i > 2)
-                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
-                if (i > 3)
-                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
-            }
-        }
-    }
-    *newBmapIndexP = bmapIndex;
-}
-
-
-
-static void
-readBitmap(Readline *      const readlineP,
-           unsigned int    const glyphWidth,
-           unsigned int    const glyphHeight,
-           const char *    const charName,
-           unsigned char * const bmap) {
-
-    int n;
-    unsigned int bmapIndex;
-
-    bmapIndex = 0;
-           
-    for (n = glyphHeight; n > 0; --n) {
-        bool eof;
-        const char * error;
-
-        readline_read(readlineP, &eof);
-
-        if (eof)
-            pm_error("End of file in bitmap for character '%s' in BDF "
-                     "font file.", charName);
-
-        if (!readlineP->arg[0])
-            pm_error("A line that is supposed to contain bitmap data, "
-                     "in hexadecimal, for character '%s' is empty", charName);
-
-        parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex,
-                       &bmapIndex, &error);
-
-        if (error) {
-            pm_error("Error in line %d of bitmap for character '%s': %s",
-                     n, charName, error);
-            pm_strfree(error);
-        }
-    }
-}
-
-
-
-static void
-createBmap(unsigned int  const glyphWidth,
-           unsigned int  const glyphHeight,
-           Readline *    const readlineP,
-           const char *  const charName,
-           const char ** const bmapP) {
-
-    unsigned char * bmap;
-    bool eof;
-    
-    if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight)
-        pm_error("Ridiculously large glyph");
-
-    MALLOCARRAY(bmap, glyphWidth * glyphHeight);
-
-    if (!bmap)
-        pm_error("no memory for font glyph byte map");
-
-    readline_read(readlineP, &eof);
-    if (eof)
-        pm_error("End of file encountered reading font glyph byte map from "
-                 "BDF font file.");
-    
-    if (streq(readlineP->arg[0], "ATTRIBUTES")) {
-        /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF)
-           Specification Version 2.1, but not in Version 2.2. 
-        */
-        bool eof;
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file encountered after ATTRIBUTES in BDF "
-                     "font file.");
-    }                
-    if (!streq(readlineP->arg[0], "BITMAP"))
-        pm_error("'%s' found where BITMAP expected in definition of "
-                 "character '%s' in BDF font file.",
-                 readlineP->arg[0], charName);
-
-    assert(streq(readlineP->arg[0], "BITMAP"));
-
-    readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap);
-
-    *bmapP = (char *)bmap;
-}
-
-
-
-static void
-readExpectedStatement(Readline *    const readlineP,
-                      const char *  const expected) {
-/*----------------------------------------------------------------------------
-  Have the readline object *readlineP read the next line from the file, but
-  expect it to be a line of type 'expected' (i.e. the verb token at the
-  beginning of the line is that, e.g. "STARTFONT").  If it isn't, fail the
-  program.
------------------------------------------------------------------------------*/
-    bool eof;
-
-    readline_read(readlineP, &eof);
-
-    if (eof)
-        pm_error("EOF in BDF font file where '%s' expected", expected);
-    else if (!streq(readlineP->arg[0], expected))
-        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
-                 readlineP->arg[0], expected);
-}
-
-
-
-static void
-skipCharacter(Readline * const readlineP) {
-/*----------------------------------------------------------------------------
-  In the BDF font file being read by readline object *readlineP, skip through
-  the end of the character we are presently in.
------------------------------------------------------------------------------*/
-    bool endChar;
-                        
-    endChar = FALSE;
-                        
-    while (!endChar) {
-        bool eof;
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file in the middle of a character (before "
-                     "ENDCHAR) in BDF font file.");
-        endChar = streq(readlineP->arg[0], "ENDCHAR");
-    }                        
-}
-
-
-
-static void
-interpEncoding(const char **  const arg,
-               unsigned int * const codepointP,
-               bool *         const badCodepointP,
-               PM_WCHAR       const maxglyph) {
-/*----------------------------------------------------------------------------
-   With arg[] being the ENCODING statement from the font, return as
-   *codepointP the codepoint that it indicates (code point is the character
-   code, e.g. in ASCII, 48 is '0').
-
-   But if the statement doesn't give an acceptable codepoint return
-   *badCodepointP == TRUE.
-
-   'maxglyph' is the maximum codepoint in the font.
------------------------------------------------------------------------------*/
-    bool gotCodepoint;
-    bool badCodepoint;
-    unsigned int codepoint;
-
-    if (!arg[1])
-        pm_error("Invalid ENCODING statement - no arguments");
-    if (atoi(arg[1]) >= 0) {
-        codepoint = atoi(arg[1]);
-        gotCodepoint = true;
-    } else {
-      if (atoi(arg[1]) == -1 && arg[2] != NULL) {
-            codepoint = atoi(arg[2]);
-            gotCodepoint = true;
-        } else
-            gotCodepoint = false;
-    }
-    if (gotCodepoint) {
-        if (codepoint > maxglyph)
-            badCodepoint = true;
-        else
-            badCodepoint = false;
-    } else
-        badCodepoint = true;
-
-    *badCodepointP = badCodepoint;
-    *codepointP    = codepoint;
-}
-
-
-
-static void
-readEncoding(Readline *     const readlineP,
-             unsigned int * const codepointP,
-             bool *         const badCodepointP,
-             PM_WCHAR       const maxglyph) {
-
-    readExpectedStatement(readlineP, "ENCODING");
-    
-    interpEncoding(readlineP->arg, codepointP, badCodepointP, maxglyph);
-}
-
-
-
-static void
-validateFontLimits(const struct font2 * const fontP) {
-
-    assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0);
-
-    if (fontP->maxwidth  <= 0 ||
-        fontP->maxheight <= 0 ||
-        fontP->maxwidth  > pbm_maxfontwidth()  ||
-        fontP->maxheight > pbm_maxfontheight() ||
-        -fontP->x + 1 > fontP->maxwidth ||
-        -fontP->y + 1 > fontP->maxheight ||
-        fontP->x > fontP->maxwidth  ||
-        fontP->y > fontP->maxheight ||
-        fontP->x + fontP->maxwidth  > pbm_maxfontwidth() || 
-        fontP->y + fontP->maxheight > pbm_maxfontheight()
-        ) {
-
-        pm_error("Global font metric(s) out of bounds."); 
-    }
-
-    if (fontP->maxglyph > PM_FONT2_MAXGLYPH)
-        pm_error("Internal error.  Glyph table too large: %u glyphs; "
-                 "Maximum possible in Netpbm is %u",
-                 fontP->maxglyph, PM_FONT2_MAXGLYPH);
-}
-
-
-
-static void
-validateGlyphLimits(const struct font2 * const fontP,
-                    const struct glyph * const glyphP,
-                    const char *         const charName) {
-
-    /* Some BDF files code space with zero width and height,
-       no bitmap data and just the xadd value.
-       We allow zero width and height, iff both are zero.
-    */
-
-    if (((glyphP->width == 0 || glyphP->height == 0) &&
-         !(glyphP->width == 0 && glyphP->height == 0)) ||
-        glyphP->width  > fontP->maxwidth  ||
-        glyphP->height > fontP->maxheight ||
-        glyphP->x < fontP->x ||
-        glyphP->y < fontP->y ||
-        glyphP->x + (int) glyphP->width  > fontP->x + fontP->maxwidth  ||
-        glyphP->y + (int) glyphP->height > fontP->y + fontP->maxheight ||
-        glyphP->xadd > pbm_maxfontwidth() ||
-        glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width >
-        pbm_maxfontwidth()
-        ) {
-
-        pm_error("Font metric(s) for char '%s' out of bounds.\n", charName);
-    }
-}
-
-
-
-static void
-processChars(Readline *     const readlineP,
-             struct font2 * const fontP,
-             PM_WCHAR       const maxglyph ) {
-/*----------------------------------------------------------------------------
-   Process the CHARS block in a BDF font file, assuming the file is positioned
-   just after the CHARS line.  Read the rest of the block and apply its
-   contents to *fontP.
------------------------------------------------------------------------------*/
-    unsigned int nCharacters;
-    unsigned int nCharsDone;
-
-    if (!readlineP->arg[1])
-        pm_error("Invalid CHARS line - no arguments");
-
-    nCharacters = atoi(readlineP->arg[1]);
-
-    nCharsDone = 0;
-
-    while (nCharsDone < nCharacters) {
-        bool eof;
-
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file after CHARS reading BDF font file");
-
-        if (streq(readlineP->arg[0], "COMMENT")) {
-            /* ignore */
-        } else if (!streq(readlineP->arg[0], "STARTCHAR"))
-            pm_error("no STARTCHAR after CHARS in BDF font file");
-        else if (!readlineP->arg[1])
-            pm_error("Invalid STARTCHAR - no arguments");
-        else {
-            const char * const charName = pm_strdup(readlineP->arg[1]);
-
-            struct glyph * glyphP;
-            unsigned int codepoint;
-            bool badCodepoint;
-
-            assert(streq(readlineP->arg[0], "STARTCHAR"));
-
-            MALLOCVAR(glyphP);
-
-            if (glyphP == NULL)
-                pm_error("no memory for font glyph for '%s' character",
-                         charName);
-
-            readEncoding(readlineP, &codepoint, &badCodepoint, maxglyph);
-
-            if (badCodepoint)
-                skipCharacter(readlineP);
-            else if (fontP->glyph[codepoint] != NULL)
-                pm_error("Multiple definition of code point %d "
-                         "in font file", (unsigned int) codepoint); 
-            else {
-                readExpectedStatement(readlineP, "SWIDTH");
-                    
-                readExpectedStatement(readlineP, "DWIDTH");
-                if (!readlineP->arg[1])
-                    pm_error("Invalid DWIDTH statement - no arguments");
-                glyphP->xadd = atoi(readlineP->arg[1]);
-
-                readExpectedStatement(readlineP, "BBX");
-                if (!readlineP->arg[1])
-                    pm_error("Invalid BBX statement - no arguments");
-                glyphP->width  = atoi(readlineP->arg[1]);
-                if (!readlineP->arg[2])
-                    pm_error("Invalid BBX statement - only 1 argument");
-                glyphP->height = atoi(readlineP->arg[2]);
-                if (!readlineP->arg[3])
-                    pm_error("Invalid BBX statement - only 2 arguments");
-                glyphP->x      = atoi(readlineP->arg[3]);
-                if (!readlineP->arg[4])
-                    pm_error("Invalid BBX statement - only 3 arguments");
-                glyphP->y      = atoi(readlineP->arg[4]);
-
-                validateGlyphLimits(fontP, glyphP, charName);
-
-                createBmap(glyphP->width, glyphP->height, readlineP, charName,
-                           &glyphP->bmap);
-                
-
-                readExpectedStatement(readlineP, "ENDCHAR");
-
-                assert(codepoint <= maxglyph); /* Ensured by readEncoding() */
-
-                fontP->glyph[codepoint] = glyphP;
-                pm_strfree(charName);
-            }
-            ++nCharsDone;
-        }
-    }
-}
-
-
-
-static void
-processBdfFontLine(Readline     * const readlineP,
-                   struct font2 * const fontP,
-                   bool         * const endOfFontP,
-                   PM_WCHAR       const maxglyph) {
-/*----------------------------------------------------------------------------
-   Process a nonblank line just read from a BDF font file.
-
-   This processing may involve reading more lines.
------------------------------------------------------------------------------*/
-    *endOfFontP = FALSE;  /* initial assumption */
-
-    assert(readlineP->arg[0] != NULL);  /* Entry condition */
-
-    if (streq(readlineP->arg[0], "COMMENT")) {
-        /* ignore */
-    } else if (streq(readlineP->arg[0], "SIZE")) {
-        /* ignore */
-    } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) {
-        /* Read off the properties and ignore them all */
-        unsigned int propCount;
-        unsigned int i;
-
-        if (!readlineP->arg[1])
-            pm_error("Invalid STARTPROPERTIES statement - no arguments");
-        propCount = atoi(readlineP->arg[1]);
-
-        for (i = 0; i < propCount; ++i) {
-            bool eof;
-            readline_read(readlineP, &eof);
-            if (eof)
-                pm_error("End of file after STARTPROPERTIES in BDF font file");
-        }
-    } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) {
-        if (!readlineP->arg[1])
-            pm_error("Invalid FONTBOUNDINGBOX  statement - no arguments");
-        fontP->maxwidth  = atoi(readlineP->arg[1]);
-        if (!readlineP->arg[2])
-            pm_error("Invalid FONTBOUNDINGBOX  statement - only 1 argument");
-        fontP->maxheight = atoi(readlineP->arg[2]);
-        if (!readlineP->arg[3])
-            pm_error("Invalid FONTBOUNDINGBOX  statement - only 2 arguments");
-        fontP->x = atoi(readlineP->arg[3]);
-        if (!readlineP->arg[4])
-            pm_error("Invalid FONTBOUNDINGBOX  statement - only 3 arguments");
-        fontP->y = atoi(readlineP->arg[4]);
-        validateFontLimits(fontP);
-    } else if (streq(readlineP->arg[0], "ENDPROPERTIES")) {
-      if (fontP->maxwidth ==0)
-      pm_error("Encountered ENDPROPERTIES before FONTBOUNDINGBOX " 
-                   "in BDF font file");
-    } else if (streq(readlineP->arg[0], "ENDFONT")) {
-        *endOfFontP = true;
-    } else if (streq(readlineP->arg[0], "CHARS")) {
-      if (fontP->maxwidth ==0)
-      pm_error("Encountered CHARS before FONTBOUNDINGBOX " 
-                   "in BDF font file");
-      else
-        processChars(readlineP, fontP, maxglyph);
-    } else {
-        /* ignore */
-    }
-}
-
-
-
-struct font2 *
-pbm_loadbdffont2(const char * const filename,
-                 PM_WCHAR     const maxglyph) {
-/*----------------------------------------------------------------------------
-   Read a BDF font file "filename" as a 'font2' structure.  A 'font2'
-   structure is more expressive than a 'font' structure, most notably in that
-   it can handle wide code points and many more glyphs.
-
-   Codepoints up to maxglyph inclusive are valid in the file.
-
-   The returned object is in new malloc'ed storage, in many pieces, and
-   cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* For our return object to be destroyable, we need to supply a destroy
-       function, and it needs to return glyph and raster memory, and
-       struct font needs to manage glyph memory separately from mapping
-       code points to glyphs.
-    */
-    FILE *         ifP;
-    Readline       readline;
-    struct font2 * font2P;
-    bool           endOfFont;
-
-    ifP = fopen(filename, "rb");
-    if (!ifP)
-        pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
-                 filename, errno, strerror(errno));
-
-    readline_init(&readline, ifP);
-
-    MALLOCVAR(font2P);
-    if (font2P == NULL)
-        pm_error("no memory for font");
-
-    MALLOCARRAY(font2P->glyph, maxglyph + 1);
-    if (font2P->glyph == NULL)
-        pm_error("no memory for font glyphs");
-
-    font2P->maxglyph = maxglyph;
-
-    font2P->oldfont = NULL;
-    {
-        /* Initialize all characters to nonexistent; we will fill the ones we
-           find in the bdf file later.
-        */
-        PM_WCHAR i;
-        for (i = 0; i <= maxglyph; ++i)
-            font2P->glyph[i] = NULL;
-    }
-
-    font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0;
-
-    readExpectedStatement(&readline, "STARTFONT");
-
-    endOfFont = FALSE;
-
-    while (!endOfFont) {
-        bool eof;
-        readline_read(&readline, &eof);
-        if (eof)
-            pm_error("End of file before ENDFONT statement in BDF font file");
-
-        processBdfFontLine(&readline, font2P, &endOfFont, maxglyph);
-    }
-    return font2P;
-}
-
-
-
-struct font *
-pbm_loadbdffont(const char * const filename) {
-/*----------------------------------------------------------------------------
-   Read a BDF font file "filename" into a traditional font structure.
-
-   Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid.
-
-   Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc.
-
-   The returned object is in new malloc'ed storage, in many pieces, and
-   cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* For our return object to deep copy the glyphs and fonts from
-       the struct font2be destroyable, we need to supply a destroy
-       function, and it needs to return glyph and raster memory, and
-       struct font needs to manage glyph memory separately from mapping
-       code points to glyphs.
-    */
-    struct font  * fontP;
-    struct font2 * font2P;
-    unsigned int   codePoint;
-
-    MALLOCVAR(fontP);
-    if (fontP == NULL)
-        pm_error("no memory for font");
-
-    font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH);
-
-    fontP->maxwidth  = font2P->maxwidth;
-    fontP->maxheight = font2P->maxheight;
-
-    fontP->x = font2P->x;
-    fontP->y = font2P->y;
-
-    for (codePoint = 0; codePoint < PM_FONT_MAXGLYPH + 1; ++codePoint)
-        fontP->glyph[codePoint] = font2P->glyph[codePoint];
-
-    fontP->oldfont = NULL;
-
-    fontP->fcols = 0;
-    fontP->frows = 0;
-
-    /* Note that *fontP2 is unfreeable.  See pbm_loadbdffont2.  And even if it
-       were, we hooked *fontP into it above, so that would have to turn into a
-       deep copy before we could free *fontP2.
-    */
-
-    return fontP;
-}
-
-
-
-struct font2 *
-pbm_expandbdffont(const struct font * const fontP) {
-/*----------------------------------------------------------------------------
-  Convert a traditional font structure into an expanded font2 structure.
-
-  This function depends upon the fact that *fontP, like any struct font,
-  cannot be destroyed.  The returned object refers to memory that belongs
-  to *fontP.
-
-  The returned object is in new malloc'ed storage, in many pieces, and
-  cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* If we ever make struct font destroyable, this function needs to
-       copy the glyphs and rasters, and struct font and struct font2 need
-       to manage glyph memory separately from mapping code points to the
-       glyphs.
-    */
-    PM_WCHAR const maxglyph = PM_FONT_MAXGLYPH;
-
-    struct font2 * font2P;
-    unsigned int   codePoint;
-
-    MALLOCVAR(font2P);
-    if (font2P == NULL)
-        pm_error("no memory for font");
-
-    MALLOCARRAY(font2P->glyph, maxglyph + 1);
-    if (font2P->glyph == NULL)
-        pm_error("no memory for font glyphs");
-
-    font2P->maxwidth  = fontP->maxwidth;
-    font2P->maxheight = fontP->maxheight;
-
-    font2P->x = fontP->x;
-    font2P->y = fontP->y;
-
-    font2P->maxglyph = maxglyph;
-
-    for (codePoint = 0; codePoint < maxglyph + 1; ++codePoint)
-        font2P->glyph[codePoint] = fontP->glyph[codePoint];
-
-    font2P->oldfont = fontP->oldfont;
-
-    font2P->fcols = fontP->fcols;
-    font2P->frows = fontP->frows;
-
-    return font2P;
-}
-
-
diff --git a/lib/libpbmfont0.c b/lib/libpbmfont0.c
new file mode 100644
index 00000000..add08047
--- /dev/null
+++ b/lib/libpbmfont0.c
@@ -0,0 +1,335 @@
+/*
+**
+** Font routines.
+**
+** Wide character stuff written by Akira Urushibata in 2018 and contributed
+** to the public domain.
+**
+** Also copyright (C) 1991 by Jef Poskanzer and licensed to the public as
+** follows.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbm.h"
+#include "pbmfont.h"
+#include "pbmfontdata.h"
+
+
+struct font *
+pbm_defaultfont(const char * const name) {
+/*----------------------------------------------------------------------------
+   Generate the built-in font with name 'name'.
+-----------------------------------------------------------------------------*/
+    struct font * retval;
+
+    if (streq(name, "bdf"))
+        retval = &pbm_defaultBdffont;
+    else if (streq(name, "fixed"))
+        retval = &pbm_defaultFixedfont;
+    else
+        pm_error( "built-in font name unknown, try 'bdf' or 'fixed'");
+
+    return retval;
+}
+
+
+
+struct font2 *
+pbm_defaultfont2(const char * const requestedFontName) {
+
+    struct font2 * font2P;
+    struct font2 * retval = NULL; /* initial value */
+    unsigned int i;
+
+    for (i = 0; retval == NULL; ++i) {
+        const char * longName;
+        const char * shortName;
+        font2P = (struct font2 * ) pbm_builtinFonts[i];
+        if (font2P == NULL)
+            break;
+
+        longName = font2P->name;
+        shortName = &longName[strlen("builtin ")];
+
+        if (streq(shortName, requestedFontName))
+             retval = font2P;
+    }
+
+    if (retval == NULL)
+        pm_error("No builtin font named %s", requestedFontName);
+
+    return retval;
+}
+
+
+
+static void
+selectFontType(const    char * const filename,
+               PM_WCHAR        const maxmaxglyph,
+               unsigned int    const isWide,
+               struct font  ** const fontPP,
+               struct font2 ** const font2PP) {
+
+    FILE * fileP;
+    struct font  * fontP  = NULL; /* initial value */
+    struct font2 * font2P = NULL; /* initial value */
+    char line[10] = "\0\0\0\0\0\0\0\0\0\0";
+        /* Initialize to suppress Valgrind error which is reported when file
+           is empty or very small.
+        */
+
+    fileP = pm_openr(filename);
+    fgets(line, 10, fileP);
+    pm_close(fileP);
+
+    if (line[0] == PBM_MAGIC1 &&
+        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
+        if (isWide == TRUE)
+            font2P = pbm_loadpbmfont2(filename);
+        else
+            fontP  = pbm_loadpbmfont(filename);
+        if (fontP == NULL && font2P == NULL)
+            pm_error("could not load PBM font file");
+
+    } else if (!strncmp(line, "STARTFONT", 9)) {
+        if (isWide == TRUE)
+            font2P = pbm_loadbdffont2(filename, maxmaxglyph);
+        else
+            fontP = pbm_loadbdffont(filename);
+        if (fontP == NULL && font2P == NULL)
+            pm_error("could not load BDF font file");
+
+    } else {
+        pm_error("font file not in a recognized format.  Does not start "
+                 "with the signature of a PBM file or BDF font file");
+        assert(false);
+        fontP = NULL;  /* defeat compiler warning */
+    }
+
+    if (isWide)
+        *font2PP = font2P;
+    else
+        *fontPP = fontP;
+}
+
+
+
+struct font *
+pbm_loadfont(const    char * const filename) {
+
+    struct font  * fontP;
+    struct font2 * font2P;
+
+    selectFontType(filename, PM_FONT_MAXGLYPH, FALSE, &fontP, &font2P);
+    return fontP;
+}
+
+
+
+struct font2 *
+pbm_loadfont2(const    char * const filename,
+              PM_WCHAR        const maxmaxglyph) {
+
+    struct font  * fontP;
+    struct font2 * font2P;
+
+    selectFontType(filename, maxmaxglyph, TRUE, &fontP, &font2P);
+    return font2P;
+}
+
+
+
+void
+pbm_createbdffont2_base(struct font2 ** const font2PP,
+                        PM_WCHAR        const maxmaxglyph) {
+
+    struct font2 * font2P;
+
+    MALLOCVAR(font2P);
+    if (font2P == NULL)
+        pm_error("no memory for font");
+
+    MALLOCARRAY(font2P->glyph, maxmaxglyph + 1);
+    if (font2P->glyph == NULL)
+        pm_error("no memory for font glyphs");
+
+    /* Initialize */
+    font2P->size = sizeof (struct font2);
+    font2P->len  = PBM_FONT2_STRUCT_SIZE(charset_string);
+
+    /*  Caller should overwrite following fields as necessary */
+    font2P->oldfont = NULL;
+    font2P->fcols = font2P->frows = 0;
+    font2P->selector = NULL;
+    font2P->default_char = 0;
+    font2P->default_char_defined = FALSE;
+    font2P->total_chars = font2P->chars = 0;
+    font2P->name = NULL;
+    font2P->charset = ENCODING_UNKNOWN;
+    font2P->charset_string = NULL;
+
+    *font2PP = font2P;
+}
+
+
+
+static void
+destroyGlyphData(struct glyph ** const glyph,
+                 PM_WCHAR        const maxglyph) {
+/*----------------------------------------------------------------------------
+  Free glyph objects and bitmap objects.
+
+  This does not work when an object is "shared" through multiple pointers
+  referencing an identical address and thus pointing to a common glyph
+  or bitmap object.
+-----------------------------------------------------------------------------*/
+
+    PM_WCHAR i;
+
+    for(i = 0; i <= maxglyph; ++i) {
+        if (glyph[i]!=NULL) {
+            free((void *) (glyph[i]->bmap));
+            free(glyph[i]);
+      }
+    }
+}
+
+
+void
+pbm_destroybdffont2_base(struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+  Free font2 structure, but not the glyph data
+---------------------------------------------------------------------------- */
+
+    free(font2P->selector);
+
+    free(font2P->name);
+    free(font2P->charset_string);
+    free(font2P->glyph);
+
+    if (font2P->oldfont !=NULL)
+       pbm_freearray(font2P->oldfont, font2P->frows);
+
+    free((void *)font2P);
+
+}
+
+
+
+void
+pbm_destroybdffont2(struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+  Free font2 structure and glyph data
+
+  Examines the 'load_fn' field to check whether the object is fixed data.
+  Do nothing if 'load_fn' is 'FIXED_DATA'.
+---------------------------------------------------------------------------- */
+
+    if (font2P->load_fn != FIXED_DATA) {
+        destroyGlyphData(font2P->glyph, font2P->maxglyph);
+        pbm_destroybdffont2_base(font2P);
+    }
+}
+
+
+
+void
+pbm_destroybdffont(struct font * const fontP) {
+/*----------------------------------------------------------------------------
+  Free font structure and glyph data.
+
+  For freeing a structure created by pbm_loadbdffont() or pbm_loadpbmfont().
+---------------------------------------------------------------------------- */
+
+    destroyGlyphData(fontP->glyph, PM_FONT_MAXGLYPH);
+
+    if (fontP->oldfont !=NULL)
+       pbm_freearray(fontP->oldfont, fontP->frows);
+
+    free(fontP);
+}
+
+
+
+struct font2 *
+pbm_expandbdffont(const struct font * const fontP) {
+/*----------------------------------------------------------------------------
+  Convert a traditional 'font' structure into an expanded 'font2' structure.
+
+  After calling this function *fontP may be freed, but not the individual
+  glyph data: fontP->glyph[0...255] .
+
+  Using this function on static data is not recommended.  Rather add
+  the extra fields to make a font2 structure.  See file pbmfontdata1.c
+  for an example.
+
+  The returned object is in new malloc'ed storage, in many pieces.
+
+  Destroy with pbm_destroybdffont2() if *fontP is read from a file.
+
+  Destroy with pbm_destroybdffont2_base() if *fontP is static data
+  and you desire to defy the above-stated recommendation.
+
+  The general function for conversion in the opposite direction
+  'font2' => 'font' is font2ToFont() in libpbmfont2.c .  It is currently
+  declared as static.
+ ---------------------------------------------------------------------------*/
+    PM_WCHAR maxglyph, codePoint;
+    unsigned int nCharacters;
+    struct font2 * font2P;
+
+    pbm_createbdffont2_base(&font2P, PM_FONT_MAXGLYPH);
+
+    font2P->maxwidth  = fontP->maxwidth;
+    font2P->maxheight = fontP->maxheight;
+
+    font2P->x = fontP->x;
+    font2P->y = fontP->y;
+
+    /* Hunt for max non-NULL entry in glyph table */
+    for (codePoint = PM_FONT_MAXGLYPH;
+         fontP->glyph[codePoint] == NULL && codePoint > 0; --codePoint)
+        ;
+
+    maxglyph = font2P->maxglyph = codePoint;
+    assert (0 <= maxglyph && maxglyph <= PM_FONT_MAXGLYPH);
+
+    if (maxglyph == 0 && fontP->glyph[0] == NULL)
+        pm_error("no glyphs loaded");
+
+    REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1);
+
+    for (codePoint = 0; codePoint <= maxglyph; ++codePoint) {
+        font2P->glyph[codePoint] = fontP->glyph[codePoint];
+
+        if (font2P->glyph[codePoint] != NULL)
+           ++nCharacters;
+    }
+
+    font2P->oldfont = fontP->oldfont;
+    font2P->fcols = fontP->fcols;
+    font2P->frows = fontP->frows;
+
+    font2P->bit_format = PBM_FORMAT;
+    font2P->total_chars = font2P->chars = nCharacters;
+    font2P->load_fn = CONVERTED_TYPE1_FONT;
+    /* Caller should be overwrite the above to a more descriptive
+       value */
+    return font2P;
+}
+
+
+
diff --git a/lib/libpbmfont1.c b/lib/libpbmfont1.c
new file mode 100644
index 00000000..2b0993a9
--- /dev/null
+++ b/lib/libpbmfont1.c
@@ -0,0 +1,359 @@
+/*
+**
+** Routines for loading a PBM sheet font file
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbm.h"
+#include "pbmfont.h"
+
+
+/*----------------------------------------------------------------------------
+
+  The routines in this file reads a font bitmap representing
+  the following text:
+
+  (0,0)
+     M ",/^_[`jpqy| M
+
+     /  !"#$%&'()*+ /
+     < ,-./01234567 <
+     > 89:;<=>?@ABC >
+     @ DEFGHIJKLMNO @
+     _ PQRSTUVWXYZ[ _
+     { \]^_`abcdefg {
+     } hijklmnopqrs }
+     ~ tuvwxyz{|}~  ~
+
+     M ",/^_[`jpqy| M
+
+  The bitmap must be cropped exactly to the edges.
+
+  The characters in the border you see are irrelevant except for
+  character size compuations.  The 12 x 8 array in the center is
+  the font.  The top left character there belongs to code point
+  0, and the code points increase in standard reading order, so
+  the bottom right character is code point 127.  You can't define
+  code points < 32 or > 127 with this font format.
+
+  The characters in the top and bottom border rows must include a
+  character with the lowest reach of any in the font (e.g. "y",
+  "_") and one with the highest reach (e.g. '"').  The characters
+  in the left and right border columns must include characters
+  with the rightmost and leftmost reach of any in the font
+  (e.g. "M" for both).
+
+  The border must be separated from the font by one blank text
+  row or text column.
+-----------------------------------------------------------------------------*/
+
+
+static unsigned int const firstCodePoint = 32;
+    /* This is the code point of the first character in a pbmfont font.
+       In ASCII, it is a space.
+    */
+
+static unsigned int const nCharsInFont = 96;
+    /* The number of characters in a pbmfont font.  A pbmfont font defines
+       characters at position 32 (ASCII space) through 127, so that's 96.
+    */
+
+
+static void
+findFirstBlankRow(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const browP) {
+
+    unsigned int row;
+    bool foundBlank;
+
+    for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) {
+        unsigned int col;
+        bit col0Value = font[row][0];
+        bool rowIsBlank;
+        rowIsBlank = true;  /* initial assumption */
+        for (col = 1; col < fcols; ++col)
+            if (font[row][col] != col0Value)
+                rowIsBlank = false;
+
+        if (rowIsBlank) {
+            foundBlank = true;
+            *browP = row;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel row in font");
+}
+
+
+
+static void
+findFirstBlankCol(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const bcolP) {
+
+    unsigned int col;
+    bool foundBlank;
+
+    for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) {
+        unsigned int row;
+        bit row0Value = font[0][col];
+        bool colIsBlank;
+        colIsBlank = true;  /* initial assumption */
+        for (row = 1; row < frows; ++row)
+            if (font[row][col] != row0Value)
+                colIsBlank = false;
+
+        if (colIsBlank) {
+            foundBlank = true;
+            *bcolP = col;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel column in font");
+}
+
+
+
+static void
+computeCharacterSize(const bit **   const font,
+                     unsigned int   const fcols,
+                     unsigned int   const frows,
+                     unsigned int * const cellWidthP,
+                     unsigned int * const cellHeightP,
+                     unsigned int * const charWidthP,
+                     unsigned int * const charHeightP) {
+
+    unsigned int firstBlankRow;
+    unsigned int firstBlankCol;
+    unsigned int heightLast11Rows;
+
+    findFirstBlankRow(font, fcols, frows, &firstBlankRow);
+
+    findFirstBlankCol(font, fcols, frows, &firstBlankCol);
+
+    heightLast11Rows = frows - firstBlankRow;
+
+    if (heightLast11Rows % 11 != 0)
+        pm_error("The rows of characters in the font do not appear to "
+                 "be all the same height.  The last 11 rows are %u pixel "
+                 "rows high (from pixel row %u up to %u), "
+                 "which is not a multiple of 11.",
+                 heightLast11Rows, firstBlankRow, frows);
+    else {
+        unsigned int widthLast15Cols;
+
+        *cellHeightP = heightLast11Rows / 11;
+
+        widthLast15Cols = fcols - firstBlankCol;
+
+        if (widthLast15Cols % 15 != 0)
+            pm_error("The columns of characters in the font do not appear to "
+                     "be all the same width.  "
+                     "The last 15 columns are %u pixel "
+                     "columns wide (from pixel col %u up to %u), "
+                     "which is not a multiple of 15.",
+                     widthLast15Cols, firstBlankCol, fcols);
+        else {
+            *cellWidthP = widthLast15Cols / 15;
+
+            *charWidthP = firstBlankCol;
+            *charHeightP = firstBlankRow;
+        }
+    }
+}
+
+
+
+struct font*
+pbm_dissectfont(const bit ** const fontsheet,
+                unsigned int const frows,
+                unsigned int const fcols) {
+/*----------------------------------------------------------------------------
+  Dissect PBM sheet font data, create a font structre,
+  load bitmap data into it.
+
+  Return value is a pointer to the newly created font structure
+
+  The input bitmap data is in memory, in one byte per pixel format.
+
+  The dissection works by finding the first blank row and column;
+  i.e the lower right corner of the "M" in the upper left corner
+  of the matrix.  That gives the height and width of the
+  maximum-sized character, which is not too useful.  But the
+  distance from there to the opposite side is an integral
+  multiple of the cell size, and that's what we need.  Then it's
+  just a matter of filling in all the coordinates.
+
+  Struct font has fields 'oldfont', 'fcols', 'frows' for backward
+  compability.  If there is any need to load data stored in this format
+  feed the above three, in order, as arguments to this function:
+
+    pbm_dissectfont(oldfont, fcols, frows);
+ ----------------------------------------------------------------------------*/
+
+    unsigned int cellWidth, cellHeight;
+        /* Dimensions in pixels of each cell of the font -- that
+           includes the glyph and the white space above and to the
+           right of it.  Each cell is a tile of the font image.  The
+           top character row and left character row don't count --
+           those cells are smaller because they are missing the white
+           space.
+        */
+    unsigned int charWidth, charHeight;
+        /* Maximum dimensions of glyph itself, inside its cell */
+
+    int row, col, ch, r, c, i;
+    struct font * fn;
+
+    computeCharacterSize(fontsheet, fcols, frows,
+                         &cellWidth, &cellHeight, &charWidth, &charHeight);
+
+    /* Now convert to a general font */
+
+    MALLOCVAR(fn);
+    if (fn == NULL)
+        pm_error("out of memory allocating font structure");
+
+    fn->maxwidth  = charWidth;
+    fn->maxheight = charHeight;
+    fn->x = fn->y = 0;
+
+    fn->oldfont = fontsheet;
+    fn->frows = frows;
+    fn->fcols = fcols;
+
+    /* Now fill in the 0,0 coords. */
+    row = cellHeight * 2;
+    col = cellWidth  * 2;
+
+    /* Load individual glyphs */
+    for ( ch = 0; ch < nCharsInFont; ++ch ) {
+        /* Allocate memory separately for each glyph.
+           pbm_loadbdffont2() does this in exactly the same manner.
+         */
+        struct glyph * const glyph =
+             (struct glyph *) malloc (sizeof (struct glyph));
+        char * const bmap = (char*) malloc(fn->maxwidth * fn->maxheight);
+
+        if ( bmap == NULL || glyph == NULL )
+            pm_error( "out of memory allocating glyph data" );
+
+        glyph->width  = fn->maxwidth;
+        glyph->height = fn->maxheight;
+        glyph->x = glyph->y = 0;
+        glyph->xadd = cellWidth;
+
+        for ( r = 0; r < glyph->height; ++r )
+            for ( c = 0; c < glyph->width; ++c )
+                bmap[r * glyph->width + c] = fontsheet[row + r][col + c];
+
+        glyph->bmap = bmap;
+        fn->glyph[firstCodePoint + ch] = glyph;
+
+        col += cellWidth;
+        if ( col >= cellWidth * 14 ) {
+            col = cellWidth * 2;
+            row += cellHeight;
+        }
+    }
+
+    /* Initialize all remaining character positions to "undefined." */
+    for (i = 0; i < firstCodePoint; ++i)
+        fn->glyph[i] = NULL;
+
+    for (i = firstCodePoint + nCharsInFont; i <= PM_FONT_MAXGLYPH; ++i)
+        fn->glyph[i] = NULL;
+
+    return fn;
+}
+
+
+
+
+struct font *
+pbm_loadpbmfont(const char * const filename) {
+/*----------------------------------------------------------------------------
+  Read PBM font sheet data from file 'filename'.
+  Load data into font structure.
+
+  When done with object, free with pbm_destroybdffont().
+-----------------------------------------------------------------------------*/
+
+    FILE * ifP;
+    bit ** fontsheet;
+    int fcols, frows;
+    struct font * retval;
+
+    ifP = pm_openr(filename);
+
+    fontsheet = pbm_readpbm(ifP, &fcols, &frows);
+
+    if ((fcols - 1) / 16 >= pbm_maxfontwidth() ||
+        (frows - 1) / 12 >= pbm_maxfontheight())
+        pm_error("Absurdly large PBM font file: %s", filename);
+    else if (fcols < 31 || frows < 23) {
+        /* Need at least one pixel per character, and this is too small to
+           have that.
+        */
+        pm_error("PBM font file '%s' too small to be a font file: %u x %u.  "
+                 "Minimum sensible size is 31 x 23",
+                 filename, fcols, frows);
+    }
+
+    pm_close(ifP);
+
+    retval = pbm_dissectfont((const bit **)fontsheet, frows, fcols);
+    return (retval);
+
+}
+
+
+
+struct font2 *
+pbm_loadpbmfont2(const char * const filename) {
+/*----------------------------------------------------------------------------
+  Like pbm_loadpbmfont, but return a pointer to struct font2.
+
+  When done with object, free with pbm_destroybdffont2().
+-----------------------------------------------------------------------------*/
+
+    const struct font * const pbmfont = pbm_loadpbmfont(filename);
+    struct font2 * const retval  = pbm_expandbdffont(pbmfont);
+
+    free ((void *)pbmfont);
+
+    /* Overwrite some fields */
+
+    retval->load_fn = LOAD_PBMSHEET;
+    retval->default_char = (PM_WCHAR) ' ';
+    retval->default_char_defined = TRUE;
+    retval->name = strdup("(PBM sheet font has no name)");
+    retval->charset = ISO646_1991_IRV;
+    retval->charset_string = strdup("ASCII");
+    retval->total_chars = retval->chars = nCharsInFont;
+
+    return(retval);
+}
+
+
+
diff --git a/lib/libpbmfont2.c b/lib/libpbmfont2.c
new file mode 100644
index 00000000..0ee7169c
--- /dev/null
+++ b/lib/libpbmfont2.c
@@ -0,0 +1,1041 @@
+/*
+**
+** Font routines.
+**
+** Wide character stuff written by Akira Urushibata in 2018 and contributed
+** to the public domain.
+**
+** BDF font code by George Phillips, copyright 1993
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** BDF font specs available from:
+** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
+** Glyph Bitmap Distribution Format (BDF) Specification
+** Version 2.2
+** 22 March 1993
+** Adobe Developer Support
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbmfont.h"
+#include "pbm.h"
+
+/*----------------------------------------------------------------------------
+  Routines for loading a BDF font file
+-----------------------------------------------------------------------------*/
+
+/* The following are not recognized in individual glyph data; library
+   routines do a pm_error if they see one:
+
+   Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET,
+   CONTENTVERSION.
+
+   The following is not recognized and is thus ignored at the global level:
+   DWIDTH
+*/
+
+
+#define MAXBDFLINE 1024
+
+/* Official Adobe document says max length of string is 65535 characters.
+   However the value 1024 is sufficient for practical uses.
+*/
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is an object for reading lines of a font file.  It reads and tokenizes
+   them into words.
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
+
+    char line[MAXBDFLINE+1];
+        /* This is the storage space for the words of the line.  The
+           words go in here, one after another, separated by NULs.
+
+           It also functions as a work area for readline_read().
+        */
+    const char * arg[7];
+        /* These are the words; each entry is a pointer into line[] (above) */
+
+    unsigned int wordCt;
+} Readline;
+
+
+
+static void
+readline_init(Readline * const readlineP,
+              FILE *     const ifP) {
+
+    readlineP->ifP = ifP;
+
+    readlineP->arg[0] = NULL;
+    readlineP->wordCt = 0;
+}
+
+
+
+static void
+tokenize(char *         const s,
+         const char **  const words,
+         unsigned int   const wordsSz,
+         unsigned int * const wordCtP) {
+/*----------------------------------------------------------------------------
+   Chop up 's' into words by changing space characters to NUL.  Return as
+   'words' an array of pointers to the beginnings of those words in 's'.
+   Terminate the words[] list with a null pointer.
+
+   'wordsSz' is the number of elements of space in 'words'.  If there are more
+   words in 's' than will fit in that space (including the terminating null
+   pointer), ignore the excess on the right.
+
+   '*wordCtP' is the number elements actually found.
+-----------------------------------------------------------------------------*/
+    unsigned int n;  /* Number of words in words[] so far */
+    char * p;
+
+    p = &s[0];
+    n = 0;
+
+    while (*p) {
+        if (!ISGRAPH(*p)) {
+            if(!ISSPACE(*p)) {
+              /* Control chars excluding 09 - 0d (space), 80-ff */
+            pm_message("Warning: non-ASCII character '%x' in "
+                       "BDF font file", *p);
+            }
+            *p++ = '\0';
+        }
+        else {
+            words[n++] = p;
+            if (n >= wordsSz - 1)
+                break;
+            while (*p && ISGRAPH(*p))
+                ++p;
+        }
+    }
+    assert(n <= wordsSz - 1);
+    words[n] = NULL;
+    *wordCtP = n;
+}
+
+
+
+static void
+readline_read(Readline * const readlineP,
+              bool *     const eofP) {
+/*----------------------------------------------------------------------------
+   Read a nonblank line from the file.  Make its contents available
+   as readlineP->arg[].
+
+   Return *eofP == true iff there is no nonblank line before EOF or we
+   are unable to read the file.
+-----------------------------------------------------------------------------*/
+    bool gotLine;
+    bool error;
+
+    for (gotLine = false, error = false; !gotLine && !error; ) {
+        char * rc;
+
+        rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP);
+        if (rc == NULL)
+            error = true;
+        else {
+            tokenize(readlineP->line, readlineP->arg,
+                     ARRAY_SIZE(readlineP->arg), &readlineP->wordCt);
+            if (readlineP->arg[0] != NULL)
+                gotLine = true;
+        }
+    }
+    *eofP = error;
+}
+
+
+
+static void
+parseBitmapRow(const char *    const hex,
+               unsigned int    const glyphWidth,
+               unsigned char * const bmap,
+               unsigned int    const origBmapIndex,
+               unsigned int *  const newBmapIndexP,
+               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Parse one row of the bitmap for a glyph, from the hexadecimal string
+   for that row in the font file, 'hex'.  The glyph is 'glyphWidth'
+   pixels wide.
+
+   We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP.
+-----------------------------------------------------------------------------*/
+    unsigned int bmapIndex;
+    int i;  /* dot counter */
+    const char * p;
+
+    bmapIndex = origBmapIndex;
+
+    for (i = glyphWidth, p = &hex[0], *errorP = NULL;
+         i > 0 && !*errorP;
+         i -= 4) {
+
+        if (*p == '\0')
+            pm_asprintf(errorP, "Not enough hexadecimal digits for glyph "
+                        "of width %u in '%s'",
+                        glyphWidth, hex);
+        else {
+            char const hdig = *p++;
+            unsigned int hdigValue;
+
+            if (hdig >= '0' && hdig <= '9')
+                hdigValue = hdig - '0';
+            else if (hdig >= 'a' && hdig <= 'f')
+                hdigValue = 10 + (hdig - 'a');
+            else if (hdig >= 'A' && hdig <= 'F')
+                hdigValue = 10 + (hdig - 'A');
+            else
+                pm_asprintf(errorP,
+                            "Invalid hex digit x%02x (%c) in bitmap data '%s'",
+                            (unsigned int)(unsigned char)hdig,
+                            isprint(hdig) ? hdig : '.',
+                            hex);
+
+            if (!*errorP) {
+                if (i > 0)
+                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
+                if (i > 1)
+                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
+                if (i > 2)
+                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
+                if (i > 3)
+                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
+            }
+        }
+    }
+    *newBmapIndexP = bmapIndex;
+}
+
+
+
+static void
+readBitmap(Readline *      const readlineP,
+           unsigned int    const glyphWidth,
+           unsigned int    const glyphHeight,
+           const char *    const charName,
+           unsigned char * const bmap) {
+
+    int n;
+    unsigned int bmapIndex;
+
+    bmapIndex = 0;
+
+    for (n = glyphHeight; n > 0; --n) {
+        bool eof;
+        const char * error;
+
+        readline_read(readlineP, &eof);
+
+        if (eof)
+            pm_error("End of file in bitmap for character '%s' in BDF "
+                     "font file.", charName);
+
+        if (!readlineP->arg[0])
+            pm_error("A line that is supposed to contain bitmap data, "
+                     "in hexadecimal, for character '%s' is empty", charName);
+
+        parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex,
+                       &bmapIndex, &error);
+
+        if (error) {
+            pm_error("Error in line %d of bitmap for character '%s': %s",
+                     n, charName, error);
+            pm_strfree(error);
+        }
+    }
+}
+
+
+
+static void
+createBmap(unsigned int  const glyphWidth,
+           unsigned int  const glyphHeight,
+           Readline *    const readlineP,
+           const char *  const charName,
+           const char ** const bmapP) {
+
+    unsigned char * bmap;
+    bool eof;
+
+    if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight)
+        pm_error("Ridiculously large glyph");
+
+    MALLOCARRAY(bmap, glyphWidth * glyphHeight);
+
+    if (!bmap)
+        pm_error("no memory for font glyph byte map");
+
+    readline_read(readlineP, &eof);
+    if (eof)
+        pm_error("End of file encountered reading font glyph byte map from "
+                 "BDF font file.");
+
+    if (streq(readlineP->arg[0], "ATTRIBUTES")) {
+        /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF)
+           Specification Version 2.1, but not in Version 2.2.
+        */
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file encountered after ATTRIBUTES in BDF "
+                     "font file.");
+    }
+    if (!streq(readlineP->arg[0], "BITMAP"))
+        pm_error("'%s' found where BITMAP expected in definition of "
+                 "character '%s' in BDF font file.",
+                 readlineP->arg[0], charName);
+
+    assert(streq(readlineP->arg[0], "BITMAP"));
+
+    readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap);
+
+    *bmapP = (char *)bmap;
+}
+
+
+
+static void
+validateWordCount(Readline *    const readlineP,
+                  unsigned int  const nWords) {
+
+    if( readlineP->wordCt != nWords )
+        pm_error("Wrong number of arguments in '%s' line in BDF font file",
+                 readlineP->arg[0]);
+
+    /* We assume that the first word in line 'arg[0]' is a valid string */
+
+}
+
+
+static void
+readExpectedStatement(Readline *    const readlineP,
+                      const char *  const expected,
+                      unsigned int  const nWords) {
+/*----------------------------------------------------------------------------
+  Have the readline object *readlineP read the next line from the file, but
+  expect it to be a line of type 'expected' (i.e. the verb token at the
+  beginning of the line is that, e.g. "STARTFONT").  Check for the number
+  of words: 'nWords'.  If either condition is not met, fail the program.
+-----------------------------------------------------------------------------*/
+
+    bool eof;
+
+    readline_read(readlineP, &eof);
+
+    if (eof)
+        pm_error("EOF in BDF font file where '%s' expected", expected);
+    else if (!streq(readlineP->arg[0], expected))
+        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
+                 readlineP->arg[0], expected);
+
+    validateWordCount(readlineP, nWords);
+
+}
+
+
+
+static void
+skipCharacter(Readline * const readlineP) {
+/*----------------------------------------------------------------------------
+  In the BDF font file being read by readline object *readlineP, skip through
+  the end of the character we are presently in.
+-----------------------------------------------------------------------------*/
+    bool endChar;
+
+    endChar = FALSE;
+
+    while (!endChar) {
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file in the middle of a character (before "
+                     "ENDCHAR) in BDF font file.");
+        endChar = streq(readlineP->arg[0], "ENDCHAR");
+    }
+}
+
+
+
+static int
+wordToInt(const char * const word) {
+
+    unsigned int absValue;
+
+    int retval;
+
+    const char * error;
+    const int sign = (word[0] == '-') ? -1 : +1;
+    const char * const absString = (sign == -1) ? &word[1] : word;
+    /* No leading spaces allowed in 'word' */
+
+    if (!ISDIGIT(absString[0]))
+      error = "Non-digit character encountered";
+
+    else {
+        pm_string_to_uint(absString, &absValue, &error);
+        if (error == NULL && absValue > INT_MAX)
+            error = "Out of range";
+    }
+
+    if (error != NULL)
+        pm_error ("Error reading numerical argument in "
+                  "BDF font file: %s %s %s", error, word, absString);
+
+    retval = sign * absValue;
+    assert (INT_MIN < retval && retval < INT_MAX);
+
+    return retval;
+}
+
+
+
+static void
+interpEncoding(const char **  const arg,
+               unsigned int * const codepointP,
+               bool *         const badCodepointP,
+               PM_WCHAR       const maxmaxglyph) {
+/*----------------------------------------------------------------------------
+   With arg[] being the ENCODING statement from the font, return as
+   *codepointP the codepoint that it indicates (code point is the character
+   code, e.g. in ASCII, 48 is '0').
+
+   But if the statement doesn't give an acceptable codepoint return
+   *badCodepointP == TRUE.
+
+   'maxmaxglyph' is the maximum codepoint in the font.
+-----------------------------------------------------------------------------*/
+    bool gotCodepoint;
+    bool badCodepoint;
+    unsigned int codepoint;
+
+    if (wordToInt(arg[1]) >= 0) {
+        codepoint = wordToInt(arg[1]);
+        gotCodepoint = true;
+    } else {
+      if (wordToInt(arg[1]) == -1 && arg[2] != NULL) {
+            codepoint = wordToInt(arg[2]);
+            gotCodepoint = true;
+        } else
+            gotCodepoint = false;
+    }
+    if (gotCodepoint) {
+        if (codepoint > maxmaxglyph)
+            badCodepoint = true;
+        else
+            badCodepoint = false;
+    } else
+        badCodepoint = true;
+
+    *badCodepointP = badCodepoint;
+    *codepointP    = codepoint;
+}
+
+
+
+static void
+readEncoding(Readline *     const readlineP,
+             unsigned int * const codepointP,
+             bool *         const badCodepointP,
+             PM_WCHAR       const maxmaxglyph) {
+
+    bool eof;
+    const char * expected = "ENCODING";
+
+    readline_read(readlineP, &eof);
+
+    if (eof)
+        pm_error("EOF in BDF font file where '%s' expected", expected);
+    else if (!streq(readlineP->arg[0], expected))
+        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
+                 readlineP->arg[0], expected);
+    else if(readlineP->wordCt != 2 &&  readlineP->wordCt != 3)
+        pm_error("Wrong number of arguments in '%s' line in BDF font file",
+                 readlineP->arg[0]);
+
+    interpEncoding(readlineP->arg, codepointP, badCodepointP, maxmaxglyph);
+}
+
+
+
+static void
+validateFontLimits(const struct font2 * const font2P) {
+
+    assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0);
+
+    if (font2P->maxwidth  <= 0 ||
+        font2P->maxheight <= 0 ||
+        font2P->maxwidth  > pbm_maxfontwidth()  ||
+        font2P->maxheight > pbm_maxfontheight() ||
+        -font2P->x + 1 > font2P->maxwidth ||
+        -font2P->y + 1 > font2P->maxheight ||
+        font2P->x > font2P->maxwidth  ||
+        font2P->y > font2P->maxheight ||
+        font2P->x + font2P->maxwidth  > pbm_maxfontwidth() ||
+        font2P->y + font2P->maxheight > pbm_maxfontheight()
+        ) {
+
+        pm_error("Global font metric(s) out of bounds.");
+    }
+
+    if (font2P->maxglyph > PM_FONT2_MAXGLYPH)
+        pm_error("Internal error.  Glyph table too large: %u glyphs; "
+                 "Maximum possible in Netpbm is %u",
+                 (unsigned int) font2P->maxglyph, PM_FONT2_MAXGLYPH);
+}
+
+
+
+static void
+validateGlyphLimits(const struct font2 * const font2P,
+                    const struct glyph * const glyphP,
+                    const char *         const charName) {
+
+    /* Some BDF files code space with zero width and height,
+       no bitmap data and just the xadd value.
+       We allow zero width and height, iff both are zero.
+
+       Some BDF files have individual glyphs with a BBX value which
+       exceeds the global maximum stated by FONTBOUNDINGBOX.
+       Abort with error when this is encountered.
+       It seems some programs including emacs and bdftopcf tolerate
+       this violation.
+    */
+
+    if (((glyphP->width == 0 || glyphP->height == 0) &&
+         !(glyphP->width == 0 && glyphP->height == 0)) ||
+        glyphP->width  > font2P->maxwidth  ||
+        glyphP->height > font2P->maxheight ||
+        glyphP->x < font2P->x ||
+        glyphP->y < font2P->y ||
+        glyphP->x + (int) glyphP->width  > font2P->x + font2P->maxwidth  ||
+        glyphP->y + (int) glyphP->height > font2P->y + font2P->maxheight ||
+        glyphP->xadd > pbm_maxfontwidth() ||
+        glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width >
+        pbm_maxfontwidth()
+        ) {
+
+        pm_error("Font metric(s) for char '%s' out of bounds.\n", charName);
+    }
+}
+
+
+
+static void
+processChars(Readline *     const readlineP,
+             struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+   Process the CHARS block in a BDF font file, assuming the file is positioned
+   just after the CHARS line.  Read the rest of the block and apply its
+   contents to *font2P.
+-----------------------------------------------------------------------------*/
+    unsigned int const nCharacters = wordToInt(readlineP->arg[1]);
+
+    unsigned int nCharsDone;
+    unsigned int nCharsValid;
+
+    for (nCharsDone = 0, nCharsValid = 0;
+         nCharsDone < nCharacters; ) {
+
+        bool eof;
+
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file after CHARS reading BDF font file");
+
+        if (streq(readlineP->arg[0], "COMMENT")) {
+            /* ignore */
+        } else if (!streq(readlineP->arg[0], "STARTCHAR"))
+            pm_error("%s detected where \'STARTCHAR\' expected "
+                     "in BDF font file", readlineP->arg[0] );
+        else {
+            const char * charName;
+
+            struct glyph * glyphP;
+            unsigned int codepoint;
+            bool badCodepoint;
+
+            if (readlineP->wordCt < 2)
+                pm_error("Wrong number of arguments in STARTCHAR line "
+                         "in BDF font file");
+            /* Character name may contain spaces: there may be more than
+               three words in the line.
+             */
+            charName = pm_strdup(readlineP->arg[1]);
+
+            assert(streq(readlineP->arg[0], "STARTCHAR"));
+
+            MALLOCVAR(glyphP);
+
+            if (glyphP == NULL)
+                pm_error("no memory for font glyph for '%s' character",
+                         charName);
+
+            readEncoding(readlineP, &codepoint, &badCodepoint,
+                         font2P->maxmaxglyph);
+
+            if (badCodepoint)
+                skipCharacter(readlineP);
+            else {
+                if (codepoint < font2P->maxglyph) {
+                    if (font2P->glyph[codepoint] != NULL)
+                        pm_error("Multiple definition of code point %u "
+                                 "in BDF font file", (unsigned int) codepoint);
+                    else
+                        pm_message("Reverse order detected in BDF file. "
+                                   "Code point %u defined after %u",
+                                    (unsigned int) codepoint,
+                                    (unsigned int) font2P->maxglyph);
+                } else {
+                    /* Initialize all characters in the gap to nonexistent */
+                    unsigned int i;
+                    unsigned int const oldMaxglyph = font2P->maxglyph;
+                    unsigned int const newMaxglyph = codepoint;
+
+                    for (i = oldMaxglyph + 1; i < newMaxglyph; ++i)
+                        font2P->glyph[i] = NULL;
+
+                    font2P->maxglyph = newMaxglyph;
+                    }
+
+                readExpectedStatement(readlineP, "SWIDTH", 3);
+
+                readExpectedStatement(readlineP, "DWIDTH", 3);
+                glyphP->xadd = wordToInt(readlineP->arg[1]);
+
+                readExpectedStatement(readlineP, "BBX", 5);
+                glyphP->width  = wordToInt(readlineP->arg[1]);
+                glyphP->height = wordToInt(readlineP->arg[2]);
+                glyphP->x      = wordToInt(readlineP->arg[3]);
+                glyphP->y      = wordToInt(readlineP->arg[4]);
+
+                validateGlyphLimits(font2P, glyphP, charName);
+
+                createBmap(glyphP->width, glyphP->height, readlineP, charName,
+                           &glyphP->bmap);
+
+                readExpectedStatement(readlineP, "ENDCHAR", 1);
+
+                assert(codepoint <= font2P->maxmaxglyph);
+                /* Ensured by readEncoding() */
+
+                font2P->glyph[codepoint] = glyphP;
+                pm_strfree(charName);
+
+                ++nCharsValid;
+            }
+            ++nCharsDone;
+        }
+    }
+    font2P->chars = nCharsValid;
+    font2P->total_chars = nCharacters;
+}
+
+
+
+static void
+processBdfFontNameLine(Readline     * const readlineP,
+                       struct font2 * const font2P) {
+
+    if (font2P->name != NULL)
+        pm_error("Multiple FONT lines in BDF font file");
+
+    font2P->name = malloc (MAXBDFLINE+1);
+    if (font2P->name == NULL)
+        pm_error("No memory for font name");
+
+    if (readlineP->wordCt == 1)
+        strcpy(font2P->name, "(no name)");
+
+    else {
+        unsigned int tokenCt;
+
+        font2P->name[0] ='\0';
+
+        for (tokenCt=1;
+             tokenCt < ARRAY_SIZE(readlineP->arg) &&
+                 readlineP->arg[tokenCt] != NULL; ++tokenCt) {
+          strcat(font2P->name, " ");
+          strcat(font2P->name, readlineP->arg[tokenCt]);
+        }
+    }
+}
+
+
+static void
+loadCharsetString(const char * const registry,
+                  const char * const encoding,
+                  char **      const string) {
+
+    unsigned int inCt, outCt;
+    char * const dest = malloc (strlen(registry) + strlen(encoding) + 1);
+    if (dest == NULL)
+        pm_error("no memory to load CHARSET_REGISTRY and CHARSET_ENCODING "
+               "from BDF file");
+
+    for (inCt = outCt = 0; inCt < strlen(registry); ++inCt) {
+        char const c = registry[inCt];
+        if (isgraph(c) && c != '"')
+            dest[outCt++] = c;
+    }
+    dest[outCt++] = '-';
+
+    for (inCt = 0; inCt < strlen(encoding); ++inCt) {
+        char const c = encoding[inCt];
+        if (isgraph(c) && c != '"')
+            dest[outCt++] = c;
+    }
+
+    dest[outCt] = '\0';
+    *string = dest;
+}
+
+
+
+
+static unsigned int const maxTokenLen = 60;
+
+
+
+static void
+doCharsetRegistry(Readline *    const readlineP,
+                  bool *        const gotRegistryP,
+                  const char ** const registryP) {
+
+    if (*gotRegistryP)
+        pm_error("Multiple CHARSET_REGISTRY lines in BDF font file");
+    else if (readlineP->arg[2] != NULL)
+        pm_message("CHARSET_REGISTRY in BDF font file is not "
+                   "a single word.  Ignoring extra element(s) %s ...",
+                   readlineP->arg[2]);
+    else if (strlen(readlineP->arg[1]) > maxTokenLen)
+        pm_message("CHARSET_REGISTRY in BDF font file is too long. "
+                   "Truncating");
+
+    *registryP = strndup(readlineP->arg[1], maxTokenLen);
+    *gotRegistryP = true;
+}
+
+
+
+static void
+doCharsetEncoding(Readline *    const readlineP,
+                  bool *        const gotEncodingP,
+                  const char ** const encodingP) {
+
+    if (*gotEncodingP)
+        pm_error("Multiple CHARSET_ENCODING lines in BDF font file");
+    else if (readlineP->arg[2] != NULL)
+        pm_message("CHARSET_ENCODING in BDF font file is not "
+                   "a single word.  Ignoring extra element(s) %s ...",
+                   readlineP->arg[2]);
+    else if (strlen(readlineP->arg[1]) > maxTokenLen)
+        pm_message("CHARSET_ENCODING in BDF font file is too long. "
+                   "Truncating");
+
+    *encodingP = strndup(readlineP->arg[1], maxTokenLen);
+    *gotEncodingP = true;
+}
+
+
+
+static void
+doDefaultChar(Readline * const readlineP,
+              bool *     const gotDefaultCharP,
+              PM_WCHAR * const defaultCharP) {
+
+    if (*gotDefaultCharP)
+        pm_error("Multiple DEFAULT_CHAR lines in BDF font file");
+    else if (readlineP->arg[1] == NULL)
+        pm_error("Malformed DEFAULT_CHAR line in BDF font file");
+    else {
+        *defaultCharP = (PM_WCHAR) wordToInt(readlineP->arg[1]);
+        *gotDefaultCharP = true;
+    }
+}
+
+
+
+static void
+processBdfPropertyLine(Readline     * const readlineP,
+                       struct font2 * const font2P) {
+
+    bool gotRegistry;
+    const char * registry;
+    bool gotEncoding;
+    const char * encoding;
+    bool gotDefaultChar;
+    PM_WCHAR defaultChar;
+    unsigned int propCt;
+    unsigned int commentCt;
+    unsigned int propTotal;
+
+    validateWordCount(readlineP, 2);   /* STARTPROPERTIES n */
+
+    propTotal = wordToInt(readlineP->arg[1]);
+
+    gotRegistry    = false;  /* initial value */
+    gotEncoding    = false;  /* initial value */
+    gotDefaultChar = false;  /* initial value */
+
+    propCt    = 0;  /* initial value */
+    commentCt = 0;  /* initial value */
+
+    do {
+        bool eof;
+
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file after STARTPROPERTIES in BDF font file");
+        else if (streq(readlineP->arg[0], "CHARSET_REGISTRY") &&
+                 readlineP->arg[1] != NULL) {
+            doCharsetRegistry(readlineP, &gotRegistry, &registry);
+        } else if (streq(readlineP->arg[0], "CHARSET_ENCODING") &&
+                   readlineP->arg[1] != NULL) {
+            doCharsetEncoding(readlineP, &gotEncoding, &encoding);
+        } else if (streq(readlineP->arg[0], "DEFAULT_CHAR")) {
+            doDefaultChar(readlineP, &gotDefaultChar, &defaultChar);
+        } else if (streq(readlineP->arg[0], "COMMENT")) {
+            ++commentCt;
+        }
+        ++propCt;
+
+    } while (!streq(readlineP->arg[0], "ENDPROPERTIES"));
+
+    --propCt; /* Subtract one for ENDPROPERTIES line */
+
+    if (propCt != propTotal && propCt - commentCt != propTotal)
+      /* Some BDF files have COMMENTs in the property section and leave
+         them out of the count.
+         Others just give a wrong count.
+       */
+        pm_message ("Note: wrong number of property lines in BDF font file. "
+                    "STARTPROPERTIES line says %u, actual count: %u. "
+                    "Proceeding.",
+                    propTotal, propCt);
+
+
+    if (gotRegistry && gotEncoding)
+        loadCharsetString(registry, encoding, &font2P->charset_string);
+    else if (gotRegistry != gotEncoding) {
+        pm_message ("CHARSET_%s absent or incomplete in BDF font file. "
+                    "Ignoring CHARSET_%s.",
+                    gotEncoding ? "REGISTRY" : "ENCODING",
+                    gotEncoding ? "ENCODING" : "REGISTRY");
+    }
+    if (gotRegistry)
+        pm_strfree(registry);
+    if (gotEncoding)
+        pm_strfree(encoding);
+
+    if (gotDefaultChar) {
+        font2P->default_char         = defaultChar;
+        font2P->default_char_defined = true;
+    }
+
+}
+
+
+static void
+processBdfFontLine(Readline     * const readlineP,
+                   struct font2 * const font2P,
+                   bool         * const endOfFontP) {
+/*----------------------------------------------------------------------------
+   Process a nonblank line just read from a BDF font file.
+
+   This processing may involve reading more lines.
+-----------------------------------------------------------------------------*/
+    *endOfFontP = FALSE;  /* initial assumption */
+
+    assert(readlineP->arg[0] != NULL);  /* Entry condition */
+
+    if (streq(readlineP->arg[0], "FONT")) {
+        processBdfFontNameLine(readlineP, font2P);
+    } else if (streq(readlineP->arg[0], "COMMENT")) {
+        /* ignore */
+    } else if (streq(readlineP->arg[0], "SIZE")) {
+        /* ignore */
+    } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) {
+      if (font2P->maxwidth == 0)
+      pm_error("Encountered STARTROPERTIES before FONTBOUNDINGBOX "
+               "in BDF font file");
+      else
+        processBdfPropertyLine(readlineP, font2P);
+    } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) {
+        validateWordCount(readlineP,5);
+
+        font2P->maxwidth  = wordToInt(readlineP->arg[1]);
+        font2P->maxheight = wordToInt(readlineP->arg[2]);
+        font2P->x = wordToInt(readlineP->arg[3]);
+        font2P->y = wordToInt(readlineP->arg[4]);
+        validateFontLimits(font2P);
+    } else if (streq(readlineP->arg[0], "ENDFONT")) {
+        *endOfFontP = true;
+    } else if (streq(readlineP->arg[0], "CHARS")) {
+      if (font2P->maxwidth == 0)
+      pm_error("Encountered CHARS before FONTBOUNDINGBOX "
+                   "in BDF font file");
+      else {
+        validateWordCount(readlineP, 2);  /* CHARS n */
+        processChars(readlineP, font2P);
+      }
+    } else {
+        /* ignore */
+    }
+
+}
+
+
+
+struct font2 *
+pbm_loadbdffont2(const char * const filename,
+                 PM_WCHAR     const maxmaxglyph) {
+/*----------------------------------------------------------------------------
+   Read a BDF font file "filename" as a 'font2' structure.  A 'font2'
+   structure is more expressive than a 'font' structure, most notably in that
+   it can handle wide code points and many more glyphs.
+
+   Codepoints up to maxmaxglyph inclusive are valid in the file.
+
+   The returned object is in new malloc'ed storage, in many pieces.
+   When done with, destroy with pbm_destroybdffont2().
+-----------------------------------------------------------------------------*/
+
+    FILE *         ifP;
+    Readline       readline;
+    struct font2 * font2P;
+    bool           endOfFont;
+
+    ifP = fopen(filename, "rb");
+    if (!ifP)
+        pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
+                 filename, errno, strerror(errno));
+
+    readline_init(&readline, ifP);
+
+    pbm_createbdffont2_base(&font2P, maxmaxglyph);
+
+    font2P->maxglyph = 0;
+        /* Initial value.  Increases as new characters are loaded */
+    font2P->glyph[0] = NULL;
+        /* Initial value.  Overwrite later if codepoint 0 is defined. */
+
+    font2P->maxmaxglyph = maxmaxglyph;
+
+    /* Initialize some values - to be overwritten if actual values are
+       stated in BDF file */
+    font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0;
+    font2P->name = font2P->charset_string = NULL;
+    font2P->chars = font2P->total_chars = 0;
+    font2P->default_char = 0;
+    font2P->default_char_defined = FALSE;
+
+    readExpectedStatement(&readline, "STARTFONT", 2);
+
+    endOfFont = FALSE;
+
+    while (!endOfFont) {
+        bool eof;
+        readline_read(&readline, &eof);
+        if (eof)
+            pm_error("End of file before ENDFONT statement in BDF font file");
+
+        processBdfFontLine(&readline, font2P, &endOfFont);
+    }
+    fclose(ifP);
+
+    if(font2P->chars == 0)
+        pm_error("No glyphs found in BDF font file "
+                 "in codepoint range 0 - %u", (unsigned int) maxmaxglyph);
+
+    REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1);
+
+    font2P->bit_format = PBM_FORMAT;
+    font2P->load_fn = LOAD_BDFFILE;
+    font2P->charset = ENCODING_UNKNOWN;
+    font2P->oldfont = NULL;  /* Legacy field */
+    font2P->fcols = font2P->frows = 0;  /* Legacy fields */
+
+    return font2P;
+}
+
+
+static struct font *
+font2ToFont(const struct font2 * const font2P) {
+            struct font  * fontP;
+            unsigned int   codePoint;
+
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
+        pm_error("no memory for font");
+
+    fontP->maxwidth  = font2P->maxwidth;
+    fontP->maxheight = font2P->maxheight;
+
+    fontP->x = font2P->x;
+    fontP->y = font2P->y;
+
+    for (codePoint = 0; codePoint <= font2P->maxglyph; ++codePoint)
+        fontP->glyph[codePoint] = font2P->glyph[codePoint];
+
+    /* font2P->maxglyph is typically 255 (PM_FONT_MAXGLYPH) or larger.
+       But in some rare cases it is smaller.
+       If an ASCII-only font is read, it will be 126 or 127.
+
+       Set remaining codepoints up to PM_FONT_MAXGLYPH, if any, to NULL
+    */
+
+    for ( ; codePoint <= PM_FONT_MAXGLYPH; ++codePoint)
+        fontP->glyph[codePoint] = NULL;
+
+    /* Give values to legacy fields */
+    fontP->oldfont = font2P->oldfont;
+    fontP->fcols = font2P->fcols;
+    fontP->frows = font2P->frows;
+
+    return fontP;
+}
+
+
+
+struct font *
+pbm_loadbdffont(const char * const filename) {
+/*----------------------------------------------------------------------------
+   Read a BDF font file "filename" into a traditional font structure.
+
+   Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid.
+
+   Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc.
+
+   The returned object is in new malloc'ed storage, in many pieces.
+   Destroy with pbm_destroybdffont().
+-----------------------------------------------------------------------------*/
+    struct font  * fontP;
+    struct font2 * const font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH);
+
+    fontP = font2ToFont(font2P);
+
+    /* Free the base structure which was created by pbm_loadbdffont2() */
+    pbm_destroybdffont2_base(font2P);
+
+    return fontP;
+}
+
+
+
diff --git a/lib/libpbmfontdump.c b/lib/libpbmfontdump.c
new file mode 100644
index 00000000..f0c950f7
--- /dev/null
+++ b/lib/libpbmfontdump.c
@@ -0,0 +1,96 @@
+/*
+**
+** Font routines.
+**
+** BDF font code Copyright 1993 by George Phillips.
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** BDF font specs available from:
+** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
+** Glyph Bitmap Distribution Format (BDF) Specification
+** Version 2.2
+** 22 March 1993
+** Adobe Developer Support
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbmfont.h"
+#include "pbm.h"
+
+
+void
+pbm_dumpfont(struct font * const fontP,
+             FILE *        const ofP) {
+/*----------------------------------------------------------------------------
+  Dump out font as C source code.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    unsigned int ng;
+
+    if (fontP->oldfont)
+        pm_message("Netpbm no longer has the capability to generate "
+                   "a font in long hexadecimal data format");
+
+    for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) {
+        if (fontP->glyph[i])
+            ++ng;
+    }
+
+    printf("static struct glyph _g[%d] = {\n", ng);
+
+    for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
+        struct glyph * const glyphP = fontP->glyph[i];
+        if (glyphP) {
+            unsigned int j;
+            printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height,
+                   glyphP->x, glyphP->y, glyphP->xadd);
+
+            for (j = 0; j < glyphP->width * glyphP->height; ++j) {
+                if (glyphP->bmap[j])
+                    printf("\\1");
+                else
+                    printf("\\0");
+            }
+            --ng;
+            printf("\" }%s\n", ng ? "," : "");
+        }
+    }
+    printf("};\n");
+
+    printf("struct font XXX_font = { %d, %d, %d, %d, {\n",
+           fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y);
+
+    {
+        unsigned int i;
+
+        for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
+            if (fontP->glyph[i])
+                printf(" _g + %d", ng++);
+            else
+                printf(" NULL");
+
+            if (i != PM_FONT_MAXGLYPH) printf(",");
+            printf("\n");
+        }
+    }
+
+    printf(" }\n};\n");
+}
+
+
+
diff --git a/lib/pam.h b/lib/pam.h
index c2cfb4c7..74b20f46 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -482,6 +482,18 @@ void
 pnm_unapplyopacityrown(struct pam * const pamP,
                        tuplen *     const tuplenrow);
 
+void
+pnm_maketuplergbn(const struct pam * const pamP,
+                  tuplen             const tuple);
+
+void
+pnm_makerowrgbn(const struct pam * const pamP,
+                tuplen *           const tuplerow);
+
+void
+pnm_makearrayrgbn(const struct pam * const pamP,
+                  tuplen **          const tuples);
+
 pnm_transformMap *
 pnm_creategammatransform(const struct pam * const pamP);
 
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
index ad5d3acf..57f19ddc 100644
--- a/lib/pbmfont.h
+++ b/lib/pbmfont.h
@@ -13,11 +13,14 @@ extern "C" {
 
 /* Maximum dimensions for fonts */
 
-#define  pbm_maxfontwidth()  65536
-#define  pbm_maxfontheight() 65536
+#define  pbm_maxfontwidth()  65535
+#define  pbm_maxfontheight() 65535
     /* These limits are not in the official Adobe BDF definition, but
        should never be a problem for practical purposes, considering that
-       a 65536 x 65536 glyph occupies 4G pixels. 
+       a 65536 x 65536 glyph occupies 4G pixels.
+
+       Note that the maximum line length allowed in a BDF file imposes
+       another restriction.
     */
 
 typedef wchar_t PM_WCHAR;
@@ -38,6 +41,39 @@ typedef wchar_t PM_WCHAR;
        As of Unicode v. 11.0.0 planes up to 16 are defined.
     */
 
+enum pbmFontLoad { FIXED_DATA           = 0,
+                   LOAD_PBMSHEET        = 1,
+                   LOAD_BDFFILE         = 2,
+                   CONVERTED_TYPE1_FONT = 9 };
+
+static const char * const pbmFontOrigin[10] =
+                 {"Fixed data",                                   /* 0 */
+                  "Loaded from PBM sheet by libnetpbm",           /* 1 */
+                  "Loaded from BDF file by libnetpbm",            /* 2 */
+                  NULL, NULL, NULL, NULL, NULL, NULL,
+                  "Expanded from type 1 font structure by libnetpbm"}; /* 9 */
+
+enum pbmFontEncoding { ENCODING_UNKNOWN = 0,
+                       ISO646_1991_IRV = 1,   /* ASCII */
+                       ISO_8859_1 = 1000, ISO_8859_2, ISO_8859_3, ISO_8859_4,
+                       ISO_8859_5,   ISO_8859_6,   ISO_8859_7,   ISO_8859_8,
+                       ISO_8859_9,   ISO_8859_10,  ISO_8859_11,  ISO_8859_12,
+                       ISO_8859_13,  ISO_8859_14,  ISO_8859_15,  ISO_8859_16,
+                       ISO_10646 = 2000 };
+
+/* For future use */
+
+/* In addition to the above, the following CHARSET_REGISTRY-CHARSET_ENCODING
+   values have been observed in actual BDF files:
+
+  ADOBE-FONTSPECIFIC, DEC-DECTECH, GOST19768.74-1, IS13194-DEVANAGARI,
+  JISX0201.1976-0, KOI8-C, KOI8-R, MISC-FONTSPECIFIC,
+  MULEARABIC-0, MULEARABIC-1, MULEARABIC-2, MULEIPA-1, MULELAO-1,
+  OMRON_UDC_ZH-0, TIS620.2529-0, TIS620.2529-1, VISCII1-1, VISCII1.1-1,
+  XTIS-0
+ */
+
+
 struct glyph {
     /* A glyph consists of white borders and the "central glyph" which
        can be anything, but normally does not have white borders because
@@ -69,9 +105,14 @@ struct glyph {
            the top half and white on the bottom, this is an array of
            800 bytes, with the first 400 having value 0x01 and the
            last 400 having value 0x00.
+
+           Do not share bmap objects among glyphs if using
+           pbm_destroybdffont() or pbm_destroybdffont2() to free
+           the font/font2 structure.
         */
 };
 
+
 struct font {
     /* This describes a combination of font and character set.  Given
        an code point in the range 0..255, this structure describes the
@@ -86,7 +127,9 @@ struct font {
            this font.  Can be negative.
         */
     struct glyph * glyph[256];
-        /* glyph[i] is the glyph for code point i */
+        /* glyph[i] is the glyph for code point i.
+           Glyph objects must be unique for pbm_destroybdffont() to work.
+        */
     const bit ** oldfont;
         /* for compatibility with old pbmtext routines */
         /* oldfont is NULL if the font is BDF derived */
@@ -95,34 +138,162 @@ struct font {
 
 
 struct font2 {
-    /* Font structure for expanded character set.  Code point is in the
-       range 0..maxglyph .
+    /* Font structure for expanded character set.
+       Code points in the range 0...maxmaxglyph are loaded.
+       Loaded code point is in the range 0..maxglyph .
+     */
+
+    /* 'size' and 'len' are necessary in order to provide forward and
+       backward compatibility between library functions and calling programs
+       as this structure grows.  See struct pam in pam.h.
      */
+    unsigned int size;
+        /* The storage size of this entire structure, in bytes */
+
+    unsigned int len;
+        /* The length, in bytes, of the information in this structure.
+           The information starts in the first byte and is contiguous.
+           This cannot be greater than 'size'
+        */
+
     int maxwidth, maxheight;
 
     int x;
-        /* The minimum value of glyph.font.  The left edge of the glyph
-           in the glyph set which advances furthest to the left. */
+         /* The minimum value of glyph.font.  The left edge of the glyph in
+            the glyph set which advances furthest to the left.
+         */
     int y;
-        /* Amount of white space that should be added between lines of
-           this font.  Can be negative.
+        /* Amount of white space that should be added between lines of this
+           font.  Can be negative.
         */
+
     struct glyph ** glyph;
-        /* glyph[i] is the glyph for code point i */
+        /* glyph[i] is the glyph for code point i
+
+           Glyph objects must be unique for pbm_destroybdffont2() to work.
+           For example space and non-break-space are often identical at the
+           image data level; they must be loaded into separate memory
+           locations if using pbm_destroybdffont2().
+         */
 
     PM_WCHAR maxglyph;
-        /* max code point for glyphs, including vacant slots */
+        /* max code point for glyphs, including vacant slots max value of
+           above i
+        */
+
+    void * selector;
+        /* Reserved
+
+           Bit array or structure indicating which code points to load.
+
+           When NULL, all available code points up to maxmaxglyph, inclusive
+           are loaded.
+       */
+
+    PM_WCHAR maxmaxglyph;
+        /* Code points above this value are not loaded, even if they occur
+           in the BDF font file
+        */
 
     const bit ** oldfont;
-        /* for compatibility with old pbmtext routines */
-        /* oldfont is NULL if the font is BDF derived */
+        /* For compatibility with old pbmtext routines.
+           Valid only when data is in the form of a PBM sheet
+        */
 
     unsigned int fcols, frows;
+        /* For compatibility with old pbmtext routines.
+           Valid only when oldfont is non-NULL
+        */
+
+    unsigned int bit_format;
+        /* PBM_FORMAT:   glyph data: 1 byte per pixel (like P1, but not ASCII)
+           RPBM_FORMAT:  glyph data: 1 bit per pixel
+           Currently only PBM_FORMAT is possible
+        */
+
+    unsigned int total_chars;
+        /* Number of glyphs defined in font file, as stated in the CHARS line
+           of the BDF file PBM sheet font.  Always 96
+        */
+
+    unsigned int chars;
+        /* Number of glyphs actually loaded into structure
+
+           Maximum: total_chars
+
+           Less than total_chars when a subset of the file is loaded
+           PBM sheet font: always 96 */
+
+    enum pbmFontLoad load_fn;
+        /* Description of the function that created the structure and loaded
+           the glyph data
+
+           Used to choose a string to show in verbose messages.
+
+           FIXED_DATA (==0) means memory for this structure was not
+           dynamically allocated by a function; all data is hardcoded in
+           source code and resides in static data.  See file pbmfontdata1.c
+        */
+
+    PM_WCHAR default_char;
+        /* Code index of what to show when there is no glyph for a requested
+           code Available in many BDF fonts between STARPROPERTIES -
+           ENDPROPERTIES.
+
+           Set to value read from BDF font file.
+
+           Common values are 0, 32, 8481, 32382, 33, 159, 255.
+        */
+
+    unsigned int default_char_defined;
+        /* boolean
+           TRUE: above field is valid; DEFAULT_CHAR is defined in font file.
+           FALSE: font file has no DEFAULT_CHAR field.
+        */
+
+    char * name;
+        /* Name of the font.  Available in BDF fonts.
+           NULL means no name.
+        */
+
+    enum pbmFontEncoding charset;
+        /* Reserved for future use.
+           Set by analyzing following charset_string.
+        */
+
+    char * charset_string;
+        /* Charset registry and encoding.
+           Available in most BDF fonts between STARPROPERTIES - ENDPROPERTIES.
+           NULL means no name.
+        */
 };
 
+
+/* PBM_FONT2_STRUCT_SIZE(x) tells you how big a struct font2 is up
+   through the member named x.  This is useful in conjunction with the
+   'len' value to determine which fields are present in the structure.
+*/
+
+/* Some compilers are really vigilant and recognize it as an error
+   to cast a 64 bit address to a 32 bit type.  Hence the roundabout
+   casting.  See PAM_MEMBER_OFFSET in pam.h .
+*/
+
+
+#define PBM_FONT2_MEMBER_OFFSET(mbrname) \
+  ((size_t)(unsigned long)(char*)&((struct font2 *)0)->mbrname)
+#define PBM_FONT2_MEMBER_SIZE(mbrname) \
+  sizeof(((struct font2 *)0)->mbrname)
+#define PBM_FONT2_STRUCT_SIZE(mbrname) \
+  (PBM_FONT2_MEMBER_OFFSET(mbrname) + PBM_FONT2_MEMBER_SIZE(mbrname))
+
+
 struct font *
 pbm_defaultfont(const char* const which);
 
+struct font2 *
+pbm_defaultfont2(const char* const which);
+
 struct font *
 pbm_dissectfont(const bit ** const font,
                 unsigned int const frows,
@@ -131,15 +302,40 @@ pbm_dissectfont(const bit ** const font,
 struct font *
 pbm_loadfont(const char * const filename);
 
+struct font2 *
+pbm_loadfont2(const    char * const filename,
+              PM_WCHAR        const maxmaxglyph);
+
 struct font *
 pbm_loadpbmfont(const char * const filename);
 
+struct font2 *
+pbm_loadpbmfont2(const char * const filename);
+
 struct font *
 pbm_loadbdffont(const char * const filename);
 
 struct font2 *
 pbm_loadbdffont2(const char * const filename,
-                 PM_WCHAR     const maxglyph);
+                 PM_WCHAR     const maxmaxglyph);
+
+struct font2 *
+pbm_loadbdffont2_select(const char * const filename,
+                        PM_WCHAR     const maxmaxglyph,
+                        const void * const selector);
+
+void
+pbm_createbdffont2_base(struct font2 ** const font2P,
+                        PM_WCHAR        const maxmaxglyph);
+
+void
+pbm_destroybdffont(struct font * const fontP);
+
+void
+pbm_destroybdffont2_base(struct font2 * const font2P);
+
+void
+pbm_destroybdffont2(struct font2 * const font2P);
 
 struct font2 *
 pbm_expandbdffont(const struct font * const font);
@@ -148,9 +344,6 @@ void
 pbm_dumpfont(struct font * const fontP,
              FILE *        const ofP);
 
-extern struct font pbm_defaultFixedfont;
-extern struct font pbm_defaultBdffont;
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/pbmfontdata.h b/lib/pbmfontdata.h
new file mode 100644
index 00000000..7ac63abc
--- /dev/null
+++ b/lib/pbmfontdata.h
@@ -0,0 +1,7 @@
+extern struct font pbm_defaultFixedfont;
+extern struct font pbm_defaultBdffont;
+
+extern struct font2 const pbm_defaultFixedfont2;
+extern struct font2 const pbm_defaultBdffont2;
+
+extern struct font2 const * pbm_builtinFonts[];
diff --git a/lib/pbmfontdata0.c b/lib/pbmfontdata0.c
new file mode 100644
index 00000000..dfafc317
--- /dev/null
+++ b/lib/pbmfontdata0.c
@@ -0,0 +1,9 @@
+#include "pbm.h"
+#include "pbmfont.h"
+#include "pbmfontdata.h"
+
+struct font2 const * pbm_builtinFonts[] = {
+    &pbm_defaultFixedfont2,
+    &pbm_defaultBdffont2,
+    NULL,
+};
diff --git a/lib/pbmfontdata1.c b/lib/pbmfontdata1.c
index 8552d29e..ab6ce28d 100644
--- a/lib/pbmfontdata1.c
+++ b/lib/pbmfontdata1.c
@@ -1,4 +1,5 @@
 #include "pbmfont.h"
+#include "pbmfontdata.h"
 
 /* Default fixed-width font
    All glyphs fit into a 7 x 12 rectangular cell.
@@ -20,7 +21,7 @@
 */
 
 static struct glyph glFxd[96] = {
-/*  32 character   */ 
+/*  32 character   */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
 /*  33 character ! */
 {7,12,0,0,7,"\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
@@ -210,9 +211,9 @@ static struct glyph glFxd[96] = {
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0" },
 /* 126 character ~ */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\1\0\0\1\0\1\0\1\0\0\1\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
-/* 127 character (?) */
+/* 127 character (Control character, retained for backward compatibility) */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" }
-}; 
+};
 
 
 
@@ -246,7 +247,22 @@ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, NULL, 0, 0
+};
+
+struct font2 const pbm_defaultFixedfont2 = {
+  sizeof(pbm_defaultFixedfont2),         /* len */
+  PBM_FONT2_STRUCT_SIZE(charset_string), /* size */
+  7, 12, 0, 0,                    /* maxwidth, maxheight, x, y */
+  pbm_defaultFixedfont.glyph,     /* glyph table */
+  255, NULL, 255,                 /* maxglyph, selector, maxmaxglyph */
+  NULL, 0, 0,                     /* oldfont, fcols, frows */
+  PBM_FORMAT,                     /* bit_format */
+  96, 96,                         /* total_chars, chars */
+  FIXED_DATA,                     /* load_fn */
+  32, 1,                          /* default_char, default_char_defined */
+  (char *) "builtin fixed",       /* name */
+  ISO646_1991_IRV, (char *)"ASCII"  /* charset, charset_string */
 };
 
 
diff --git a/lib/pbmfontdata2.c b/lib/pbmfontdata2.c
index 336fc773..11dd84e6 100644
--- a/lib/pbmfontdata2.c
+++ b/lib/pbmfontdata2.c
@@ -1,4 +1,5 @@
 #include "pbmfont.h"
+#include "pbmfontdata.h"
 
 /* Default proportional font.
    BDF-style advance value, bounding box dimensions and point of origin.
@@ -15,7 +16,7 @@
    from a libnetpbm font file or builtin font.
 */
 
-static struct glyph glBdf[190] = {
+static struct glyph glBdf[191] = {
 /*  32 character    */
 { 1, 1, 0, 0, 3, "\0" },
 /*  33 character !  */
@@ -204,8 +205,10 @@ static struct glyph glBdf[190] = {
 { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\1\1" },
 /* 125 character }  */
 { 4, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\1\0\0" },
-/* 160 */
+/* 126 character ~  */
 { 6, 2, 0, 3, 7, "\0\1\1\0\0\1\1\0\0\1\1\0" },
+/* 160 */
+{ 1, 1, 0, 0, 3, "\0" },
 /* 161 */
 { 1, 9, 1, -3, 4, "\1\0\1\1\1\1\1\1\1" },
 /* 162 */
@@ -421,7 +424,7 @@ glBdf+84, glBdf+85, glBdf+86, glBdf+87, glBdf+88, glBdf+89,
 glBdf+90, glBdf+91, glBdf+92, glBdf+93, glBdf+94,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 glBdf+95,  glBdf+96,  glBdf+97,  glBdf+98,  glBdf+99,  glBdf+100,
 glBdf+101, glBdf+102, glBdf+103, glBdf+104, glBdf+105, glBdf+106,
 glBdf+107, glBdf+108, glBdf+109, glBdf+110, glBdf+111, glBdf+112,
@@ -437,7 +440,23 @@ glBdf+161, glBdf+162, glBdf+163, glBdf+164, glBdf+165, glBdf+166,
 glBdf+167, glBdf+168, glBdf+169, glBdf+170, glBdf+171, glBdf+172,
 glBdf+173, glBdf+174, glBdf+175, glBdf+176, glBdf+177, glBdf+178,
 glBdf+179, glBdf+180, glBdf+181, glBdf+182, glBdf+183, glBdf+184,
-glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189  }
+glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189, glBdf+190 },
+NULL, 0, 0
+};
+
+struct font2 const pbm_defaultBdffont2 = {
+  sizeof(pbm_defaultFixedfont2),         /* len */
+  PBM_FONT2_STRUCT_SIZE(charset_string), /* size */
+  14, 15, -1, -3,                 /* maxwidth, maxheight, x, y */
+  pbm_defaultBdffont.glyph,       /* glyph table */
+  255, NULL, 255,                 /* maxglyph, selector, maxmaxglyph */
+  NULL, 0, 0,                     /* oldfont, fcols, frows */
+  PBM_FORMAT,                     /* bit_format */
+  190, 190,                       /* total_chars, chars */
+  FIXED_DATA,                     /* load_fn */
+  32, 1,                          /* default_char, default_char_defined */
+  (char *) "builtin bdf",         /* name */
+  ISO_8859_1, (char *)"ISO8859-1" /* charset, charset_string */
 };
 
 
diff --git a/test/Makefile b/test/Makefile
index 4f0c063f..c640dfff 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -31,4 +31,4 @@ include $(SRCDIR)/common.mk
 distclean clean: cleanlocal
 .PHONY: cleanlocal
 cleanlocal:
-	rm -f $(PROGS) $(OKSTOGENERATE)
+	rm -f $(PROGS) $(patsubst %.rand-ok,%.ok,$(wildcard *.rand-ok))
diff --git a/test/all-in-place.ok b/test/all-in-place.ok
index b9db6ee1..81eaa320 100644
--- a/test/all-in-place.ok
+++ b/test/all-in-place.ok
@@ -36,6 +36,7 @@ mtvtoppm: ok
 neotoppm: ok
 palmtopnm: ok
 pamaddnoise: ok
+pamaltsat: ok
 pamarith: ok
 pambackground: ok
 pambayer: ok
@@ -56,7 +57,9 @@ pamfix: ok
 pamflip: ok
 pamfunc: ok
 pamgauss: ok
+pamgetcolor: ok
 pamgradient: ok
+pamlevels: ok
 pamlookup: ok
 pammasksharpen: ok
 pammixinterlace: ok
@@ -106,6 +109,7 @@ pamtotiff: ok
 pamtouil: ok
 pamtowinicon: ok
 pamtoxvmini: ok
+pamtris: ok
 pamundice: ok
 pamunlookup: ok
 pamvalidate: ok
diff --git a/test/all-in-place.test b/test/all-in-place.test
index a52739ed..cf402b6f 100755
--- a/test/all-in-place.test
+++ b/test/all-in-place.test
@@ -78,6 +78,7 @@ ordinary_testprogs="\
   neotoppm \
   palmtopnm \
   pamaddnoise \
+  pamaltsat \
   pamarith \
   pambackground \
   pambayer \
@@ -98,7 +99,9 @@ ordinary_testprogs="\
   pamflip \
   pamfunc \
   pamgauss \
+  pamgetcolor \
   pamgradient \
+  pamlevels \
   pamlookup \
   pammasksharpen \
   pammixinterlace \
@@ -148,6 +151,7 @@ ordinary_testprogs="\
   pamtouil \
   pamtowinicon \
   pamtoxvmini \
+  pamtris \
   pamundice \
   pamunlookup \
   pamvalidate \
diff --git a/test/pbmtext-bdf.ok b/test/pbmtext-bdf.ok
index 5ab8b4af..b1486493 100644
--- a/test/pbmtext-bdf.ok
+++ b/test/pbmtext-bdf.ok
@@ -6,3 +6,16 @@
 1
 1
 1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+0
+0
diff --git a/test/pbmtext-bdf.test b/test/pbmtext-bdf.test
index 1bd7c52c..50df7b75 100755
--- a/test/pbmtext-bdf.test
+++ b/test/pbmtext-bdf.test
@@ -5,7 +5,7 @@
 tmpdir=${tmpdir:-/tmp}
 
 font_bdf=${tmpdir}/font.bdf
-font_corrupt_bdf=${tmpdir}/fontcorrupt.bdf
+font_corrupt=${tmpdir}/fontcorrupt
 
 # Though this BDF font file defines only three letters, it is valid.
 
@@ -69,31 +69,101 @@ pbmtext -font ${font_bdf} ABC | cksum
 
 
 # Test 2
-# The rest should all fail.  Writes 1 seven times.
+# These should all fail.  Writes 1 eightteen times.
 
 echo "Test whether corrupted BDF font files are properly handled." 1>&2
-echo "Error messages will appear." 1>&2
-echo 1>&2
+echo "Error messages should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
 
 pbmtext -font ${font_bdf} BCD
 echo $?
 
+for token in "STARTPROPERTIES" "CHARS" "STARTCHAR" "ENCODING" "DWIDTH"
+do
+  font_corrupt_bdf=${font_corrupt}.naked_${token}.bdf
+  sed 's/^'${token}' .*$/'${token}'/' \
+    ${font_bdf} >  ${font_corrupt_bdf}
+  pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+  echo $?
+  rm ${font_corrupt_bdf}
+done
+
+font_corrupt_bdf=${font_corrupt}.fbbx_narrow.bdf
 sed 's/FONTBOUNDINGBOX 4 5 0 0/FONTBOUNDINGBOX 4 4 0 0/' \
+  ${font_bdf} > ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
+font_corrupt_bdf=${font_corrupt}.fbbx_low.bdf
+sed 's/FONTBOUNDINGBOX 4 5 0 0/FONTBOUNDINGBOX 3 5 0 0/' \
+  ${font_bdf} >  ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
+font_corrupt_bdf=${font_corrupt}.bbx_only3fields.bdf
+sed 's/BBX 4 5 0 0/BBX 4 5 0/' \
+  ${font_bdf} >  ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
+font_corrupt_bdf=${font_corrupt}.bbx_wide.bdf
+sed 's/BBX 4 5 0 0/BBX 9 5 0 0/' \
+  ${font_bdf} >  ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
+font_corrupt_bdf=${font_corrupt}.bbx_zerowidth.bdf
+sed 's/BBX 4 5 0 0/BBX 0 5 0 0/' \
   ${font_bdf} >  ${font_corrupt_bdf}
 pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
 echo $?
 rm ${font_corrupt_bdf}
 
+font_corrupt_bdf=${font_corrupt}.bbx_tall.bdf
 sed 's/BBX 4 5 0 0/BBX 4 6 0 0/' \
   ${font_bdf} >  ${font_corrupt_bdf}
 pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
 echo $?
 rm ${font_corrupt_bdf}
 
+font_corrupt_bdf=${font_corrupt}.bbx_low.bdf
+sed 's/BBX 4 5 0 0/BBX 4 1 0 0/' \
+  ${font_bdf} >  ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
+font_corrupt_bdf=${font_corrupt}.bbx_zeroheight.bdf
+sed 's/BBX 4 5 0 0/BBX 4 0 0 0/' \
+  ${font_bdf} >  ${font_corrupt_bdf}
+pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+echo $?
+rm ${font_corrupt_bdf}
+
 for delete_line in 14 16 18 20
   do
+  font_corrupt_bdf=${font_corrupt}.del${delete_line}.pdf
   sed "${delete_line}"d ${font_bdf} >  ${font_corrupt_bdf}
   pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
   echo $?
   rm ${font_corrupt_bdf}
   done
+
+
+# Test 2
+# These should succeed.  Warning messages will be displayed.
+# Writes 1 two times.
+
+for token in "CHARSET_ENCODING" "CHARSET_REGISTRY"
+do
+  font_corrupt_bdf=${font_corrupt}.naked_${token}.bdf
+  sed 's/^'${token}' .*$/'${token}'/' \
+    ${font_bdf} >  ${font_corrupt_bdf}
+  pbmtext -font ${font_corrupt_bdf} ABC > /dev/null
+  echo $?
+  rm ${font_corrupt_bdf}
+done
diff --git a/test/pbmtext-iso88591.ok b/test/pbmtext-iso88591.ok
index d1516357..6cc1a856 100644
--- a/test/pbmtext-iso88591.ok
+++ b/test/pbmtext-iso88591.ok
@@ -1,4 +1,4 @@
-3491766365 5110
-3491766365 5110
-259944121 191
-259944121 191
+3806607098 5110
+3806607098 5110
+2858870527 192
+2858870527 192
diff --git a/test/pbmtext-iso88591.test b/test/pbmtext-iso88591.test
index 34346f5c..bc5e83ab 100755
--- a/test/pbmtext-iso88591.test
+++ b/test/pbmtext-iso88591.test
@@ -21,26 +21,26 @@ if [ $? -ne 0  ]
 fi
 
 # Two rows
-# Should print 3491766365 5110 twice
+# Should print 3806607098 5110 twice
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=160;i<=255;++i) printf("%c",i); }' | \
     pbmtext -builtin bdf | cksum
 
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i);  print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=160;i<=255;++i) printf("%c",i); }' | \
     LC_ALL=en_US.iso88591 pbmtext -builtin bdf -wchar | cksum
 
 
 # Two rows
-# Should print 259944121 191 twice
+# Should print 2858870527 192 twice
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i); print "" }' | cksum
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i); print ""}' | \
     LC_ALL=en_US.iso88591 pbmtext -builtin bdf -wchar -text-dump | cksum
\ No newline at end of file
diff --git a/test/pbmtext-utf8.ok b/test/pbmtext-utf8.ok
index 864c530a..9e65dec4 100644
--- a/test/pbmtext-utf8.ok
+++ b/test/pbmtext-utf8.ok
@@ -1,8 +1,8 @@
-1240895458 5110
-1240895458 5110
-898975479 2272
-898975479 2272
+2066913605 5110
+2066913605 5110
+2920616515 2301
+2920616515 2301
 0
- !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
- !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
+ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
+ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
 1
diff --git a/test/pbmtext-utf8.test b/test/pbmtext-utf8.test
index ca1f45a2..010c02db 100755
--- a/test/pbmtext-utf8.test
+++ b/test/pbmtext-utf8.test
@@ -21,14 +21,14 @@ fi
 
 # Test 1.
 # Two rows
-# Should print 1240895458 5110 twice
+# Should print 2066913605 5110 twice
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i); }' | \
     pbmtext -builtin bdf | cksum
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i);  }' | \
     iconv -f iso8859-1 -t utf-8 | \
     LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar | cksum
@@ -36,13 +36,13 @@ awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
 
 # Test 2.
 # One row
-# Should print 898975479 2272 twice
+# Should print 2920616515 2301 twice
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print "" }' | \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print "" }' | \
     pbmtext -builtin bdf | cksum
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i);  print ""}' | \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i);  print ""}' | \
     LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar | cksum
 
 
@@ -55,12 +55,12 @@ output=${tmpdir}/output
 # Output may be affected by locale.  Compare with cmp.
 # Should print 0
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i); print "" }' | \
-    iconv -f iso88591 -t utf8 > ${output}
+    iconv -f iso8859-1 -t utf-8 > ${output}
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \
              for (i=161;i<=255;++i) printf("%c",i); print "" }' | \
     iconv -f iso8859-1 -t utf-8 | \
     LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar -text-dump | \
@@ -73,15 +73,19 @@ rm ${output}
 # Test 4.
 # One row
 # Should print the following twice:
-# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
+# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print "" } '
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print "" } '
 
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i);  print ""}' | \
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""}' | \
         LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar -text-dump
 
 
+echo "Invalid utf-8 sequence as input." 1>&2
+echo "An error message should appear below the line." 1>&2
+echo "-----------------------------------------------------------" 1>&2
+
 # Test 5.
 # Invalid utf-8 sequence
 # Should print 1
diff --git a/test/pbmtext.ok b/test/pbmtext.ok
index 000c0897..96e351f9 100644
--- a/test/pbmtext.ok
+++ b/test/pbmtext.ok
@@ -12,5 +12,5 @@
 1647614653 2027
 1647614653 2027
 1647614653 2027
-3233136020 4535
-1216262214 5711
+2547645687 4564
+1174281741 5741
diff --git a/test/pbmtext.test b/test/pbmtext.test
index c92ed599..38578636 100755
--- a/test/pbmtext.test
+++ b/test/pbmtext.test
@@ -82,13 +82,13 @@ rm ${fontRectangle_txt} ${font_pbm}
 # One long row
 # Should print 3233136020 4535
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i);
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i);
              for (i=160;i<=255;++i) printf("%c",i); }' | \
     pbmtext -builtin bdf | cksum
 
 # One tall column
 # Should print 1216262214 5711
 LC_ALL=C \
-awk 'BEGIN { for (i=32; i<=125;++i) printf("%c\n",i);
+awk 'BEGIN { for (i=32; i<=126;++i) printf("%c\n",i);
              for (i=160;i<=255;++i) printf("%c\n",i); }' | \
     pbmtext -nomargins -builtin bdf | cksum
diff --git a/test/pgmnoise.ok b/test/pgmnoise.ok
deleted file mode 100644
index 138218c2..00000000
--- a/test/pgmnoise.ok
+++ /dev/null
@@ -1 +0,0 @@
-2005134911 10015
diff --git a/test/ppmforge.ok b/test/ppmforge.ok
deleted file mode 100644
index e4a4c9e2..00000000
--- a/test/ppmforge.ok
+++ /dev/null
@@ -1 +0,0 @@
-3634219838 196623
diff --git a/test/ppmpat-random.ok b/test/ppmpat-random.ok
deleted file mode 100644
index 4d298cec..00000000
--- a/test/ppmpat-random.ok
+++ /dev/null
@@ -1,3 +0,0 @@
-2219119109 36015
-3436846137 16813
-908097729 16813
diff --git a/test/ppmrough.ok b/test/ppmrough.ok
deleted file mode 100644
index 83643849..00000000
--- a/test/ppmrough.ok
+++ /dev/null
@@ -1 +0,0 @@
-378403602 30015
diff --git a/version.mk b/version.mk
index abafa08b..c8aa2495 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 83
-NETPBM_POINT_RELEASE = 2
+NETPBM_MINOR_RELEASE = 84
+NETPBM_POINT_RELEASE = 0