changeset 1957:ef08af6887b7

Revamp the bitmap font system to use single SDL_Surface for the font graphics instead of N surfaces for each separate glyph.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 30 Jun 2018 05:24:12 +0300
parents 6147dd3fa5d2
children e76bb047386e
files src/dmtext.h src/dmtext_bm.c tools/fontconv.c tools/libgutil.c
diffstat 4 files changed, 371 insertions(+), 263 deletions(-) [+]
line wrap: on
line diff
--- a/src/dmtext.h	Sat Jun 30 05:22:15 2018 +0300
+++ b/src/dmtext.h	Sat Jun 30 05:24:12 2018 +0300
@@ -24,14 +24,13 @@
 
 // DMFONT format constants
 #define DMFONT_MAGIC         "DMFONT"
-#define DMFONT_VERSION       0x0100
+#define DMFONT_VERSION       0x0200
 
 #define DMFONT_MIN_WIDTH     3
 #define DMFONT_MIN_HEIGHT    3
 #define DMFONT_MAX_WIDTH     128
 #define DMFONT_MAX_HEIGHT    128
 #define DMFONT_MAX_GLYPHS    1024
-
 #define DMFONT_NPALETTE      256
 
 // Legacy TSFONT loading support
@@ -41,29 +40,52 @@
 
 typedef struct
 {
-    int width, height;     // Dimensions
-    int nglyphs;           // Size of glyphs array
-    SDL_Surface **glyphs;  // NOTE! Not all glyphs may be allocated
+    int width, height,       // Dimensions of this glyph
+        index;               // Index to surface, value of "maxglyph" means unused
+} DMBitmapGlyph;
+
+
+typedef struct
+{
+    int width, height;       // Dimensions
+    int nglyphs,             // How many glyphs worth of data is allocated for the surface
+        maxglyph;            // Number of character indices, e.g. typically 256
+    size_t gsize;            // Size of one glyph in bytes, e.g. glyphs->pitch * font->height
+
+    DMBitmapGlyph *glyphMap;
+    SDL_Surface *glyphs;     // Surface containing glyphs
 } DMBitmapFont;
 
 
-DMBitmapFont *dmNewBitmapFont(const int nglyphs, const int width, const int height);
+DMBitmapFont *dmNewBitmapFont(const int nglyphs, const int maxglyph, const int width, const int height, const int bpp);
 int dmFreeBitmapFont(DMBitmapFont *font);
 int dmLoadBitmapFont(DMResource *res, DMBitmapFont **pfont);
 int dmSetBitmapFontPalette(DMBitmapFont *font, const SDL_Color *pal, const int start, const int size);
 
 
-void dmDrawBMTextConst(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *str);
-void dmDrawBMTextVA(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *fmt, va_list ap);
-void dmDrawBMText(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *fmt, ...);
+void dmDrawBMTextConst(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, int xc, int yc, const char *str);
+void dmDrawBMTextVA(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, const int xc, const int yc, const char *fmt, va_list ap);
+void dmDrawBMText(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, const int xc, const int yc, const char *fmt, ...);
 
 
-static inline SDL_Surface *dmGetBMGlyph(DMBitmapFont *font, int ch)
+static inline void dmInitializeGetBMGlyphSurface(SDL_Surface *surf, DMBitmapFont *font)
 {
+    memcpy(surf, font->glyphs, sizeof(SDL_Surface));
+}
+
+static inline void dmGetBMGlyph(SDL_Surface *surf, DMBitmapFont *font, int ch)
+{
+    DMBitmapGlyph *glyph;
     if (ch < 0 || ch >= font->nglyphs || ch == '\n' || ch == '\r')
         ch = 32;
 
-    return font->glyphs[ch];
+    glyph = &font->glyphMap[ch];
+    surf->w = glyph->width;
+    surf->h = glyph->height;
+    surf->pixels = font->glyphs->pixels + font->gsize * glyph->index;
 }
 #endif
 
--- a/src/dmtext_bm.c	Sat Jun 30 05:22:15 2018 +0300
+++ b/src/dmtext_bm.c	Sat Jun 30 05:24:12 2018 +0300
@@ -7,26 +7,33 @@
 #include "dmtext.h"
 
 
