about summary refs log tree commit diff
path: root/generator/pbmtext.c
diff options
context:
space:
mode:
Diffstat (limited to 'generator/pbmtext.c')
-rw-r--r--generator/pbmtext.c1596
1 files changed, 1050 insertions, 546 deletions
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
index 9f4366d4..e6f27865 100644
--- a/generator/pbmtext.c
+++ b/generator/pbmtext.c
@@ -10,41 +10,93 @@
 ** implied warranty.
 */
 
+#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
 #include <string.h>
+#include <math.h>
 #include <limits.h>
 #include <assert.h>
+#include <setjmp.h>
+#include <locale.h>
+#include <wchar.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
+#include "pm.h"
 #include "pbm.h"
 #include "pbmfont.h"
 
-struct cmdlineInfo {
+#define  MAXLINECHARS 5000
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char * text;    /* text from command line or NULL if none */
+    const PM_WCHAR * text; /* text from command line or NULL if none */
     const char * font;    /* -font option value or NULL if none */
     const char * builtin; /* -builtin option value or NULL if none */
-    unsigned int dump;   
-        /* undocumented dump option for installing a new built-in font */
-    float space;   /* -space option value or default */
-    unsigned int width;     /* -width option value or zero */
-    int lspace;    /* lspace option value or default */
-    unsigned int nomargins;     /* -nomargins */
-    unsigned int verbose;
+    float space;          /* -space option value or default */
+    int lspace;           /* -lspace option value or default */
+    unsigned int width;   /* -width option value or zero */
+    unsigned int wchar;   /* -wchar option specified  */
+    unsigned int nomargins;  /* -nomargins option specified  */
+    unsigned int dryrun;     /* -dry-run option specified */
+    unsigned int textdump;   /* -text-dump option specified */
+    unsigned int verbose;    /* -verbose option specified */
+        /* undocumented option */
+    unsigned int dumpsheet; /* font data sheet in PBM format for -font */
 };
 
 
 
+static const PM_WCHAR *
+textFmCmdLine(int argc, const char ** argv) {
+
+    char * text;
+    PM_WCHAR * wtext;
+    unsigned int i;
+    unsigned int totaltextsize;
+
+    MALLOCARRAY(text, MAXLINECHARS+1);
+
+    if (!text)
+        pm_error("Unable to allocate memory for a buffer of up to %u "
+                 "characters of text", MAXLINECHARS);
+
+    text[0] = '\0';
+
+    for (i = 1, totaltextsize = 1; i < argc; ++i) {
+        if (i > 1) {
+            strcat(text, " ");
+        }
+        totaltextsize += strlen(argv[i]) + 1;
+        if (totaltextsize > MAXLINECHARS)
+            pm_error("input text too long");
+        strcat(text, argv[i]);
+    }
+    MALLOCARRAY(wtext, totaltextsize * sizeof(PM_WCHAR));
+
+    if (!wtext)
+        pm_error("Unable to allocate memory for a buffer of up to %u "
+                 "wide characters of text", totaltextsize);
+
+    for (i = 0; i < totaltextsize; ++i)
+        wtext[i] = (PM_WCHAR) text[i];
+
+    free(text);
+
+    return wtext;
+}
+
+
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -59,110 +111,151 @@ parseCommandLine(int argc, const char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "font",      OPT_STRING, &cmdlineP->font, NULL,        0);
-    OPTENT3(0, "builtin",   OPT_STRING, &cmdlineP->builtin, NULL,     0);
-    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdlineP->dump,        0);
-    OPTENT3(0, "space",     OPT_FLOAT,  &cmdlineP->space, NULL,       0);
-    OPTENT3(0, "width",     OPT_UINT,   &cmdlineP->width, NULL,       0);
-    OPTENT3(0, "lspace",    OPT_INT,    &cmdlineP->lspace, NULL,      0);
-    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdlineP->nomargins,   0);
-    OPTENT3(0, "verbose",   OPT_FLAG,   NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,    NULL,   0);
+    OPTENT3(0, "builtin",    OPT_STRING, &cmdlineP->builtin, NULL,   0);
+    OPTENT3(0, "space",      OPT_FLOAT,  &cmdlineP->space,   NULL,   0);
+    OPTENT3(0, "lspace",     OPT_INT,    &cmdlineP->lspace,  NULL,   0);
+    OPTENT3(0, "width",      OPT_UINT,   &cmdlineP->width,   NULL,   0);
+    OPTENT3(0, "nomargins",  OPT_FLAG,   NULL, &cmdlineP->nomargins, 0);
+    OPTENT3(0, "wchar",      OPT_FLAG,   NULL, &cmdlineP->wchar,     0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,   0);
+    OPTENT3(0, "dry-run",    OPT_FLAG,   NULL, &cmdlineP->dryrun,    0);
+    OPTENT3(0, "text-dump",  OPT_FLAG,   NULL, &cmdlineP->textdump,  0);
+    OPTENT3(0, "dump-sheet", OPT_FLAG,   NULL, &cmdlineP->dumpsheet, 0);
 
     /* Set the defaults */
-    cmdlineP->font = NULL;
+    cmdlineP->font    = NULL;
     cmdlineP->builtin = NULL;
-    cmdlineP->space = 0.0;
-    cmdlineP->width = 0;
-    cmdlineP->lspace = 0;
+    cmdlineP->space   = 0.0;
+    cmdlineP->width   = 0;
+    cmdlineP->lspace  = 0;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->width > 0 && cmdlineP->nomargins) {
+        pm_message("-nomargins has no effect when -width is specified");
+        cmdlineP->nomargins = FALSE;
+    } else if (cmdlineP->width > INT_MAX-10)
+        pm_error("-width value too large");
+
+    if (cmdlineP->space > pbm_maxfontwidth())
+        pm_error("-space value too large");
+    else if (cmdlineP->space < -pbm_maxfontwidth())
+        pm_error("negative -space value too large");
+
+    if (cmdlineP->lspace > pbm_maxfontheight())
+        pm_error("-lspace value too large");
+    else if (cmdlineP->lspace < -pbm_maxfontheight())
+        pm_error("negative -lspace value too large");
+
+    if (cmdlineP->textdump) {
+        if (cmdlineP->dryrun)
+            pm_error("You cannot specify both -dry-run and -text-dump");
+        else if (cmdlineP->dumpsheet)
+            pm_error("You cannot specify both -dump-sheet and -text-dump");
+    }
+
+    if (cmdlineP->dryrun && cmdlineP->dumpsheet)
+        pm_error("You cannot specify both -dry-run and -dump-sheet");
 
     if (argc-1 == 0)
         cmdlineP->text = NULL;
-    else {
-        char *text;
-        int i;
-        int totaltextsize;
-
-        totaltextsize = 1;  /* initial value */
-        
-        text = malloc(totaltextsize);  /* initial allocation */
-        text[0] = '\0';
-        
-        for (i = 1; i < argc; ++i) {
-            if (i > 1) {
-                totaltextsize += 1;
-                text = realloc(text, totaltextsize);
-                if (text == NULL)
-                    pm_error("out of memory allocating space for input text");
-                strcat(text, " ");
-            } 
-            totaltextsize += strlen(argv[i]);
-            text = realloc(text, totaltextsize);
-            if (text == NULL)
-                pm_error("out of memory allocating space for input text");
-            strcat(text, argv[i]);
-        }
-        cmdlineP->text = text;
+    else {  /* Text to render is part of command line */
+        if (cmdlineP->wchar)
+            pm_error("-wchar is not valid when text is from command line");
+
+        cmdlineP->text = textFmCmdLine(argc, argv);
+
+
     }
+    free(option_def);
 }
 
 
 
 static void
