about summary refs log tree commit diff
path: root/generator
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2020-06-28 17:52:14 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2020-06-28 17:52:14 +0000
commit1cd3ef0dd6c2236d0b329879bacd9360d01c88a1 (patch)
treea60016b5dd48fbf681ebfb43a6ca0d7e20a391d8 /generator
parentcbbf595776cee6b8d8a24becf49ee6468eeba712 (diff)
downloadnetpbm-mirror-1cd3ef0dd6c2236d0b329879bacd9360d01c88a1.tar.gz
netpbm-mirror-1cd3ef0dd6c2236d0b329879bacd9360d01c88a1.tar.xz
netpbm-mirror-1cd3ef0dd6c2236d0b329879bacd9360d01c88a1.zip
Promote Development to Advaanced - Release 10.91
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@3877 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator')
-rw-r--r--generator/pamstereogram.c278
-rw-r--r--generator/pbmtext.c382
-rw-r--r--generator/pbmtextps.c2
-rw-r--r--generator/ppmforge.c2
-rw-r--r--generator/ppmmake.c2
-rw-r--r--generator/ppmrough.c2
6 files changed, 488 insertions, 180 deletions
diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c
index 6e5f5ce0..9b861b51 100644
--- a/generator/pamstereogram.c
+++ b/generator/pamstereogram.c
@@ -16,7 +16,7 @@
  *
  * ----------------------------------------------------------------------
  *
- * Copyright (C) 2006-2015 Scott Pakin <scott+pbm@pakin.org>
+ * Copyright (C) 2006-2020 Scott Pakin <scott+pbm@pakin.org>
  *
  * All rights reserved.
  *
@@ -94,6 +94,7 @@ struct cmdlineInfo {
     enum outputType outputType;  /* Type of output file */
     unsigned int xbegin;         /* -xbegin option */
     unsigned int xbeginSpec;     /* -xbegin option count */
+    unsigned int tileable;       /* -tileable option */
 };
 
 
@@ -207,6 +208,8 @@ parseCommandLine(int                  argc,
             &planesSpec,              0);
     OPTENT3(0, "xbegin",          OPT_UINT,   &cmdlineP->xbegin,
             &cmdlineP->xbeginSpec,    0);
