목차

Bitmap font loader for raylib

Purpose

Raylib's internal Bitmap Font loader support only one font atlas.

So if you want import large amount of font glyphs - like hangeul(korean language) - you must make font atlas resolution too big .For instance, 3096 X 3096 resolution will be needed. But it's not convenient.

That is due to page parameter is lacked. Raylib's internal LoadBMFont() method is not support pages parameter which is in FNT file.

So I modified that.

This is my modified font library

LoadBMFontEx library

dkfont.c
/*  ========================================================
        Bitmap Font Loader Extender  
 
        Author : Dongkun Lee.  
        Version : 1.2.
        Date : 2023. 11. 21.  
 
        History : 2023. 11. 2. ver 1. 0.
        Description : 
            This is helper utility to load bitmap font(fnt) that have multiple atlas. 
            Just load fnt font by LadBMFontEx("filename");
            It support up to 10 atlas.
*/
 
#include <stdlib.h>
#include "raylib.h"
#include "string.h"
#include "stdio.h"
 
static Font LoadBMFontEX(const char *fileName);
 
static int GetLine(const char *origin, char *buffer, int maxLength);
// Load a BMFont file (AngelCode font file)
// REQUIRES: strstr(), sscanf(), strrchr(), memcpy()
Font LoadBMFontEX(const char *fileName)
{
   #define MAX_BUFFER_SIZE     256
 
    Font font = { 0 };
 
    char buffer[MAX_BUFFER_SIZE] = { 0 };
    char *searchPoint = NULL;
 
    int fontSize = 0;
    int glyphCount = 0;
 
    int imWidth = 0;
    int imHeight = 0;
    int totalPage = 1;   // page variable
    char imFileName[10][129] = { 0 };  // up to ten png file.
 
    int base = 0;       // Useless data
    int readBytes = 0;  // Data bytes read
    int readVars = 0;   // Variables filled by sscanf()
 
    char *fileText = LoadFileText(fileName);
 
    if (fileText == NULL) return font;
 
    char *fileTextPtr = fileText;
 
    // NOTE: We skip first line, it contains no useful information
    readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
    fileTextPtr += (readBytes + 1);
 
    // Read line data
    readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
    searchPoint = strstr(buffer, "lineHeight");
    readVars = sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i pages=%i", &fontSize, &base, &imWidth, &imHeight, &totalPage);
    fileTextPtr += (readBytes + 1);
 
    if (readVars < 4) { UnloadFileText(fileText); return font; } // Some data not available, file malformed
 
    for (int i = 0; i < totalPage; i++)
    {
        readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
        searchPoint = strstr(buffer, "file");
        readVars = sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName[i]);
        fileTextPtr += (readBytes + 1);
 
        if (readVars < 1) { UnloadFileText(fileText); return font; } // No fileName read
    }
 
    readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
    searchPoint = strstr(buffer, "count");
    readVars = sscanf(searchPoint, "count=%i", &glyphCount);
    fileTextPtr += (readBytes + 1);
 
    if (readVars < 1) { UnloadFileText(fileText); return font; } // No glyphCount read
 
    // Compose correct path using route of .fnt file (fileName) and imFileName
    char **imPath;
    char *lastSlash = NULL;
    imPath = malloc(sizeof(char) * 100);  // imPath Initialization
 
    for (int i = 0; i< totalPage; i++)
    {
        lastSlash = strrchr(fileName, '/');
        if (lastSlash == NULL) lastSlash = strrchr(fileName, '\\');
 
        if (lastSlash != NULL)
        {
            // NOTE: We need some extra space to avoid memory corruption on next allocations!
            imPath[i] = (char *)RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName[i]) + 4, 1);
            memcpy(imPath[i], fileName, TextLength(fileName) - TextLength(lastSlash) + 1);
            memcpy(imPath[i] + TextLength(fileName) - TextLength(lastSlash) + 1, imFileName[i], TextLength(imFileName[i]));
        }
        else imPath[i] = imFileName[i];
 
    TRACELOGD("    > Image loading path: %s", imPath[i]);
    }
 
    // Resize and ReDraw Font Image 
    Image fullFont = LoadImage(imPath[0]);;
 
    Image imFont[totalPage];   // font atlas
 
    for (int i = 0; i < totalPage; i++)
    {
        imFont[i] =  LoadImage(imPath[i]);
 
        if (imFont[i].format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)
        {
            // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel
            Image imFontAlpha = {
                .data = RL_CALLOC(imFont[i].width*imFont[i].height, 2),
                .width = imFont[i].width,
                .height = imFont[i].height,
                .mipmaps = 1,
                .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA
            };
 
            for (int p = 0, pi = 0; p < (imFont[i].width*imFont[i].height*2); p += 2, pi++)
            {
                ((unsigned char *)(imFontAlpha.data))[p] = 0xff;
                ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont[i].data)[pi];
            }
 
            UnloadImage(imFont[i]);
            imFont[i] = imFontAlpha;
        }
 
        if (lastSlash != NULL) RL_FREE(imPath[i]);
    } 
 
    fullFont =  imFont[0];
 
    // If multiple atlas, then merge atlas
    if (totalPage > 1)
    {
        // Resize and ReDraw Font Image 
        ImageResizeCanvas(&fullFont, imWidth, imHeight * totalPage, 0, 0, BLACK); 
 
        for (int index = 1; index <= totalPage; index++)
        {
            Rectangle srcRec = { 0.0f, 0.0f, (float)imWidth, (float)imHeight};
            Rectangle destRec = { 0.0f, (float)imHeight * (float)index, (float)imWidth, (float)imHeight};
            ImageDraw(&fullFont, imFont[index], srcRec, destRec, WHITE);              
        }
    }
 
    font.texture = LoadTextureFromImage(fullFont);
 
 
 
    // Fill font characters info data
    font.baseSize = fontSize;
    font.glyphCount = glyphCount;
    font.glyphPadding = 0;
    font.glyphs = (GlyphInfo *)RL_MALLOC(glyphCount*sizeof(GlyphInfo));
    font.recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle));
 
    int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX, pageID;
 
    for (int i = 0; i < glyphCount; i++)
    {
        readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
        readVars = sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i",
                       &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID);
        fileTextPtr += (readBytes + 1);
 
        if (readVars == 9)  // Make sure all char data has been properly read
        {
            // Get character rectangle in the font atlas texture
            font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight * pageID, (float)charWidth, (float)charHeight };
 
            // Save data properly in sprite font
            font.glyphs[i].value = charId;
            font.glyphs[i].offsetX = charOffsetX;
            font.glyphs[i].offsetY = charOffsetY;
            font.glyphs[i].advanceX = charAdvanceX;
 
            // Fill character image data from imFont data
            font.glyphs[i].image = ImageFromImage(fullFont, font.recs[i]);
        }
        else TRACELOG(LOG_WARNING, "FONT: [%s] Some characters data not correctly provided", fileName);
    }
 
    UnloadImage(fullFont);
    UnloadFileText(fileText);
 
    if (font.texture.id == 0)
    {
        UnloadFont(font);
        font = GetFontDefault();
        TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName);
    }
    else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully (%i glyphs)", fileName, font.glyphCount);
 
    free(imPath);
    return font;
}
 
static int GetLine(const char *origin, char *buffer, int maxLength)
{
    int count = 0;
    for (; count < maxLength; count++) if (origin[count] == '\n') break;
    memcpy(buffer, origin, count);
    return count;
}

Usage

Just define font by using “LoadBMFontEx(filename)”.

It support multiple font atlas which is associated with fnt file.

If you don't know hot to make bitmap font, then down load bmfont.

Github

This is github repository

Raylib bitmap font loader Extender