about summary refs log tree commit diff
path: root/lib/libpbmfont2.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2018-07-04 15:15:43 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2018-07-04 15:15:43 +0000
commite4e6614dfa133420adb1763a962fff8ea04ab941 (patch)
tree4525ff694a98283e2ea2532e4ea3dda0a0da5f2f /lib/libpbmfont2.c
parent333ca756cb899f193aa450a1033fde8cbe318081 (diff)
downloadnetpbm-mirror-e4e6614dfa133420adb1763a962fff8ea04ab941.tar.gz
netpbm-mirror-e4e6614dfa133420adb1763a962fff8ea04ab941.tar.xz
netpbm-mirror-e4e6614dfa133420adb1763a962fff8ea04ab941.zip
Rewrite font processing for proper memory management; make built-in fonts work with wide characters; fix wild pointer dereference with invalid BDF input
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@3291 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'lib/libpbmfont2.c')
-rw-r--r--lib/libpbmfont2.c995
1 files changed, 995 insertions, 0 deletions
diff --git a/lib/libpbmfont2.c b/lib/libpbmfont2.c
new file mode 100644
index 00000000..993015a2
--- /dev/null
+++ b/lib/libpbmfont2.c
@@ -0,0 +1,995 @@
+/*
+**
+** Font routines.
+**
+** BDF font code by George Phillips, copyright 1993
+**
+** Wide character stuff written by Akira Urushibata, copyright 2018
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** BDF font specs available from:
+** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
+** Glyph Bitmap Distribution Format (BDF) Specification
+** Version 2.2
+** 22 March 1993
+** Adobe Developer Support
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbmfont.h"
+#include "pbm.h"
+
+/*----------------------------------------------------------------------------
+  Routines for loading a BDF font file
+-----------------------------------------------------------------------------*/
+
+/* The following are not recognized in individual glyph data; library
+   routines do a pm_error if they see one:
+
+   Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET,
+   CONTENTVERSION.
+
+   The following is not recognized and is thus ignored at the global level:
+   DWIDTH
+*/
+
+
+#define MAXBDFLINE 1024
+
+/* Official Adobe document says max length of string is 65535 characters.
+   However the value 1024 is sufficient for practical uses.
+*/
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is an object for reading lines of a font file.  It reads and tokenizes
+   them into words.
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
+
+    char line[MAXBDFLINE+1];
+        /* This is the storage space for the words of the line.  The
+           words go in here, one after another, separated by NULs.
+
+           It also functions as a work area for readline_read().
+        */
+    const char * arg[7];
+        /* These are the words; each entry is a pointer into line[] (above) */
+
+    unsigned int wordCt;
+} Readline;
+
+
+
+static void
+readline_init(Readline * const readlineP,
+              FILE *     const ifP) {
+
+    readlineP->ifP = ifP;
+
+    readlineP->arg[0] = NULL;
+    readlineP->wordCt = 0;
+}
+
+
+
+static void
+tokenize(char *         const s,
+         const char **  const words,
+         unsigned int   const wordsSz,
+         unsigned int * const wordCtP) {
+/*----------------------------------------------------------------------------
+   Chop up 's' into words by changing space characters to NUL.  Return as
+   'words' an array of pointers to the beginnings of those words in 's'.
+   Terminate the words[] list with a null pointer.
+
+   'wordsSz' is the number of elements of space in 'words'.  If there are more
+   words in 's' than will fit in that space (including the terminating null
+   pointer), ignore the excess on the right.
+
+   '*wordCtP' is the number elements actually found.
+-----------------------------------------------------------------------------*/
+    unsigned int n;  /* Number of words in words[] so far */
+    char * p;
+
+    p = &s[0];
+    n = 0;
+
+    while (*p) {
+        if (!ISGRAPH(*p)) {
+            if(!ISSPACE(*p)) {
+              /* Control chars excluding 09 - 0d (space), 80-ff */
+            pm_message("Warning: non-ASCII character '%x' in "
+                       "BDF font file", *p);
+            }
+            *p++ = '\0';
+        }
+        else {
+            words[n++] = p;
+            if (n >= wordsSz - 1)
+                break;
+            while (*p && ISGRAPH(*p))
+                ++p;
+        }
+    }
+    assert(n <= wordsSz - 1);
+    words[n] = NULL;
+    *wordCtP = n;
+}
+
+
+
+static void
+readline_read(Readline * const readlineP,
+              bool *     const eofP) {
+/*----------------------------------------------------------------------------
+   Read a nonblank line from the file.  Make its contents available
+   as readlineP->arg[].
+
+   Return *eofP == true iff there is no nonblank line before EOF or we
+   are unable to read the file.
+-----------------------------------------------------------------------------*/
+    bool gotLine;
+    bool error;
+
+    for (gotLine = false, error = false; !gotLine && !error; ) {
+        char * rc;
+
+        rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP);
+        if (rc == NULL)
+            error = true;
+        else {
+            tokenize(readlineP->line, readlineP->arg,
+                     ARRAY_SIZE(readlineP->arg), &readlineP->wordCt);
+            if (readlineP->arg[0] != NULL)
+                gotLine = true;
+        }
+    }
+    *eofP = error;
+}
+
+
+
+static void
+parseBitmapRow(const char *    const hex,
+               unsigned int    const glyphWidth,
+               unsigned char * const bmap,
+               unsigned int    const origBmapIndex,
+               unsigned int *  const newBmapIndexP,
+               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Parse one row of the bitmap for a glyph, from the hexadecimal string
+   for that row in the font file, 'hex'.  The glyph is 'glyphWidth'
+   pixels wide.
+
+   We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP.
+-----------------------------------------------------------------------------*/
+    unsigned int bmapIndex;
+    int i;  /* dot counter */
+    const char * p;
+
+    bmapIndex = origBmapIndex;
+
+    for (i = glyphWidth, p = &hex[0], *errorP = NULL;
+         i > 0 && !*errorP;
+         i -= 4) {
+
+        if (*p == '\0')
+            pm_asprintf(errorP, "Not enough hexadecimal digits for glyph "
+                        "of width %u in '%s'",
+                        glyphWidth, hex);
+        else {
+            char const hdig = *p++;
+            unsigned int hdigValue;
+
+            if (hdig >= '0' && hdig <= '9')
+                hdigValue = hdig - '0';
+            else if (hdig >= 'a' && hdig <= 'f')
+                hdigValue = 10 + (hdig - 'a');
+            else if (hdig >= 'A' && hdig <= 'F')
+                hdigValue = 10 + (hdig - 'A');
+            else
+                pm_asprintf(errorP,
+                            "Invalid hex digit x%02x (%c) in bitmap data '%s'",
+                            (unsigned int)(unsigned char)hdig,
+                            isprint(hdig) ? hdig : '.',
+                            hex);
+
+            if (!*errorP) {
+                if (i > 0)
+                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
+                if (i > 1)
+                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
+                if (i > 2)
+                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
+                if (i > 3)
+                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
+            }
+        }
+    }
+    *newBmapIndexP = bmapIndex;
+}
+
+
+
+static void
+readBitmap(Readline *      const readlineP,
+           unsigned int    const glyphWidth,
+           unsigned int    const glyphHeight,
+           const char *    const charName,
+           unsigned char * const bmap) {
+
+    int n;
+    unsigned int bmapIndex;
+
+    bmapIndex = 0;
+
+    for (n = glyphHeight; n > 0; --n) {
+        bool eof;
+        const char * error;
+
+        readline_read(readlineP, &eof);
+
+        if (eof)
+            pm_error("End of file in bitmap for character '%s' in BDF "
+                     "font file.", charName);
+
+        if (!readlineP->arg[0])
+            pm_error("A line that is supposed to contain bitmap data, "
+                     "in hexadecimal, for character '%s' is empty", charName);
+
+        parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex,
+                       &bmapIndex, &error);
+
+        if (error) {
+            pm_error("Error in line %d of bitmap for character '%s': %s",
+                     n, charName, error);
+            pm_strfree(error);
+        }
+    }
+}
+
+
+
+static void
+createBmap(unsigned int  const glyphWidth,
+           unsigned int  const glyphHeight,
+           Readline *    const readlineP,
+           const char *  const charName,
+           const char ** const bmapP) {
+
+    unsigned char * bmap;
+    bool eof;
+
+    if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight)
+        pm_error("Ridiculously large glyph");
+
+    MALLOCARRAY(bmap, glyphWidth * glyphHeight);
+
+    if (!bmap)
+        pm_error("no memory for font glyph byte map");
+
+    readline_read(readlineP, &eof);
+    if (eof)
+        pm_error("End of file encountered reading font glyph byte map from "
+                 "BDF font file.");
+
+    if (streq(readlineP->arg[0], "ATTRIBUTES")) {
+        /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF)
+           Specification Version 2.1, but not in Version 2.2.
+        */
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file encountered after ATTRIBUTES in BDF "
+                     "font file.");
+    }
+    if (!streq(readlineP->arg[0], "BITMAP"))
+        pm_error("'%s' found where BITMAP expected in definition of "
+                 "character '%s' in BDF font file.",
+                 readlineP->arg[0], charName);
+
+    assert(streq(readlineP->arg[0], "BITMAP"));
+
+    readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap);
+
+    *bmapP = (char *)bmap;
+}
+
+
+
+static void
+validateWordCount(Readline *    const readlineP,
+                  unsigned int  const nWords) {
+
+    if( readlineP->wordCt != nWords )
+        pm_error("Wrong number of arguments in '%s' line in BDF font file",
+                 readlineP->arg[0]);
+
+    /* We assume that the first word in line 'arg[0]' is a valid string */
+
+}
+
+
+static void
+readExpectedStatement(Readline *    const readlineP,
+                      const char *  const expected,
+                      unsigned int  const nWords) {
+/*----------------------------------------------------------------------------
+  Have the readline object *readlineP read the next line from the file, but
+  expect it to be a line of type 'expected' (i.e. the verb token at the
+  beginning of the line is that, e.g. "STARTFONT").  Check for the number
+  of words: 'nWords'.  If either condition is not met, fail the program.
+-----------------------------------------------------------------------------*/
+
+    bool eof;
+
+    readline_read(readlineP, &eof);
+
+    if (eof)
+        pm_error("EOF in BDF font file where '%s' expected", expected);
+    else if (!streq(readlineP->arg[0], expected))
+        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
+                 readlineP->arg[0], expected);
+
+    validateWordCount(readlineP, nWords);
+
+}
+
+
+
+static void
+skipCharacter(Readline * const readlineP) {
+/*----------------------------------------------------------------------------
+  In the BDF font file being read by readline object *readlineP, skip through
+  the end of the character we are presently in.
+-----------------------------------------------------------------------------*/
+    bool endChar;
+
+    endChar = FALSE;
+
+    while (!endChar) {
+        bool eof;
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file in the middle of a character (before "
+                     "ENDCHAR) in BDF font file.");
+        endChar = streq(readlineP->arg[0], "ENDCHAR");
+    }
+}
+
+
+
+static int
+wordToInt(const char * const word) {
+
+    unsigned int absValue;
+
+    int retval;
+
+    const char * error;
+    const int sign = (word[0] == '-') ? -1 : +1;
+    const char * const absString = (sign == -1) ? &word[1] : word;
+    /* No leading spaces allowed in 'word' */
+
+    if (!ISDIGIT(absString[0]))
+      error = "Non-digit character encountered";
+
+    else {
+        pm_string_to_uint(absString, &absValue, &error);
+        if (error == NULL && absValue > INT_MAX)
+            error = "Out of range";
+    }
+
+    if (error != NULL)
+        pm_error ("Error reading numerical argument in "
+                  "BDF font file: %s %s %s", error, word, absString);
+
+    retval = sign * absValue;
+    assert (INT_MIN < retval && retval < INT_MAX);
+
+    return retval;
+}
+
+
+
+static void
+interpEncoding(const char **  const arg,
+               unsigned int * const codepointP,
+               bool *         const badCodepointP,
+               PM_WCHAR       const maxmaxglyph) {
+/*----------------------------------------------------------------------------
+   With arg[] being the ENCODING statement from the font, return as
+   *codepointP the codepoint that it indicates (code point is the character
+   code, e.g. in ASCII, 48 is '0').
+
+   But if the statement doesn't give an acceptable codepoint return
+   *badCodepointP == TRUE.
+
+   'maxmaxglyph' is the maximum codepoint in the font.
+-----------------------------------------------------------------------------*/
+    bool gotCodepoint;
+    bool badCodepoint;
+    unsigned int codepoint;
+
+    if (wordToInt(arg[1]) >= 0) {
+        codepoint = wordToInt(arg[1]);
+        gotCodepoint = true;
+    } else {
+      if (wordToInt(arg[1]) == -1 && arg[2] != NULL) {
+            codepoint = wordToInt(arg[2]);
+            gotCodepoint = true;
+        } else
+            gotCodepoint = false;
+    }
+    if (gotCodepoint) {
+        if (codepoint > maxmaxglyph)
+            badCodepoint = true;
+        else
+            badCodepoint = false;
+    } else
+        badCodepoint = true;
+
+    *badCodepointP = badCodepoint;
+    *codepointP    = codepoint;
+}
+
+
+
+static void
+readEncoding(Readline *     const readlineP,
+             unsigned int * const codepointP,
+             bool *         const badCodepointP,
+             PM_WCHAR       const maxmaxglyph) {
+
+    bool eof;
+    const char * expected = "ENCODING";
+
+    readline_read(readlineP, &eof);
+
+    if (eof)
+        pm_error("EOF in BDF font file where '%s' expected", expected);
+    else if (!streq(readlineP->arg[0], expected))
+        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
+                 readlineP->arg[0], expected);
+    else if(readlineP->wordCt != 2 &&  readlineP->wordCt != 3)
+        pm_error("Wrong number of arguments in '%s' line in BDF font file",
+                 readlineP->arg[0]);
+
+    interpEncoding(readlineP->arg, codepointP, badCodepointP, maxmaxglyph);
+}
+
+
+
+static void
+validateFontLimits(const struct font2 * const font2P) {
+
+    assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0);
+
+    if (font2P->maxwidth  <= 0 ||
+        font2P->maxheight <= 0 ||
+        font2P->maxwidth  > pbm_maxfontwidth()  ||
+        font2P->maxheight > pbm_maxfontheight() ||
+        -font2P->x + 1 > font2P->maxwidth ||
+        -font2P->y + 1 > font2P->maxheight ||
+        font2P->x > font2P->maxwidth  ||
+        font2P->y > font2P->maxheight ||
+        font2P->x + font2P->maxwidth  > pbm_maxfontwidth() ||
+        font2P->y + font2P->maxheight > pbm_maxfontheight()
+        ) {
+
+        pm_error("Global font metric(s) out of bounds.");
+    }
+
+    if (font2P->maxglyph > PM_FONT2_MAXGLYPH)
+        pm_error("Internal error.  Glyph table too large: %u glyphs; "
+                 "Maximum possible in Netpbm is %u",
+                 (unsigned int) font2P->maxglyph, PM_FONT2_MAXGLYPH);
+}
+
+
+
+static void
+validateGlyphLimits(const struct font2 * const font2P,
+                    const struct glyph * const glyphP,
+                    const char *         const charName) {
+
+    /* Some BDF files code space with zero width and height,
+       no bitmap data and just the xadd value.
+       We allow zero width and height, iff both are zero.
+
+       Some BDF files have individual glyphs with a BBX value which
+       exceeds the global maximum stated by FONTBOUNDINGBOX.
+       Abort with error when this is encountered.
+       It seems some programs including emacs and bdftopcf tolerate
+       this violation.
+    */
+
+    if (((glyphP->width == 0 || glyphP->height == 0) &&
+         !(glyphP->width == 0 && glyphP->height == 0)) ||
+        glyphP->width  > font2P->maxwidth  ||
+        glyphP->height > font2P->maxheight ||
+        glyphP->x < font2P->x ||
+        glyphP->y < font2P->y ||
+        glyphP->x + (int) glyphP->width  > font2P->x + font2P->maxwidth  ||
+        glyphP->y + (int) glyphP->height > font2P->y + font2P->maxheight ||
+        glyphP->xadd > pbm_maxfontwidth() ||
+        glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width >
+        pbm_maxfontwidth()
+        ) {
+
+        pm_error("Font metric(s) for char '%s' out of bounds.\n", charName);
+    }
+}
+
+
+
+static void
+processChars(Readline *     const readlineP,
+             struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+   Process the CHARS block in a BDF font file, assuming the file is positioned
+   just after the CHARS line.  Read the rest of the block and apply its
+   contents to *font2P.
+-----------------------------------------------------------------------------*/
+    unsigned int const nCharacters = wordToInt(readlineP->arg[1]);
+
+    unsigned int nCharsDone  = 0;   /* Initial value */
+    unsigned int nCharsValid = 0;   /* Initial value */
+
+    while (nCharsDone < nCharacters) {
+        bool eof;
+
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file after CHARS reading BDF font file");
+
+        if (streq(readlineP->arg[0], "COMMENT")) {
+            /* ignore */
+        } else if (!streq(readlineP->arg[0], "STARTCHAR"))
+            pm_error("%s detected where STARTCHAR expected "
+                     "in BDF font file", readlineP->arg[0] );
+        else {
+            const char * charName;
+
+            struct glyph * glyphP;
+            unsigned int codepoint;
+            bool badCodepoint;
+
+            if (readlineP->wordCt < 2)
+                pm_error("Wrong number of arguments in STARTCHAR line "
+                         "in BDF font file");
+            /* Character name may contain spaces: there may be more than
+               three words in the line.
+             */
+            charName = pm_strdup(readlineP->arg[1]);
+
+            assert(streq(readlineP->arg[0], "STARTCHAR"));
+
+            MALLOCVAR(glyphP);
+
+            if (glyphP == NULL)
+                pm_error("no memory for font glyph for '%s' character",
+                         charName);
+
+            readEncoding(readlineP, &codepoint, &badCodepoint,
+                         font2P->maxmaxglyph);
+
+            if (badCodepoint)
+                skipCharacter(readlineP);
+            else {
+                if (codepoint < font2P->maxglyph) {
+                    if (font2P->glyph[codepoint] != NULL)
+                        pm_error("Multiple definition of code point %u "
+                                 "in BDF font file", (unsigned int) codepoint);
+                    else
+                        pm_message("Reverse order detected in BDF file. "
+                                   "Code point %u defined after %u",
+                                    (unsigned int) codepoint,
+                                    (unsigned int) font2P->maxglyph);
+                } else {
+                    /* Initialize all characters in the gap to nonexistent */
+                    unsigned int i;
+                    unsigned int const oldMaxglyph = font2P->maxglyph;
+                    unsigned int const newMaxglyph = codepoint;
+
+                    for (i = oldMaxglyph + 1; i < newMaxglyph; ++i)
+                        font2P->glyph[i] = NULL;
+
+                    font2P->maxglyph = newMaxglyph;
+                    }
+
+                readExpectedStatement(readlineP, "SWIDTH", 3);
+
+                readExpectedStatement(readlineP, "DWIDTH", 3);
+                glyphP->xadd = wordToInt(readlineP->arg[1]);
+
+                readExpectedStatement(readlineP, "BBX", 5);
+                glyphP->width  = wordToInt(readlineP->arg[1]);
+                glyphP->height = wordToInt(readlineP->arg[2]);
+                glyphP->x      = wordToInt(readlineP->arg[3]);
+                glyphP->y      = wordToInt(readlineP->arg[4]);
+
+                validateGlyphLimits(font2P, glyphP, charName);
+
+                createBmap(glyphP->width, glyphP->height, readlineP, charName,
+                           &glyphP->bmap);
+
+                readExpectedStatement(readlineP, "ENDCHAR", 1);
+
+                assert(codepoint <= font2P->maxmaxglyph);
+                /* Ensured by readEncoding() */
+
+                font2P->glyph[codepoint] = glyphP;
+                pm_strfree(charName);
+
+                ++nCharsValid;
+            }
+            ++nCharsDone;
+        }
+    }
+    font2P->chars = nCharsValid;
+    font2P->total_chars = nCharacters;
+}
+
+
+
+static void
+processBdfFontNameLine(Readline     * const readlineP,
+                       struct font2 * const font2P) {
+
+    if (font2P->name != NULL)
+        pm_error("Multiple FONT lines in BDF font file");
+
+    font2P->name = malloc (MAXBDFLINE+1);
+    if (font2P->name == NULL)
+        pm_error("No memory for font name");
+
+    if (readlineP->wordCt == 1)
+        strcpy(font2P->name, "(no name)");
+
+    else {
+        unsigned int tokenCt;
+
+        font2P->name[0] ='\0';
+
+        for (tokenCt=1;
+             tokenCt < ARRAY_SIZE(readlineP->arg) &&
+                 readlineP->arg[tokenCt] != NULL; ++tokenCt) {
+          strcat(font2P->name, " ");
+          strcat(font2P->name, readlineP->arg[tokenCt]);
+        }
+    }
+}
+
+
+static void
+loadCharsetString(char *  const registry,
+                  char *  const encoding,
+                  char ** const string) {
+
+    unsigned int inCt, outCt;
+    char * const dest = malloc (strlen(registry) + strlen(encoding) + 1);
+    if (dest == NULL)
+        pm_error("no memory to load CHARSET_REGISTRY and CHARSET_ENCODING "
+               "from BDF file");
+
+    for (inCt = outCt = 0; inCt < strlen(registry); ++inCt) {
+        char const c = registry[inCt];
+        if (isgraph(c) && c != '"')
+            dest[outCt++] = c;
+    }
+    dest[outCt++] = '-';
+
+    for (inCt = 0; inCt < strlen(encoding); ++inCt) {
+        char const c = encoding[inCt];
+        if (isgraph(c) && c != '"')
+            dest[outCt++] = c;
+    }
+
+    dest[outCt] = '\0';
+    *string = dest;
+}
+
+
+
+static void
+processBdfPropertyLine(Readline     * const readlineP,
+                       struct font2 * const font2P) {
+
+    char * registry;
+    char * encoding;
+    unsigned int propTotal;
+    bool gotRegistry = FALSE;    /* Initial value */
+    bool gotEncoding = FALSE;    /* Initial value */
+    PM_WCHAR defaultChar = 0;    /* Initial value */
+    bool gotDefaultchar = FALSE; /* Initial value */
+    unsigned int propCt = 0;     /* Initial value */
+    unsigned int commentCt = 0;  /* Initial value */
+    unsigned int const maxTokenLen = 60;
+
+    validateWordCount(readlineP, 2);   /* STARTPROPERTIES n */
+
+    propTotal = wordToInt(readlineP->arg[1]);
+
+    do {
+        bool eof;
+
+        readline_read(readlineP, &eof);
+        if (eof)
+            pm_error("End of file after STARTPROPERTIES in BDF font file");
+        else if (streq(readlineP->arg[0], "CHARSET_REGISTRY")) {
+            if (gotRegistry)
+                pm_error("Multiple CHARSET_REGISTRY lines in BDF font file");
+            else if (readlineP->arg[2] != NULL)
+                pm_message("CHARSET_REGISTRY in BDF font file is not "
+                           "a single word.  Ignoring extra element(s) %s ...",
+                           readlineP->arg[2]);
+            else if (strlen(readlineP->arg[1]) > maxTokenLen)
+                pm_message("CHARSET_REGISTRY in BDF font file is too long. "
+                           "Truncating");
+
+            registry = strndup (readlineP->arg[1], maxTokenLen);
+            gotRegistry = TRUE;
+            }
+        else if (streq(readlineP->arg[0], "CHARSET_ENCODING")) {
+            if (gotEncoding)
+                pm_error("Multiple CHARSET_ENCODING lines in BDF font file");
+            else if (readlineP->arg[2] != NULL)
+                pm_message("CHARSET_ENCODING in BDF font file is not "
+                           "a single word.  Ignoring extra element(s) %s ...",
+                           readlineP->arg[2]);
+            else if (strlen(readlineP->arg[1]) > maxTokenLen)
+                pm_message("CHARSET_ENCODING in BDF font file is too long. "
+                           "Truncating");
+
+            encoding = strndup (readlineP->arg[1], maxTokenLen);
+            gotEncoding = TRUE;
+        }
+        else if (streq(readlineP->arg[0], "DEFAULT_CHAR")) {
+            if (gotDefaultchar)
+                pm_error("Multiple DEFAULT_CHAR lines in BDF font file");
+            else if (readlineP->arg[1] == NULL)
+                pm_error("Malformed DEFAULT_CHAR lines in BDF font file");
+            else {
+                defaultChar = (PM_WCHAR) wordToInt(readlineP->arg[1]);
+                gotDefaultchar = TRUE;
+            }
+        }
+        else if (streq(readlineP->arg[0], "COMMENT")) {
+            commentCt++;
+        }
+        propCt++;
+
+    } while (!streq(readlineP->arg[0], "ENDPROPERTIES"));
+
+    --propCt; /* Subtract one for ENDPROPERTIES line */
+
+    if (propCt != propTotal && propCt - commentCt != propTotal)
+      /* Some BDF files have COMMENTs in the property section and leave
+         them out of the count.
+         Others just give a wrong count.
+       */
+        pm_message ("Note: wrong number of property lines in BDF font file. "
+                    "STARTPROPERTIES line says %u, actual count: %u. "
+                    "Proceeding.",
+                    propTotal, propCt);
+
+
+    if (gotRegistry && gotEncoding)
+        loadCharsetString(registry, encoding, &font2P->charset_string);
+    else if (gotRegistry != gotEncoding) {
+        pm_message ("CHARSET_%s absent in BDF font file. "
+                    "Ignoring CHARSET_%s.",
+                    gotEncoding ? "REGISTRY" : "ENCODING",
+                    gotEncoding ? "ENCODING" : "REGISTRY");
+    }
+    free(registry); free(encoding);
+
+    if (gotDefaultchar) {
+      font2P->default_char = defaultChar;
+      font2P->default_char_defined = TRUE;
+    }
+
+}
+
+
+static void
+processBdfFontLine(Readline     * const readlineP,
+                   struct font2 * const font2P,
+                   bool         * const endOfFontP) {
+/*----------------------------------------------------------------------------
+   Process a nonblank line just read from a BDF font file.
+
+   This processing may involve reading more lines.
+-----------------------------------------------------------------------------*/
+    *endOfFontP = FALSE;  /* initial assumption */
+
+    assert(readlineP->arg[0] != NULL);  /* Entry condition */
+
+    if (streq(readlineP->arg[0], "FONT")) {
+        processBdfFontNameLine(readlineP, font2P);
+    } else if (streq(readlineP->arg[0], "COMMENT")) {
+        /* ignore */
+    } else if (streq(readlineP->arg[0], "SIZE")) {
+        /* ignore */
+    } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) {
+      if (font2P->maxwidth == 0)
+      pm_error("Encountered STARTROPERTIES before FONTBOUNDINGBOX "
+               "in BDF font file");
+      else
+        processBdfPropertyLine(readlineP, font2P);
+    } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) {
+        validateWordCount(readlineP,5);
+
+        font2P->maxwidth  = wordToInt(readlineP->arg[1]);
+        font2P->maxheight = wordToInt(readlineP->arg[2]);
+        font2P->x = wordToInt(readlineP->arg[3]);
+        font2P->y = wordToInt(readlineP->arg[4]);
+        validateFontLimits(font2P);
+    } else if (streq(readlineP->arg[0], "ENDFONT")) {
+        *endOfFontP = true;
+    } else if (streq(readlineP->arg[0], "CHARS")) {
+      if (font2P->maxwidth == 0)
+      pm_error("Encountered CHARS before FONTBOUNDINGBOX "
+                   "in BDF font file");
+      else {
+        validateWordCount(readlineP, 2);  /* CHARS n */
+        processChars(readlineP, font2P);
+      }
+    } else {
+        /* ignore */
+    }
+
+}
+
+
+
+struct font2 *
+pbm_loadbdffont2(const char * const filename,
+                 PM_WCHAR     const maxmaxglyph) {
+/*----------------------------------------------------------------------------
+   Read a BDF font file "filename" as a 'font2' structure.  A 'font2'
+   structure is more expressive than a 'font' structure, most notably in that
+   it can handle wide code points and many more glyphs.
+
+   Codepoints up to maxmaxglyph inclusive are valid in the file.
+
+   The returned object is in new malloc'ed storage, in many pieces.
+   When done with, destroy with pbm_destroybdffont2().
+-----------------------------------------------------------------------------*/
+
+    FILE *         ifP;
+    Readline       readline;
+    struct font2 * font2P;
+    bool           endOfFont;
+
+    ifP = fopen(filename, "rb");
+    if (!ifP)
+        pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
+                 filename, errno, strerror(errno));
+
+    readline_init(&readline, ifP);
+
+    pbm_createbdffont2_base(&font2P, maxmaxglyph);
+
+    font2P->maxglyph = 0;
+    /* Initial value.  Increases as new characters are loaded */
+    font2P->glyph[0] = NULL;
+    /* Initial value.  Overwrite later if codepoint 0 is defined. */
+
+    font2P->maxmaxglyph = maxmaxglyph;
+
+    /* Initialize some values - to be overwritten if actual values are
+       stated in BDF file */
+    font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0;
+    font2P->name = font2P->charset_string = NULL;
+    font2P->chars = font2P->total_chars = 0;
+    font2P->default_char = 0;
+    font2P->default_char_defined = FALSE;
+
+    readExpectedStatement(&readline, "STARTFONT", 2);
+
+    endOfFont = FALSE;
+
+    while (!endOfFont) {
+        bool eof;
+        readline_read(&readline, &eof);
+        if (eof)
+            pm_error("End of file before ENDFONT statement in BDF font file");
+
+        processBdfFontLine(&readline, font2P, &endOfFont);
+    }
+    fclose(ifP);
+
+    if(font2P->chars == 0)
+        pm_error("No glyphs found in BDF font file "
+                 "in codepoint range 0 - %u", (unsigned int) maxmaxglyph);
+
+    REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1);
+
+    font2P->bit_format = PBM_FORMAT;
+    font2P->load_fn = LOAD_BDFFILE;
+    font2P->charset = ENCODING_UNKNOWN;
+    font2P->oldfont = NULL;  /* Legacy field */
+    font2P->fcols = font2P->frows = 0;  /* Legacy fields */
+
+    return font2P;
+}
+
+
+static struct font *
+font2ToFont(const struct font2 * const font2P) {
+            struct font  * fontP;
+            unsigned int   codePoint;
+
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
+        pm_error("no memory for font");
+
+    fontP->maxwidth  = font2P->maxwidth;
+    fontP->maxheight = font2P->maxheight;
+
+    fontP->x = font2P->x;
+    fontP->y = font2P->y;
+
+    for (codePoint = 0; codePoint <= font2P->maxglyph; ++codePoint)
+        fontP->glyph[codePoint] = font2P->glyph[codePoint];
+
+    /* font2P->maxglyph is typically 255 (PM_FONT_MAXGLYPH) or larger.
+       But in some rare cases it is smaller.
+       If an ASCII-only font is read, it will be 126 or 127.
+
+       Set remaining codepoints up to PM_FONT_MAXGLYPH, if any, to NULL
+    */
+
+    for ( ; codePoint <= PM_FONT_MAXGLYPH; ++codePoint)
+        fontP->glyph[codePoint] = NULL;
+
+    /* Give values to legacy fields */
+    fontP->oldfont = font2P->oldfont;
+    fontP->fcols = font2P->fcols;
+    fontP->frows = font2P->frows;
+
+    return fontP;
+}
+
+
+
+struct font *
+pbm_loadbdffont(const char * const filename) {
+/*----------------------------------------------------------------------------
+   Read a BDF font file "filename" into a traditional font structure.
+
+   Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid.
+
+   Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc.
+
+   The returned object is in new malloc'ed storage, in many pieces.
+   Destroy with pbm_destroybdffont().
+-----------------------------------------------------------------------------*/
+    struct font  * fontP;
+    struct font2 * const font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH);
+
+    fontP = font2ToFont(font2P);
+
+    /* Free the base structure which was created by pbm_loadbdffont2() */
+    pbm_destroybdffont2_base(font2P);
+
+    return fontP;
+}
+
+
+