-void dmDrawBMTextConst(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *fmt)
+void dmDrawBMTextConst(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, int xc, int yc, const char *fmt)
 {
     const char *ptr = fmt;
-    DMUnscaledBlitFunc blit = NULL;
+    DMUnscaledBlitFunc blit = dmGetUnscaledBlitFunc(font->glyphs->format, screen->format, mode);
+    SDL_Surface surf;
+    Uint8 *orig = font->glyphs->pixels;
+
+    memcpy(&surf, font->glyphs, sizeof(SDL_Surface));
 
     while (*ptr)
     {
-        int ch = *ptr++;
-        SDL_Surface *glyph;
-
-        if (ch >= 0 && ch < font->nglyphs && (glyph = font->glyphs[ch]) != NULL)
+        unsigned char ch = *ptr++;
+        if (ch < font->maxglyph)
         {
-            if (blit == NULL)
-                blit = dmGetUnscaledBlitFunc(glyph->format, screen->format, mode);
+            DMBitmapGlyph *glyph = &font->glyphMap[ch];
+            if (glyph->index >= 0)
+            {
+                surf.pixels = orig + glyph->index * font->gsize;
+                surf.w = glyph->width;
+                surf.h = glyph->height;
 
-            if (blit != NULL)
-            {
-                blit(glyph, xc, yc, screen);
-                xc += condensed ? glyph->w : font->width;
+                blit(&surf, xc, yc, screen);
+                xc += condensed ? glyph->width : font->width;
             }
+            else
+                xc += font->width;
         }
         else
             xc += font->width;
@@ -34,7 +41,9 @@
 }
 
 
-void dmDrawBMTextVA(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *fmt, va_list ap)
+void dmDrawBMTextVA(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, const int xc, const int yc,
+    const char *fmt, va_list ap)
 {
     char tmp[512];
     vsnprintf(tmp, sizeof(tmp), fmt, ap);
@@ -43,7 +52,9 @@
 }
 
 
-void dmDrawBMText(SDL_Surface *screen, DMBitmapFont *font, BOOL condensed, int mode, int xc, int yc, const char *fmt, ...)
+void dmDrawBMText(SDL_Surface *screen, const DMBitmapFont *font,
+    const BOOL condensed, const int mode, const int xc, const int yc,
+    const char *fmt, ...)
 {
     va_list ap;
 
@@ -53,42 +64,53 @@
 }
 
 
-DMBitmapFont *dmNewBitmapFont(const int nglyphs, const int width, const int height)
+DMBitmapFont *dmNewBitmapFont(const int nglyphs, const int maxglyph,
+    const int width, const int height, const int bpp)
 {
     DMBitmapFont *font = dmMalloc0(sizeof(DMBitmapFont));
-    if (font == NULL)
+    if (font == NULL || (bpp != 8 && bpp != 32))
         return NULL;
 
     font->width = width;
     font->height = height;
     font->nglyphs = nglyphs;
-    font->glyphs = dmCalloc(nglyphs, sizeof(SDL_Surface *));
-    if (font->glyphs == NULL)
+    font->maxglyph = maxglyph;
+
+    if ((font->glyphMap = dmCalloc(font->maxglyph, sizeof(DMBitmapGlyph))) == NULL)
+        goto error;
+
+    if ((font->glyphs = SDL_CreateRGBSurfaceWithFormat(
+        0, width, height * (nglyphs + 1), bpp,
+        bpp == 8 ? SDL_PIXELFORMAT_INDEX8 : SDL_PIXELFORMAT_RGBA32)) == NULL)
+        goto error;
+
+    font->gsize = font->height * font->glyphs->pitch;
+
+    for (int i = 0; i < font->maxglyph; i++)
     {
-        dmFree(font);
-        return NULL;
+        DMBitmapGlyph *glyph = &font->glyphMap[i];
+        glyph->width  = width;
+        glyph->height = height;
+        glyph->index  = -1;     // means that this index is empty/unused
     }
 
     return font;
+
+error:
+    dmFreeBitmapFont(font);
+    return NULL;
 }
 
 
 int dmFreeBitmapFont(DMBitmapFont *font)
 {
-    int i;
-
     if (font == NULL)
         return DMERR_NULLPTR;
 
-    for (i = 0; i < font->nglyphs; i++)
-    {
-        if (font->glyphs[i] != NULL)
-        {
-            SDL_FreeSurface(font->glyphs[i]);
-            font->glyphs[i] = NULL;
-        }
-    }
+    if (font->glyphs != NULL)
+        SDL_FreeSurface(font->glyphs);
 
+    dmFree(font->glyphMap);
     dmFree(font);
     return DMERR_OK;
 }
@@ -100,22 +122,13 @@
  */
 int dmSetBitmapFontPalette(DMBitmapFont *font, const SDL_Color *pal, const int start, const int size)
 {
-    int i;
-
     if (font == NULL)
         return DMERR_NULLPTR;
 
     if (start < 0 || size < 1)
         return DMERR_INVALID_ARGS;
 
-    for (i = 0; i < font->nglyphs; i++)
-    {
-        SDL_Surface *glyph = font->glyphs[i];
-        if (glyph != NULL)
-        {
-            SDL_SetPaletteColors(glyph->format->palette, pal, start, size);
-        }
-    }
+    SDL_SetPaletteColors(font->glyphs->format->palette, pal, start, size);
 
     return DMERR_OK;
 }
@@ -123,32 +136,34 @@
 
 //#define FN_DEBUG
 
-int dmLoadBitmapFont(DMResource *res, DMBitmapFont **pfont)
+
+int dmLoadBitmapFont(DMResource *fp, DMBitmapFont **pfont)
 {
     DMBitmapFont *font;
     char magic[8];
     Uint16 version, nglyphs, maxglyph;
-    int width, height;
+    Uint8 width, height, bpp;
     BOOL tsfont = FALSE;
 
     // Check magic and version
-    if (!dmf_read_str(res, (Uint8 *) &magic, 6) ||
-        !dmf_read_le16(res, &version))
+    if (!dmf_read_str(fp, (Uint8 *) &magic, 6) ||
+        !dmf_read_le16(fp, &version))
         return DMERR_FREAD;
 
     // Check if it is a legacy TSFONT file
     if (memcmp(magic, TSFONT_MAGIC, 6) == 0)
     {
         // Yep, we handle these a bit differently
-        int encoding = dmfgetc(res);
+        int encoding = dmfgetc(fp);
         tsfont = TRUE;
 
-        if (version > TSFONT_VERSION)
-            return DMERR_VERSION;
+#ifdef FN_DEBUG
+        fprintf(stderr, "TSFONT v%d.%d (0x%04x), encoding=%d\n",
+            version >> 8, version & 0xff, version, encoding);
+#endif
 
-#ifdef FN_DEBUG
-        fprintf(stderr, "TSFONT v%d.%d (0x%04x), encoding=%d\n", version >> 8, version & 0xff, version, encoding);
-#endif
+        if (version > TSFONT_VERSION || version < 0x0200)
+            return DMERR_VERSION;
 
         // There were only two encodings, 0 = none and 1 = RLE
         // of which RLE was never actually used ... derp.
@@ -156,140 +171,138 @@
             return DMERR_NOT_SUPPORTED;
     }
     else
+    if (memcmp(magic, DMFONT_MAGIC, 6) == 0)
     {
-        if (memcmp(magic, DMFONT_MAGIC, 6) != 0)
-            return DMERR_INVALID;
+#ifdef FN_DEBUG
+        fprintf(stderr, "DMFONT v%d.%d (0x%04x)\n",
+            version >> 8, version & 0xff, version);
+#endif
 
         if (version > DMFONT_VERSION)
             return DMERR_VERSION;
     }
+    else
+        return DMERR_INVALID;
 
     // Read other header data
     if (tsfont)
     {
-        // TSFONT only has number of glyphs stored in the file
-        nglyphs = dmfgetc(res);
-
-        // Maximum glyph number
-        maxglyph = 256;
-    }
-    else
-    {
-        dmf_read_le16(res, &nglyphs);
-        dmf_read_le16(res, &maxglyph);
-    }
+        // TSFONT has number of glyphs and dimensions
+        Uint8 tmp, unused;
+        if (!dmf_read_byte(fp, &tmp) ||
+            !dmf_read_byte(fp, &width) ||
+            !dmf_read_byte(fp, &height) ||
+            !dmf_read_byte(fp, &unused))
+            return DMERR_FREAD;
 
-    width = dmfgetc(res);
-    height = dmfgetc(res);
-
-#ifdef FN_DEBUG
-    fprintf(stderr, "nglyphs=%d (0x%02x), maxglyph=%d (0x%02x) width=%d, height=%d\n",
-        nglyphs, nglyphs, maxglyph, maxglyph, width, height);
-#endif
-
-    if (tsfont)
-    {
-        // TSFONT color assignments (boolean) .. we discard this.
-        dmfgetc(res);
+        nglyphs = tmp;
+        maxglyph = 256;
+        bpp = 8;
 
         // Very old TSFONTs have some extra data that is not used
         // .. can't actually even remember what it was for.
         if (version == 0x0200)
         {
-            int i;
-            for (i = 0; i < 32; i++)
-                dmfgetc(res);
+            for (int i = 0; i < 32; i++)
+            {
+                if (!dmf_read_byte(fp, &unused))
+                    return DMERR_FREAD;
+            }
         }
     }
+    else
+    {
+        // DMFONT has Uint16 values for nglyphs and maxglyph, plus BPP
+        if (!dmf_read_le16(fp, &nglyphs) ||
+            !dmf_read_le16(fp, &maxglyph) ||
+            !dmf_read_byte(fp, &width) ||
+            !dmf_read_byte(fp, &height) ||
+            !dmf_read_byte(fp, &bpp))
+            return DMERR_FREAD;
+    }
+
+#ifdef FN_DEBUG
+    fprintf(stderr, "nglyphs=%d, maxglyph=%d, width=%d, height=%d, bpp=%d\n",
+        nglyphs, maxglyph, width, height, bpp);
+#endif
 
     if (width < DMFONT_MIN_WIDTH ||
         height < DMFONT_MIN_HEIGHT ||
         width > DMFONT_MAX_WIDTH ||
         height > DMFONT_MAX_HEIGHT ||
         nglyphs > DMFONT_MAX_GLYPHS ||
+        nglyphs > maxglyph ||
         maxglyph > DMFONT_MAX_GLYPHS ||
         maxglyph < 1)
         return DMERR_INVALID_DATA;
 
+    if (bpp != 8 && bpp != 32)
+        return DMERR_NOT_SUPPORTED;
+
     // Allocate font
-    if ((*pfont = font = dmNewBitmapFont(maxglyph, width, height)) == NULL)
+    if ((*pfont = font = dmNewBitmapFont(nglyphs, maxglyph, width, height, bpp)) == NULL)
         return DMERR_MALLOC;
 
-    // Read glyph data, if any
-    if (nglyphs > 0)
+    // Setup palette for 8bpp fonts
+    if (bpp == 8)
     {
-        int n, i;
-        Uint32 BitsPerPixel, Rmask, Gmask, Bmask, Amask;
         SDL_Color pal[DMFONT_NPALETTE];
-
-        // Setup palette for 8bpp fonts
-        for (n = 0; n < DMFONT_NPALETTE; n++)
+        for (int n = 0; n < DMFONT_NPALETTE; n++)
         {
-            pal[n].r = pal[n].g = pal[n].b = 0;
+            pal[n].r = pal[n].g = pal[n].b = n > 0 ? 255 : 0;
             pal[n].a = n > 0 ? 255 : 0;
         }
+        SDL_SetPaletteColors(font->glyphs->format->palette, pal, 0, DMFONT_NPALETTE);
+    }
 
-        if (tsfont)
-        {
-            BitsPerPixel = 8;
-            Rmask = Gmask = Bmask = Amask = 0;
-        }
-        else
-        {
-            BitsPerPixel = dmfgetc(res);
-            dmf_read_le32(res, &Rmask);
-            dmf_read_le32(res, &Gmask);
-            dmf_read_le32(res, &Bmask);
-            dmf_read_le32(res, &Amask);
-        }
+    // Read glyph data, if any
+    for (int i = 0; i < nglyphs; i++)
+    {
+        DMBitmapGlyph *glyph;
+        Uint8 gwidth, gheight;
+        Uint16 gindex;
+        Uint8 *pixels;
 
-        for (i = 0; i < nglyphs; i++)
-        {
-            int y;
-            Uint16 index;
-            Uint8 *pixels;
-            SDL_Surface *glyph;
+        // TSFONT format has only byte sized index
+        if (tsfont)
+            gindex = dmfgetc(fp);
+        else
+            dmf_read_le16(fp, &gindex);
 
-            // TSFONT format has only byte sized index
-            if (tsfont)
-                index = dmfgetc(res);
-            else
-                dmf_read_le16(res, &index);
-
-            // Read dimensions
-            width = dmfgetc(res);
-            height = dmfgetc(res);
+        // Read dimensions
+        if (!dmf_read_byte(fp, &gwidth) ||
+            !dmf_read_byte(fp, &gheight))
+            return DMERR_FREAD;
 
 #ifdef FN_DEBUG
-            fprintf(stderr, "#%d @ %d - w=%d, h=%d\n", i, index, width, height);
+        fprintf(stderr, "#%d @ %d - %d x %d\n", i, gindex, gwidth, gheight);
 #endif
 
-            if (width < DMFONT_MIN_WIDTH ||
-                height < DMFONT_MIN_HEIGHT ||
-                width > DMFONT_MAX_WIDTH ||
-                height > DMFONT_MAX_HEIGHT ||
-                index >= maxglyph)
-                return DMERR_INVALID_DATA;
-
-            // Allocate bitmap
-            font->glyphs[index] = glyph = SDL_CreateRGBSurface(
-                SDL_SWSURFACE, width, height,
-                BitsPerPixel, Rmask, Gmask, Bmask, Amask);
+        // Check the glyph data
+        if (gwidth < DMFONT_MIN_WIDTH ||
+            gheight < DMFONT_MIN_HEIGHT ||
+            gwidth > DMFONT_MAX_WIDTH ||
+            gheight > DMFONT_MAX_HEIGHT ||
+            gwidth > width ||
+            gheight > height ||
+            gindex >= maxglyph)
+            return DMERR_INVALID_DATA;
 
-            if (glyph == NULL)
-                return DMERR_MALLOC;
-
-            if (BitsPerPixel == 8)
-                SDL_SetPaletteColors(glyph->format->palette, pal, 0, DMFONT_NPALETTE);
+        // Set glyph data
+        glyph = &font->glyphMap[gindex];
+        glyph->width  = gwidth;
+        glyph->height = gheight;
+        glyph->index  = i;
 
-            // Read pixel data
-            pixels = glyph->pixels;
-            for (y = 0; y < glyph->h; y++)
-            {
-                if (dmfread(pixels, glyph->format->BytesPerPixel, glyph->w, res) != (size_t) glyph->w)
-                    return DMERR_FREAD;
-                pixels += glyph->pitch;
-            }
+        // Read pixel data
+        pixels = font->glyphs->pixels + (i * font->gsize);
+        for (int y = 0; y < glyph->height; y++)
+        {
+            if (dmfread(pixels, font->glyphs->format->BytesPerPixel,
+                glyph->width, fp) != (size_t) glyph->width)
+                return DMERR_FREAD;
+
+            pixels += font->glyphs->pitch;
         }
     }
 
--- a/tools/fontconv.c	Sat Jun 30 05:22:15 2018 +0300
+++ b/tools/fontconv.c	Sat Jun 30 05:24:12 2018 +0300
@@ -16,7 +16,8 @@
 char    *optInFilename = NULL, *optOutFilename = NULL;
 
 int     optSplitWidth = 8,
-        optSplitHeight = 8;
+        optSplitHeight = 8,
+        optBPP = 32;
 
 SDL_Color optColor = { 255, 255, 255, 100 };
 
@@ -28,6 +29,7 @@
     {  2, 's', "size",     "Set glyph dimensions (-s W:H) for image->font conversion", OPT_ARGREQ },
 #ifdef DM_GFX_TTF_TEXT
     {  3, 'c', "color",    "TTF font rendering color (def: 0xFFFFFF)", OPT_ARGREQ },
+    {  4, 'b', "bpp",      "Render font in 8 or 32 bits per pixel (default 32)", OPT_ARGREQ },
 #endif
 };
 
@@ -100,6 +102,21 @@
             }
             break;
 
+        case 4:
+            if (sscanf(optArg, "%d", &optBPP) != 1)
+            {
+                dmErrorMsg("Invalid argument for -b option, '%s'.\n",
+                    optArg);
+                return FALSE;
+            }
+            if (optBPP != 8 && optBPP != 32)
+            {
+                dmErrorMsg("Invalid bit depth %d, must be 8 or 32.\n",
+                    optBPP);
+                return FALSE;
+            }
+            break;
+
         default:
             dmErrorMsg("Unknown argument '%s'.\n", currArg);
             return FALSE;
@@ -111,10 +128,10 @@
 
 BOOL argHandleFile(char *currArg)
 {
-    if (!optInFilename)
+    if (optInFilename == NULL)
         optInFilename = currArg;
     else
-    if (!optOutFilename)
+    if (optOutFilename == NULL)
         optOutFilename = currArg;
     else
     {
@@ -128,49 +145,45 @@
 
 int dmCreateBitmapFontFromImage(SDL_Surface *image, int width, int height, DMBitmapFont **pfont)
 {
-    int nglyph, xc, yc, xglyphs, yglyphs;
+    int nglyph, xglyphs, yglyphs;
     DMBitmapFont *font;
 
-    if (image->w < width || width < 2 || image->h < height || height < 2)
+    if (image->w < width || width < 2 ||
+        image->h < height || height < 2)
         return DMERR_INVALID_ARGS;
 
     xglyphs = image->w / width;
     yglyphs = image->h / height;
 
-    if ((font = dmNewBitmapFont(xglyphs * yglyphs, width, height)) == NULL)
+    if ((font = dmNewBitmapFont(
+        xglyphs * yglyphs,
+        xglyphs * yglyphs,
+        width, height, image->format->BitsPerPixel)) == NULL)
         return DMERR_MALLOC;
 
-    dmMsg(1, "%d x %d split as %d x %d blocks => %d x %d = %d glyphs.\n",
+    dmMsg(1, "%d x %d split as %d x %d blocks => %d x %d = %d glyphs, bpp=%d.\n",
         image->w, image->h,
         width, height,
-        xglyphs, yglyphs, xglyphs * yglyphs);
+        xglyphs, yglyphs,
+        xglyphs * yglyphs, image->format->BitsPerPixel);
 
     nglyph = 0;
-    for (yc = 0; yc < yglyphs; yc++)
-    for (xc = 0; xc < xglyphs; xc++)
+    for (int yc = 0; yc < yglyphs; yc++)
+    for (int xc = 0; xc < xglyphs; xc++)
     {
-        SDL_Surface *glyph = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
-            image->format->BitsPerPixel,
-            image->format->Rmask,
-            image->format->Gmask,
-            image->format->Bmask,
-            image->format->Amask);
+        DMBitmapGlyph *glyph = &font->glyphMap[nglyph++];
+        SDL_Rect src, dst;
 
-        if (glyph == NULL)
-        {
-            dmFreeBitmapFont(font);
-            return DMERR_MALLOC;
-        }
+        src.x = xc * width;
+        src.y = yc * height;
+        dst.w = src.w = width;
+        dst.h = src.h = height;
 
-        SDL_Rect r;
-        r.x = xc * width;
-        r.y = yc * height;
-        r.w = width;
-        r.h = height;
+        dst.x = 0;
+        dst.y = nglyph * height;
+        glyph->index = nglyph;
 
-        SDL_BlitSurface(image, &r, glyph, NULL);
-
-        font->glyphs[nglyph++] = glyph;
+        SDL_BlitSurface(image, &src, font->glyphs, &dst);
     }
 
     *pfont = font;
@@ -178,82 +191,51 @@
 }
 
 
-int dmSaveBitmapFont(DMResource *res, DMBitmapFont *font)
+int dmSaveBitmapFont(DMResource *fp, DMBitmapFont *font)
 {
-    SDL_Surface *glyph;
-    int maxglyph, nglyphs, n;
     if (font == NULL)
         return DMERR_NULLPTR;
 
-    if (font->nglyphs > DMFONT_MAX_GLYPHS ||
+    if (font->nglyphs > font->maxglyph ||
+        font->maxglyph > DMFONT_MAX_GLYPHS ||
         font->width > DMFONT_MAX_WIDTH ||
         font->height > DMFONT_MAX_HEIGHT ||
         font->width < DMFONT_MIN_WIDTH ||
         font->height < DMFONT_MIN_HEIGHT)
         return DMERR_INVALID_DATA;
 
-    // Count number of actually existing glyphs
-    for (maxglyph = nglyphs = n = 0; n < font->nglyphs; n++)
-    {
-        SDL_Surface *glyph = font->glyphs[n];
-        if (glyph != NULL)
-        {
-            maxglyph = n;
-            if (glyph->w < DMFONT_MIN_WIDTH ||
-                glyph->h < DMFONT_MIN_HEIGHT ||
-                glyph->w > DMFONT_MAX_WIDTH ||
-                glyph->h > DMFONT_MAX_HEIGHT)
-                continue;
-            nglyphs++;
-        }
-    }
-
-    if (nglyphs == 0)
-        return DMERR_INVALID_DATA;
-
     // Write the DMFONT header
-    if (!dmf_write_str(res, (Uint8 *) DMFONT_MAGIC, 6))
+    if (!dmf_write_str(fp, (Uint8 *) DMFONT_MAGIC, 6) ||
+        !dmf_write_le16(fp, DMFONT_VERSION) ||
+        !dmf_write_le16(fp, font->nglyphs) ||
+        !dmf_write_le16(fp, font->maxglyph) ||
+        !dmf_write_byte(fp, font->width) ||
+        !dmf_write_byte(fp, font->height) ||
+        !dmf_write_byte(fp, font->glyphs->format->BitsPerPixel))
         return DMERR_FWRITE;
 
-    dmf_write_le16(res, DMFONT_VERSION);
-    dmf_write_le16(res, nglyphs);
-    dmf_write_le16(res, maxglyph + 1);
-    dmfputc(font->width, res);
-    dmfputc(font->height, res);
-
-    // Store glyph format data
-    glyph = font->glyphs[maxglyph];
-    dmfputc(glyph->format->BitsPerPixel, res);
-    dmf_write_le32(res, glyph->format->Rmask);
-    dmf_write_le32(res, glyph->format->Gmask);
-    dmf_write_le32(res, glyph->format->Bmask);
-    dmf_write_le32(res, glyph->format->Amask);
-
-    for (n = 0; n < font->nglyphs; n++)
+    // Write the glyph data
+    for (int index = 0; index < font->maxglyph; index++)
     {
-        glyph = font->glyphs[n];
-        if (glyph != NULL)
+        DMBitmapGlyph *glyph = &font->glyphMap[index];
+        if (glyph->index < font->maxglyph)
         {
-            int y;
-            Uint8 *pixels = glyph->pixels;
-
-            if (glyph->w < DMFONT_MIN_WIDTH ||
-                glyph->h < DMFONT_MIN_HEIGHT ||
-                glyph->w > DMFONT_MAX_WIDTH ||
-                glyph->h > DMFONT_MAX_HEIGHT)
-                continue;
+            Uint8 *pixels = font->glyphs->pixels + font->gsize * glyph->index;
 
             // Each glyph has its table index and w/h stored
-            dmf_write_le16(res, n);
-            dmfputc(glyph->w, res);
-            dmfputc(glyph->h, res);
+            if (!dmf_write_le16(fp, index) ||
+                !dmf_write_byte(fp, glyph->width) ||
+                !dmf_write_byte(fp, glyph->height))
+                return DMERR_FWRITE;
 
             // Write the pixel data
-            for (y = 0; y < glyph->h; y++)
+            for (int y = 0; y < glyph->height; y++)
             {
-                if (dmfwrite(pixels, glyph->format->BytesPerPixel, glyph->w, res) != (size_t) glyph->w)
+                if (dmfwrite(pixels, font->glyphs->format->BytesPerPixel,
+                    glyph->width, fp) != (size_t) glyph->width)
                     return DMERR_FWRITE;
-                pixels += glyph->pitch;
+
+                pixels += font->glyphs->pitch;
             }
         }
     }
@@ -273,7 +255,7 @@
     TTF_Font *ttf = NULL;
 #endif
 
-    dmInitProg("fontconv", "Bitmap font converter", "0.3", NULL, NULL);
+    dmInitProg("fontconv", "Bitmap font converter", "0.4", NULL, NULL);
     dmVerbosity = 1;
 
     // Parse arguments
@@ -282,7 +264,7 @@
         exit(1);
 
     // Check arguments
-    if (!optInFilename || !optOutFilename)
+    if (optInFilename == NULL || optOutFilename == NULL)
     {
         dmErrorMsg("Input or output file not specified!\n");
         return 1;
@@ -292,7 +274,7 @@
     if (TTF_Init() < 0)
     {
         dmErrorMsg("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
-        goto error_exit;
+        goto out;
     }
     initTTF = TRUE;
 #endif
@@ -308,29 +290,85 @@
 
     if ((res = dmLoadBitmapFont(inFile, &font)) == DMERR_OK)
     {
-        dmMsg(1, "Input is a TSFONT/DMFONT font file.\n");
+        dmMsg(1, "Input is a TSFONT/DMFONT font file, %d x %d, %d glyphs (%d max).\n",
+            font->width, font->height, font->nglyphs, font->maxglyph);
+    }
+    else
+    if (res != DMERR_INVALID)
+    {
+        dmErrorMsg("Input is a TSFONT/DMFONT font file, but there is an error: %s\n",
+            dmErrorStr(res));
+        goto out;
     }
 #ifdef DM_GFX_TTF_TEXT
     else
-    if ((ttf = TTF_OpenFont(optInFilename, optSplitWidth)) != NULL)
+    if ((ttf = TTF_OpenFont(optInFilename, optSplitWidth - 1)) != NULL)
     {
-        int i;
-        dmMsg(1, "Input is a TTF TrueType font, rendering at %d x %d.\n",
-            optSplitWidth, optSplitHeight);
+        int gmin = 34, gmax = 127;
+
+        dmMsg(1, "Input is a TTF TrueType font, rendering at %d x %d, %d bpp.\n",
+            optSplitWidth, optSplitHeight, optBPP);
+
+        dmMsg(1, "Rendering glyph range %d to %d inclusive.\n",
+            gmin, gmax);
 
         TTF_SetFontStyle(ttf, TTF_STYLE_NORMAL);
 
-        if ((font = dmNewBitmapFont(256, optSplitWidth - 1, optSplitHeight+4)) == NULL)
+        // Create the bitmap font
+        if ((font = dmNewBitmapFont(gmax - gmin + 1, 256,
+            optSplitWidth - 6, optSplitHeight + 2, optBPP)) == NULL)
         {
-            goto error_exit;
+            dmErrorMsg("Could not allocate bitmap font!\n");
+            goto out;
         }
 
-        for (i = 0; i < 255; i++)
+        // Render glyphs from the normal ASCII range only
+        for (int index = 0, nglyph = gmin; nglyph <= gmax; nglyph++)
         {
+            SDL_Surface *tmp;
             char str[2];
-            str[0] = i;
+            str[0] = nglyph;
             str[1] = 0;
-            font->glyphs[i] = TTF_RenderText_Blended(ttf, str, optColor);
+
+            // Render the glyph from TTF to surface
+            if (optBPP == 8)
+                tmp = TTF_RenderText_Solid(ttf, str, optColor);
+            else
+                tmp = TTF_RenderText_Blended(ttf, str, optColor);
+
+            if (tmp != NULL)
+            {
+                DMBitmapGlyph *glyph = &font->glyphMap[nglyph];
+                int minx, miny, advance;
+                SDL_Rect src, dst;
+
+                if (TTF_GlyphMetrics(ttf, nglyph, &minx, NULL, &miny, NULL, &advance) == -1)
+                {
+                    dmErrorMsg("Could not get TTF glyph metrics for character '%c' (%d).\n",
+                        nglyph, nglyph);
+                    goto out;
+                }
+
+                src.x = 0;
+                src.y = 0;
+                src.w = tmp->w;
+                src.h = tmp->h;
+
+                dst.x = 0;
+                dst.y = index * font->height;
+                dst.w = tmp->w;
+                dst.h = tmp->h;
+//                dst.h = font->height;
+
+                // Set glyph data
+                glyph->width  = font->width;
+                glyph->height = font->height;
+                glyph->index  = index;
+
+                SDL_BlitSurface(tmp, NULL, font->glyphs, &dst);
+                SDL_FreeSurface(tmp);
+                index++;
+            }
         }
     }
 #endif
@@ -341,7 +379,7 @@
         if ((fontbmap = dmLoadImage(inFile)) == NULL)
         {
             dmErrorMsg("Could not load image file '%s'.\n", optInFilename);
-            goto error_exit;
+            goto out;
         }
 
         dmMsg(1, "Input is a bitmap image (%d x %d, %d bpp), splitting to %d x %d.\n",
@@ -352,23 +390,51 @@
         {
             dmErrorMsg("Could not create a font from image, %d: %s\n",
                 res, dmErrorStr(res));
-            goto error_exit;
+            goto out;
         }
     }
 
     if (font == NULL)
     {
         dmErrorMsg("No font loaded.\n");
-        goto error_exit;
+        goto out;
     }
 
-    dmMsg(1, "Outputting a DMFONT format bitmap font.\n");
+    // Count number of actually existing glyphs despite that we should have
+    // that information in font->nglyphs. Also sanity check the glyphs.
+    font->nglyphs = 0;
+    for (int n = 0; n < font->maxglyph; n++)
+    {
+        DMBitmapGlyph *glyph = &font->glyphMap[n];
+
+        if (glyph->width < DMFONT_MIN_WIDTH ||
+            glyph->height < DMFONT_MIN_HEIGHT ||
+            glyph->width > DMFONT_MAX_WIDTH ||
+            glyph->height > DMFONT_MAX_HEIGHT ||
+            glyph->width > font->width ||
+            glyph->height > font->height)
+        {
+            dmErrorMsg("Invalid glyph #%d: %d x %d (font %d x %d)\n",
+                n,
+                glyph->width, glyph->height,
+                font->width, font->height);
+            goto out;
+        }
+
+        if (glyph->index < font->maxglyph)
+            font->nglyphs++;
+    }
+
+    dmMsg(1, "Outputting a DMFONT format bitmap font, %d x %d with %d glyphs (%d max), %d bpp.\n",
+        font->width, font->height,
+        font->nglyphs, font->maxglyph,
+        font->glyphs->format->BitsPerPixel);
 
     if ((res = dmf_open_stdio(optOutFilename, "wb", &outFile)) != DMERR_OK)
     {
         dmErrorMsg("Error creating file '%s', %d: %s\n",
             optInFilename, res, dmErrorStr(res));
-        goto error_exit;
+        goto out;
     }
 
     res = dmSaveBitmapFont(outFile, font);
@@ -380,7 +446,7 @@
             res, dmErrorStr(res));
     }
 
-error_exit:
+out:
 
 #ifdef DM_GFX_TTF_TEXT
     if (initTTF)
--- a/tools/libgutil.c	Sat Jun 30 05:22:15 2018 +0300
+++ b/tools/libgutil.c	Sat Jun 30 05:24:12 2018 +0300
@@ -117,12 +117,15 @@
 void dmDrawBMTextConstQ(SDL_Surface *screen, DMBitmapFont *font, int mode, int xc, int yc, const char *fmt)
 {
     const char *ptr = fmt;
-    DMUnscaledBlitFunc blit = NULL;
+    DMUnscaledBlitFunc blit = dmGetUnscaledBlitFunc(font->glyphs->format, screen->format, mode);
+    Uint8 *orig = font->glyphs->pixels;
+    SDL_Surface surf;
+
+    memcpy(&surf, font->glyphs, sizeof(SDL_Surface));
 
     while (*ptr)
     {
         int ch = toupper(*ptr++);
-        SDL_Surface *glyph;
 
         if (ch == '_')
         {
@@ -130,16 +133,20 @@
             continue;
         }
 
-        if (ch >= 0 && ch < font->nglyphs && (glyph = font->glyphs[ch]) != NULL)
+        if (ch < font->maxglyph && ch != ' ')
         {
-            if (blit == NULL)
-                blit = dmGetUnscaledBlitFunc(glyph->format, screen->format, mode);
+            DMBitmapGlyph *glyph = &font->glyphMap[ch];
 
-            blit(glyph, xc, yc, screen);
-            xc += font->width;
+            if (glyph->index >= 0)
+            {
+                surf.pixels = orig + glyph->index * font->gsize;
+                surf.w = glyph->width;
+                surf.h = glyph->height;
+
+                blit(&surf, xc, yc, screen);
+            }
         }
-        else
-            xc += font->width;
+        xc += font->width;
     }
 }