+    OPTENT3(0, "tileable",        OPT_FLAG,   NULL,
+            (unsigned int *)&cmdlineP->tileable,  0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -291,6 +294,11 @@ parseCommandLine(int                  argc,
     if (cmdlineP->makemask && cmdlineP->patfile)
         pm_error("You may not specify both -makemask and -patfile");
 
+    if (cmdlineP->tileable && cmdlineP->xbeginSpec)
+        pm_error("You may not specify both -tileable and -xbegin");
+    if (cmdlineP->tileable && !cmdlineP->patfile)
+        pm_error("-tileable is valid only with -patfile");
+
     if (cmdlineP->patfile && blackandwhite)
         pm_error("-blackandwhite is not valid with -patfile");
     if (cmdlineP->patfile && grayscale)
@@ -844,11 +852,12 @@ makeStereoRow(const struct pam * const inPamP,
               unsigned int       const dpi,
               unsigned int       const optWidth,
               unsigned int       const smoothing) {
+/*----------------------------------------------------------------------------
+  Given a row of the depth map inRow[], compute the sameL and sameR arrays,
+  which indicate for each pixel which pixel to its left and right it should be
+  colored the same as.
+-----------------------------------------------------------------------------*/
 
-/* Given a row of the depth map, compute the sameL and sameR arrays,
- * which indicate for each pixel which pixel to its left and right it
- * should be colored the same as.
- */
 #define Z(X) (inRow[X][0]/(double)inPamP->maxval)
 
     unsigned int col;
@@ -865,7 +874,7 @@ makeStereoRow(const struct pam * const inPamP,
 
         if (left >= 0 && right < inPamP->width) {
             bool isVisible;
-        
+
             if (sameL[right] != right) {
                 /* Right point already linked */
                 if (sameL[right] < left) {
@@ -1114,12 +1123,15 @@ static void
 makeImageRowMts(outGenerator *       const outGenP,
                 unsigned int         const row,
                 const unsigned int * const same,
-                unsigned int *       const sameFp,
+                unsigned int *       const colNumBuffer,
                 tuple *              const rowBuffer,
                 const tuple *        const outRow) {
 /*----------------------------------------------------------------------------
   Make a row of a mapped-texture stereogram.
 -----------------------------------------------------------------------------*/
+    unsigned int * const sameFp = colNumBuffer;
+        /* Fixed point of same[] */
+
     unsigned int * tuplesInCol;
         /* tuplesInCol[C] is the number of tuples averaged together to make
            Column C.
@@ -1165,7 +1177,6 @@ makeImageRowMts(outGenerator *       const outGenP,
 static void
 makeImageRow(outGenerator *       const outGenP,
              unsigned int         const row,
-             unsigned int         const optWidth,
              unsigned int         const xbegin,
              const unsigned int * const sameL,
              const unsigned int * const sameR,
@@ -1243,6 +1254,165 @@ invertHeightRow(const struct pam * const heightPamP,
 
 
 static void
+makeOneImageRow(unsigned int         const row,
+                outGenerator *       const outputGeneratorP,
+                bool                 const makeMask,
+                const unsigned int * const sameL,
+                const unsigned int * const sameR,
+                unsigned int *       const colNumBuffer,
+                unsigned int         const xbegin,
+                tuple *              const rowBuffer,
+                tuple *              const outRow) {
+
+    if (makeMask)
+        makeMaskRow(&outputGeneratorP->pam, xbegin, sameL, sameR, outRow);
+    else {
+        if (outputGeneratorP->textureP)
+            makeImageRowMts(outputGeneratorP, row, sameR, colNumBuffer,
+                            rowBuffer, outRow);
+        else
+            makeImageRow(outputGeneratorP, row,
+                         xbegin, sameL, sameR, outRow);
+    }
+}
+
+
+
+static void
+constructRowTileable(const struct pam *   const inPamP,
+                     outGenerator *       const outputGeneratorP,
+                     bool                 const makeMask,
+                     const unsigned int * const sameL,
+                     const unsigned int * const sameR,
+                     unsigned int         const row,
+                     tuple *              const outRow,
+                     tuple *              const outRowBuf1,
+                     tuple *              const outRowBuf2,
+                     unsigned int *       const colNumBuf2) {
+
+    tuple * const outRowMax = outRowBuf1;
+
+    unsigned int col;
+
+    /* Create two rows with extreme xbegin values and blend the second
+       into the first.  outRow[] serves as both the buffer for the xbegin=0
+       version and the merged output.  outRowMax[] is the buffer for the
+       xbegin=maximum case.
+    */
+    makeOneImageRow(row, outputGeneratorP, makeMask, sameL, sameR, colNumBuf2,
+                    0, outRowBuf2, outRow);
+
+    makeOneImageRow(row, outputGeneratorP, makeMask, sameL, sameR, colNumBuf2,
+                    inPamP->width - 1, outRowBuf2, outRowMax);
+
+    for (col = 0; col < inPamP->width; ++col) {
+        unsigned int plane;
+        unsigned int oplane;
+
+        if (outputGeneratorP->pam.have_opacity)
+            oplane = outputGeneratorP->pam.opacity_plane;
+
+        for (plane = 0; plane < outputGeneratorP->pam.color_depth; ++plane) {
+
+            sample samp, sampMax;
+
+            if (outputGeneratorP->pam.have_opacity) {
+                /* If one sample is fully transparent, use the
+                   other sample for both purposes
+                */
+                if (outRow[col][oplane] == 0)
+                    samp = sampMax = outRowMax[col][plane];
+                else if (outRowMax[col][oplane] == 0)
+                    samp = sampMax = outRow[col][plane];
+                else {
+                    samp    = outRow[col][plane];
+                    sampMax = outRowMax[col][plane];
+                }
+            } else {
+                samp    = outRow[col][plane];
+                sampMax = outRowMax[col][plane];
+            }
+
+            outRow[col][plane] =
+                (col*sampMax + (inPamP->width - col - 1)*samp) /
+                (inPamP->width - 1);
+        }
+        if (outputGeneratorP->pam.have_opacity) {
+            sample samp, sampMax;
+
+            /* Take the maximum alpha for partially transparent samples. */
+            samp    = outRow[col][oplane];
+            sampMax = outRowMax[col][oplane];
+            outRow[col][oplane] = MAX(samp, sampMax);
+        }
+    }
+}
+
+
+
+static void
+doRow(const struct pam * const inPamP,
+      outGenerator *     const outputGeneratorP,
+      double             const depthOfField,
+      double             const eyesep,
+      unsigned int       const dpi,
+      bool               const crossEyed,
+      bool               const makeMask,
+      bool               const tileable,
+      unsigned int       const magnifypat,
+      unsigned int       const smoothing,
+      unsigned int       const xbegin,
+      unsigned int       const row,
+      tuple *            const inRow,
+      tuple *            const outRowBuf0,
+      tuple *            const outRowBuf1,
+      tuple *            const outRowBuf2,
+      unsigned int *     const colNumBuf0,
+      unsigned int *     const colNumBuf1,
+      unsigned int *     const colNumBuf2) {
+
+    tuple *        const outRow = outRowBuf0;
+    unsigned int * const sameL  = colNumBuf0;
+        /* sameL[N] is the column number of a pixel to the
+           left forced to have the same color as the one in column N
+        */
+    unsigned int * const sameR = colNumBuf1;
+        /* sameR[N] is the column number of a pixel to the
+           right forced to have the same color as the one in column N
+        */
+
+    pnm_readpamrow(inPamP, inRow);
+
+    if (crossEyed)
+        /* Invert heights for cross-eyed (as opposed to wall-eyed)
+           people.
+        */
+        invertHeightRow(inPamP, inRow);
+
+    /* Determine color constraints. */
+    makeStereoRow(inPamP, inRow, sameL, sameR, depthOfField, eyesep, dpi,
+                  ROUNDU(eyesep * dpi)/(magnifypat * 2),
+                  smoothing);
+
+    /* Construct a single row. */
+    if (tileable) {
+        constructRowTileable(inPamP, outputGeneratorP, makeMask,
+                             sameL, sameR, row, outRow,
+                             outRowBuf1, outRowBuf2, colNumBuf2);
+
+    } else {
+        makeOneImageRow(row, outputGeneratorP, makeMask,
+                        sameL, sameR, colNumBuf2,
+                        xbegin, outRowBuf1, outRow);
+    }
+
+    /* Write the resulting row. */
+    pnm_writepamrow(&outputGeneratorP->pam, outRow);
+}
+
+
+
+static void
 makeImageRows(const struct pam * const inPamP,
               outGenerator *     const outputGeneratorP,
               double             const depthOfField,
@@ -1250,72 +1420,50 @@ makeImageRows(const struct pam * const inPamP,
               unsigned int       const dpi,
               bool               const crossEyed,
               bool               const makeMask,
+              bool               const tileable,
               unsigned int       const magnifypat,
               unsigned int       const smoothing,
               unsigned int       const xbegin) {
 
-    tuple * inRow;     /* One row of pixels read from the height-map file */
-    tuple * outRow;    /* One row of pixels to write to the height-map file */
-    unsigned int * sameR;
-        /* Malloced array: sameR[N] is the column number of a pixel to the
-           right forced to have the same color as the one in column N
-        */
-    unsigned int * sameL;
-        /* Malloced array: sameL[N] is the column number of a pixel to the
-           left forced to have the same color as the one in column N
-        */
-    unsigned int * sameRfp;
-        /* Malloced array: Fixed point of sameR[] */
-    tuple * rowBuffer;     /* Scratch row needed for texture manipulation */
+    tuple * inRow;    /* Buffer for use in reading from the height-map image */
+    tuple * outRowBuf0; /* Buffer for use in generating output rows */
+    tuple * outRowBuf1; /* Buffer for use in generating output rows */
+    tuple * outRowBuf2; /* Buffer for use in generating output rows */
+    unsigned int * colNumBuf0;
+    unsigned int * colNumBuf1;
+    unsigned int * colNumBuf2;
     unsigned int row;      /* Current row in the input and output files */
 
     inRow = pnm_allocpamrow(inPamP);
-    outRow = pnm_allocpamrow(&outputGeneratorP->pam);
-    MALLOCARRAY(sameR, inPamP->width);
-    if (sameR == NULL)
-        pm_error("Unable to allocate space for \"sameR\" array.");
-    MALLOCARRAY(sameL, inPamP->width);
-    if (sameL == NULL)
-        pm_error("Unable to allocate space for \"sameL\" array.");
-
-    MALLOCARRAY(sameRfp, inPamP->width);
-    if (sameRfp == NULL)
-        pm_error("Unable to allocate space for \"sameRfp\" array.");
-    rowBuffer = pnm_allocpamrow(&outputGeneratorP->pam);
+    outRowBuf0 = pnm_allocpamrow(&outputGeneratorP->pam);
+    outRowBuf1 = pnm_allocpamrow(&outputGeneratorP->pam);
+    outRowBuf2 = pnm_allocpamrow(&outputGeneratorP->pam);
+    MALLOCARRAY(colNumBuf0, inPamP->width);
+    if (colNumBuf0 == NULL)
+        pm_error("Unable to allocate space for %u column buffer",
+                 inPamP->width);
+    MALLOCARRAY(colNumBuf1, inPamP->width);
+    if (colNumBuf1 == NULL)
+        pm_error("Unable to allocate space for %u column buffer",
+                 inPamP->width);
+    MALLOCARRAY(colNumBuf2, inPamP->width);
+    if (colNumBuf2 == NULL)
+        pm_error("Unable to allocate space for %u column buffer",
+                 inPamP->width);
 
     for (row = 0; row < inPamP->height; ++row) {
-        pnm_readpamrow(inPamP, inRow);
-        if (crossEyed)
-            /* Invert heights for cross-eyed (as opposed to wall-eyed)
-               people.
-            */
-            invertHeightRow(inPamP, inRow);
-
-        /* Determine color constraints. */
-        makeStereoRow(inPamP, inRow, sameL, sameR, depthOfField, eyesep, dpi,
-                      ROUNDU(eyesep * dpi)/(magnifypat * 2),
-                      smoothing);
-
-        if (makeMask)
-            makeMaskRow(&outputGeneratorP->pam, xbegin, sameL, sameR, outRow);
-        else {
-            if (outputGeneratorP->textureP)
-                makeImageRowMts(outputGeneratorP, row, sameR, sameRfp,
-                                rowBuffer, outRow);
-            else
-                makeImageRow(outputGeneratorP, row,
-                             ROUNDU(eyesep * dpi)/(magnifypat * 2),
-                             xbegin, sameL, sameR, outRow);
-        }
-        /* Write the resulting row. */
-        pnm_writepamrow(&outputGeneratorP->pam, outRow);
+        doRow(inPamP, outputGeneratorP,  depthOfField, eyesep, dpi,
+              crossEyed, makeMask, tileable, magnifypat, smoothing, xbegin,
+              row,
+              inRow, outRowBuf0, outRowBuf1, outRowBuf2,
+              colNumBuf0, colNumBuf1, colNumBuf2);
     }
 
-    pnm_freepamrow(rowBuffer);
-    free(sameRfp);
-    free(sameL);
-    free(sameR);
-    pnm_freepamrow(outRow);
+    free(colNumBuf2);
+    free(colNumBuf1);
+    free(colNumBuf0);
+    pnm_freepamrow(outRowBuf1);
+    pnm_freepamrow(outRowBuf0);
     pnm_freepamrow(inRow);
 }
 
@@ -1361,8 +1509,8 @@ produceStereogram(FILE *             const ifP,
 
     makeImageRows(&inPam, outputGeneratorP,
                   cmdline.depth, cmdline.eyesep, cmdline.dpi,
-                  cmdline.crosseyed, cmdline.makemask, cmdline.magnifypat,
-                  cmdline.smoothing, xbegin);
+                  cmdline.crosseyed, cmdline.makemask, cmdline.tileable,
+                  cmdline.magnifypat, cmdline.smoothing, xbegin);
 
     if (cmdline.guidebottom)
         drawguides(cmdline.guidesize, &outputGeneratorP->pam,
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index e52a296e..7ea64857 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -30,6 +30,7 @@
 #include "pbm.h"
 #include "pbmfont.h"
 
+
 /* Max length of input text.  Valid for text which is part of the
    command line and also for text fed from standard input.
    Note that newline is counted as a character.
@@ -55,6 +56,7 @@ struct CmdlineInfo {
     unsigned int nomargins;  /* -nomargins option specified  */
     unsigned int dryrun;     /* -dry-run option specified */
     unsigned int textdump;   /* -text-dump option specified */
+    unsigned int entirefont; /* -load-entire-font option specified */
     unsigned int verbose;    /* -verbose option specified */
         /* undocumented option */
     unsigned int dumpsheet; /* font data sheet in PBM format for -font */
@@ -133,6 +135,7 @@ parseCommandLine(int argc, const char ** argv,
     OPTENT3(0, "dry-run",    OPT_FLAG,   NULL, &cmdlineP->dryrun,    0);
     OPTENT3(0, "text-dump",  OPT_FLAG,   NULL, &cmdlineP->textdump,  0);
     OPTENT3(0, "dump-sheet", OPT_FLAG,   NULL, &cmdlineP->dumpsheet, 0);
+    OPTENT3(0, "load-entire-font", OPT_FLAG,   NULL, &cmdlineP->entirefont, 0);
 
     /* Set the defaults */
     cmdlineP->font    = NULL;
@@ -166,6 +169,8 @@ parseCommandLine(int argc, const char ** argv,
 
     if (cmdlineP->font != NULL && cmdlineP->builtin != NULL)
         pm_error("You cannot specify both -font and -builtin");
+    else if (cmdlineP->font == NULL && cmdlineP->entirefont)
+        pm_error("You cannot specify -load-entire-font without -font");
 
     if (cmdlineP->textdump) {
         if (cmdlineP->dryrun)
@@ -206,12 +211,10 @@ reportFont(const struct font2 * const fontP) {
 
 
 
-
-
-
 static struct font2 *
-font2FromFile(const char * const fileName,
-              PM_WCHAR     const maxmaxglyph) {
+font2FromFile(const char *               const fileName,
+              PM_WCHAR                   const maxmaxglyph,
+              const struct pm_selector * const selectorP) {
 
     struct font2 * font2P;
 
@@ -224,7 +227,7 @@ font2FromFile(const char * const fileName,
         /* This is the normal program flow */
         pm_setjmpbuf(&jmpbuf);
 
-        font2P = pbm_loadfont2(fileName, maxmaxglyph);
+        font2P = pbm_loadfont2select(fileName, maxmaxglyph, selectorP);
 
         pm_setjmpbuf(NULL);
     } else {
@@ -241,23 +244,176 @@ font2FromFile(const char * const fileName,
 
 
 
+static bool
+codepointIsValid(struct font2 * const fontP,
+                 PM_WCHAR       const codepoint) {
+/*----------------------------------------------------------------------------
+  'codepoint' is a valid entry in the font indicated by 'fontP'.
+-----------------------------------------------------------------------------*/
+    bool retval;
+
+    assert(pm_selector_is_marked(fontP->selectorP, codepoint));
+
+    if (codepoint > fontP->maxglyph || fontP->glyph[codepoint] == NULL)
+        retval = false;
+    else retval = true;
+
+    return (retval);
+
+}
+
+
+
+static const char *
+charDescription(PM_WCHAR const codepoint) {
+/*----------------------------------------------------------------------------
+   Descriptive string for codepoint 'codepoint'.
+
+   Certain codepoints appear frequently in text files and cause problems when
+   missing in the font set, so we give those descriptions.  For other
+   codepoint, we just return a null string.
+-----------------------------------------------------------------------------*/
+
+  const char * name;
+
+  switch (codepoint) {
+  case '\r' : name="carriage return";  break;
+  case '\n' : name="line feed";        break; /* for future use */
+  case '\t' : name="tab";              break; /* for future use */
+  case ' '  : name="space";            break;
+  case 0xFEFF: name="byte order mark"; break;
+  default : name=""; break;
+  }
+
+  return name;
+}
+
+
+
+enum FixMode {SILENT, /* convert silently */
+              WARN,   /* output message to stderr */
+              QUIT    /* abort */ };
+
+
+
+static void
+reportAbsentGlyphs(bool                       const wchar,
+                   struct font2 *             const fontP,
+                   const struct pm_selector * const textSelectorP,
+                   unsigned int *             const missingCharCtP) {
+/*----------------------------------------------------------------------------
+   Compare the glyph entries in *fontP with the requests in *textSelectorP.
+
+   Note that we may need the space character as a substitute for missing
+   glyphs while the input text has no spaces.  In rare cases the font may not
+   have a space character.
+
+   Currently, this program reads the font file only once.  A future version
+   may opt to read it a second time to load the substitute glyph.
+-----------------------------------------------------------------------------*/
+    PM_WCHAR     codepoint;
+    unsigned int missingCharCt;
+
+    for (codepoint = textSelectorP->min, missingCharCt = 0;
+         codepoint <= textSelectorP->max; ++codepoint) {
+
+        if (pm_selector_is_marked(textSelectorP, codepoint) &&
+            !codepointIsValid(fontP, codepoint)) {
+            ++missingCharCt;
+            if (missingCharCt == 1)  { /* initial */
+                pm_message("failed to load glyph data for these code points "
+                           "in input:");
+            }
+
+            pm_message(wchar ? "+%05X %s" : "%02X %s",
+                       (unsigned int) codepoint,
+                       charDescription(codepoint));
+        }
+    }
+
+    *missingCharCtP = missingCharCt;
+}
+
+
+
 static void
-computeFont(struct CmdlineInfo const cmdline,
-            struct font2 **    const fontPP) {
+validateFont(bool                       const wchar,
+             struct font2 *             const fontP,
+             const struct pm_selector * const textSelectorP,
+             enum   FixMode             const fixmode,
+             bool                       const verbose,
+             bool *                     const hasAllCharsP) {
+/*----------------------------------------------------------------------------
+   If any glyphs required by the text indicated by *textSelectorP are missing
+   from font *fontP, issue a warning message or abort the program according to
+   'fixmode'.
+
+   Abort the program if one or more characters are missing and the space
+   character is one of them.
+
+   Return (if we return) as *hasAllCharsP whether the font has all the glyphs.
+-----------------------------------------------------------------------------*/
+    unsigned int missingCharCt;
+
+    assert (textSelectorP != NULL);
+    assert(pm_selector_marked_ct(textSelectorP) >= 0);
+
+    reportAbsentGlyphs(wchar, fontP, textSelectorP, &missingCharCt);
+
+    if (missingCharCt > 0) {
+        if (verbose)
+            pm_message("%u characters absent in font", missingCharCt);
+
+        if (fixmode == QUIT)
+            pm_error("aborting");
+        else if (!codepointIsValid(fontP, L' '))
+            pm_error ("replacement character (space) absent; aborting");
+        else
+            pm_message("undefined code points will be converted to space");
+    }
+
+    *hasAllCharsP = (missingCharCt == 0);
+}
+
 
-    struct font2 * font2P;
 
-    if (cmdline.font)
-        font2P = font2FromFile(cmdline.font,
-                               cmdline.wchar ? PM_FONT2_MAXGLYPH :
-                                               PM_FONT_MAXGLYPH);
-    else if (cmdline.builtin)
+static void
+computeFont(struct CmdlineInfo         const cmdline,
+            struct font2 **            const fontPP,
+            const struct pm_selector * const textSelectorP,
+            enum   FixMode             const fixmode,
+            bool *                     const fontHasAllCharsP) {
+
+    struct font2 *       font2P;
+    struct pm_selector * fontSelectorP;
+
+    if (cmdline.font) {
+        if(cmdline.entirefont)
+            fontSelectorP = NULL;
+        else if(!pm_selector_is_marked(textSelectorP, L' ')) {
+            pm_selector_copy(MAX(textSelectorP->max, L' '),
+                             textSelectorP, &fontSelectorP);
+            pm_selector_mark(fontSelectorP, L' ');
+        } else
+            fontSelectorP = (struct pm_selector *) textSelectorP;
+
+        font2P = font2FromFile(cmdline.font, cmdline.wchar ?
+                               PM_FONT2_MAXGLYPH : PM_FONT_MAXGLYPH,
+                               fontSelectorP);
+    } else if (cmdline.builtin)
         font2P = pbm_defaultfont2(cmdline.builtin);
     else
         font2P = pbm_defaultfont2(cmdline.wchar ? "bdf" : "bdf");
 
-    if (cmdline.verbose)
+    if (cmdline.verbose) {
         reportFont(font2P);
+        pm_message("%u code points found in text",
+                   pm_selector_marked_ct(textSelectorP));
+    }
+
+    validateFont(cmdline.wchar, font2P, textSelectorP, fixmode,
+                 cmdline.verbose,
+                 fontHasAllCharsP);
 
     *fontPP = font2P;
 }
@@ -266,9 +422,6 @@ computeFont(struct CmdlineInfo const cmdline,
 
 struct Text {
     PM_WCHAR **  textArray;  /* malloc'ed */
-        /* This is strictly characters that are in user's font - no control
-           characters, no undefined code points.
-        */
     unsigned int allocatedLineCount;
     unsigned int lineCount;
 };
@@ -309,28 +462,19 @@ freeTextArray(struct Text const text) {
 
 
 
-enum FixMode {SILENT, /* convert silently */
-              WARN,   /* output message to stderr */
-              QUIT    /* abort */ };
-
 
 static void
-fixControlChars(const PM_WCHAR  * const input,
-                struct font2    * const fontP,
-                const PM_WCHAR ** const outputP,
-                enum FixMode      const fixMode) {
+setupSelector(const PM_WCHAR *     const input,
+              const PM_WCHAR **    const outputP,
+              struct pm_selector * const selectorP) {
 /*----------------------------------------------------------------------------
-   Return a translation of input[] that can be rendered as glyphs in
-   the font 'fontP'.  Return it as newly malloced *outputP.
+   Read through input[] and record the codepoints encountered.  Return it as
+   newly malloced *outputP.
 
    Expand tabs to spaces.
 
    Remove any trailing newline.  (But leave intermediate ones as line
    delimiters).
-
-   Depending on value of fixMode, turn anything that isn't a code point
-   in the font to a single space (which isn't guaranteed to be in the
-   font either, of course).
 -----------------------------------------------------------------------------*/
     /* We don't know in advance how big the output will be because of the
        tab expansions.  So we make sure before processing each input
@@ -370,33 +514,19 @@ fixControlChars(const PM_WCHAR  * const input,
             unsigned int const nextTabStop =
                 (outCursor + tabSize) / tabSize * tabSize;
 
-            if (fontP->glyph[L' '] == NULL)
-                pm_error("space character not defined in font");
-
             while (outCursor < nextTabStop)
                 output[outCursor++] = L' ';
-        } else if (currentChar > fontP->maxglyph ||
-                   !fontP->glyph[currentChar]) {
-            if (currentChar > PM_FONT2_MAXGLYPH)
+
+            pm_selector_mark(selectorP, L' ');
+
+        } else if (currentChar > PM_FONT2_MAXGLYPH)
                 pm_message("code point %X is beyond what this program "
                            "can handle.  Max=%X",
                            (unsigned int)currentChar, PM_FONT2_MAXGLYPH);
-
-            /* Turn this unknown char into a single space. */
-            if (fontP->glyph[L' '] == NULL)
-                pm_error("space character not defined in font");
-            else if (fixMode == QUIT)
-                pm_error("code point %X not defined in font",
-                         (unsigned int) currentChar );
-            else {
-                if (fixMode == WARN)
-                    pm_message("converting code point %X to space",
-                               (unsigned int) currentChar );
-                output[outCursor++] = ' ';
-            }
-        } else
+        else {
             output[outCursor++] = input[inCursor];
-
+            pm_selector_mark(selectorP, currentChar);
+        }
         assert(outCursor <= outputSize);
     }
     output[outCursor++] = L'\0';
@@ -437,7 +567,8 @@ getEdges(double               const currentPosition,
     double rightEdge;
 
     if (glyphP == NULL)
-        pm_error("Unrenderable char: %04X", (unsigned int) currentChar);
+        pm_error("encountered unrenderable char: %04X",
+                  (unsigned int) currentChar);
     else {
         leftEdge  =  (int) MIN(currentPosition + glyphP->x, currLeftEdge);
         rightEdge =  MAX(currentPosition + glyphP->x + glyphP->width,
@@ -475,8 +606,8 @@ advancePosition(double               const currentPosition,
             pm_error("Negative -space value too large");
         else
             pm_error("Abnormal horizontal advance value %d "
-                     "for code point 0x%lx.",
-                     glyphP->xadd, (unsigned long int) currentChar);
+                     "for code point +%05X",
+                     glyphP->xadd, (unsigned int) currentChar);
     }
     else if (currentPosition + advance > INT_MAX)
         pm_error("Image is too wide");
@@ -535,8 +666,8 @@ getLineDimensions(PM_WCHAR             const line[],
 
     for (cursor = 0; line[cursor] != L'\0'; ++cursor) {
         PM_WCHAR          const currentChar = line[cursor];
-        unsigned long int const glyphIndex  = (unsigned long int) currentChar;
-        struct glyph *    const glyphP      = fontP->glyph[glyphIndex];
+        unsigned int      const index       = (unsigned int) currentChar;
+        struct glyph *    const glyphP      = fontP->glyph[index];
 
         getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
                  &leftEdge, &rightEdge);
@@ -593,9 +724,8 @@ getCharsWithinWidth(PM_WCHAR             const line[],
              currentWidth <= targetWidth && line[cursor] != L'\0';
              ++cursor) {
             PM_WCHAR const currentChar = line[cursor];
-            unsigned long int const glyphIndex =
-              (unsigned long int) currentChar;
-            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+            unsigned int const index = (unsigned int) currentChar;
+            struct glyph * const glyphP = fontP->glyph[index];
 
             getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
                      &leftEdge, &rightEdge);
@@ -694,9 +824,8 @@ insertCharacters(bit **         const bits,
 
         for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
             PM_WCHAR const currentChar = lp.textArray[line][cursor];
-            unsigned long int const glyphIndex =
-                (unsigned long int)currentChar;
-            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+            unsigned int const index = (unsigned int) currentChar;
+            struct glyph * const glyphP = fontP->glyph[index];
             int const toprow =
                 row + fontP->maxheight + fontP->y - glyphP->height - glyphP->y;
                 /* row number in image of top row in glyph */
@@ -943,10 +1072,9 @@ fgetNarrowWideString(PM_WCHAR *    const widestring,
 
 
 static void
-getText(PM_WCHAR       const cmdlineText[],
-        struct font2 * const fontP,
-        struct Text  * const inputTextP,
-        enum FixMode   const fixMode) {
+getText(PM_WCHAR             const cmdlineText[],
+        struct Text *        const inputTextP,
+        struct pm_selector * const selectorP) {
 /*----------------------------------------------------------------------------
    Get as *inputTextP the text to format, given that the text on the
    command line (one word per command line argument, separated by spaces),
@@ -969,8 +1097,8 @@ getText(PM_WCHAR       const cmdlineText[],
         MALLOCARRAY_NOFAIL(inputText.textArray, 1);
         inputText.allocatedLineCount = 1;
         inputText.lineCount = 1;
-        fixControlChars(cmdlineText, fontP,
-                        (const PM_WCHAR**)&inputText.textArray[0], fixMode);
+        setupSelector(cmdlineText, (const PM_WCHAR**) &inputText.textArray[0],
+                      selectorP);
         free((void *) cmdlineText);
     } else {
         /* Read text from stdin. */
@@ -978,7 +1106,7 @@ getText(PM_WCHAR       const cmdlineText[],
         unsigned int const lineBufTerm = LINEBUFSIZE - 1;
 
         unsigned int maxlines;
-            /* Maximum number of lines for which we presently have space in
+            /* Maximum number of lines for which we currently have space in
                the text array
             */
         PM_WCHAR *   buf;
@@ -1021,9 +1149,9 @@ getText(PM_WCHAR       const cmdlineText[],
                         if (textArray == NULL)
                             pm_error("out of memory");
                     }
-                    fixControlChars(buf, fontP,
-                                    (const PM_WCHAR **)&textArray[lineCount],
-                                    fixMode);
+                    setupSelector(buf,
+                                  (const PM_WCHAR **) &textArray[lineCount],
+                                  selectorP);
                     if (textArray[lineCount] == NULL)
                         pm_error("out of memory");
                     ++lineCount;
@@ -1064,6 +1192,33 @@ computeMargins(struct CmdlineInfo const cmdline,
 
 
 static void
+refineText(struct Text        const inputText,
+           struct font2 *     const fontP) {
+/*----------------------------------------------------------------------------
+   Replace missing characters with space
+
+   A future version of this program may provide various alternatives
+   here including simply deleting the offending character, based on a
+   command-line option
+-----------------------------------------------------------------------------*/
+    PM_WCHAR ** const textArray = inputText.textArray;
+
+    unsigned int lineNum;
+
+    for (lineNum = 0; lineNum < inputText.lineCount; ++lineNum) {
+        PM_WCHAR * const line = textArray[lineNum];
+
+        unsigned int cursor;
+
+        for (cursor = 0; line[cursor] != L'\0'; ++cursor)
+            if ( !codepointIsValid(fontP, line[cursor]) )
+                line[cursor] = L' ';
+    }
+}
+
+
+
+static void
 formatText(struct CmdlineInfo const cmdline,
            struct Text        const inputText,
            struct font2 *     const fontP,
@@ -1191,7 +1346,7 @@ renderText(unsigned int   const cols,
                      space, cols, rows, lspace, fixedAdvance);
 
     /* Free all font data */
-    pbm_destroybdffont2(fontP); 
+    pbm_destroybdffont2(fontP);
 
     {
         unsigned int row;
@@ -1224,36 +1379,31 @@ L"M \",/^_[`jpqy| M" };
 
 
 static void
-validateText(const PM_WCHAR ** const textArray,
-             struct font2    * const fontP) {
-/*----------------------------------------------------------------------------
-   Abort the program if there are characters in 'textArray' which cannot be
-   rendered in font *fontP.
------------------------------------------------------------------------------*/
-    const PM_WCHAR * output;
-    unsigned int textRow;
-
-    for (textRow = 0; textRow < 12; ++textRow)
-        fixControlChars(textArray[textRow], fontP, &output, QUIT);
-
-    free((PM_WCHAR *)output);
-}
+renderSheet(struct CmdlineInfo const cmdline,
+            FILE *             const ofP) {
 
+    struct Text const sheetText =
+        { (PM_WCHAR ** const) sheetTextArray, 12, 12};
+    static unsigned char const sheetRequestArray[16] = {
+         0x00, 0x00, 0x00, 0x00,  0xff, 0xff, 0xff, 0xff,
+         0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xfe};
 
+    struct pm_selector * selectorP;
+    struct font2 *       fontP;
+    bool                 fontIsComplete;
 
-static void
-renderSheet(struct font2 * const fontP,
-            FILE *         const ofP) {
+    pm_selector_create_fixed(sheetRequestArray, 32, 126,95, &selectorP);
 
-    int const cols  = fontP->maxwidth  * 16;
-    int const rows  = fontP->maxheight * 12;
-    struct Text const sheetText =
-        { (PM_WCHAR ** const) sheetTextArray, 12, 12};
+    computeFont(cmdline, &fontP, selectorP, QUIT, &fontIsComplete);
 
-    validateText(sheetTextArray, fontP);
+    {
+        unsigned int const cols  = fontP->maxwidth  * 16;
+        unsigned int const rows  = fontP->maxheight * 12;
 
-    renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0),
-               0.0, 0, TRUE, ofP);
+        renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0),
+                   0.0, 0, TRUE, ofP);
+    }
+    pm_selector_destroy(selectorP);
 }
 
 
@@ -1304,8 +1454,8 @@ textDumpOutput(struct Text   const lp,
 
 static void
 pbmtext(struct CmdlineInfo const cmdline,
-        struct font2 *     const fontP,
-        FILE *             const ofP) {
+        FILE *             const ofP,
+        bool               const wchar) {
 
     unsigned int rows, cols;
         /* Dimensions in pixels of the output image */
@@ -1315,19 +1465,30 @@ pbmtext(struct CmdlineInfo const cmdline,
     unsigned int hmargin0;
     struct Text inputText;
     struct Text formattedText;
+    struct font2 * fontP;
+    struct pm_selector * selectorP;
     unsigned int maxleftb, maxleftb0;
+    bool fontIsComplete;
+
+    pm_selector_create(wchar ? PM_FONT2_MAXGLYPH : PM_FONT_MAXGLYPH,
+                       &selectorP);
 
-    getText(cmdline.text, fontP, &inputText,
-            cmdline.verbose ? WARN : SILENT);
+    getText(cmdline.text, &inputText, selectorP);
+
+    if (pm_selector_marked_ct(selectorP) == 0)
+        pm_error("No input text.  Aborting.");
+
+    computeFont(cmdline, &fontP, selectorP, cmdline.verbose ? WARN : SILENT,
+                &fontIsComplete);
 
     computeMargins(cmdline, inputText, fontP, &vmargin, &hmargin0);
 
+    if (!fontIsComplete)
+        refineText(inputText, fontP);
+
     formatText(cmdline, inputText, fontP, hmargin0,
                &formattedText, &maxleftb0);
 
-    if (formattedText.lineCount == 0)
-        pm_error("No input text");
-
     computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, &rows);
 
     computeImageWidth(formattedText, fontP, cmdline.space,
@@ -1361,6 +1522,8 @@ pbmtext(struct CmdlineInfo const cmdline,
                    maxleftb, cmdline.space, cmdline.lspace, FALSE, ofP);
 
     freeTextArray(formattedText);
+
+    pm_selector_destroy(selectorP);
 }
 
 
@@ -1369,7 +1532,6 @@ int
 main(int argc, const char *argv[]) {
 
     struct CmdlineInfo cmdline;
-    struct font2 * fontP;
 
     pm_proginit(&argc, argv);
 
@@ -1389,12 +1551,10 @@ main(int argc, const char *argv[]) {
     if (cmdline.verbose)
         pm_message("LC_CTYPE is set to '%s'", setlocale(LC_CTYPE, NULL) );
 
-    computeFont(cmdline, &fontP);
-
     if (cmdline.dumpsheet)
-        renderSheet(fontP, stdout);
+        renderSheet(cmdline, stdout);
     else
-        pbmtext(cmdline, fontP, stdout);
+        pbmtext(cmdline, stdout, cmdline.wchar);
 
     pm_close(stdout);
 
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
index f543618d..0c3656e7 100644
--- a/generator/pbmtextps.c
+++ b/generator/pbmtextps.c
@@ -190,7 +190,7 @@ parseCommandLine(int argc, const char ** argv,
     unsigned int leftmarginSpec, rightmarginSpec;
     unsigned int topmarginSpec, bottommarginSpec;
 
-    MALLOCARRAY(option_def, 100);
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "resolution",    OPT_UINT,
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
index 8fec9fee..114f7f18 100644
--- a/generator/ppmforge.c
+++ b/generator/ppmforge.c
@@ -631,7 +631,7 @@ createPlanetStuff(bool             const clouds,
     MALLOCARRAY(bxc, cols);
 
     if (u == NULL || u1 == NULL || bxf == NULL || bxc == NULL)
-        pm_error("Cannot allocate %d element interpolation tables.", cols);
+        pm_error("Cannot allocate %u element interpolation tables.", cols);
     {
         unsigned int j;
         for (j = 0; j < cols; j++) {
diff --git a/generator/ppmmake.c b/generator/ppmmake.c
index 2d4bbca8..7bac9601 100644
--- a/generator/ppmmake.c
+++ b/generator/ppmmake.c
@@ -46,7 +46,7 @@ parseCommandLine(int argc, char ** argv,
     unsigned int maxvalSpec;
     unsigned int option_def_index;
 
-    MALLOCARRAY(option_def, 100);
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
diff --git a/generator/ppmrough.c b/generator/ppmrough.c
index e749c9c2..626ee989 100644
--- a/generator/ppmrough.c
+++ b/generator/ppmrough.c
@@ -47,7 +47,7 @@ parseCommandLine(int argc, const char ** argv,
 
     unsigned int option_def_index;
 
-    MALLOCARRAY(option_def, 100);
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "width",       OPT_UINT,   &cmdlineP->width,   NULL, 0);