diff tools/fontconv.c @ 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 73545a442ffe
children b99e04c356ec
line wrap: on
line diff
--- 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)