/* ======================================================== 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; }