about summary refs log tree commit diff
path: root/lib
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
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')
-rw-r--r--lib/Makefile5
-rw-r--r--lib/libpbmfont.c1155
-rw-r--r--lib/libpbmfont0.c333
-rw-r--r--lib/libpbmfont1.c359
-rw-r--r--lib/libpbmfont2.c995
-rw-r--r--lib/libpbmfontdump.c96
-rw-r--r--lib/pbmfont.h229
-rw-r--r--lib/pbmfontdata.h7
-rw-r--r--lib/pbmfontdata0.c9
-rw-r--r--lib/pbmfontdata1.c24
-rw-r--r--lib/pbmfontdata2.c27
11 files changed, 2056 insertions, 1183 deletions
diff --git a/lib/Makefile b/lib/Makefile
index a0f33745..65177758 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -28,7 +28,8 @@ endif
 LIBOBJECTS = libpm.o pmfileio.o fileio.o colorname.o \
 	libpamd.o \
 	libpbm1.o libpbm2.o libpbm3.o \
-	libpbmfont.o pbmfontdata1.o pbmfontdata2.o \
+	libpbmfont0.o libpbmfont1.o libpbmfont2.o \
+	pbmfontdata0.o pbmfontdata1.o pbmfontdata2.o \
 	libpgm1.o libpgm2.o \
 	libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \
 	libppmd.o ppmdfont.o standardppmdfont.o path.o \
@@ -56,7 +57,7 @@ MANUALS3 = libnetpbm
 MANUALS5 = pbm pgm ppm pnm pam
 
 INTERFACE_HEADERS = colorname.h \
