I write code in D, so the examples will be in it, but you can help me in any language that has bindings to Freetype and Raylib.
I'm trying to make the font glyphs load and render through Freetype, and the text render through Raylib.
I load the font from a file or memory (it doesn't matter) into the face. Then I get each character of the font in turn, load the glyph for that character, render the glyph into a bitmap, convert the bitmap into a Raylib image, create a Raylib glyph with that image, accumulate that glyph into an array. After processing all the characters, I load the Raylib glyph array into an atlas, get the font texture from there, and load it into the font. Next I update the images in the Raylib font glyphs to what is contained in the texture according to the glyph rect.
Right now I have the following code:
Font loadFont(FT_Library library, string fontPath, uint fontSize)
{
FT_Face face;
if (FT_New_Face(library, fontPath.ptr, 0, &face))
{
throw new Exception("Freetype font load error for font");
}
FT_Set_Pixel_Sizes(face, 0, fontSize);
// Raylib font
Font font;
font.baseSize = fontSize;
font.glyphCount = 0;
font.glyphPadding = 1;
GlyphInfo[] glyphs;
FT_ULong charcode;
uint glyphIndex;
// Get the first character and its index
charcode = FT_Get_First_Char(face, &glyphIndex);
while (glyphIndex != 0)
{
// Load glyph by index
if (FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT) != 0)
{
writeln("Failed to load glyph for codepoint: " ~ charcode.to!string);
charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
continue;
}
// Glyph rendering (if it not a bitmap, convert it to bitmap)
if (face.glyph.format != FT_GLYPH_FORMAT_BITMAP)
{
if (FT_Render_Glyph(face.glyph, FT_RENDER_MODE_NORMAL) != 0)
{
writeln("Failed to render glyph for codepoint: " ~ charcode.to!string);
charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
continue;
}
}
FT_Bitmap bitmap = face.glyph.bitmap;
GlyphInfo glyphInfo;
glyphInfo.value = charcode;
glyphInfo.offsetX = face.glyph.bitmap_left;
glyphInfo.offsetY = face.glyph.bitmap_top;
glyphInfo.advanceX = face.glyph.advance.x >> 6;
// If glyph is empty, create an empty image, or do nothing?
if (bitmap.width == 0 || bitmap.rows == 0)
{
glyphInfo.image = GenImageColor(1, 1, Colors.BLANK);
}
else
{
// Convert bitmap to image
glyphInfo.image = bitmapToImage(bitmap);
}
glyphs ~= glyphInfo;
font.glyphCount++;
charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
}
font.glyphs = glyphs.ptr;
// Create font atlas
Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, glyphPadding, 0);
// Load texture from atlas
font.texture = LoadTextureFromImage(atlas);
for (int i = 0; i < font.glyphCount; i++)
{
if (font.glyphs[i].image.data !is null)
{
UnloadImage(glyphs[i].image);
}
// Check if rectangle is valid
Rectangle rec = font.recs[i];
if (rec.width <= 0 || rec.height <= 0 || rec.x < 0 || rec.y < 0)
{
writeln("Invalid rectangle for glyph: " ~ i.to!string);
continue;
}
// Create new glyph image from atlas
Image newImage = ImageFromImage(atlas, rec);
// Check if image was created successfully
if (newImage.data is null || newImage.width <= 0 || newImage.height <= 0)
{
writeln("Failed to create image from atlas for glyph: " ~ i.to!string);
continue;
}
// Assign new image to glyph
font.glyphs[i].image = newImage;
}
UnloadImage(atlas);
// Free original glyphs images
for (int i = 0; i < font.glyphCount; i++)
{
if (glyphs[i].image.data !is null)
{
UnloadImage(glyphs[i].image);
}
}
// Free freetype face
FT_Done_Face(face);
return font;
}
Image bitmapToImage(FT_Bitmap bitmap)
{
Image image;
switch (bitmap.pixel_mode)
{
// the pixel model in the test font is exactly like this
case FT_PIXEL_MODE_GRAY:
{
// For 8-bit grayscale (1 byte per pixel)
ubyte[] pixels = new ubyte[bitmap.width * bitmap.rows];
foreach (y; 0 .. bitmap.rows)
{
foreach (x; 0 .. bitmap.width)
{
int index = y * bitmap.width + x;
ubyte grayValue = bitmap.buffer[y * bitmap.pitch + x];
pixels[index] = grayValue;
}
}
image.data = cast(void*) pixels.ptr;
image.width = bitmap.width;
image.height = bitmap.rows;
image.mipmaps = 1;
image.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
}
break;
case FT_PIXEL_MODE_NONE:
default:
return GenImageColor(1, 1, Colors.BLANK);
}
return image;
}
Full code on GitHub: https://github.com/bagomot/raylib_freetype/blob/master/source/app.d
This code just crashes the program when I try to use it, probably a memory problem and/or I'm not working with glyphs correctly. Let me know if you have any ideas. If you can, describe to me the full order of loading, rendering glyphs and creating from this font for Raylib.
For example, I used the Inter font: https://fonts.google.com/specimen/Inter
The problem is you are using the GC to allocate data and then freeing that data with raylib.
Raylib expects all data to be allocated with C malloc. If you free data that is not really C heap data, you will cause heap corruption.
I changed this one line, and it's now "working".
ubyte[] pixels = (cast(ubyte*)malloc(bitmap.width * bitmap.rows))[0 .. bitmap.width * bitmap.rows];
It still crashes at the end, but I think that's because you are doing some cleanup at the end that has the same problem. For example, font.glyphs
is GC-allocated, and I'm pretty sure raylib will try and free that as well.