-reportFont(struct font * const fontP) {
-
-    unsigned int n;
-    unsigned int c;
+reportFont(const struct font2 * const fontP) {
 
     pm_message("FONT:");
-    pm_message("  character dimensions: %uw x %uh",
+    pm_message("  Name: %s", fontP->name);
+    pm_message("  Encoding: %s", fontP->charset_string);
+    pm_message("  Origin: %s", pbmFontOrigin[fontP->load_fn]);
+    pm_message("  Character dimensions: %uw x %uh",
                fontP->maxwidth, fontP->maxheight);
     pm_message("  Additional vert white space: %d pixels", fontP->y);
+    pm_message("  # characters loaded: %u", fontP->chars);
+}
+
+
+
+
+
+
+static struct font2 *
+font2FromFile(const char * const fileName,
+              PM_WCHAR     const maxmaxglyph) {
+
+    struct font2 * font2P;
+
+    jmp_buf jmpbuf;
+    int rc;
 
-    for (c = 0, n = 0; c < ARRAY_SIZE(fontP->glyph); ++c)
-        if (fontP->glyph[c])
-            ++n;
+    rc = setjmp(jmpbuf);
 
-    pm_message("  # characters: %u", n);
+    if (rc == 0) {
+        /* This is the normal program flow */
+        pm_setjmpbuf(&jmpbuf);
+
+        font2P = pbm_loadfont2(fileName, maxmaxglyph);
+
+        pm_setjmpbuf(NULL);
+    } else {
+        /* This is the second pass, after pbm_loadbdffont2 does a longjmp
+           because it fails.
+        */
+        pm_setjmpbuf(NULL);
+
+        pm_error("Failed to load font from file '%s'", fileName);
+    }
+
+    return font2P;
 }
 
 
 
 static void
-computeFont(struct cmdlineInfo const cmdline,
-            struct font **     const fontPP) {
+computeFont(struct CmdlineInfo const cmdline,
+            struct font2 **    const fontPP) {
 
-    struct font * fontP;
+    struct font2 * font2P;
 
     if (cmdline.font)
-        fontP = pbm_loadfont(cmdline.font);
-    else {
-        if (cmdline.builtin)
-            fontP = pbm_defaultfont(cmdline.builtin);
-        else
-            fontP = pbm_defaultfont("bdf");
-    }
+        font2P = font2FromFile(cmdline.font,
+                               cmdline.wchar ? PM_FONT2_MAXGLYPH :
+                                               PM_FONT_MAXGLYPH);
+    else if (cmdline.builtin)
+        font2P = pbm_defaultfont2(cmdline.builtin);
+    else
+        font2P = pbm_defaultfont2(cmdline.wchar ? "bdf" : "bdf");
 
     if (cmdline.verbose)
-        reportFont(fontP);
+        reportFont(font2P);
 
-    if (cmdline.dump) {
-        pbm_dumpfont(fontP);
-        exit(0);
-    }
-    *fontPP = fontP;
+    *fontPP = font2P;
 }
 
 
 
-struct text {
-    char **      textArray;  /* malloc'ed */
+struct Text {
+    PM_WCHAR **  textArray;  /* malloc'ed */
+        /* This is strictly characters that are in user's font - no control
+           characters, no undefined code points.
+        */
     unsigned int allocatedLineCount;
     unsigned int lineCount;
 };
@@ -170,41 +263,49 @@ struct text {
 
 
 static void
-allocTextArray(struct text * const textP,
+allocTextArray(struct Text * const textP,
                unsigned int  const maxLineCount,
                unsigned int  const maxColumnCount) {
 
     unsigned int line;
 
-    textP->allocatedLineCount = maxLineCount;
-
+    textP->allocatedLineCount = maxColumnCount > 0 ? maxLineCount : 0;
     MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
-    
-    for (line = 0; line < maxLineCount; ++line)
-        MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
 
+    for (line = 0; line < maxLineCount; ++line) {
+        if (maxColumnCount > 0)
+            MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
+    else
+        textP->textArray[line] = NULL;
+    }
     textP->lineCount = 0;
 }
 
 
 
 static void
-freeTextArray(struct text const text) {
+freeTextArray(struct Text const text) {
 
     unsigned int line;
 
     for (line = 0; line < text.allocatedLineCount; ++line)
-        free((char **)text.textArray[line]);
+        free((PM_WCHAR **)text.textArray[line]);
 
     free(text.textArray);
 }
 
 
 
+enum FixMode {SILENT, /* convert silently */
+              WARN,   /* output message to stderr */
+              QUIT    /* abort */ };
+
+
 static void
-fixControlChars(const char *  const input,
-                struct font * const fontP,
-                const char ** const outputP) {
+fixControlChars(const PM_WCHAR  * const input,
+                struct font2    * const fontP,
+                const PM_WCHAR ** const outputP,
+                enum FixMode      const fixMode) {
 /*----------------------------------------------------------------------------
    Return a translation of input[] that can be rendered as glyphs in
    the font 'fontP'.  Return it as newly malloced *outputP.
@@ -214,8 +315,9 @@ fixControlChars(const char *  const input,
    Remove any trailing newline.  (But leave intermediate ones as line
    delimiters).
 
-   Turn anything that isn't a code point in the font to a single space
-   (which isn't guaranteed to be in the font either, of course).
+   Depending on value of fixMode, turn anything that isn't a code point
+   in the font to a single space (which isn't guaranteed to be in the
+   font either, of course).
 -----------------------------------------------------------------------------*/
     /* We don't know in advance how big the output will be because of the
        tab expansions.  So we make sure before processing each input
@@ -228,10 +330,10 @@ fixControlChars(const char *  const input,
     unsigned int const tabSize = 8;
 
     unsigned int inCursor, outCursor;
-    char * output;      /* Output buffer.  Malloced */
+    PM_WCHAR * output;      /* Output buffer.  Malloced */
     size_t outputSize;  /* Currently allocated size of 'output' */
 
-    outputSize = strlen(input) + 1 + tabSize;
+    outputSize = wcslen(input) + 1 + tabSize;
         /* Leave room for one worst case tab expansion and NUL terminator */
     MALLOCARRAY(output, outputSize);
 
@@ -239,7 +341,8 @@ fixControlChars(const char *  const input,
         pm_error("Couldn't allocate %u bytes for a line of text.",
                  (unsigned)outputSize);
 
-    for (inCursor = 0, outCursor = 0; input[inCursor] != '\0'; ++inCursor) {
+    for (inCursor = 0, outCursor = 0; input[inCursor] != L'\0'; ++inCursor) {
+        PM_WCHAR const currentChar = input[inCursor];
         if (outCursor + 1 + tabSize > outputSize) {
             outputSize = outCursor + 1 + 4 * tabSize;
             REALLOCARRAY(output, outputSize);
@@ -247,24 +350,43 @@ fixControlChars(const char *  const input,
                 pm_error("Couldn't allocate %u bytes for a line of text.",
                          (unsigned)outputSize);
         }
-        if (input[inCursor] == '\n' && input[inCursor+1] == '\0') {
+        if (currentChar == L'\n' && input[inCursor+1] == L'\0') {
             /* This is a terminating newline.  We don't do those. */
-        } else if (input[inCursor] == '\t') { 
+        } else if (currentChar == L'\t') {
             /* Expand this tab into the right number of spaces. */
             unsigned int const nextTabStop =
                 (outCursor + tabSize) / tabSize * tabSize;
 
+            if (fontP->glyph[L' '] == NULL)
+                pm_error("space character not defined in font");
+
             while (outCursor < nextTabStop)
-                output[outCursor++] = ' ';
-        } else if (!fontP->glyph[(unsigned char)input[inCursor]]) {
+                output[outCursor++] = L' ';
+        } else if (currentChar > fontP->maxglyph ||
+                   !fontP->glyph[currentChar]) {
+            if (currentChar > PM_FONT2_MAXGLYPH)
+                pm_message("code point %X is beyond what this program "
+                           "can handle.  Max=%X",
+                           (unsigned int)currentChar, PM_FONT2_MAXGLYPH);
+
             /* Turn this unknown char into a single space. */
-            output[outCursor++] = ' ';
+            if (fontP->glyph[L' '] == NULL)
+                pm_error("space character not defined in font");
+            else if (fixMode == QUIT)
+                pm_error("code point %X not defined in font",
+                         (unsigned int) currentChar );
+            else {
+                if (fixMode == WARN)
+                    pm_message("converting code point %X to space",
+                               (unsigned int) currentChar );
+                output[outCursor++] = ' ';
+            }
         } else
             output[outCursor++] = input[inCursor];
 
         assert(outCursor <= outputSize);
     }
-    output[outCursor++] = '\0';
+    output[outCursor++] = L'\0';
 
     assert(outCursor <= outputSize);
 
@@ -274,478 +396,702 @@ fixControlChars(const char *  const input,
 
 
 static void
-fill_rect(bit** const bits, 
-          int   const row0, 
-          int   const col0, 
-          int   const height, 
-          int   const width, 
-          bit   const color) {
-
-    int row;
-
-    for (row = row0; row < row0 + height; ++row) {
-        int col;
-        for (col = col0; col < col0 + width; ++col)
-            bits[row][col] = color;
+clearBackground(bit ** const bits,
+                int    const cols,
+                int    const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int colChar;
+        for (colChar = 0; colChar < pbm_packed_bytes(cols); ++colChar)
+            bits[row][colChar] = 0x00;
     }
 }
 
 
 
 static void
-get_line_dimensions(const char line[], const struct font * const font_p, 
-                    const float intercharacter_space,
-                    double * const bwidP, int * const backup_space_needed_p) {
+getEdges(double               const currentPosition,
+         PM_WCHAR             const currentChar,
+         const struct glyph * const glyphP,
+         int                  const currLeftEdge,
+         double               const currRightEdge,
+         int                * const newLeftEdgeP,
+         double             * const newRightEdgeP) {
+
+    int leftEdge;
+    double rightEdge;
+
+    if (glyphP == NULL)
+        pm_error("Unrenderable char: %04X", (unsigned int) currentChar);
+    else {
+        leftEdge  =  (int) MIN(currentPosition + glyphP->x, currLeftEdge);
+        rightEdge =  MAX(currentPosition + glyphP->x + glyphP->width,
+                         currRightEdge);
+    }
+    *newLeftEdgeP  = leftEdge;
+    *newRightEdgeP = rightEdge;
+}
+
+
+
+static void
+advancePosition(double               const currentPosition,
+                PM_WCHAR             const currentChar,
+                const struct glyph * const glyphP,
+                float                const space,
+                double               const accumulatedSpace,
+                double             * const newPositionP,
+                double             * const newAccumulatedSpaceP) {
+/*----------------------------------------------------------------------------
+  Advance position according to value for glyph.
+  Add extra intercharacter space if -space option was used.
+
+  The advance value must be zero or positive.
+----------------------------------------------------------------------------*/
+
+    /* Start position of next character */
+    /* Must not move left from current position */
+    int const fullPixels = (int) (accumulatedSpace + space);
+        /* round toward 0 */
+    int const advance    = (int) glyphP->xadd + fullPixels;
+
+    if (advance < 0) {
+        if (space < 0)
+            pm_error("Negative -space value too large");
+        else
+            pm_error("Abnormal horizontal advance value %d "
+                     "for code point 0x%lx.",
+                     glyphP->xadd, (unsigned long int) currentChar);
+    }
+    else if (currentPosition + advance > INT_MAX)
+        pm_error("Image is too wide");
+    else {
+        *newPositionP = currentPosition + advance;
+        *newAccumulatedSpaceP = accumulatedSpace + space
+            - (double) fullPixels;
+    }
+}
+
+
+
+static void
+getLineDimensions(PM_WCHAR             const line[],
+                  const struct font2 * const fontP,
+                  float                const intercharacterSpace,
+                  double *             const rightEdgeP,
+                  int    *             const leftEdgeP) {
 /*----------------------------------------------------------------------------
-   Determine the width in pixels of the line of text line[] in the font
-   *font_p, and return it as *bwidP.  Also determine how much of this
-   width goes to the left of the nominal starting point of the line because
-   the first character in the line has a "backup" distance.  Return that
-   as *backup_space_needed_p.
+   Determine the left edge and right edge in pixels of the line of text
+   line[] in the font *fontP, and return them as *leftEdgeP and *rightEdgeP.
+   *leftEdgeP will be negative if the leftmost character in the line has a
+   "backup" distance.
+
+   Note that the right (left) edge may not belong to the last (first)
+   character in the text line.  This happens when the font is slanted
+   (xadd is smaller than width) and/or intercharacter space is negative.
+   This is illustrated by the following:
+
+     pbmtext -nomargin "ART." | pnmshear -30 -noantialias
+
+   Also note that there may be no black pixels on what is reported as an edge.
+   This often happens with fixed-width font in which the white areas on the
+   sides are not trimmed.
 -----------------------------------------------------------------------------*/
-    int cursor;  /* cursor into the line of text */
-    double accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we are 
-           stepping through.  Because the intercharacter space might not be
-           an integer, we accumulate it here and realize full pixels whenever
-           we have more than one pixel.  Note that this can be negative
-           (which means were crowding, rather than spreading, text).
+    unsigned int cursor;  /* cursor into the line of text */
+    double currentPosition;
+        /* sum of xadd values and intercharacter space so far in line.  this
+           is never negative.
         */
-    double bwid;
-    bool no_chars_yet; 
-        /* We haven't seen any renderable characters yet in the line. */
-    struct glyph * lastGlyphP;
-        /* Glyph of last character processed so far.  Undefined if
-           'no_chars_yet'.
+    double accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we are stepping
+           through.  Because the intercharacter space might not be an integer,
+           we accumulate it here and realize full pixels whenever we have more
+           than one pixel.  Note that this can be negative (which means were
+           crowding, rather than spreading, text).
         */
+    int leftEdge;
+    double rightEdge;
 
-    no_chars_yet = TRUE;   /* initial value */
-    accumulatedIcs = 0.0;  /* initial value */
-    bwid = 0.0;  /* initial value */
-    
-    for (cursor = 0; line[cursor] != '\0'; cursor++) {
-        struct glyph * const glyphP = 
-            font_p->glyph[(unsigned char)line[cursor]];
-
-        if (glyphP) {
-            if (no_chars_yet) {
-                no_chars_yet = FALSE;
-                if (glyphP->x < 0) 
-                    *backup_space_needed_p = -glyphP->x;
-                else {
-                    *backup_space_needed_p = 0;
-                    bwid += glyphP->x;
-                }
-            } else {
-                /* handle extra intercharacter space (-space option) */
-                accumulatedIcs += intercharacter_space;
-                if (accumulatedIcs >= INT_MAX)
-                    pm_error("Image width too large.");
-                if (accumulatedIcs <= INT_MIN)
-                    pm_error("Absurdly large negative -space value.");
-                {
-                    int const fullPixels = (int) accumulatedIcs;
-                    bwid           += fullPixels;
-                    accumulatedIcs -= fullPixels;
-                }
-            }
-            lastGlyphP = glyphP;
-            bwid += glyphP->xadd;
-        }
+    currentPosition = 0;  /* initial value */
+    accumulatedIcs  = 0.0;  /* initial value */
+
+    leftEdge  = INT_MAX;  /* initial value */
+    rightEdge = INT_MIN;  /* initial value */
+
+    for (cursor = 0; line[cursor] != L'\0'; ++cursor) {
+        PM_WCHAR          const currentChar = line[cursor];
+        unsigned long int const glyphIndex  = (unsigned long int) currentChar;
+        struct glyph *    const glyphP      = fontP->glyph[glyphIndex];
+
+        getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
+                 &leftEdge, &rightEdge);
+
+        advancePosition(currentPosition, currentChar, glyphP,
+                        intercharacterSpace, accumulatedIcs,
+                        &currentPosition, &accumulatedIcs);
     }
-    if (no_chars_yet)
-        /* Line has no renderable characters */
-        *backup_space_needed_p = 0;
-    else {
-        /* Line has at least one renderable character.
-           Recalculate width of last character in line so it ends
-           right at the right edge of the glyph (no extra space to
-           anticipate another character).
-        */
-        bwid -= lastGlyphP->xadd;
-        bwid += lastGlyphP->width + lastGlyphP->x;
+
+    if (line[0] == L'\0') {     /* Empty line */
+        leftEdge  = 0;
+        rightEdge = 0.0;
     }
-    if (bwid > INT_MAX)
-        pm_error("Image width too large.");
-    else
-        *bwidP = bwid; 
+
+    *leftEdgeP  = leftEdge;
+    *rightEdgeP = rightEdge;
 }
 
 
 
 static void
-insert_character(const struct glyph * const glyph, 
-                 int                  const toprow, 
-                 int                  const leftcol,
-                 bit **               const bits) {
+getCharsWithinWidth(PM_WCHAR             const line[],
+                    const struct font2 * const fontP,
+                    float                const intercharacter_space,
+                    unsigned int         const targetWidth,
+                    unsigned int       * const charCountP,
+                    int                * const leftEdgeP) {
+/*----------------------------------------------------------------------------
+   Determine how many characters of text line[] fit into an image of target
+   width targetWidth.
+
+   *leftEdgeP will be negative if the leftmost character in the line has a
+   "backup" distance and zero if it does not.
+-----------------------------------------------------------------------------*/
+    if (line[0] == L'\0') {
+        /* Empty line */
+        *leftEdgeP = 0;
+        *charCountP = 0;
+    } else {
+        unsigned int cursor;  /* cursor into the line of text */
+        double currentPosition;
+        double accumulatedIcs;
+        int leftEdge;
+        double rightEdge;
+        unsigned int currentWidth;
+
+        currentPosition = 0;    /* initial value */
+        accumulatedIcs  = 0.0;  /* initial value */
+
+        leftEdge     = INT_MAX;  /* initial value */
+        rightEdge    = INT_MIN;  /* initial value */
+
+        for (cursor = 0, currentWidth = 0;
+             currentWidth <= targetWidth && line[cursor] != L'\0';
+             ++cursor) {
+            PM_WCHAR const currentChar = line[cursor];
+            unsigned long int const glyphIndex =
+              (unsigned long int) currentChar;
+            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+
+            getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge,
+                     &leftEdge, &rightEdge);
+
+            advancePosition(currentPosition, currentChar, glyphP,
+                            intercharacter_space, accumulatedIcs,
+                            &currentPosition, &accumulatedIcs);
+
+            currentWidth = rightEdge - ((leftEdge > 0 ) ? 0 : leftEdge);
+        }
+
+        if (currentWidth > targetWidth) {
+            if (cursor == 1)
+                pm_error("-width value too small "
+                         "to accomodate single character");
+            else
+                *charCountP = cursor - 1;
+        } else
+            *charCountP = cursor;
+
+        *leftEdgeP  = leftEdge;
+    }
+}
+
+
+
+static void
+insertCharacter(const struct glyph * const glyphP,
+                int                  const toprow,
+                int                  const leftcol,
+                unsigned int         const cols,
+                unsigned int         const rows,
+                bit **               const bits) {
 /*----------------------------------------------------------------------------
    Insert one character (whose glyph is 'glyph') into the image bits[].
    Its top left corner shall be row 'toprow', column 'leftcol'.
 -----------------------------------------------------------------------------*/
+    if (glyphP->width == 0 && glyphP->height == 0) {
+        /* No bitmap data.  Some BDF files code space this way */
+    } else {
+        unsigned int glyph_y;  /* Y position within the glyph */
+
+        if (leftcol + glyphP->x < 0 ||
+            leftcol + glyphP->x + glyphP->width > cols ||
+            toprow < 0 ||
+            toprow + glyphP->height >rows )
+            pm_error("internal error.  Rendering out of bounds");
 
-    int glyph_y, glyph_x;  /* position within the glyph */
+        for (glyph_y = 0; glyph_y < glyphP->height; ++glyph_y) {
+            unsigned int glyph_x;  /* position within the glyph */
 
-    for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) {
-        for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) {
-            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
-                bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = 
-                    PBM_BLACK;
+            for (glyph_x = 0; glyph_x < glyphP->width; ++glyph_x) {
+                if (glyphP->bmap[glyph_y * glyphP->width + glyph_x]) {
+                    unsigned int const col = leftcol + glyphP->x + glyph_x;
+                    bits[toprow+glyph_y][col/8] |= PBM_BLACK << (7-col%8);
+                }
+            }
         }
     }
-}    
+}
 
 
 
 static void
-insert_characters(bit **        const bits, 
-                  struct text   const lp,
-                  struct font * const fontP, 
-                  int           const topmargin, 
-                  int           const leftmargin,
-                  float         const intercharacter_space,
-                  int           const lspace) {
+insertCharacters(bit **         const bits,
+                 struct Text    const lp,
+                 struct font2 * const fontP,
+                 int            const topmargin,
+                 int            const leftmargin,
+                 float          const intercharacter_space,
+                 unsigned int   const cols,
+                 unsigned int   const rows,
+                 int            const lspace,
+                 bool           const fixedAdvance) {
 /*----------------------------------------------------------------------------
    Render the text 'lp' into the image 'bits' using font *fontP and
    putting 'intercharacter_space' pixels between characters and
    'lspace' pixels between the lines.
 -----------------------------------------------------------------------------*/
-    int line;  /* Line number in input text */
+    unsigned int line;  /* Line number in input text */
 
     for (line = 0; line < lp.lineCount; ++line) {
-        int row;  /* row in image of top of current typeline */
-        int leftcol;  /* Column in image of left edge of current glyph */
-        int cursor;  /* cursor into a line of input text */
-        float accumulated_ics;
+        unsigned int row;  /* row in image of top of current typeline */
+        double leftcol;  /* Column in image of left edge of current glyph */
+        unsigned int cursor;  /* cursor into a line of input text */
+        double accumulatedIcs;
             /* accumulated intercharacter space so far in the line we
                are building.  Because the intercharacter space might
                not be an integer, we accumulate it here and realize
-               full pixels whenever we have more than one pixel. 
+               full pixels whenever we have more than one pixel.
             */
 
         row = topmargin + line * (fontP->maxheight + lspace);
         leftcol = leftmargin;
-        accumulated_ics = 0.0;  /* initial value */
-    
+        accumulatedIcs = 0.0;  /* initial value */
+
         for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
-            unsigned int const glyphIndex = 
-                (unsigned char)lp.textArray[line][cursor];
-            struct glyph* glyph;   /* the glyph for this character */
-
-            glyph = fontP->glyph[glyphIndex];
-            if (glyph != NULL) {
-                const int toprow = row + fontP->maxheight + fontP->y 
-                    - glyph->height - glyph->y;
-                    /* row number in image of top row in glyph */
-                
-                insert_character(glyph, toprow, leftcol, bits);
-
-                leftcol += glyph->xadd;
-                {
-                    /* handle extra intercharacter space (-space option) */
-                    int full_pixels;  /* integer part of accumulated_ics */
-                    accumulated_ics += intercharacter_space;
-                    full_pixels = (int) accumulated_ics;
-                    if (full_pixels > 0) {
-                        leftcol += full_pixels;
-                        accumulated_ics -= full_pixels;
-                    }
-                }
-            }
+            PM_WCHAR const currentChar = lp.textArray[line][cursor];
+            unsigned long int const glyphIndex =
+                (unsigned long int)currentChar;
+            struct glyph * const glyphP = fontP->glyph[glyphIndex];
+            int const toprow =
+                row + fontP->maxheight + fontP->y - glyphP->height - glyphP->y;
+                /* row number in image of top row in glyph */
+
+            assert(glyphP != NULL);
+
+            insertCharacter(glyphP, toprow, leftcol, cols, rows, bits);
+
+        if (fixedAdvance)
+            leftcol += fontP->maxwidth;
+        else
+            advancePosition(leftcol, currentChar, glyphP,
+                            intercharacter_space, accumulatedIcs,
+                            &leftcol, &accumulatedIcs);
         }
     }
 }
 
 
-struct outputTextCursor {
-    struct text text;
-        /* The output text.  The lineCount field of this represents
-           the number of lines we have completed.  The line after that
-           is the one we are currently filling.
-        */
-    unsigned int maxWidth;
-        /* A line of output can't be wider than this many pixels */
-    float intercharacterSpace;
-        /* The amount of extra space, in characters, that should be added
-           between every two characters (Pbmtext -space option)
-        */
-    unsigned int columnNo;
-        /* The column Number (starting at 0) in the current line that we are
-           filling where the next character goes.
-        */
-    bool noCharsYet;
-        /* We haven't put any renderable characters yet in the
-           output line. 
-        */
-    unsigned int widthSoFar;
-        /* The accumulated width, in pixels, of all the characters now
-           in the current output line 
-        */
-    float accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we
-           are stepping through.  Because the intercharacter space
-           might not be an integer, we accumulate it here and
-           realize full pixels whenever we have more than one
-           pixel.  Note that this is negative if we're crowding, rather
-           than spreading, characters.
-        */
-};
 
+static void
+flowText(struct Text    const inputText,
+         int            const targetWidth,
+         struct font2 * const fontP,
+         float          const intercharacterSpace,
+         struct Text  * const outputTextP,
+         unsigned int * const maxleftbP) {
 
+    unsigned int outputLineNum;
+    unsigned int incursor;   /* cursor into the line we are reading */
+    unsigned int const maxLineCount = 50; /* max output lines */
+    int leftEdge;
+    int leftExtreme = 0;
+    unsigned int charCount;
 
-static void
-initializeFlowedOutputLine(struct outputTextCursor * const cursorP) {
+    allocTextArray(outputTextP, maxLineCount, 0);
 
-    cursorP->columnNo = 0;
-    cursorP->noCharsYet = TRUE;
-    cursorP->widthSoFar = 0.0;
-    cursorP->accumulatedIcs = 0.0;
-}
+    for (incursor = 0, outputLineNum = 0;
+         inputText.textArray[0][incursor] != L'\0'; ) {
 
+        unsigned int outcursor;
 
+        getCharsWithinWidth(&inputText.textArray[0][incursor], fontP,
+                            intercharacterSpace, targetWidth,
+                            &charCount, &leftEdge);
 
-static void
-initializeFlowedOutput(struct outputTextCursor * const cursorP,
-                       unsigned int              const maxLines,
-                       unsigned int              const maxWidth,
-                       float                     const intercharacterSpace) {
-    
-    allocTextArray(&cursorP->text, maxLines, maxWidth);
-    cursorP->maxWidth = maxWidth;
-    cursorP->intercharacterSpace = intercharacterSpace;
-    initializeFlowedOutputLine(cursorP);
-}
+        MALLOCARRAY(outputTextP->textArray[outputLineNum], charCount+1);
 
+        if (!outputTextP->textArray[outputLineNum])
+            pm_error("Unable to allocate memory for the text of line %u, "
+                     "%u characters long", outputLineNum, charCount);
 
+        ++outputTextP->allocatedLineCount;
 
-static void
-finishOutputLine(struct outputTextCursor * const cursorP) {
+        for (outcursor = 0; outcursor < charCount; ++outcursor, ++incursor)
+            outputTextP->textArray[outputLineNum][outcursor] =
+                inputText.textArray[0][incursor];
+
+        outputTextP->textArray[outputLineNum][charCount] = L'\0';
+        ++outputLineNum;
+        if (outputLineNum >= maxLineCount)
+            pm_error("-width too small.  too many output lines");
 
-    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
-        char * const currentLine = 
-            cursorP->text.textArray[cursorP->text.lineCount];
-        currentLine[cursorP->columnNo++] = '\0';
-        ++cursorP->text.lineCount;
+        leftExtreme = MIN(leftEdge, leftExtreme);
     }
+    outputTextP->lineCount = outputLineNum;
+    *maxleftbP = (unsigned int) -leftExtreme;
 }
 
 
 
 static void
-placeCharacterInOutput(char                      const lastch,
-                       struct font *             const fontP, 
-                       struct outputTextCursor * const cursorP) {
-/*----------------------------------------------------------------------------
-   Place a character of text in the text array at the position indicated
-   by *cursorP, keeping track of what space this character will occupy
-   when this text array is ultimately rendered using font *fontP.
+truncateText(struct Text    const inputText,
+             unsigned int   const targetWidth,
+             struct font2 * const fontP,
+             float          const intercharacterSpace,
+             unsigned int * const maxleftbP) {
 
-   Note that while we compute how much space the character will take when
-   rendered, we don't render it.
------------------------------------------------------------------------------*/
-    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
-        unsigned int const glyphIndex = (unsigned char)lastch;
-        if (fontP->glyph[glyphIndex]) {
-            if (cursorP->noCharsYet) {
-                cursorP->noCharsYet = FALSE;
-                if (fontP->glyph[glyphIndex]->x > 0) 
-                    cursorP->widthSoFar += fontP->glyph[glyphIndex]->x;
-            } else {
-                /* handle extra intercharacter space (-space option) */
-                cursorP->accumulatedIcs += cursorP->intercharacterSpace;
-                {
-                    int const fullPixels = (int)cursorP->accumulatedIcs;
-                    cursorP->widthSoFar     += fullPixels;
-                    cursorP->accumulatedIcs -= fullPixels;
-                }
-            }
-            cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd;
-        }
-        if (cursorP->widthSoFar < cursorP->maxWidth) {
-            char * const currentLine = 
-                cursorP->text.textArray[cursorP->text.lineCount];
-            currentLine[cursorP->columnNo++] = lastch;
-        } else {
-            /* Line is full; finish it off, start the next one, and
-               place the character there.
-            */
-            /* TODO: We really should back up to the previous white space
-               character and move the rest of the line to the next line
-            */
-            finishOutputLine(cursorP);
-            initializeFlowedOutputLine(cursorP);
-            placeCharacterInOutput(lastch, fontP, cursorP);
+    unsigned int lineNum;  /* Line number on which we are currently working */
+    int leftEdge;
+    int leftExtreme = 0;
+
+    for (lineNum = 0; lineNum < inputText.lineCount; ++lineNum) {
+        PM_WCHAR * const currentLine = inputText.textArray[lineNum];
+
+        unsigned int charCount;
+
+        getCharsWithinWidth(currentLine, fontP,
+                            intercharacterSpace, targetWidth,
+                            &charCount, &leftEdge);
+
+        if (currentLine[charCount] != L'\0') {
+            pm_message("truncating line %u from %u to %u characters",
+                       lineNum, (unsigned) wcslen(currentLine), charCount);
+            currentLine[charCount] = L'\0';
         }
+
+        leftExtreme = MIN(leftEdge, leftExtreme);
     }
+    *maxleftbP = (unsigned int) - leftExtreme;
 }
 
 
 
 static void
-flowText(struct text    const inputText,
-         int            const width, 
-         struct font *  const fontP, 
-         float          const intercharacterSpace,
-         struct text *  const outputTextP) {
-    
-    unsigned int const maxLineCount = 50;
-
-    unsigned int inputLine;  
-        /* Input line number on which we are currently working */
-    struct outputTextCursor outputCursor;
-
-    for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) {
-        unsigned int incursor;   /* cursor into the line we are reading */
-
-        initializeFlowedOutput(&outputCursor, maxLineCount,
-                               width, intercharacterSpace);
-        
-        for (incursor = 0; 
-             inputText.textArray[inputLine][incursor] != '\0'; 
-             ++incursor)
-            placeCharacterInOutput(inputText.textArray[inputLine][incursor],
-                                   fontP, &outputCursor);
-        finishOutputLine(&outputCursor);
+fgetWideString(PM_WCHAR *    const widestring,
+               unsigned int  const size,
+               FILE *        const ifP,
+               bool *        const eofP,
+               const char ** const errorP) {
+
+    wchar_t * rc;
+
+    assert(widestring);
+    assert(size > 0);
+
+    rc = fgetws(widestring, size, ifP);
+
+    if (rc == NULL) {
+        if (feof(ifP)) {
+            *eofP   = true;
+            *errorP = NULL;
+        } else if (ferror(ifP) && errno == EILSEQ)
+            pm_asprintf(errorP,
+                        "fgetws(): conversion error: sequence is "
+                        "invalid for locale '%s'",
+                        setlocale(LC_CTYPE, NULL));
+        else
+            pm_asprintf(errorP,
+                        "fgetws() of max %u bytes failed",
+                        size);
+    } else {
+        *eofP   = false;
+        *errorP = NULL;
     }
-    *outputTextP = outputCursor.text;
 }
 
 
 
 static void
-truncateText(struct text   const inputText, 
-             unsigned int  const width, 
-             struct font * const fontP, 
-             float         const intercharacterSpace,
-             struct text * const outputTextP) {
-
-    struct text truncatedText;
-    int line;  /* Line number on which we are currently working */
-
-    allocTextArray(&truncatedText, inputText.lineCount, width);
-
-    for (line = 0; line < inputText.lineCount; ++line){
-        int cursor;  /* cursor into the line of text */
-        unsigned char lastch;  /* line[cursor] */
-        int widthSoFar;
-            /* How long the line we've built, in pixels, is so far */
-        float accumulatedIcs;
-        /* accumulated intercharacter space so far in the line we are 
-           stepping through.  Because the intercharacter space might not be
-           an integer, we accumulate it here and realize full pixels whenever
-           we have more than one pixel.  Note that this is negative if we're
-           crowding, not spreading, characters.
-        */
+fgetNarrowString(PM_WCHAR *    const widestring,
+                 unsigned int  const size,
+                 FILE *        const ifP,
+                 bool *        const eofP,
+                 const char ** const errorP) {
 
-        int noCharsYet; 
-        /* logical: we haven't seen any renderable characters yet in 
-           the line.
-        */
-        noCharsYet = TRUE;   /* initial value */
-        widthSoFar = 0;  /* initial value */
-        accumulatedIcs = 0.0;  /* initial value */
+    char * bufNarrow;
+    char * rc;
 
-        truncatedText.textArray[line][0] = '\0';  /* Start with empty line */
-
-        for (cursor = 0; 
-             inputText.textArray[line][cursor] != '\0' && widthSoFar < width; 
-             cursor++) {
-            lastch = inputText.textArray[line][cursor];
-            if (fontP->glyph[(unsigned char)lastch]) {
-                if (noCharsYet) {
-                    noCharsYet = FALSE;
-                    if (fontP->glyph[lastch]->x > 0) 
-                        widthSoFar += fontP->glyph[lastch]->x;
-                } else {
-                    /* handle extra intercharacter space (-space option) */
-                    accumulatedIcs += intercharacterSpace;
-                    {
-                        int const fullPixels = (int) intercharacterSpace;
-                        widthSoFar     += fullPixels;
-                        accumulatedIcs -= fullPixels;
-                    }
-                }
-                widthSoFar += fontP->glyph[lastch]->xadd;
-            }
-            if (widthSoFar < width) {
-                truncatedText.textArray[line][cursor] = 
-                    inputText.textArray[line][cursor];
-                truncatedText.textArray[line][cursor+1] = '\0';
-            }
-        }
+    assert(widestring);
+    assert(size > 0);
+
+    MALLOCARRAY_NOFAIL(bufNarrow, MAXLINECHARS+1);
+
+    rc = fgets(bufNarrow, size, ifP);
+
+    if (rc == NULL) {
+        if (feof(ifP)) {
+            *eofP   = true;
+            *errorP = NULL;
+        } else
+            pm_asprintf(errorP, "Error reading file");
+    } else {
+        size_t cnt;
+
+        for (cnt = 0; cnt < size && bufNarrow[cnt] != '\0'; ++cnt)
+            widestring[cnt] = (PM_WCHAR)(unsigned char) bufNarrow[cnt];
+
+        widestring[cnt] = L'\0';
+
+        *eofP   = false;
+        *errorP = NULL;
     }
-    truncatedText.lineCount = inputText.lineCount;
-    *outputTextP = truncatedText;
+    free(bufNarrow);
 }
 
 
 
 static void
-getText(const char          cmdline_text[], 
-        struct font * const fontP,
-        struct text * const input_textP) {
-
-    struct text input_text;
-
-    if (cmdline_text) {
-        MALLOCARRAY_NOFAIL(input_text.textArray, 1);
-        input_text.allocatedLineCount = 1;
-        input_text.lineCount = 1;
-        fixControlChars(cmdline_text, fontP,
-                        (const char**)&input_text.textArray[0]);
+fgetNarrowWideString(PM_WCHAR *    const widestring,
+                     unsigned int  const size,
+                     FILE *        const ifP,
+                     bool *        const eofP,
+                     const char ** const errorP) {
+/*----------------------------------------------------------------------------
+  Return the next line from file *ifP, as *widestring.
+
+  Lines are delimited by newline characters and EOF.
+
+  'size' is the size in characters of the buffer at *widestring.  If the line
+  to which the file is positioned is longer than that minus 1, we consider it
+  to be only that long and consider the next character of the actual line to
+  be the first character of the next line.  We leave the file positioned
+  to that character.
+
+  Return *eofP == true iff we encounter end of file (and therefore don't read
+  a line).
+
+  If we can't read the file (or sense EOF), return as *errorP a text
+  explanation of why; otherwise, return *errorP = NULL.
+
+  The line we return is null-terminated.  But it also includes any embedded
+  null characters that are within the line in the file.  It is not strictly
+  possible for Caller to tell whether a null character in *widestring comes
+  from the file or is the one we put there, so Caller should just ignore any
+  null character and anything after it.  It is also not possible for Caller to
+  tell if we trunctaed the actual line because of 'size' if there is a null
+  character in the line.  This means there just isn't any way to get
+  reasonable behavior from this function if the input file contains null
+  characters (but at least the damage is limited to presenting arbitrary text
+  as the contents of the file - the program won't crash).
+
+  Null characters never appear within normal text (including wide-character
+  text).  If there is one in the input file, it is probably because the input
+  is corrupted.
+
+  The line we return may or may not end in a newline character.  It ends in a
+  newline character unless it doesn't fit in 'size' characters or it is the
+  last line in the file and doesn't end in newline.
+-----------------------------------------------------------------------------*/
+    /* The limitations described above with respect to null characters in
+       *ifP are derived from the same limitations in POSIX 'fgets' and
+       'fgetws'.  To avoid them, we would have to read *ifP one character
+       at a time with 'fgetc' and 'fgetwc'.
+    */
+
+    int const wideCode = fwide(ifP, 0);
+        /* Width orientation for *ifP: positive means wide, negative means
+           byte, zero means undecided.
+        */
+
+    assert(widestring);
+    assert(size > 0);
+
+    if (wideCode > 0)
+        /* *ifP is wide-oriented */
+        fgetWideString(widestring, size, ifP, eofP, errorP);
+    else
+        fgetNarrowString(widestring, size, ifP, eofP, errorP);
+}
+
+
+
+
+static void
+getText(PM_WCHAR       const cmdlineText[],
+        struct font2 * const fontP,
+        struct Text  * const inputTextP,
+        enum FixMode   const fixMode) {
+/*----------------------------------------------------------------------------
+   Get as *inputTextP the text to format, given that the text on the
+   command line (one word per command line argument, separated by spaces),
+   is 'cmdlineText'.
+
+   If 'cmdlineText' is null, that means to get the text from Standard Input.
+   Otherwise, 'cmdlineText' is that text.
+
+   But we return text as only renderable characters - characters in *fontP -
+   with control characters interpreted or otherwise fixed, according to
+   'fixMode'.
+
+   If *inputTextP indicates Standard Input and Standard Input contains null
+   characters, we will truncate lines or consider a single line to be multiple
+   lines.
+-----------------------------------------------------------------------------*/
+    struct Text inputText;
+
+    if (cmdlineText) {
+        MALLOCARRAY_NOFAIL(inputText.textArray, 1);
+        inputText.allocatedLineCount = 1;
+        inputText.lineCount = 1;
+        fixControlChars(cmdlineText, fontP,
+                        (const PM_WCHAR**)&inputText.textArray[0], fixMode);
+        free((void *) cmdlineText);
     } else {
         /* Read text from stdin. */
 
-        unsigned int maxlines;  
-            /* Maximum number of lines for which we presently have space
-               in the text array 
+        unsigned int maxlines;
+            /* Maximum number of lines for which we presently have space in
+               the text array
             */
-        char buf[5000];
-        char ** text_array;
+        PM_WCHAR *   buf;
+        PM_WCHAR **  textArray;
         unsigned int lineCount;
+        bool         eof;
+
+        MALLOCARRAY(buf, MAXLINECHARS+1);
+
+        if (!buf)
+            pm_error("Unable to allocate memory for up to %u characters of "
+                     "text", MAXLINECHARS);
 
         maxlines = 50;  /* initial value */
-        MALLOCARRAY_NOFAIL(text_array, maxlines);
-        
-        lineCount = 0;  /* initial value */
-        while (fgets(buf, sizeof(buf), stdin) != NULL) {
-            if (strlen(buf) + 1 >= sizeof(buf))
-                pm_error("A line of input text is longer than %u characters."
-                         "Cannot process.", (unsigned)sizeof(buf)-1);
-            if (lineCount >= maxlines) {
-                maxlines *= 2;
-                REALLOCARRAY(text_array, maxlines);
-                if (text_array == NULL)
-                    pm_error("out of memory");
+        MALLOCARRAY(textArray, maxlines);
+
+        if (!textArray)
+            pm_error("Unable to allocate memory for a buffer for up to %u "
+                     "lines of text", maxlines);
+
+        for (lineCount = 0, eof = false; !eof; ) {
+            const char * error;
+            fgetNarrowWideString(buf, MAXLINECHARS, stdin, &eof, &error);
+            if (error)
+                pm_error("Unable to read line %u from file.  %s",
+                         lineCount, error);
+            else {
+                if (!eof) {
+                    if (wcslen(buf) + 1 >= MAXLINECHARS)
+                        pm_error(
+                            "Line %u (starting at zero) of input text "
+                            "is longer than %u characters."
+                            "Cannot process",
+                            lineCount, (unsigned int) MAXLINECHARS-1);
+                    if (lineCount >= maxlines) {
+                        maxlines *= 2;
+                        REALLOCARRAY(textArray, maxlines);
+                        if (textArray == NULL)
+                            pm_error("out of memory");
+                    }
+                    fixControlChars(buf, fontP,
+                                    (const PM_WCHAR **)&textArray[lineCount],
+                                    fixMode);
+                    if (textArray[lineCount] == NULL)
+                        pm_error("out of memory");
+                    ++lineCount;
+                }
             }
-            fixControlChars(buf, fontP, (const char **)&text_array[lineCount]);
-            if (text_array[lineCount] == NULL)
-                pm_error("out of memory");
-            ++lineCount;
         }
-        input_text.textArray = text_array;
-        input_text.lineCount = lineCount;
-        input_text.allocatedLineCount = lineCount;
+        inputText.textArray = textArray;
+        inputText.lineCount = lineCount;
+        inputText.allocatedLineCount = lineCount;
+        free(buf);
+    }
+    *inputTextP = inputText;
+}
+
+
+
+static void
+computeMargins(struct CmdlineInfo const cmdline,
+               struct Text        const inputText,
+               struct font2 *     const fontP,
+               unsigned int *     const vmarginP,
+               unsigned int *     const hmarginP) {
+
+    if (cmdline.nomargins) {
+        *vmarginP = 0;
+        *hmarginP = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            *vmarginP = fontP->maxheight / 2;
+            *hmarginP = fontP->maxwidth;
+        } else {
+            *vmarginP = fontP->maxheight;
+            *hmarginP = 2 * fontP->maxwidth;
+        }
     }
-    *input_textP = input_text;
 }
 
 
 
 static void
-computeImageHeight(struct text         const formattedText, 
-                   const struct font * const fontP,
-                   int                 const interlineSpace,
-                   unsigned int        const vmargin,
-                   unsigned int *      const rowsP) {
+formatText(struct CmdlineInfo const cmdline,
+           struct Text        const inputText,
+           struct font2 *     const fontP,
+           unsigned int       const hmargin,
+           struct Text *      const formattedTextP,
+           unsigned int *     const maxleftb0P) {
+/*----------------------------------------------------------------------------
+  Flow or truncate lines to meet user's width request.
+-----------------------------------------------------------------------------*/
+    if (cmdline.width > 0) {
+        unsigned int const fontMargin = fontP->x < 0 ? -fontP->x : 0;
+
+        if (cmdline.width > INT_MAX -10)
+            pm_error("-width value too large: %u", cmdline.width);
+        else if (cmdline.width < 2 * hmargin)
+            pm_error("-width value too small: %u", cmdline.width);
+        else if (inputText.lineCount == 1) {
+            flowText(inputText, cmdline.width - fontMargin,
+                     fontP, cmdline.space, formattedTextP, maxleftb0P);
+            freeTextArray(inputText);
+        } else {
+            truncateText(inputText, cmdline.width - fontMargin,
+                         fontP, cmdline.space, maxleftb0P);
+            *formattedTextP = inputText;
+        }
+    } else
+        *formattedTextP = inputText;
+}
+
+
+
+static void
+computeImageHeight(struct Text          const formattedText,
+                   const struct font2 * const fontP,
+                   int                  const interlineSpace,
+                   unsigned int         const vmargin,
+                   unsigned int       * const rowsP) {
 
     if (interlineSpace < 0 && fontP->maxheight < -interlineSpace)
         pm_error("-lspace value (%d) negative and exceeds font height.",
-                 interlineSpace);     
+                 interlineSpace);
     else {
-        double const rowsD = 2 * (double) vmargin + 
-            (double) formattedText.lineCount * fontP->maxheight + 
+        double const rowsD = 2 * (double) vmargin +
+            (double) formattedText.lineCount * fontP->maxheight +
             (double) (formattedText.lineCount-1) * interlineSpace;
-        
+
         if (rowsD > INT_MAX-10)
             pm_error("Image height too large.");
         else
@@ -756,128 +1102,286 @@ computeImageHeight(struct text         const formattedText,
 
 
 static void
-computeImageWidth(struct text         const formattedText, 
-                  const struct font * const fontP,
-                  float               const intercharacterSpace,
-                  unsigned int        const hmargin,
-                  unsigned int *      const colsP,
-                  int *               const maxleftbP) {
+computeImageWidth(struct Text          const formattedText,
+                  const struct font2 * const fontP,
+                  float                const intercharacterSpace,
+                  unsigned int         const hmargin,
+                  unsigned int *       const colsP,
+                  unsigned int *       const maxleftbP) {
 
     if (intercharacterSpace < 0 && fontP->maxwidth < -intercharacterSpace)
-        pm_error("-space value (%f) negative; exceeds font width.",
-                 intercharacterSpace);     
+        pm_error("negative -space value %.2f exceeds font width",
+                 intercharacterSpace);
     else {
         /* Find the widest line, and the one that backs up the most past
            the nominal start of the line.
         */
-    
-        unsigned int line;
-        double maxwidth;
-        int maxleftb;
+
+        unsigned int lineNum;
+        double rightExtreme;
+        int leftExtreme;
         double colsD;
 
-        for (line = 0, maxwidth = 0.0, maxleftb = 0;
-             line < formattedText.lineCount;
-             ++line) {
-
-            double bwid;
-            int backupSpaceNeeded;
-            
-            get_line_dimensions(formattedText.textArray[line], fontP,
-                                intercharacterSpace,
-                                &bwid, &backupSpaceNeeded);
-            
-            maxwidth = MAX(maxwidth, bwid);
-            maxleftb = MAX(maxleftb, backupSpaceNeeded);
+        rightExtreme = 0.0;  /* initial value */
+        leftExtreme = 0;     /* initial value */
+
+        for (lineNum = 0; lineNum < formattedText.lineCount;  ++lineNum) {
+            double rightEdge;
+            int leftEdge;
+
+            getLineDimensions(formattedText.textArray[lineNum], fontP,
+                              intercharacterSpace,
+                              &rightEdge, &leftEdge);
+            rightExtreme = MAX(rightExtreme, rightEdge);
+            leftExtreme  = MIN(leftExtreme,  leftEdge);
         }
-        colsD = 2 * (double) hmargin + (double) maxwidth;
-    
+        leftExtreme = MIN(leftExtreme, 0);
+
+        colsD = (double) (-leftExtreme) + rightExtreme + 2 * hmargin;
+
         if (colsD > INT_MAX-10)
             pm_error("Image width too large.");
         else
             *colsP = (unsigned int) colsD;
-    
-        *maxleftbP = maxleftb;
+
+        *maxleftbP = (unsigned int) - leftExtreme;
     }
 }
 
 
 
-int
-main(int argc, const char *argv[]) {
+static void
+renderText(unsigned int   const cols,
+           unsigned int   const rows,
+           struct font2 * const fontP,
+           unsigned int   const hmargin,
+           unsigned int   const vmargin,
+           struct Text    const formattedText,
+           unsigned int   const maxleftb,
+           float          const space,
+           int            const lspace,
+           bool           const fixedAdvance,
+           FILE *         const ofP) {
+
+    bit ** const bits = pbm_allocarray(pbm_packed_bytes(cols), rows);
 
-    struct cmdlineInfo cmdline;
-    bit ** bits;
-    unsigned int rows, cols;
-    struct font * fontP;
-    unsigned int vmargin, hmargin;
-    struct text inputText;
-    struct text formattedText;
-    int maxleftb;
+    /* Fill background with white */
+    clearBackground(bits, cols, rows);
 
-    pm_proginit(&argc, argv);
+    /* Put the text in  */
+    insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb,
+                     space, cols, rows, lspace, fixedAdvance);
 
-    parseCommandLine(argc, argv, &cmdline);
-    
-    computeFont(cmdline, &fontP);
+    /* Free all font data */
+    pbm_destroybdffont2(fontP); 
 
-    getText(cmdline.text, fontP, &inputText);
-       
-    if (cmdline.nomargins) {
-        vmargin = 0;
-        hmargin = 0;
+    {
+        unsigned int row;
+
+        pbm_writepbminit(ofP, cols, rows, 0);
+
+        for (row = 0; row < rows; ++row)
+            pbm_writepbmrow_packed(ofP, bits[row], cols, 0);
+    }
+
+    pbm_freearray(bits, rows);
+}
+
+
+
+static PM_WCHAR const * sheetTextArray[] = {
+L"M \",/^_[`jpqy| M",
+L"                ",
+L"/  !\"#$%&'()*+ /",
+L"< ,-./01234567 <",
+L"> 89:;<=>?@ABC >",
+L"@ DEFGHIJKLMNO @",
+L"_ PQRSTUVWXYZ[ _",
+L"{ \\]^_`abcdefg {",
+L"} hijklmnopqrs }",
+L"~ tuvwxyz{|}~  ~",
+L"                ",
+L"M \",/^_[`jpqy| M" };
+
+
+
+static void
+validateText(const PM_WCHAR ** const textArray,
+             struct font2    * const fontP) {
+/*----------------------------------------------------------------------------
+   Abort the program if there are characters in 'textArray' which cannot be
+   rendered in font *fontP.
+-----------------------------------------------------------------------------*/
+    const PM_WCHAR * output;
+    unsigned int textRow;
+
+    for (textRow = 0; textRow < 12; ++textRow)
+        fixControlChars(textArray[textRow], fontP, &output, QUIT);
+
+    free((PM_WCHAR *)output);
+}
+
+
+
+static void
+renderSheet(struct font2 * const fontP,
+            FILE *         const ofP) {
+
+    int const cols  = fontP->maxwidth  * 16;
+    int const rows  = fontP->maxheight * 12;
+    struct Text const sheetText =
+        { (PM_WCHAR ** const) sheetTextArray, 12, 12};
+
+    validateText(sheetTextArray, fontP);
+
+    renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0),
+               0.0, 0, TRUE, ofP);
+}
+
+
+
+static void
+dryrunOutput(unsigned int const cols,
+             unsigned int const rows,
+             FILE *       const ofP) {
+
+    fprintf(ofP, "%u %u\n", cols, rows);
+}
+
+
+
+static void
+textDumpOutput(struct Text   const lp,
+               FILE *        const ofP) {
+/*----------------------------------------------------------------------------
+   Output the text 'lp' as characters.  (Do not render.)
+
+   Note that the output stream is wide-oriented; it cannot be mixed with
+   narrow-oriented output.  The libnetpbm library functions are
+   narrow-oriented.  Thus, when this output is specified, it must not be mixed
+   with any output from the library; it should be the sole output.
+-----------------------------------------------------------------------------*/
+    int rc;
+
+    rc = fwide(ofP, 1);
+    if (rc != 1) {
+        /* This occurs when narrow-oriented output to ofP happens before we
+           get here.
+        */
+        pm_error("Failed to set output stream to wide "
+                 "(fwide() returned %d.  Maybe the output file "
+                 "was written in narrow mode before this program was invoked?",
+                 rc);
     } else {
-        if (inputText.lineCount == 1) {
-            vmargin = fontP->maxheight / 2;
-            hmargin = fontP->maxwidth;
-        } else {
-            vmargin = fontP->maxheight;
-            hmargin = 2 * fontP->maxwidth;
+        unsigned int line;  /* Line number in input text */
+
+        for (line = 0; line < lp.lineCount; ++line) {
+            fputws(lp.textArray[line], ofP);
+            fputwc(L'\n', ofP);
         }
     }
-    
-    if (cmdline.width > 0) {
-        if (cmdline.width > INT_MAX -10)
-            pm_error("-width value too large: %u", cmdline.width);
-            
-        /* Flow or truncate lines to meet user's width request */
-        if (inputText.lineCount == 1) 
-            flowText(inputText, cmdline.width, fontP, cmdline.space,
-                     &formattedText);
-        else
-            truncateText(inputText, cmdline.width, fontP, cmdline.space,
-                         &formattedText);
-        freeTextArray(inputText);
-    } else
-        formattedText = inputText;
-        
+}
+
+
+
+static void
+pbmtext(struct CmdlineInfo const cmdline,
+        struct font2 *     const fontP,
+        FILE *             const ofP) {
+
+    unsigned int rows, cols;
+        /* Dimensions in pixels of the output image */
+    unsigned int cols0;
+    unsigned int vmargin, hmargin;
+        /* Margins in pixels we add to the output image */
+    unsigned int hmargin0;
+    struct Text inputText;
+    struct Text formattedText;
+    unsigned int maxleftb, maxleftb0;
+
+    getText(cmdline.text, fontP, &inputText,
+            cmdline.verbose ? WARN : SILENT);
+
+    computeMargins(cmdline, inputText, fontP, &vmargin, &hmargin0);
+
+    formatText(cmdline, inputText, fontP, hmargin0,
+               &formattedText, &maxleftb0);
+
     if (formattedText.lineCount == 0)
-        pm_error("No input text.");
-    
-    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin,
-                       &rows);
+        pm_error("No input text");
+
+    computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, &rows);
 
-    computeImageWidth(formattedText, fontP, cmdline.space, hmargin,
-                      &cols, &maxleftb);
+    computeImageWidth(formattedText, fontP, cmdline.space,
+                      cmdline.width > 0 ? 0 : hmargin0, &cols0, &maxleftb);
 
-    if (cols == 0 || rows == 0)
+    if (cols0 == 0 || rows == 0)
         pm_error("Input is all whitespace and/or non-renderable characters.");
 
-    bits = pbm_allocarray(cols, rows);
+    if (cmdline.width == 0) {
+        cols    = cols0;
+        hmargin = hmargin0;
+    } else {
+        if (cmdline.width < cols0)
+            pm_error("internal error: calculated image width (%u) exceeds "
+                     "specified -width value: %u",
+                     cols0, cmdline.width);
+        else if (maxleftb0 != maxleftb)
+            pm_error("internal error: contradicting backup values");
+        else {
+            hmargin = MIN(hmargin0, (cmdline.width - cols0) / 2);
+            cols = cmdline.width;
+        }
+    }
 
-    /* Fill background with white */
-    fill_rect(bits, 0, 0, rows, cols, PBM_WHITE);
+    if (cmdline.dryrun)
+        dryrunOutput(cols, rows, ofP);
+    else if (cmdline.textdump)
+        textDumpOutput(formattedText, ofP);
+    else
+        renderText(cols, rows, fontP, hmargin, vmargin, formattedText,
+                   maxleftb, cmdline.space, cmdline.lspace, FALSE, ofP);
 
-    /* Put the text in  */
-    insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
-                      cmdline.space, cmdline.lspace);
+    freeTextArray(formattedText);
+}
 
-    pbm_writepbm(stdout, bits, cols, rows, 0);
 
-    pbm_freearray(bits, rows);
 
-    freeTextArray(formattedText);
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+    struct font2 * fontP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.wchar) {
+        char * newLocale;
+        newLocale = setlocale(LC_ALL, "");
+        if (!newLocale)
+            pm_error("Failed to set locale (LC_ALL) from environment");
+
+        /* Orient standard input stream to wide */
+        fwide(stdin,  1);
+    } else
+        fwide(stdin, -1);
+
+    if (cmdline.verbose)
+        pm_message("LC_CTYPE is set to '%s'", setlocale(LC_CTYPE, NULL) );
+
+    computeFont(cmdline, &fontP);
+
+    if (cmdline.dumpsheet)
+        renderSheet(fontP, stdout);
+    else
+        pbmtext(cmdline, fontP, stdout);
+
     pm_close(stdout);
 
     return 0;
 }
+
+
+