-	pam.h pamdraw.h pammap.h pbm.h pbmfont.h \
+	pam.h pamdraw.h pammap.h pbm.h pbmfont.h pbmfontdata.h \
 	pgm.h pm.h pm_gamma.h pm_system.h pnm.h \
 	ppm.h ppmcmap.h ppmdfont.h ppmdraw.h ppmfloyd.h \
 	util/mallocvar.h util/runlength.h util/shhopt.h \
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
deleted file mode 100644
index 8f308eda..00000000
--- a/lib/libpbmfont.c
+++ /dev/null
@@ -1,1155 +0,0 @@
-/*
-**
-** Font routines.
-**
-** BDF font code Copyright 1993 by George Phillips.
-**
-** Copyright (C) 1991 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-**
-** BDF font specs available from:
-** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
-** Glyph Bitmap Distribution Format (BDF) Specification
-** Version 2.2
-** 22 March 1993
-** Adobe Developer Support
-*/
-
-#include <assert.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "netpbm/pm_c_util.h"
-#include "netpbm/mallocvar.h"
-#include "netpbm/nstring.h"
-
-#include "pbmfont.h"
-#include "pbm.h"
-
-static unsigned int const firstCodePoint = 32;
-    /* This is the code point of the first character in a pbmfont font.
-       In ASCII, it is a space.
-    */
-
-static unsigned int const nCharsInFont = 96;
-    /* The number of characters in a pbmfont font.  A pbmfont font defines
-       characters at position 32 (ASCII space) through 127, so that's 96.
-    */
-
-
-struct font *
-pbm_defaultfont(const char * const name) {
-/*----------------------------------------------------------------------------
-   Generate the built-in font with name 'name'.
------------------------------------------------------------------------------*/
-    struct font * retval;
-
-    if (streq(name, "bdf"))
-        retval = &pbm_defaultBdffont;
-    else if (streq(name, "fixed"))
-        retval = &pbm_defaultFixedfont;
-    else
-        pm_error( "built-in font name unknown, try 'bdf' or 'fixed'");
-
-    return retval;
-}
-
-
-
-static void
-findFirstBlankRow(const bit **   const font,
-                  unsigned int   const fcols,
-                  unsigned int   const frows,
-                  unsigned int * const browP) {
-
-    unsigned int row;
-    bool foundBlank;
-
-    for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) {
-        unsigned int col;
-        bit col0Value = font[row][0];
-        bool rowIsBlank;
-        rowIsBlank = true;  /* initial assumption */
-        for (col = 1; col < fcols; ++col)
-            if (font[row][col] != col0Value)
-                rowIsBlank = false;
-
-        if (rowIsBlank) {
-            foundBlank = true;
-            *browP = row;
-        }
-    }
-
-    if (!foundBlank)
-        pm_error("couldn't find blank pixel row in font");
-}
-
-
-
-static void
-findFirstBlankCol(const bit **   const font,
-                  unsigned int   const fcols,
-                  unsigned int   const frows,
-                  unsigned int * const bcolP) {
-
-    unsigned int col;
-    bool foundBlank;
-
-    for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) {
-        unsigned int row;
-        bit row0Value = font[0][col];
-        bool colIsBlank;
-        colIsBlank = true;  /* initial assumption */
-        for (row = 1; row < frows; ++row)
-            if (font[row][col] != row0Value)
-                colIsBlank = false;
-
-        if (colIsBlank) {
-            foundBlank = true;
-            *bcolP = col;
-        }
-    }
-
-    if (!foundBlank)
-        pm_error("couldn't find blank pixel column in font");
-}
-
-
-
-static void
-computeCharacterSize(const bit **   const font,
-                     unsigned int   const fcols,
-                     unsigned int   const frows,
-                     unsigned int * const cellWidthP,
-                     unsigned int * const cellHeightP,
-                     unsigned int * const charWidthP,
-                     unsigned int * const charHeightP) {
-
-    unsigned int firstBlankRow;
-    unsigned int firstBlankCol;
-    unsigned int heightLast11Rows;
-
-    findFirstBlankRow(font, fcols, frows, &firstBlankRow);
-
-    findFirstBlankCol(font, fcols, frows, &firstBlankCol);
-
-    heightLast11Rows = frows - firstBlankRow;
-
-    if (heightLast11Rows % 11 != 0)
-        pm_error("The rows of characters in the font do not appear to "
-                 "be all the same height.  The last 11 rows are %u pixel "
-                 "rows high (from pixel row %u up to %u), "
-                 "which is not a multiple of 11.",
-                 heightLast11Rows, firstBlankRow, frows);
-    else {
-        unsigned int widthLast15Cols;
-
-        *cellHeightP = heightLast11Rows / 11;
-
-        widthLast15Cols = fcols - firstBlankCol;
-
-        if (widthLast15Cols % 15 != 0)
-            pm_error("The columns of characters in the font do not appear to "
-                     "be all the same width.  "
-                     "The last 15 columns are %u pixel "
-                     "columns wide (from pixel col %u up to %u), "
-                     "which is not a multiple of 15.",
-                     widthLast15Cols, firstBlankCol, fcols);
-        else {
-            *cellWidthP = widthLast15Cols / 15;
-
-            *charWidthP = firstBlankCol;
-            *charHeightP = firstBlankRow;
-        }
-    }
-}
-
-
-
-struct font*
-pbm_dissectfont(const bit ** const font,
-                unsigned int const frows,
-                unsigned int const fcols) {
-    /*
-       This routine expects a font bitmap representing the following text:
-      
-       (0,0)
-          M ",/^_[`jpqy| M
-      
-          /  !"#$%&'()*+ /
-          < ,-./01234567 <
-          > 89:;<=>?@ABC >
-          @ DEFGHIJKLMNO @
-          _ PQRSTUVWXYZ[ _
-          { \]^_`abcdefg {
-          } hijklmnopqrs }
-          ~ tuvwxyz{|}~  ~
-      
-          M ",/^_[`jpqy| M
-      
-       The bitmap must be cropped exactly to the edges.
-      
-       The characters in the border you see are irrelevant except for
-       character size compuations.  The 12 x 8 array in the center is
-       the font.  The top left character there belongs to code point
-       0, and the code points increase in standard reading order, so
-       the bottom right character is code point 127.  You can't define
-       code points < 32 or > 127 with this font format.
-
-       The characters in the top and bottom border rows must include a
-       character with the lowest reach of any in the font (e.g. "y",
-       "_") and one with the highest reach (e.g. '"').  The characters
-       in the left and right border columns must include characters
-       with the rightmost and leftmost reach of any in the font
-       (e.g. "M" for both).
-
-       The border must be separated from the font by one blank text
-       row or text column.
-      
-       The dissection works by finding the first blank row and column;
-       i.e the lower right corner of the "M" in the upper left corner
-       of the matrix.  That gives the height and width of the
-       maximum-sized character, which is not too useful.  But the
-       distance from there to the opposite side is an integral
-       multiple of the cell size, and that's what we need.  Then it's
-       just a matter of filling in all the coordinates.  */
-    
-    unsigned int cellWidth, cellHeight;
-        /* Dimensions in pixels of each cell of the font -- that
-           includes the glyph and the white space above and to the
-           right of it.  Each cell is a tile of the font image.  The
-           top character row and left character row don't count --
-           those cells are smaller because they are missing the white
-           space.
-        */
-    unsigned int charWidth, charHeight;
-        /* Maximum dimensions of glyph itself, inside its cell */
-
-    int row, col, ch, r, c, i;
-    struct font * fn;
-    struct glyph * glyph;
-    char* bmap;
-
-    computeCharacterSize(font, fcols, frows,
-                         &cellWidth, &cellHeight, &charWidth, &charHeight);
-
-    /* Now convert to a general font */
-
-    MALLOCVAR(fn);
-    if (fn == NULL)
-        pm_error("out of memory allocating font structure");
-
-    fn->maxwidth  = charWidth;
-    fn->maxheight = charHeight;
-    fn->x = fn->y = 0;
-
-    fn->oldfont = font;
-    fn->frows = frows;
-    fn->fcols = fcols;
-    
-    /* Initialize all character positions to "undefined."  Those that
-       are defined in the font will be filled in below.
-    */
-    for (i = 0; i < PM_FONT_MAXGLYPH + 1; i++)
-        fn->glyph[i] = NULL;
-
-    MALLOCARRAY(glyph, nCharsInFont);
-    if ( glyph == NULL )
-        pm_error( "out of memory allocating glyphs" );
-    
-    bmap = (char*) malloc( fn->maxwidth * fn->maxheight * nCharsInFont );
-    if ( bmap == (char*) 0)
-        pm_error( "out of memory allocating glyph data" );
-
-    /* Now fill in the 0,0 coords. */
-    row = cellHeight * 2;
-    col = cellWidth * 2;
-    for (i = 0; i < firstCodePoint; ++i)
-        fn->glyph[i] = NULL;
-
-    for ( ch = 0; ch < nCharsInFont; ++ch ) {
-        glyph[ch].width = fn->maxwidth;
-        glyph[ch].height = fn->maxheight;
-        glyph[ch].x = glyph[ch].y = 0;
-        glyph[ch].xadd = cellWidth;
-
-        for ( r = 0; r < glyph[ch].height; ++r )
-            for ( c = 0; c < glyph[ch].width; ++c )
-                bmap[r * glyph[ch].width + c] = font[row + r][col + c];
-    
-        glyph[ch].bmap = bmap;
-        bmap += glyph[ch].width * glyph[ch].height;
-
-        fn->glyph[firstCodePoint + ch] = &glyph[ch];
-
-        col += cellWidth;
-        if ( col >= cellWidth * 14 ) {
-            col = cellWidth * 2;
-            row += cellHeight;
-        }
-    }
-    for (i = firstCodePoint + nCharsInFont; i < PM_FONT_MAXGLYPH + 1; ++i)
-        fn->glyph[i] = NULL;
-    
-    return fn;
-}
-
-
-
-struct font *
-pbm_loadfont(const char * const filename) {
-
-    FILE * fileP;
-    struct font * fontP;
-    char line[10] = "\0\0\0\0\0\0\0\0\0\0";
-        /* Initialize to suppress Valgrind error which is reported when file
-           is empty or very small.
-        */
-
-    fileP = pm_openr(filename);
-    fgets(line, 10, fileP);
-    pm_close(fileP);
-
-    if (line[0] == PBM_MAGIC1 && 
-        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
-        fontP = pbm_loadpbmfont(filename);
-    } else if (!strncmp(line, "STARTFONT", 9)) {
-        fontP = pbm_loadbdffont(filename);
-        if (!fontP)
-            pm_error("could not load BDF font file");
-    } else {
-        pm_error("font file not in a recognized format.  Does not start "
-                 "with the signature of a PBM file or BDF font file");
-        assert(false);
-        fontP = NULL;  /* defeat compiler warning */
-    }
-    return fontP;
-}
-
-
-
-struct font *
-pbm_loadpbmfont(const char * const filename) {
-
-    FILE * ifP;
-    bit ** font;
-    int fcols, frows;
-
-    ifP = pm_openr(filename);
-
-    font = pbm_readpbm(ifP, &fcols, &frows);
-
-    if ((fcols - 1) / 16 >= pbm_maxfontwidth() ||
-       (frows - 1) / 12 >= pbm_maxfontheight())
-        pm_error("Absurdly large PBM font file: %s", filename);
-    else if (fcols < 31 || frows < 23) {
-        /* Need at least one pixel per character, and this is too small to
-           have that.
-        */
-        pm_error("PBM font file '%s' too small to be a font file: %u x %u.  "
-                 "Minimum sensible size is 31 x 23",
-                 filename, fcols, frows);
-    }
-
-    pm_close(ifP);
-
-    return pbm_dissectfont((const bit **)font, frows, fcols);
-}
-
-
-
-void
-pbm_dumpfont(struct font * const fontP,
-             FILE *        const ofP) {
-/*----------------------------------------------------------------------------
-  Dump out font as C source code.
------------------------------------------------------------------------------*/
-    unsigned int i;
-    unsigned int ng;
-
-    if (fontP->oldfont)
-        pm_message("Netpbm no longer has the capability to generate "
-                   "a font in long hexadecimal data format");
-
-    for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) {
-        if (fontP->glyph[i])
-            ++ng;
-    }
-
-    printf("static struct glyph _g[%d] = {\n", ng);
-
-    for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
-        struct glyph * const glyphP = fontP->glyph[i];
-        if (glyphP) {
-            unsigned int j;
-            printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height,
-                   glyphP->x, glyphP->y, glyphP->xadd);
-            
-            for (j = 0; j < glyphP->width * glyphP->height; ++j) {
-                if (glyphP->bmap[j])
-                    printf("\\1");
-                else
-                    printf("\\0");
-            }    
-            --ng;
-            printf("\" }%s\n", ng ? "," : "");
-        }
-    }
-    printf("};\n");
-
-    printf("struct font XXX_font = { %d, %d, %d, %d, {\n",
-           fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y);
-
-    {
-        unsigned int i;
-
-        for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
-            if (fontP->glyph[i])
-                printf(" _g + %d", ng++);
-            else
-                printf(" NULL");
-        
-            if (i != PM_FONT_MAXGLYPH) printf(",");
-            printf("\n");
-        }
-    }
-
-    printf(" }\n};\n");
-}
-
-
-/*----------------------------------------------------------------------------
-  Routines for loading a BDF font file
------------------------------------------------------------------------------*/
-
-
-/* The following are not recognized in individual glyph data; library
-   routines do a pm_error if they see one:
-
-   Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET,
-   CONTENTVERSION.
-
-   The following is not recognized and is thus ignored at the global level:
-   DWIDTH
-*/
-
-
-#define MAXBDFLINE 1024 
-
-/* Official Adobe document says max length of string is 65535 characters.
-   However the value 1024 is sufficient for practical uses.
-*/
-
-typedef struct {
-/*----------------------------------------------------------------------------
-   This is an object for reading lines of a font file.  It reads and tokenizes
-   them into words.
------------------------------------------------------------------------------*/
-    FILE * ifP;
-
-    char line[MAXBDFLINE+1];
-        /* This is the storage space for the words of the line.  The
-           words go in here, one after another, separated by NULs.
-
-           It also functions as a work area for readline_read().
-        */
-    const char * arg[6];
-        /* These are the words; each entry is a pointer into line[] (above) */
-} Readline;
-
-
-
-static void
-readline_init(Readline * const readlineP,
-              FILE *     const ifP) {
-
-    readlineP->ifP = ifP;
-
-    readlineP->arg[0] = NULL;
-}
-
-
-
-static void
-tokenize(char *         const s,
-         const char **  const words,
-         unsigned int   const wordsSz) {
-/*----------------------------------------------------------------------------
-   Chop up 's' into words by changing space characters to NUL.  Return as
-   'words' an array of pointers to the beginnings of those words in 's'.
-   Terminate the words[] list with a null pointer.
-
-   'wordsSz' is the number of elements of space in 'words'.  If there are more
-   words in 's' than will fit in that space (including the terminating null
-   pointer), ignore the excess on the right.
------------------------------------------------------------------------------*/
-    unsigned int n;  /* Number of words in words[] so far */
-    char * p;
-
-    p = &s[0];
-    n = 0;
-
-    while (*p) {
-        if (ISSPACE(*p))
-            *p++ = '\0';
-        else {
-            words[n++] = p;
-            if (n >= wordsSz - 1)
-                break;
-            while (*p && !ISSPACE(*p))
-                ++p;
-        }
-    }
-    assert(n <= wordsSz - 1);
-    words[n] = NULL;
-}
-
-
-
-static void
-readline_read(Readline * const readlineP,
-              bool *     const eofP) {
-/*----------------------------------------------------------------------------
-   Read a nonblank line from the file.  Make its contents available
-   as readlineP->arg[].
-
-   Return *eofP == true iff there is no nonblank line before EOF or we
-   are unable to read the file.
------------------------------------------------------------------------------*/
-    bool gotLine;
-    bool error;
-
-    for (gotLine = false, error = false; !gotLine && !error; ) {
-        char * rc;
-
-        rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP);
-        if (rc == NULL)
-            error = true;
-        else {
-            tokenize(readlineP->line,
-                     readlineP->arg, ARRAY_SIZE(readlineP->arg));
-            if (readlineP->arg[0] != NULL)
-                gotLine = true;
-        }
-    }
-    *eofP = error;
-}
-
-
-
-static void
-parseBitmapRow(const char *    const hex,
-               unsigned int    const glyphWidth,
-               unsigned char * const bmap,
-               unsigned int    const origBmapIndex,
-               unsigned int *  const newBmapIndexP,
-               const char **   const errorP) {
-/*----------------------------------------------------------------------------
-   Parse one row of the bitmap for a glyph, from the hexadecimal string
-   for that row in the font file, 'hex'.  The glyph is 'glyphWidth'
-   pixels wide.
-
-   We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP.
------------------------------------------------------------------------------*/
-    unsigned int bmapIndex;
-    int i;  /* dot counter */
-    const char * p;
-
-    bmapIndex = origBmapIndex;
-
-    for (i = glyphWidth, p = &hex[0], *errorP = NULL;
-         i > 0 && !*errorP;
-         i -= 4) {
-
-        if (*p == '\0')
-            pm_asprintf(errorP, "Not enough hexadecimal digits for glyph "
-                        "of width %u in '%s'",
-                        glyphWidth, hex);
-        else {
-            char const hdig = *p++;
-            unsigned int hdigValue;
-
-            if (hdig >= '0' && hdig <= '9')
-                hdigValue = hdig - '0';
-            else if (hdig >= 'a' && hdig <= 'f')
-                hdigValue = 10 + (hdig - 'a');
-            else if (hdig >= 'A' && hdig <= 'F')
-                hdigValue = 10 + (hdig - 'A');
-            else 
-                pm_asprintf(errorP,
-                            "Invalid hex digit x%02x (%c) in bitmap data '%s'",
-                            (unsigned int)(unsigned char)hdig, 
-                            isprint(hdig) ? hdig : '.',
-                            hex);
-
-            if (!*errorP) {
-                if (i > 0)
-                    bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0;
-                if (i > 1)
-                    bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0;
-                if (i > 2)
-                    bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0;
-                if (i > 3)
-                    bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0;
-            }
-        }
-    }
-    *newBmapIndexP = bmapIndex;
-}
-
-
-
-static void
-readBitmap(Readline *      const readlineP,
-           unsigned int    const glyphWidth,
-           unsigned int    const glyphHeight,
-           const char *    const charName,
-           unsigned char * const bmap) {
-
-    int n;
-    unsigned int bmapIndex;
-
-    bmapIndex = 0;
-           
-    for (n = glyphHeight; n > 0; --n) {
-        bool eof;
-        const char * error;
-
-        readline_read(readlineP, &eof);
-
-        if (eof)
-            pm_error("End of file in bitmap for character '%s' in BDF "
-                     "font file.", charName);
-
-        if (!readlineP->arg[0])
-            pm_error("A line that is supposed to contain bitmap data, "
-                     "in hexadecimal, for character '%s' is empty", charName);
-
-        parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex,
-                       &bmapIndex, &error);
-
-        if (error) {
-            pm_error("Error in line %d of bitmap for character '%s': %s",
-                     n, charName, error);
-            pm_strfree(error);
-        }
-    }
-}
-
-
-
-static void
-createBmap(unsigned int  const glyphWidth,
-           unsigned int  const glyphHeight,
-           Readline *    const readlineP,
-           const char *  const charName,
-           const char ** const bmapP) {
-
-    unsigned char * bmap;
-    bool eof;
-    
-    if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight)
-        pm_error("Ridiculously large glyph");
-
-    MALLOCARRAY(bmap, glyphWidth * glyphHeight);
-
-    if (!bmap)
-        pm_error("no memory for font glyph byte map");
-
-    readline_read(readlineP, &eof);
-    if (eof)
-        pm_error("End of file encountered reading font glyph byte map from "
-                 "BDF font file.");
-    
-    if (streq(readlineP->arg[0], "ATTRIBUTES")) {
-        /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF)
-           Specification Version 2.1, but not in Version 2.2. 
-        */
-        bool eof;
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file encountered after ATTRIBUTES in BDF "
-                     "font file.");
-    }                
-    if (!streq(readlineP->arg[0], "BITMAP"))
-        pm_error("'%s' found where BITMAP expected in definition of "
-                 "character '%s' in BDF font file.",
-                 readlineP->arg[0], charName);
-
-    assert(streq(readlineP->arg[0], "BITMAP"));
-
-    readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap);
-
-    *bmapP = (char *)bmap;
-}
-
-
-
-static void
-readExpectedStatement(Readline *    const readlineP,
-                      const char *  const expected) {
-/*----------------------------------------------------------------------------
-  Have the readline object *readlineP read the next line from the file, but
-  expect it to be a line of type 'expected' (i.e. the verb token at the
-  beginning of the line is that, e.g. "STARTFONT").  If it isn't, fail the
-  program.
------------------------------------------------------------------------------*/
-    bool eof;
-
-    readline_read(readlineP, &eof);
-
-    if (eof)
-        pm_error("EOF in BDF font file where '%s' expected", expected);
-    else if (!streq(readlineP->arg[0], expected))
-        pm_error("Statement of type '%s' where '%s' expected in BDF font file",
-                 readlineP->arg[0], expected);
-}
-
-
-
-static void
-skipCharacter(Readline * const readlineP) {
-/*----------------------------------------------------------------------------
-  In the BDF font file being read by readline object *readlineP, skip through
-  the end of the character we are presently in.
------------------------------------------------------------------------------*/
-    bool endChar;
-                        
-    endChar = FALSE;
-                        
-    while (!endChar) {
-        bool eof;
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file in the middle of a character (before "
-                     "ENDCHAR) in BDF font file.");
-        endChar = streq(readlineP->arg[0], "ENDCHAR");
-    }                        
-}
-
-
-
-static void
-interpEncoding(const char **  const arg,
-               unsigned int * const codepointP,
-               bool *         const badCodepointP,
-               PM_WCHAR       const maxglyph) {
-/*----------------------------------------------------------------------------
-   With arg[] being the ENCODING statement from the font, return as
-   *codepointP the codepoint that it indicates (code point is the character
-   code, e.g. in ASCII, 48 is '0').
-
-   But if the statement doesn't give an acceptable codepoint return
-   *badCodepointP == TRUE.
-
-   'maxglyph' is the maximum codepoint in the font.
------------------------------------------------------------------------------*/
-    bool gotCodepoint;
-    bool badCodepoint;
-    unsigned int codepoint;
-
-    if (atoi(arg[1]) >= 0) {
-        codepoint = atoi(arg[1]);
-        gotCodepoint = true;
-    } else {
-      if (atoi(arg[1]) == -1 && arg[2] != NULL) {
-            codepoint = atoi(arg[2]);
-            gotCodepoint = true;
-        } else
-            gotCodepoint = false;
-    }
-    if (gotCodepoint) {
-        if (codepoint > maxglyph)
-            badCodepoint = true;
-        else
-            badCodepoint = false;
-    } else
-        badCodepoint = true;
-
-    *badCodepointP = badCodepoint;
-    *codepointP    = codepoint;
-}
-
-
-
-static void
-readEncoding(Readline *     const readlineP,
-             unsigned int * const codepointP,
-             bool *         const badCodepointP,
-             PM_WCHAR       const maxglyph) {
-
-    readExpectedStatement(readlineP, "ENCODING");
-    
-    interpEncoding(readlineP->arg, codepointP, badCodepointP, maxglyph);
-}
-
-
-
-static void
-validateFontLimits(const struct font2 * const fontP) {
-
-    assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0);
-
-    if (fontP->maxwidth  <= 0 ||
-        fontP->maxheight <= 0 ||
-        fontP->maxwidth  > pbm_maxfontwidth()  ||
-        fontP->maxheight > pbm_maxfontheight() ||
-        -fontP->x + 1 > fontP->maxwidth ||
-        -fontP->y + 1 > fontP->maxheight ||
-        fontP->x > fontP->maxwidth  ||
-        fontP->y > fontP->maxheight ||
-        fontP->x + fontP->maxwidth  > pbm_maxfontwidth() || 
-        fontP->y + fontP->maxheight > pbm_maxfontheight()
-        ) {
-
-        pm_error("Global font metric(s) out of bounds."); 
-    }
-
-    if (fontP->maxglyph > PM_FONT2_MAXGLYPH)
-        pm_error("Internal error.  Glyph table too large: %u glyphs; "
-                 "Maximum possible in Netpbm is %u",
-                 fontP->maxglyph, PM_FONT2_MAXGLYPH);
-}
-
-
-
-static void
-validateGlyphLimits(const struct font2 * const fontP,
-                    const struct glyph * const glyphP,
-                    const char *         const charName) {
-
-    /* Some BDF files code space with zero width and height,
-       no bitmap data and just the xadd value.
-       We allow zero width and height, iff both are zero.
-    */
-
-    if (((glyphP->width == 0 || glyphP->height == 0) &&
-         !(glyphP->width == 0 && glyphP->height == 0)) ||
-        glyphP->width  > fontP->maxwidth  ||
-        glyphP->height > fontP->maxheight ||
-        glyphP->x < fontP->x ||
-        glyphP->y < fontP->y ||
-        glyphP->x + (int) glyphP->width  > fontP->x + fontP->maxwidth  ||
-        glyphP->y + (int) glyphP->height > fontP->y + fontP->maxheight ||
-        glyphP->xadd > pbm_maxfontwidth() ||
-        glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width >
-        pbm_maxfontwidth()
-        ) {
-
-        pm_error("Font metric(s) for char '%s' out of bounds.\n", charName);
-    }
-}
-
-
-
-static void
-processChars(Readline *     const readlineP,
-             struct font2 * const fontP,
-             PM_WCHAR       const maxglyph ) {
-/*----------------------------------------------------------------------------
-   Process the CHARS block in a BDF font file, assuming the file is positioned
-   just after the CHARS line.  Read the rest of the block and apply its
-   contents to *fontP.
------------------------------------------------------------------------------*/
-    unsigned int const nCharacters = atoi(readlineP->arg[1]);
-
-    unsigned int nCharsDone;
-
-    nCharsDone = 0;
-
-    while (nCharsDone < nCharacters) {
-        bool eof;
-
-        readline_read(readlineP, &eof);
-        if (eof)
-            pm_error("End of file after CHARS reading BDF font file");
-
-        if (streq(readlineP->arg[0], "COMMENT")) {
-            /* ignore */
-        } else if (!streq(readlineP->arg[0], "STARTCHAR"))
-            pm_error("no STARTCHAR after CHARS in BDF font file");
-        else {
-            const char * const charName = pm_strdup(readlineP->arg[1]);
-
-            struct glyph * glyphP;
-            unsigned int codepoint;
-            bool badCodepoint;
-
-            assert(streq(readlineP->arg[0], "STARTCHAR"));
-
-            MALLOCVAR(glyphP);
-
-            if (glyphP == NULL)
-                pm_error("no memory for font glyph for '%s' character",
-                         charName);
-
-            readEncoding(readlineP, &codepoint, &badCodepoint, maxglyph);
-
-            if (badCodepoint)
-                skipCharacter(readlineP);
-            else if (fontP->glyph[codepoint] != NULL)
-                pm_error("Multiple definition of code point %d "
-                         "in font file", (unsigned int) codepoint); 
-            else {
-                readExpectedStatement(readlineP, "SWIDTH");
-                    
-                readExpectedStatement(readlineP, "DWIDTH");
-                glyphP->xadd = atoi(readlineP->arg[1]);
-
-                readExpectedStatement(readlineP, "BBX");
-                glyphP->width  = atoi(readlineP->arg[1]);
-                glyphP->height = atoi(readlineP->arg[2]);
-                glyphP->x      = atoi(readlineP->arg[3]);
-                glyphP->y      = atoi(readlineP->arg[4]);
-
-                validateGlyphLimits(fontP, glyphP, charName);
-
-                createBmap(glyphP->width, glyphP->height, readlineP, charName,
-                           &glyphP->bmap);
-                
-
-                readExpectedStatement(readlineP, "ENDCHAR");
-
-                assert(codepoint <= maxglyph); /* Ensured by readEncoding() */
-
-                fontP->glyph[codepoint] = glyphP;
-                pm_strfree(charName);
-            }
-            ++nCharsDone;
-        }
-    }
-}
-
-
-
-static void
-processBdfFontLine(Readline     * const readlineP,
-                   struct font2 * const fontP,
-                   bool         * const endOfFontP,
-                   PM_WCHAR       const maxglyph) {
-/*----------------------------------------------------------------------------
-   Process a nonblank line just read from a BDF font file.
-
-   This processing may involve reading more lines.
------------------------------------------------------------------------------*/
-    *endOfFontP = FALSE;  /* initial assumption */
-
-    assert(readlineP->arg[0] != NULL);  /* Entry condition */
-
-    if (streq(readlineP->arg[0], "COMMENT")) {
-        /* ignore */
-    } else if (streq(readlineP->arg[0], "SIZE")) {
-        /* ignore */
-    } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) {
-        /* Read off the properties and ignore them all */
-        unsigned int const propCount = atoi(readlineP->arg[1]);
-
-        unsigned int i;
-        for (i = 0; i < propCount; ++i) {
-            bool eof;
-            readline_read(readlineP, &eof);
-            if (eof)
-                pm_error("End of file after STARTPROPERTIES in BDF font file");
-        }
-    } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) {
-        fontP->maxwidth  = atoi(readlineP->arg[1]);
-        fontP->maxheight = atoi(readlineP->arg[2]);
-        fontP->x = atoi(readlineP->arg[3]);
-        fontP->y = atoi(readlineP->arg[4]);
-        validateFontLimits(fontP);
-    } else if (streq(readlineP->arg[0], "ENDPROPERTIES")) {
-      if (fontP->maxwidth ==0)
-      pm_error("Encountered ENDPROPERTIES before FONTBOUNDINGBOX " 
-                   "in BDF font file");
-    } else if (streq(readlineP->arg[0], "ENDFONT")) {
-        *endOfFontP = true;
-    } else if (streq(readlineP->arg[0], "CHARS")) {
-      if (fontP->maxwidth ==0)
-      pm_error("Encountered CHARS before FONTBOUNDINGBOX " 
-                   "in BDF font file");
-      else
-        processChars(readlineP, fontP, maxglyph);
-    } else {
-        /* ignore */
-    }
-}
-
-
-
-struct font2 *
-pbm_loadbdffont2(const char * const filename,
-                 PM_WCHAR     const maxglyph) {
-/*----------------------------------------------------------------------------
-   Read a BDF font file "filename" as a 'font2' structure.  A 'font2'
-   structure is more expressive than a 'font' structure, most notably in that
-   it can handle wide code points and many more glyphs.
-
-   Codepoints up to maxglyph inclusive are valid in the file.
-
-   The returned object is in new malloc'ed storage, in many pieces, and
-   cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* For our return object to be destroyable, we need to supply a destroy
-       function, and it needs to return glyph and raster memory, and
-       struct font needs to manage glyph memory separately from mapping
-       code points to glyphs.
-    */
-    FILE *         ifP;
-    Readline       readline;
-    struct font2 * font2P;
-    bool           endOfFont;
-
-    ifP = fopen(filename, "rb");
-    if (!ifP)
-        pm_error("Unable to open BDF font file name '%s'.  errno=%d (%s)",
-                 filename, errno, strerror(errno));
-
-    readline_init(&readline, ifP);
-
-    MALLOCVAR(font2P);
-    if (font2P == NULL)
-        pm_error("no memory for font");
-
-    MALLOCARRAY(font2P->glyph, maxglyph + 1);
-    if (font2P->glyph == NULL)
-        pm_error("no memory for font glyphs");
-
-    font2P->maxglyph = maxglyph;
-
-    font2P->oldfont = NULL;
-    {
-        /* Initialize all characters to nonexistent; we will fill the ones we
-           find in the bdf file later.
-        */
-        PM_WCHAR i;
-        for (i = 0; i <= maxglyph; ++i)
-            font2P->glyph[i] = NULL;
-    }
-
-    font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0;
-
-    readExpectedStatement(&readline, "STARTFONT");
-
-    endOfFont = FALSE;
-
-    while (!endOfFont) {
-        bool eof;
-        readline_read(&readline, &eof);
-        if (eof)
-            pm_error("End of file before ENDFONT statement in BDF font file");
-
-        processBdfFontLine(&readline, font2P, &endOfFont, maxglyph);
-    }
-    return font2P;
-}
-
-
-
-struct font *
-pbm_loadbdffont(const char * const filename) {
-/*----------------------------------------------------------------------------
-   Read a BDF font file "filename" into a traditional font structure.
-
-   Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid.
-
-   Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc.
-
-   The returned object is in new malloc'ed storage, in many pieces, and
-   cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* For our return object to deep copy the glyphs and fonts from
-       the struct font2be destroyable, we need to supply a destroy
-       function, and it needs to return glyph and raster memory, and
-       struct font needs to manage glyph memory separately from mapping
-       code points to glyphs.
-    */
-    struct font  * fontP;
-    struct font2 * font2P;
-    unsigned int   codePoint;
-
-    MALLOCVAR(fontP);
-    if (fontP == NULL)
-        pm_error("no memory for font");
-
-    font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH);
-
-    fontP->maxwidth  = font2P->maxwidth;
-    fontP->maxheight = font2P->maxheight;
-
-    fontP->x = font2P->x;
-    fontP->y = font2P->y;
-
-    for (codePoint = 0; codePoint < PM_FONT_MAXGLYPH + 1; ++codePoint)
-        fontP->glyph[codePoint] = font2P->glyph[codePoint];
-
-    fontP->oldfont = NULL;
-
-    fontP->fcols = 0;
-    fontP->frows = 0;
-
-    /* Note that *fontP2 is unfreeable.  See pbm_loadbdffont2.  And even if it
-       were, we hooked *fontP into it above, so that would have to turn into a
-       deep copy before we could free *fontP2.
-    */
-
-    return fontP;
-}
-
-
-
-struct font2 *
-pbm_expandbdffont(const struct font * const fontP) {
-/*----------------------------------------------------------------------------
-  Convert a traditional font structure into an expanded font2 structure.
-
-  This function depends upon the fact that *fontP, like any struct font,
-  cannot be destroyed.  The returned object refers to memory that belongs
-  to *fontP.
-
-  The returned object is in new malloc'ed storage, in many pieces, and
-  cannot be destroyed.
------------------------------------------------------------------------------*/
-    /* If we ever make struct font destroyable, this function needs to
-       copy the glyphs and rasters, and struct font and struct font2 need
-       to manage glyph memory separately from mapping code points to the
-       glyphs.
-    */
-    PM_WCHAR const maxglyph = PM_FONT_MAXGLYPH;
-
-    struct font2 * font2P;
-    unsigned int   codePoint;
-
-    MALLOCVAR(font2P);
-    if (font2P == NULL)
-        pm_error("no memory for font");
-
-    MALLOCARRAY(font2P->glyph, maxglyph + 1);
-    if (font2P->glyph == NULL)
-        pm_error("no memory for font glyphs");
-
-    font2P->maxwidth  = fontP->maxwidth;
-    font2P->maxheight = fontP->maxheight;
-
-    font2P->x = fontP->x;
-    font2P->y = fontP->y;
-
-    font2P->maxglyph = maxglyph;
-
-    for (codePoint = 0; codePoint < maxglyph + 1; ++codePoint)
-        font2P->glyph[codePoint] = fontP->glyph[codePoint];
-
-    font2P->oldfont = fontP->oldfont;
-
-    font2P->fcols = fontP->fcols;
-    font2P->frows = fontP->frows;
-
-    return font2P;
-}
-
-
diff --git a/lib/libpbmfont0.c b/lib/libpbmfont0.c
new file mode 100644
index 00000000..5a645249
--- /dev/null
+++ b/lib/libpbmfont0.c
@@ -0,0 +1,333 @@
+/*
+**
+** Font routines.
+**
+** Wide character stuff written by Akira Urushibata, 2018
+**
+** Also copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbm.h"
+#include "pbmfont.h"
+#include "pbmfontdata.h"
+
+
+struct font *
+pbm_defaultfont(const char * const name) {
+/*----------------------------------------------------------------------------
+   Generate the built-in font with name 'name'.
+-----------------------------------------------------------------------------*/
+    struct font * retval;
+
+    if (streq(name, "bdf"))
+        retval = &pbm_defaultBdffont;
+    else if (streq(name, "fixed"))
+        retval = &pbm_defaultFixedfont;
+    else
+        pm_error( "built-in font name unknown, try 'bdf' or 'fixed'");
+
+    return retval;
+}
+
+
+
+struct font2 *
+pbm_defaultfont2(const char * const requestedFontName) {
+
+    struct font2 * font2P;
+    struct font2 * retval = NULL; /* initial value */
+    unsigned int i;
+
+    for (i = 0; retval == NULL; ++i) {
+        const char * longName;
+        const char * shortName;
+        font2P = (struct font2 * ) pbm_builtinFonts[i];
+        if (font2P == NULL)
+            break;
+
+        longName = font2P->name;
+        shortName = &longName[strlen("builtin ")];
+
+        if (streq(shortName, requestedFontName))
+             retval = font2P;
+    }
+
+    if (retval == NULL)
+        pm_error("No builtin font named %s", requestedFontName);
+
+    return retval;
+}
+
+
+
+static void
+selectFontType(const    char * const filename,
+               PM_WCHAR        const maxmaxglyph,
+               unsigned int    const isWide,
+               struct font  ** const fontPP,
+               struct font2 ** const font2PP) {
+
+    FILE * fileP;
+    struct font  * fontP  = NULL; /* initial value */
+    struct font2 * font2P = NULL; /* initial value */
+    char line[10] = "\0\0\0\0\0\0\0\0\0\0";
+        /* Initialize to suppress Valgrind error which is reported when file
+           is empty or very small.
+        */
+
+    fileP = pm_openr(filename);
+    fgets(line, 10, fileP);
+    pm_close(fileP);
+
+    if (line[0] == PBM_MAGIC1 &&
+        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
+        if (isWide == TRUE)
+            font2P = pbm_loadpbmfont2(filename);
+        else
+            fontP  = pbm_loadpbmfont(filename);
+        if (fontP == NULL && font2P == NULL)
+            pm_error("could not load PBM font file");
+
+    } else if (!strncmp(line, "STARTFONT", 9)) {
+        if (isWide == TRUE)
+            font2P = pbm_loadbdffont2(filename, maxmaxglyph);
+        else
+            fontP = pbm_loadbdffont(filename);
+        if (fontP == NULL && font2P == NULL)
+            pm_error("could not load BDF font file");
+
+    } else {
+        pm_error("font file not in a recognized format.  Does not start "
+                 "with the signature of a PBM file or BDF font file");
+        assert(false);
+        fontP = NULL;  /* defeat compiler warning */
+    }
+
+    if (isWide)
+        *font2PP = font2P;
+    else
+        *fontPP = fontP;
+}
+
+
+
+struct font *
+pbm_loadfont(const    char * const filename) {
+
+    struct font  * fontP;
+    struct font2 * font2P;
+
+    selectFontType(filename, PM_FONT_MAXGLYPH, FALSE, &fontP, &font2P);
+    return fontP;
+}
+
+
+
+struct font2 *
+pbm_loadfont2(const    char * const filename,
+              PM_WCHAR        const maxmaxglyph) {
+
+    struct font  * fontP;
+    struct font2 * font2P;
+
+    selectFontType(filename, maxmaxglyph, TRUE, &fontP, &font2P);
+    return font2P;
+}
+
+
+
+void
+pbm_createbdffont2_base(struct font2 ** const font2PP,
+                        PM_WCHAR        const maxmaxglyph) {
+
+    struct font2 * font2P;
+
+    MALLOCVAR(font2P);
+    if (font2P == NULL)
+        pm_error("no memory for font");
+
+    MALLOCARRAY(font2P->glyph, maxmaxglyph + 1);
+    if (font2P->glyph == NULL)
+        pm_error("no memory for font glyphs");
+
+    /* Initialize */
+    font2P->size = sizeof (struct font2);
+    font2P->len  = PBM_FONT2_STRUCT_SIZE(charset_string);
+
+    /*  Caller should overwrite following fields as necessary */
+    font2P->oldfont = NULL;
+    font2P->fcols = font2P->frows = 0;
+    font2P->selector = NULL;
+    font2P->default_char = 0;
+    font2P->default_char_defined = FALSE;
+    font2P->total_chars = font2P->chars = 0;
+    font2P->name = NULL;
+    font2P->charset = ENCODING_UNKNOWN;
+    font2P->charset_string = NULL;
+
+    *font2PP = font2P;
+}
+
+
+
+static void
+destroyGlyphData(struct glyph ** const glyph,
+                 PM_WCHAR        const maxglyph) {
+/*----------------------------------------------------------------------------
+  Free glyph objects and bitmap objects.
+
+  This does not work when an object is "shared" through multiple pointers
+  referencing an identical address and thus pointing to a common glyph
+  or bitmap object.
+-----------------------------------------------------------------------------*/
+
+    PM_WCHAR i;
+
+    for(i = 0; i <= maxglyph; ++i) {
+        if (glyph[i]!=NULL) {
+            free((void *) (glyph[i]->bmap));
+            free(glyph[i]);
+      }
+    }
+}
+
+
+void
+pbm_destroybdffont2_base(struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+  Free font2 structure, but not the glyph data
+---------------------------------------------------------------------------- */
+
+    free(font2P->selector);
+
+    free(font2P->name);
+    free(font2P->charset_string);
+    free(font2P->glyph);
+
+    if (font2P->oldfont !=NULL)
+       pbm_freearray(font2P->oldfont, font2P->frows);
+
+    free((void *)font2P);
+
+}
+
+
+
+void
+pbm_destroybdffont2(struct font2 * const font2P) {
+/*----------------------------------------------------------------------------
+  Free font2 structure and glyph data
+
+  Examines the 'load_fn' field to check whether the object is fixed data.
+  Do nothing if 'load_fn' is 'FIXED_DATA'.
+---------------------------------------------------------------------------- */
+
+    if (font2P->load_fn != FIXED_DATA) {
+        destroyGlyphData(font2P->glyph, font2P->maxglyph);
+        pbm_destroybdffont2_base(font2P);
+    }
+}
+
+
+
+void
+pbm_destroybdffont(struct font * const fontP) {
+/*----------------------------------------------------------------------------
+  Free font structure and glyph data.
+
+  For freeing a structure created by pbm_loadbdffont() or pbm_loadpbmfont().
+---------------------------------------------------------------------------- */
+
+    destroyGlyphData(fontP->glyph, PM_FONT_MAXGLYPH);
+
+    if (fontP->oldfont !=NULL)
+       pbm_freearray(fontP->oldfont, fontP->frows);
+
+    free(fontP);
+}
+
+
+
+struct font2 *
+pbm_expandbdffont(const struct font * const fontP) {
+/*----------------------------------------------------------------------------
+  Convert a traditional 'font' structure into an expanded 'font2' structure.
+
+  After calling this function *fontP may be freed, but not the individual
+  glyph data: fontP->glyph[0...255] .
+
+  Using this function on static data is not recommended.  Rather add
+  the extra fields to make a font2 structure.  See file pbmfontdata1.c
+  for an example.
+
+  The returned object is in new malloc'ed storage, in many pieces.
+
+  Destroy with pbm_destroybdffont2() if *fontP is read from a file.
+
+  Destroy with pbm_destroybdffont2_base() if *fontP is static data
+  and you desire to defy the above-stated recommendation.
+
+  The general function for conversion in the opposite direction
+  'font2' => 'font' is font2ToFont() in libpbmfont2.c .  It is currently
+  declared as static.
+ ---------------------------------------------------------------------------*/
+    PM_WCHAR maxglyph, codePoint;
+    unsigned int nCharacters;
+    struct font2 * font2P;
+
+    pbm_createbdffont2_base(&font2P, PM_FONT_MAXGLYPH);
+
+    font2P->maxwidth  = fontP->maxwidth;
+    font2P->maxheight = fontP->maxheight;
+
+    font2P->x = fontP->x;
+    font2P->y = fontP->y;
+
+    /* Hunt for max non-NULL entry in glyph table */
+    for (codePoint = PM_FONT_MAXGLYPH;
+         fontP->glyph[codePoint] == NULL && codePoint > 0; --codePoint)
+        ;
+
+    maxglyph = font2P->maxglyph = codePoint;
+    assert (0 <= maxglyph && maxglyph <= PM_FONT_MAXGLYPH);
+
+    if (maxglyph == 0 && fontP->glyph[0] == NULL)
+        pm_error("no glyphs loaded");
+
+    REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1);
+
+    for (codePoint = 0; codePoint <= maxglyph; ++codePoint) {
+        font2P->glyph[codePoint] = fontP->glyph[codePoint];
+
+        if (font2P->glyph[codePoint] != NULL)
+           ++nCharacters;
+    }
+
+    font2P->oldfont = fontP->oldfont;
+    font2P->fcols = fontP->fcols;
+    font2P->frows = fontP->frows;
+
+    font2P->bit_format = PBM_FORMAT;
+    font2P->total_chars = font2P->chars = nCharacters;
+    font2P->load_fn = CONVERTED_TYPE1_FONT;
+    /* Caller should be overwrite the above to a more descriptive
+       value */
+    return font2P;
+}
+
+
+
diff --git a/lib/libpbmfont1.c b/lib/libpbmfont1.c
new file mode 100644
index 00000000..2b0993a9
--- /dev/null
+++ b/lib/libpbmfont1.c
@@ -0,0 +1,359 @@
+/*
+**
+** Routines for loading a PBM sheet font file
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbm.h"
+#include "pbmfont.h"
+
+
+/*----------------------------------------------------------------------------
+
+  The routines in this file reads a font bitmap representing
+  the following text:
+
+  (0,0)
+     M ",/^_[`jpqy| M
+
+     /  !"#$%&'()*+ /
+     < ,-./01234567 <
+     > 89:;<=>?@ABC >
+     @ DEFGHIJKLMNO @
+     _ PQRSTUVWXYZ[ _
+     { \]^_`abcdefg {
+     } hijklmnopqrs }
+     ~ tuvwxyz{|}~  ~
+
+     M ",/^_[`jpqy| M
+
+  The bitmap must be cropped exactly to the edges.
+
+  The characters in the border you see are irrelevant except for
+  character size compuations.  The 12 x 8 array in the center is
+  the font.  The top left character there belongs to code point
+  0, and the code points increase in standard reading order, so
+  the bottom right character is code point 127.  You can't define
+  code points < 32 or > 127 with this font format.
+
+  The characters in the top and bottom border rows must include a
+  character with the lowest reach of any in the font (e.g. "y",
+  "_") and one with the highest reach (e.g. '"').  The characters
+  in the left and right border columns must include characters
+  with the rightmost and leftmost reach of any in the font
+  (e.g. "M" for both).
+
+  The border must be separated from the font by one blank text
+  row or text column.
+-----------------------------------------------------------------------------*/
+
+
+static unsigned int const firstCodePoint = 32;
+    /* This is the code point of the first character in a pbmfont font.
+       In ASCII, it is a space.
+    */
+
+static unsigned int const nCharsInFont = 96;
+    /* The number of characters in a pbmfont font.  A pbmfont font defines
+       characters at position 32 (ASCII space) through 127, so that's 96.
+    */
+
+
+static void
+findFirstBlankRow(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const browP) {
+
+    unsigned int row;
+    bool foundBlank;
+
+    for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) {
+        unsigned int col;
+        bit col0Value = font[row][0];
+        bool rowIsBlank;
+        rowIsBlank = true;  /* initial assumption */
+        for (col = 1; col < fcols; ++col)
+            if (font[row][col] != col0Value)
+                rowIsBlank = false;
+
+        if (rowIsBlank) {
+            foundBlank = true;
+            *browP = row;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel row in font");
+}
+
+
+
+static void
+findFirstBlankCol(const bit **   const font,
+                  unsigned int   const fcols,
+                  unsigned int   const frows,
+                  unsigned int * const bcolP) {
+
+    unsigned int col;
+    bool foundBlank;
+
+    for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) {
+        unsigned int row;
+        bit row0Value = font[0][col];
+        bool colIsBlank;
+        colIsBlank = true;  /* initial assumption */
+        for (row = 1; row < frows; ++row)
+            if (font[row][col] != row0Value)
+                colIsBlank = false;
+
+        if (colIsBlank) {
+            foundBlank = true;
+            *bcolP = col;
+        }
+    }
+
+    if (!foundBlank)
+        pm_error("couldn't find blank pixel column in font");
+}
+
+
+
+static void
+computeCharacterSize(const bit **   const font,
+                     unsigned int   const fcols,
+                     unsigned int   const frows,
+                     unsigned int * const cellWidthP,
+                     unsigned int * const cellHeightP,
+                     unsigned int * const charWidthP,
+                     unsigned int * const charHeightP) {
+
+    unsigned int firstBlankRow;
+    unsigned int firstBlankCol;
+    unsigned int heightLast11Rows;
+
+    findFirstBlankRow(font, fcols, frows, &firstBlankRow);
+
+    findFirstBlankCol(font, fcols, frows, &firstBlankCol);
+
+    heightLast11Rows = frows - firstBlankRow;
+
+    if (heightLast11Rows % 11 != 0)
+        pm_error("The rows of characters in the font do not appear to "
+                 "be all the same height.  The last 11 rows are %u pixel "
+                 "rows high (from pixel row %u up to %u), "
+                 "which is not a multiple of 11.",
+                 heightLast11Rows, firstBlankRow, frows);
+    else {
+        unsigned int widthLast15Cols;
+
+        *cellHeightP = heightLast11Rows / 11;
+
+        widthLast15Cols = fcols - firstBlankCol;
+
+        if (widthLast15Cols % 15 != 0)
+            pm_error("The columns of characters in the font do not appear to "
+                     "be all the same width.  "
+                     "The last 15 columns are %u pixel "
+                     "columns wide (from pixel col %u up to %u), "
+                     "which is not a multiple of 15.",
+                     widthLast15Cols, firstBlankCol, fcols);
+        else {
+            *cellWidthP = widthLast15Cols / 15;
+
+            *charWidthP = firstBlankCol;
+            *charHeightP = firstBlankRow;
+        }
+    }
+}
+
+
+
+struct font*
+pbm_dissectfont(const bit ** const fontsheet,
+                unsigned int const frows,
+                unsigned int const fcols) {
+/*----------------------------------------------------------------------------
+  Dissect PBM sheet font data, create a font structre,
+  load bitmap data into it.
+
+  Return value is a pointer to the newly created font structure
+
+  The input bitmap data is in memory, in one byte per pixel format.
+
+  The dissection works by finding the first blank row and column;
+  i.e the lower right corner of the "M" in the upper left corner
+  of the matrix.  That gives the height and width of the
+  maximum-sized character, which is not too useful.  But the
+  distance from there to the opposite side is an integral
+  multiple of the cell size, and that's what we need.  Then it's
+  just a matter of filling in all the coordinates.
+
+  Struct font has fields 'oldfont', 'fcols', 'frows' for backward
+  compability.  If there is any need to load data stored in this format
+  feed the above three, in order, as arguments to this function:
+
+    pbm_dissectfont(oldfont, fcols, frows);
+ ----------------------------------------------------------------------------*/
+
+    unsigned int cellWidth, cellHeight;
+        /* Dimensions in pixels of each cell of the font -- that
+           includes the glyph and the white space above and to the
+           right of it.  Each cell is a tile of the font image.  The
+           top character row and left character row don't count --
+           those cells are smaller because they are missing the white
+           space.
+        */
+    unsigned int charWidth, charHeight;
+        /* Maximum dimensions of glyph itself, inside its cell */
+
+    int row, col, ch, r, c, i;
+    struct font * fn;
+
+    computeCharacterSize(fontsheet, fcols, frows,
+                         &cellWidth, &cellHeight, &charWidth, &charHeight);
+
+    /* Now convert to a general font */
+
+    MALLOCVAR(fn);
+    if (fn == NULL)
+        pm_error("out of memory allocating font structure");
+
+    fn->maxwidth  = charWidth;
+    fn->maxheight = charHeight;
+    fn->x = fn->y = 0;
+
+    fn->oldfont = fontsheet;
+    fn->frows = frows;
+    fn->fcols = fcols;
+
+    /* Now fill in the 0,0 coords. */
+    row = cellHeight * 2;
+    col = cellWidth  * 2;
+
+    /* Load individual glyphs */
+    for ( ch = 0; ch < nCharsInFont; ++ch ) {
+        /* Allocate memory separately for each glyph.
+           pbm_loadbdffont2() does this in exactly the same manner.
+         */
+        struct glyph * const glyph =
+             (struct glyph *) malloc (sizeof (struct glyph));
+        char * const bmap = (char*) malloc(fn->maxwidth * fn->maxheight);
+
+        if ( bmap == NULL || glyph == NULL )
+            pm_error( "out of memory allocating glyph data" );
+
+        glyph->width  = fn->maxwidth;
+        glyph->height = fn->maxheight;
+        glyph->x = glyph->y = 0;
+        glyph->xadd = cellWidth;
+
+        for ( r = 0; r < glyph->height; ++r )
+            for ( c = 0; c < glyph->width; ++c )
+                bmap[r * glyph->width + c] = fontsheet[row + r][col + c];
+
+        glyph->bmap = bmap;
+        fn->glyph[firstCodePoint + ch] = glyph;
+
+        col += cellWidth;
+        if ( col >= cellWidth * 14 ) {
+            col = cellWidth * 2;
+            row += cellHeight;
+        }
+    }
+
+    /* Initialize all remaining character positions to "undefined." */
+    for (i = 0; i < firstCodePoint; ++i)
+        fn->glyph[i] = NULL;
+
+    for (i = firstCodePoint + nCharsInFont; i <= PM_FONT_MAXGLYPH; ++i)
+        fn->glyph[i] = NULL;
+
+    return fn;
+}
+
+
+
+
+struct font *
+pbm_loadpbmfont(const char * const filename) {
+/*----------------------------------------------------------------------------
+  Read PBM font sheet data from file 'filename'.
+  Load data into font structure.
+
+  When done with object, free with pbm_destroybdffont().
+-----------------------------------------------------------------------------*/
+
+    FILE * ifP;
+    bit ** fontsheet;
+    int fcols, frows;
+    struct font * retval;
+
+    ifP = pm_openr(filename);
+
+    fontsheet = pbm_readpbm(ifP, &fcols, &frows);
+
+    if ((fcols - 1) / 16 >= pbm_maxfontwidth() ||
+        (frows - 1) / 12 >= pbm_maxfontheight())
+        pm_error("Absurdly large PBM font file: %s", filename);
+    else if (fcols < 31 || frows < 23) {
+        /* Need at least one pixel per character, and this is too small to
+           have that.
+        */
+        pm_error("PBM font file '%s' too small to be a font file: %u x %u.  "
+                 "Minimum sensible size is 31 x 23",
+                 filename, fcols, frows);
+    }
+
+    pm_close(ifP);
+
+    retval = pbm_dissectfont((const bit **)fontsheet, frows, fcols);
+    return (retval);
+
+}
+
+
+
+struct font2 *
+pbm_loadpbmfont2(const char * const filename) {
+/*----------------------------------------------------------------------------
+  Like pbm_loadpbmfont, but return a pointer to struct font2.
+
+  When done with object, free with pbm_destroybdffont2().
+-----------------------------------------------------------------------------*/
+
+    const struct font * const pbmfont = pbm_loadpbmfont(filename);
+    struct font2 * const retval  = pbm_expandbdffont(pbmfont);
+
+    free ((void *)pbmfont);
+
+    /* Overwrite some fields */
+
+    retval->load_fn = LOAD_PBMSHEET;
+    retval->default_char = (PM_WCHAR) ' ';
+    retval->default_char_defined = TRUE;
+    retval->name = strdup("(PBM sheet font has no name)");
+    retval->charset = ISO646_1991_IRV;
+    retval->charset_string = strdup("ASCII");
+    retval->total_chars = retval->chars = nCharsInFont;
+
+    return(retval);
+}
+
+
+
diff --git a/lib/libpbmfont2.c b/lib/libpbmfont2.c
new file mode 100644
index 00000000..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;
+}
+
+
+
diff --git a/lib/libpbmfontdump.c b/lib/libpbmfontdump.c
new file mode 100644
index 00000000..f0c950f7
--- /dev/null
+++ b/lib/libpbmfontdump.c
@@ -0,0 +1,96 @@
+/*
+**
+** Font routines.
+**
+** BDF font code Copyright 1993 by George Phillips.
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** BDF font specs available from:
+** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf
+** Glyph Bitmap Distribution Format (BDF) Specification
+** Version 2.2
+** 22 March 1993
+** Adobe Developer Support
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
+#include "pbmfont.h"
+#include "pbm.h"
+
+
+void
+pbm_dumpfont(struct font * const fontP,
+             FILE *        const ofP) {
+/*----------------------------------------------------------------------------
+  Dump out font as C source code.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    unsigned int ng;
+
+    if (fontP->oldfont)
+        pm_message("Netpbm no longer has the capability to generate "
+                   "a font in long hexadecimal data format");
+
+    for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) {
+        if (fontP->glyph[i])
+            ++ng;
+    }
+
+    printf("static struct glyph _g[%d] = {\n", ng);
+
+    for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
+        struct glyph * const glyphP = fontP->glyph[i];
+        if (glyphP) {
+            unsigned int j;
+            printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height,
+                   glyphP->x, glyphP->y, glyphP->xadd);
+
+            for (j = 0; j < glyphP->width * glyphP->height; ++j) {
+                if (glyphP->bmap[j])
+                    printf("\\1");
+                else
+                    printf("\\0");
+            }
+            --ng;
+            printf("\" }%s\n", ng ? "," : "");
+        }
+    }
+    printf("};\n");
+
+    printf("struct font XXX_font = { %d, %d, %d, %d, {\n",
+           fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y);
+
+    {
+        unsigned int i;
+
+        for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) {
+            if (fontP->glyph[i])
+                printf(" _g + %d", ng++);
+            else
+                printf(" NULL");
+
+            if (i != PM_FONT_MAXGLYPH) printf(",");
+            printf("\n");
+        }
+    }
+
+    printf(" }\n};\n");
+}
+
+
+
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
index ad5d3acf..57f19ddc 100644
--- a/lib/pbmfont.h
+++ b/lib/pbmfont.h
@@ -13,11 +13,14 @@ extern "C" {
 
 /* Maximum dimensions for fonts */
 
