{{:raylib:util:raylib_bitmap_font_loader_extender.png?600|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 ====== /* ======================================================== 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 #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 [[http://www.angelcode.com/products/bmfont/|bmfont]]. ===== Github ===== This is github repository [[https://github.com/DongkunLee/raylibbmfontextender|Raylib bitmap font loader Extender]]