[rtext] Add BDF font support (#3735)

* Add BDF font support

* Include font ascent in glyph y-offset when loading BDF font
This commit is contained in:
Stanley Fuller 2024-02-04 05:28:12 +11:00 committed by GitHub
parent 29ff658d92
commit 0932cd3059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 341 additions and 30 deletions

View File

@ -181,6 +181,7 @@
// Selected desired font fileformats to be supported for loading
#define SUPPORT_FILEFORMAT_FNT 1
#define SUPPORT_FILEFORMAT_TTF 1
#define SUPPORT_FILEFORMAT_BDF 1
// Support text management functions
// If not defined, still some functions are supported: TextLength(), TextFormat()

View File

@ -12,6 +12,7 @@
*
* #define SUPPORT_FILEFORMAT_FNT
* #define SUPPORT_FILEFORMAT_TTF
* #define SUPPORT_FILEFORMAT_BDF
* Selected desired fileformats to be supported for loading. Some of those formats are
* supported by default, to remove support, just comment unrequired #define in this module
*
@ -70,14 +71,27 @@
#include <stdarg.h> // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()]
#include <ctype.h> // Required for: toupper(), tolower() [Used in TextToUpper(), TextToLower()]
#if defined(SUPPORT_FILEFORMAT_TTF)
#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
#if defined(__GNUC__) // GCC and Clang
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#define STB_RECT_PACK_IMPLEMENTATION
#include "external/stb_rect_pack.h" // Required for: ttf font rectangles packaging
#include "external/stb_rect_pack.h" // Required for: ttf/bdf font rectangles packaging
#include <math.h> // Required for: ttf/bdf font rectangles packaging
#if defined(__GNUC__) // GCC and Clang
#pragma GCC diagnostic pop
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_TTF)
#if defined(__GNUC__) // GCC and Clang
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
@ -127,6 +141,9 @@ static Font defaultFont = { 0 };
#if defined(SUPPORT_FILEFORMAT_FNT)
static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file)
#endif
#if defined(SUPPORT_FILEFORMAT_BDF)
static GlyphInfo *LoadBDFFontData(const unsigned char *fileData, int dataSize, int *codepoints, int codepointCount, int* outFontSize);
#endif
static int textLineSpacing = 15; // Text vertical line spacing in pixels
#if defined(SUPPORT_DEFAULT_FONT)
@ -334,6 +351,10 @@ Font LoadFont(const char *fileName)
#if defined(SUPPORT_FILEFORMAT_FNT)
if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName);
else
#endif
#if defined(SUPPORT_FILEFORMAT_BDF)
if (IsFileExtension(fileName, ".bdf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS);
else
#endif
{
Image image = LoadImage(fileName);
@ -355,7 +376,7 @@ Font LoadFont(const char *fileName)
return font;
}
// Load Font from TTF font file with generation parameters
// Load Font from TTF or BDF font file with generation parameters
// NOTE: You can pass an array with desired characters, those characters should be available in the font
// if array is NULL, default char set is selected 32..126
Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount)
@ -511,35 +532,49 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int
char fileExtLower[16] = { 0 };
strcpy(fileExtLower, TextToLower(fileType));
#if defined(SUPPORT_FILEFORMAT_TTF)
font.baseSize = fontSize;
font.glyphCount = (codepointCount > 0)? codepointCount : 95;
font.glyphPadding = 0;
#if defined(SUPPORT_FILEFORMAT_TTF)
if (TextIsEqual(fileExtLower, ".ttf") ||
TextIsEqual(fileExtLower, ".otf"))
{
font.baseSize = fontSize;
font.glyphCount = (codepointCount > 0)? codepointCount : 95;
font.glyphPadding = 0;
font.glyphs = LoadFontData(fileData, dataSize, font.baseSize, codepoints, font.glyphCount, FONT_DEFAULT);
if (font.glyphs != NULL)
{
font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING;
Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0);
font.texture = LoadTextureFromImage(atlas);
// Update glyphs[i].image to use alpha, required to be used on ImageDrawText()
for (int i = 0; i < font.glyphCount; i++)
{
UnloadImage(font.glyphs[i].image);
font.glyphs[i].image = ImageFromImage(atlas, font.recs[i]);
}
UnloadImage(atlas);
TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount);
}
else font = GetFontDefault();
}
else
#endif
#if defined(SUPPORT_FILEFORMAT_BDF)
if (TextIsEqual(fileExtLower, ".bdf"))
{
font.glyphs = LoadBDFFontData(fileData, dataSize, codepoints, font.glyphCount, &font.baseSize);
}
else
#endif
{
font.glyphs = NULL;
}
#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
if (font.glyphs != NULL)
{
font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING;
Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0);
font.texture = LoadTextureFromImage(atlas);
// Update glyphs[i].image to use alpha, required to be used on ImageDrawText()
for (int i = 0; i < font.glyphCount; i++)
{
UnloadImage(font.glyphs[i].image);
font.glyphs[i].image = ImageFromImage(atlas, font.recs[i]);
}
UnloadImage(atlas);
TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount);
}
else font = GetFontDefault();
#else
font = GetFontDefault();
#endif
@ -699,7 +734,7 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz
// Generate image font atlas using chars info
// NOTE: Packing method: 0-Default, 1-Skyline
#if defined(SUPPORT_FILEFORMAT_TTF)
#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod)
{
Image atlas = { 0 };
@ -2036,18 +2071,21 @@ int GetCodepointPrevious(const char *text, int *codepointSize)
//----------------------------------------------------------------------------------
// Module specific Functions Definition
//----------------------------------------------------------------------------------
#if defined(SUPPORT_FILEFORMAT_FNT)
#if defined(SUPPORT_FILEFORMAT_FNT) || defined(SUPPORT_FILEFORMAT_BDF)
// Read a line from memory
// REQUIRES: memcpy()
// NOTE: Returns the number of bytes read
static int GetLine(const char *origin, char *buffer, int maxLength)
{
int count = 0;
for (; count < maxLength; count++) if (origin[count] == '\n') break;
for (; count < maxLength - 1; count++) if (origin[count] == '\n') break;
memcpy(buffer, origin, count);
buffer[count] = '\0';
return count;
}
#endif
#if defined(SUPPORT_FILEFORMAT_FNT)
// Load a BMFont file (AngelCode font file)
// REQUIRES: strstr(), sscanf(), strrchr(), memcpy()
static Font LoadBMFont(const char *fileName)
@ -2213,4 +2251,276 @@ static Font LoadBMFont(const char *fileName)
#endif
#if defined(SUPPORT_FILEFORMAT_BDF)
// Convert hexadecimal to decimal (single digit)
static char HexToInt(char hex) {
if (hex >= '0' && hex <= '9')
{
return hex - '0';
}
else if (hex >= 'a' && hex <= 'f')
{
return hex - 'a' + 10;
}
else if (hex >= 'A' && hex <= 'F')
{
return hex - 'A' + 10;
}
else
{
return 0;
}
}
// Load font data for further use
// NOTE: Requires BDF font memory data
static GlyphInfo *LoadBDFFontData(const unsigned char *fileData, int dataSize, int *codepoints, int codepointCount, int* outFontSize)
{
#define MAX_BUFFER_SIZE 256
char buffer[MAX_BUFFER_SIZE] = { 0 };
GlyphInfo *glyphs = NULL;
bool genFontChars = false;
int totalReadBytes = 0; // Data bytes read (total)
int readBytes = 0; // Data bytes read (line)
int readVars = 0; // Variables filled by sscanf()
const char *fileText = (const char*)fileData;
const char *fileTextPtr = fileText;
bool fontMalformed = false; // Is the font malformed
bool fontStarted = false; // Has font started (STARTFONT)
int fontBBw = 0; // Font base character bounding box width
int fontBBh = 0; // Font base character bounding box height
int fontBBxoff0 = 0; // Font base character bounding box X0 offset
int fontBByoff0 = 0; // Font base character bounding box Y0 offset
int fontAscent = 0; // Font ascent
bool charStarted = false; // Has character started (STARTCHAR)
bool charBitmapStarted = false; // Has bitmap data started (BITMAP)
int charBitmapNextRow = 0; // Y position for the next row of bitmap data
int charEncoding = -1; // The unicode value of the character (-1 if not set)
int charBBw = 0; // Character bounding box width
int charBBh = 0; // Character bounding box height
int charBBxoff0 = 0; // Character bounding box X0 offset
int charBByoff0 = 0; // Character bounding box Y0 offset
int charDWidthX = 0; // Character advance X
int charDWidthY = 0; // Character advance Y (unused)
GlyphInfo *charGlyphInfo = NULL; // Pointer to output glyph info (NULL if not set)
if (fileData == NULL) return glyphs;
// In case no chars count provided, default to 95
codepointCount = (codepointCount > 0)? codepointCount : 95;
// Fill fontChars in case not provided externally
// NOTE: By default we fill glyphCount consecutively, starting at 32 (Space)
if (codepoints == NULL)
{
codepoints = (int *)RL_MALLOC(codepointCount*sizeof(int));
for (int i = 0; i < codepointCount; i++) codepoints[i] = i + 32;
genFontChars = true;
}
glyphs = (GlyphInfo *)RL_CALLOC(codepointCount, sizeof(GlyphInfo));
while (totalReadBytes <= dataSize)
{
readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
totalReadBytes += (readBytes + 1);
fileTextPtr += (readBytes + 1);
// COMMENT
if (strstr(buffer, "COMMENT") != NULL) continue; // Ignore line
if (charStarted)
{
// ENDCHAR
if (strstr(buffer, "ENDCHAR") != NULL)
{
charStarted = false;
continue;
}
if (charBitmapStarted)
{
if (charGlyphInfo != NULL) {
int pixelY = charBitmapNextRow++;
if (pixelY >= charGlyphInfo->image.height)
{
break;
}
for (int x = 0; x < readBytes; x++)
{
char byte = HexToInt(buffer[x]);
for (int bitX = 0; bitX < 4; bitX++)
{
int pixelX = ((x * 4) + bitX);
if (pixelX >= charGlyphInfo->image.width)
{
break;
}
if ((byte & (8 >> bitX)) > 0)
{
((unsigned char*)charGlyphInfo->image.data)[(pixelY * charGlyphInfo->image.width) + pixelX] = 255;
}
}
}
}
continue;
}
// ENCODING
if (strstr(buffer, "ENCODING") != NULL)
{
readVars = sscanf(buffer, "ENCODING %i", &charEncoding);
continue;
}
// BBX
if (strstr(buffer, "BBX") != NULL)
{
readVars = sscanf(buffer, "BBX %i %i %i %i", &charBBw, &charBBh, &charBBxoff0, &charBByoff0);
continue;
}
// DWIDTH
if (strstr(buffer, "DWIDTH") != NULL)
{
readVars = sscanf(buffer, "DWIDTH %i %i", &charDWidthX, &charDWidthY);
continue;
}
// BITMAP
if (strstr(buffer, "BITMAP") != NULL)
{
// Search for glyph index in codepoints
charGlyphInfo = NULL;
for (int codepointIndex = 0; codepointIndex < codepointCount; codepointIndex++)
{
if (codepoints[codepointIndex] == charEncoding)
{
charGlyphInfo = &glyphs[codepointIndex];
break;
}
}
// Init glyph info
if (charGlyphInfo != NULL)
{
charGlyphInfo->value = charEncoding;
charGlyphInfo->offsetX = charBBxoff0 + fontBByoff0;
charGlyphInfo->offsetY = fontBBh - (charBBh + charBByoff0 + fontBByoff0 + fontAscent);
charGlyphInfo->advanceX = charDWidthX;
charGlyphInfo->image.data = RL_CALLOC(charBBw * charBBh, 1);
charGlyphInfo->image.width = charBBw;
charGlyphInfo->image.height = charBBh;
charGlyphInfo->image.mipmaps = 1;
charGlyphInfo->image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
}
charBitmapStarted = true;
charBitmapNextRow = 0;
continue;
}
}
else if (fontStarted)
{
// ENDFONT
if (strstr(buffer, "ENDFONT") != NULL)
{
fontStarted = false;
break;
}
// SIZE
if (strstr(buffer, "SIZE") != NULL)
{
if (outFontSize != NULL)
{
readVars = sscanf(buffer, "SIZE %i", outFontSize);
}
continue;
}
// PIXEL_SIZE
if (strstr(buffer, "PIXEL_SIZE") != NULL)
{
if (outFontSize != NULL)
{
readVars = sscanf(buffer, "PIXEL_SIZE %i", outFontSize);
}
continue;
}
// FONTBOUNDINGBOX
if (strstr(buffer, "FONTBOUNDINGBOX") != NULL)
{
readVars = sscanf(buffer, "FONTBOUNDINGBOX %i %i %i %i", &fontBBw, &fontBBh, &fontBBxoff0, &fontBByoff0);
continue;
}
// FONT_ASCENT
if (strstr(buffer, "FONT_ASCENT") != NULL)
{
readVars = sscanf(buffer, "FONT_ASCENT %i", &fontAscent);
continue;
}
// STARTCHAR
if (strstr(buffer, "STARTCHAR") != NULL)
{
charStarted = true;
charEncoding = -1;
charGlyphInfo = NULL;
charBBw = 0;
charBBh = 0;
charBBxoff0 = 0;
charBByoff0 = 0;
charDWidthX = 0;
charDWidthY = 0;
charGlyphInfo = NULL;
charBitmapStarted = false;
charBitmapNextRow = 0;
continue;
}
}
else
{
// STARTFONT
if (strstr(buffer, "STARTFONT") != NULL)
{
if (fontStarted)
{
fontMalformed = true;
break;
}
else
{
fontStarted = true;
continue;
}
}
}
}
if (genFontChars) RL_FREE(codepoints);
if (fontMalformed)
{
RL_FREE(glyphs);
glyphs = NULL;
}
return glyphs;
}
#endif
#endif // SUPPORT_MODULE_RTEXT