-#define  pbm_maxfontwidth()  65536
-#define  pbm_maxfontheight() 65536
+#define  pbm_maxfontwidth()  65535
+#define  pbm_maxfontheight() 65535
     /* These limits are not in the official Adobe BDF definition, but
        should never be a problem for practical purposes, considering that
-       a 65536 x 65536 glyph occupies 4G pixels. 
+       a 65536 x 65536 glyph occupies 4G pixels.
+
+       Note that the maximum line length allowed in a BDF file imposes
+       another restriction.
     */
 
 typedef wchar_t PM_WCHAR;
@@ -38,6 +41,39 @@ typedef wchar_t PM_WCHAR;
        As of Unicode v. 11.0.0 planes up to 16 are defined.
     */
 
+enum pbmFontLoad { FIXED_DATA           = 0,
+                   LOAD_PBMSHEET        = 1,
+                   LOAD_BDFFILE         = 2,
+                   CONVERTED_TYPE1_FONT = 9 };
+
+static const char * const pbmFontOrigin[10] =
+                 {"Fixed data",                                   /* 0 */
+                  "Loaded from PBM sheet by libnetpbm",           /* 1 */
+                  "Loaded from BDF file by libnetpbm",            /* 2 */
+                  NULL, NULL, NULL, NULL, NULL, NULL,
+                  "Expanded from type 1 font structure by libnetpbm"}; /* 9 */
+
+enum pbmFontEncoding { ENCODING_UNKNOWN = 0,
+                       ISO646_1991_IRV = 1,   /* ASCII */
+                       ISO_8859_1 = 1000, ISO_8859_2, ISO_8859_3, ISO_8859_4,
+                       ISO_8859_5,   ISO_8859_6,   ISO_8859_7,   ISO_8859_8,
+                       ISO_8859_9,   ISO_8859_10,  ISO_8859_11,  ISO_8859_12,
+                       ISO_8859_13,  ISO_8859_14,  ISO_8859_15,  ISO_8859_16,
+                       ISO_10646 = 2000 };
+
+/* For future use */
+
+/* In addition to the above, the following CHARSET_REGISTRY-CHARSET_ENCODING
+   values have been observed in actual BDF files:
+
+  ADOBE-FONTSPECIFIC, DEC-DECTECH, GOST19768.74-1, IS13194-DEVANAGARI,
+  JISX0201.1976-0, KOI8-C, KOI8-R, MISC-FONTSPECIFIC,
+  MULEARABIC-0, MULEARABIC-1, MULEARABIC-2, MULEIPA-1, MULELAO-1,
+  OMRON_UDC_ZH-0, TIS620.2529-0, TIS620.2529-1, VISCII1-1, VISCII1.1-1,
+  XTIS-0
+ */
+
+
 struct glyph {
     /* A glyph consists of white borders and the "central glyph" which
        can be anything, but normally does not have white borders because
@@ -69,9 +105,14 @@ struct glyph {
            the top half and white on the bottom, this is an array of
            800 bytes, with the first 400 having value 0x01 and the
            last 400 having value 0x00.
+
+           Do not share bmap objects among glyphs if using
+           pbm_destroybdffont() or pbm_destroybdffont2() to free
+           the font/font2 structure.
         */
 };
 
+
 struct font {
     /* This describes a combination of font and character set.  Given
        an code point in the range 0..255, this structure describes the
@@ -86,7 +127,9 @@ struct font {
            this font.  Can be negative.
         */
     struct glyph * glyph[256];
-        /* glyph[i] is the glyph for code point i */
+        /* glyph[i] is the glyph for code point i.
+           Glyph objects must be unique for pbm_destroybdffont() to work.
+        */
     const bit ** oldfont;
         /* for compatibility with old pbmtext routines */
         /* oldfont is NULL if the font is BDF derived */
@@ -95,34 +138,162 @@ struct font {
 
 
 struct font2 {
-    /* Font structure for expanded character set.  Code point is in the
-       range 0..maxglyph .
+    /* Font structure for expanded character set.
+       Code points in the range 0...maxmaxglyph are loaded.
+       Loaded code point is in the range 0..maxglyph .
+     */
+
+    /* 'size' and 'len' are necessary in order to provide forward and
+       backward compatibility between library functions and calling programs
+       as this structure grows.  See struct pam in pam.h.
      */
+    unsigned int size;
+        /* The storage size of this entire structure, in bytes */
+
+    unsigned int len;
+        /* The length, in bytes, of the information in this structure.
+           The information starts in the first byte and is contiguous.
+           This cannot be greater than 'size'
+        */
+
     int maxwidth, maxheight;
 
     int x;
-        /* The minimum value of glyph.font.  The left edge of the glyph
-           in the glyph set which advances furthest to the left. */
+         /* The minimum value of glyph.font.  The left edge of the glyph in
+            the glyph set which advances furthest to the left.
+         */
     int y;
-        /* Amount of white space that should be added between lines of
-           this font.  Can be negative.
+        /* Amount of white space that should be added between lines of this
+           font.  Can be negative.
         */
+
     struct glyph ** glyph;
-        /* glyph[i] is the glyph for code point i */
+        /* glyph[i] is the glyph for code point i
+
+           Glyph objects must be unique for pbm_destroybdffont2() to work.
+           For example space and non-break-space are often identical at the
+           image data level; they must be loaded into separate memory
+           locations if using pbm_destroybdffont2().
+         */
 
     PM_WCHAR maxglyph;
-        /* max code point for glyphs, including vacant slots */
+        /* max code point for glyphs, including vacant slots max value of
+           above i
+        */
+
+    void * selector;
+        /* Reserved
+
+           Bit array or structure indicating which code points to load.
+
+           When NULL, all available code points up to maxmaxglyph, inclusive
+           are loaded.
+       */
+
+    PM_WCHAR maxmaxglyph;
+        /* Code points above this value are not loaded, even if they occur
+           in the BDF font file
+        */
 
     const bit ** oldfont;
-        /* for compatibility with old pbmtext routines */
-        /* oldfont is NULL if the font is BDF derived */
+        /* For compatibility with old pbmtext routines.
+           Valid only when data is in the form of a PBM sheet
+        */
 
     unsigned int fcols, frows;
+        /* For compatibility with old pbmtext routines.
+           Valid only when oldfont is non-NULL
+        */
+
+    unsigned int bit_format;
+        /* PBM_FORMAT:   glyph data: 1 byte per pixel (like P1, but not ASCII)
+           RPBM_FORMAT:  glyph data: 1 bit per pixel
+           Currently only PBM_FORMAT is possible
+        */
+
+    unsigned int total_chars;
+        /* Number of glyphs defined in font file, as stated in the CHARS line
+           of the BDF file PBM sheet font.  Always 96
+        */
+
+    unsigned int chars;
+        /* Number of glyphs actually loaded into structure
+
+           Maximum: total_chars
+
+           Less than total_chars when a subset of the file is loaded
+           PBM sheet font: always 96 */
+
+    enum pbmFontLoad load_fn;
+        /* Description of the function that created the structure and loaded
+           the glyph data
+
+           Used to choose a string to show in verbose messages.
+
+           FIXED_DATA (==0) means memory for this structure was not
+           dynamically allocated by a function; all data is hardcoded in
+           source code and resides in static data.  See file pbmfontdata1.c
+        */
+
+    PM_WCHAR default_char;
+        /* Code index of what to show when there is no glyph for a requested
+           code Available in many BDF fonts between STARPROPERTIES -
+           ENDPROPERTIES.
+
+           Set to value read from BDF font file.
+
+           Common values are 0, 32, 8481, 32382, 33, 159, 255.
+        */
+
+    unsigned int default_char_defined;
+        /* boolean
+           TRUE: above field is valid; DEFAULT_CHAR is defined in font file.
+           FALSE: font file has no DEFAULT_CHAR field.
+        */
+
+    char * name;
+        /* Name of the font.  Available in BDF fonts.
+           NULL means no name.
+        */
+
+    enum pbmFontEncoding charset;
+        /* Reserved for future use.
+           Set by analyzing following charset_string.
+        */
+
+    char * charset_string;
+        /* Charset registry and encoding.
+           Available in most BDF fonts between STARPROPERTIES - ENDPROPERTIES.
+           NULL means no name.
+        */
 };
 
+
+/* PBM_FONT2_STRUCT_SIZE(x) tells you how big a struct font2 is up
+   through the member named x.  This is useful in conjunction with the
+   'len' value to determine which fields are present in the structure.
+*/
+
+/* Some compilers are really vigilant and recognize it as an error
+   to cast a 64 bit address to a 32 bit type.  Hence the roundabout
+   casting.  See PAM_MEMBER_OFFSET in pam.h .
+*/
+
+
+#define PBM_FONT2_MEMBER_OFFSET(mbrname) \
+  ((size_t)(unsigned long)(char*)&((struct font2 *)0)->mbrname)
+#define PBM_FONT2_MEMBER_SIZE(mbrname) \
+  sizeof(((struct font2 *)0)->mbrname)
+#define PBM_FONT2_STRUCT_SIZE(mbrname) \
+  (PBM_FONT2_MEMBER_OFFSET(mbrname) + PBM_FONT2_MEMBER_SIZE(mbrname))
+
+
 struct font *
 pbm_defaultfont(const char* const which);
 
+struct font2 *
+pbm_defaultfont2(const char* const which);
+
 struct font *
 pbm_dissectfont(const bit ** const font,
                 unsigned int const frows,
@@ -131,15 +302,40 @@ pbm_dissectfont(const bit ** const font,
 struct font *
 pbm_loadfont(const char * const filename);
 
+struct font2 *
+pbm_loadfont2(const    char * const filename,
+              PM_WCHAR        const maxmaxglyph);
+
 struct font *
 pbm_loadpbmfont(const char * const filename);
 
+struct font2 *
+pbm_loadpbmfont2(const char * const filename);
+
 struct font *
 pbm_loadbdffont(const char * const filename);
 
 struct font2 *
 pbm_loadbdffont2(const char * const filename,
-                 PM_WCHAR     const maxglyph);
+                 PM_WCHAR     const maxmaxglyph);
+
+struct font2 *
+pbm_loadbdffont2_select(const char * const filename,
+                        PM_WCHAR     const maxmaxglyph,
+                        const void * const selector);
+
+void
+pbm_createbdffont2_base(struct font2 ** const font2P,
+                        PM_WCHAR        const maxmaxglyph);
+
+void
+pbm_destroybdffont(struct font * const fontP);
+
+void
+pbm_destroybdffont2_base(struct font2 * const font2P);
+
+void
+pbm_destroybdffont2(struct font2 * const font2P);
 
 struct font2 *
 pbm_expandbdffont(const struct font * const font);
@@ -148,9 +344,6 @@ void
 pbm_dumpfont(struct font * const fontP,
              FILE *        const ofP);
 
-extern struct font pbm_defaultFixedfont;
-extern struct font pbm_defaultBdffont;
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/pbmfontdata.h b/lib/pbmfontdata.h
new file mode 100644
index 00000000..7ac63abc
--- /dev/null
+++ b/lib/pbmfontdata.h
@@ -0,0 +1,7 @@
+extern struct font pbm_defaultFixedfont;
+extern struct font pbm_defaultBdffont;
+
+extern struct font2 const pbm_defaultFixedfont2;
+extern struct font2 const pbm_defaultBdffont2;
+
+extern struct font2 const * pbm_builtinFonts[];
diff --git a/lib/pbmfontdata0.c b/lib/pbmfontdata0.c
new file mode 100644
index 00000000..dfafc317
--- /dev/null
+++ b/lib/pbmfontdata0.c
@@ -0,0 +1,9 @@
+#include "pbm.h"
+#include "pbmfont.h"
+#include "pbmfontdata.h"
+
+struct font2 const * pbm_builtinFonts[] = {
+    &pbm_defaultFixedfont2,
+    &pbm_defaultBdffont2,
+    NULL,
+};
diff --git a/lib/pbmfontdata1.c b/lib/pbmfontdata1.c
index 8552d29e..ab6ce28d 100644
--- a/lib/pbmfontdata1.c
+++ b/lib/pbmfontdata1.c
@@ -1,4 +1,5 @@
 #include "pbmfont.h"
+#include "pbmfontdata.h"
 
 /* Default fixed-width font
    All glyphs fit into a 7 x 12 rectangular cell.
@@ -20,7 +21,7 @@
 */
 
 static struct glyph glFxd[96] = {
-/*  32 character   */ 
+/*  32 character   */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
 /*  33 character ! */
 {7,12,0,0,7,"\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
@@ -210,9 +211,9 @@ static struct glyph glFxd[96] = {
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0" },
 /* 126 character ~ */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\1\0\0\1\0\1\0\1\0\0\1\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" },
-/* 127 character (?) */
+/* 127 character (Control character, retained for backward compatibility) */
 {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" }
-}; 
+};
 
 
 
@@ -246,7 +247,22 @@ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, NULL, 0, 0
+};
+
+struct font2 const pbm_defaultFixedfont2 = {
+  sizeof(pbm_defaultFixedfont2),         /* len */
+  PBM_FONT2_STRUCT_SIZE(charset_string), /* size */
+  7, 12, 0, 0,                    /* maxwidth, maxheight, x, y */
+  pbm_defaultFixedfont.glyph,     /* glyph table */
+  255, NULL, 255,                 /* maxglyph, selector, maxmaxglyph */
+  NULL, 0, 0,                     /* oldfont, fcols, frows */
+  PBM_FORMAT,                     /* bit_format */
+  96, 96,                         /* total_chars, chars */
+  FIXED_DATA,                     /* load_fn */
+  32, 1,                          /* default_char, default_char_defined */
+  (char *) "builtin fixed",       /* name */
+  ISO646_1991_IRV, (char *)"ASCII"  /* charset, charset_string */
 };
 
 
diff --git a/lib/pbmfontdata2.c b/lib/pbmfontdata2.c
index 336fc773..11dd84e6 100644
--- a/lib/pbmfontdata2.c
+++ b/lib/pbmfontdata2.c
@@ -1,4 +1,5 @@
 #include "pbmfont.h"
+#include "pbmfontdata.h"
 
 /* Default proportional font.
    BDF-style advance value, bounding box dimensions and point of origin.
@@ -15,7 +16,7 @@
    from a libnetpbm font file or builtin font.
 */
 
-static struct glyph glBdf[190] = {
+static struct glyph glBdf[191] = {
 /*  32 character    */
 { 1, 1, 0, 0, 3, "\0" },
 /*  33 character !  */
@@ -204,8 +205,10 @@ static struct glyph glBdf[190] = {
 { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\1\1" },
 /* 125 character }  */
 { 4, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\1\0\0" },
-/* 160 */
+/* 126 character ~  */
 { 6, 2, 0, 3, 7, "\0\1\1\0\0\1\1\0\0\1\1\0" },
+/* 160 */
+{ 1, 1, 0, 0, 3, "\0" },
 /* 161 */
 { 1, 9, 1, -3, 4, "\1\0\1\1\1\1\1\1\1" },
 /* 162 */
@@ -421,7 +424,7 @@ glBdf+84, glBdf+85, glBdf+86, glBdf+87, glBdf+88, glBdf+89,
 glBdf+90, glBdf+91, glBdf+92, glBdf+93, glBdf+94,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 glBdf+95,  glBdf+96,  glBdf+97,  glBdf+98,  glBdf+99,  glBdf+100,
 glBdf+101, glBdf+102, glBdf+103, glBdf+104, glBdf+105, glBdf+106,
 glBdf+107, glBdf+108, glBdf+109, glBdf+110, glBdf+111, glBdf+112,
@@ -437,7 +440,23 @@ glBdf+161, glBdf+162, glBdf+163, glBdf+164, glBdf+165, glBdf+166,
 glBdf+167, glBdf+168, glBdf+169, glBdf+170, glBdf+171, glBdf+172,
 glBdf+173, glBdf+174, glBdf+175, glBdf+176, glBdf+177, glBdf+178,
 glBdf+179, glBdf+180, glBdf+181, glBdf+182, glBdf+183, glBdf+184,
-glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189  }
+glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189, glBdf+190 },
+NULL, 0, 0
+};
+
+struct font2 const pbm_defaultBdffont2 = {
+  sizeof(pbm_defaultFixedfont2),         /* len */
+  PBM_FONT2_STRUCT_SIZE(charset_string), /* size */
+  14, 15, -1, -3,                 /* maxwidth, maxheight, x, y */
+  pbm_defaultBdffont.glyph,       /* glyph table */
+  255, NULL, 255,                 /* maxglyph, selector, maxmaxglyph */
+  NULL, 0, 0,                     /* oldfont, fcols, frows */
+  PBM_FORMAT,                     /* bit_format */
+  190, 190,                       /* total_chars, chars */
+  FIXED_DATA,                     /* load_fn */
+  32, 1,                          /* default_char, default_char_defined */
+  (char *) "builtin bdf",         /* name */
+  ISO_8859_1, (char *)"ISO8859-1" /* charset, charset_string */
 };