You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于FreeType在Raylib中加载渲染字体的问题排查与实现指引

FreeType + Raylib 自定义字体加载崩溃排查与正确实现流程

问题背景

使用D语言开发,通过FreeType加载字体字形并转换为Raylib可渲染的Font对象,程序运行时崩溃,推测存在内存管理或字形处理错误。测试字体为Inter。

崩溃原因排查

原代码存在以下关键问题:

  1. 野指针内存问题:bitmapToImage中创建的ubyte[] pixels是局部数组,函数返回后会被D的GC回收,导致Raylib Image的data指针指向无效内存。
  2. 重复资源释放:生成图集后,先循环释放一次原字形Image,后续又重复释放,触发double free错误。
  3. GlyphInfo数组指针失效:直接将动态数组glyphs.ptr赋值给font.glyphs,动态数组扩容或函数结束后,指针可能指向已回收的内存区域。
  4. 未初始化的Font成员:font.recs未初始化就传入GenImageFontAtlas,可能导致内存访问错误。
  5. 字形偏移错误:FreeType的bitmap_top是字形基线到顶部的向上偏移,而Raylib的GlyphInfo.offsetY是基线向下的偏移,未做转换会导致渲染位置错乱。
  6. 参数传递错误:调用GenImageFontAtlas时使用未定义的glyphPadding变量,应使用font.glyphPadding。

正确实现流程

  1. 初始化FreeType库(外部完成,传入函数的library需确保已初始化)
  2. 加载字体Face:通过FT_New_Face从文件加载字体,设置像素尺寸FT_Set_Pixel_Sizes
  3. 遍历并处理所有字形:
    • 使用FT_Get_First_Char/FT_Get_Next_Char遍历字符集
    • 加载字形FT_Load_Glyph,渲染为位图FT_Render_Glyph
    • 将FreeType位图转换为Raylib Image(使用Raylib的内存管理函数确保内存安全)
    • 填充GlyphInfo结构,修正偏移量和步进值
  4. 生成字体图集:调用GenImageFontAtlas将所有字形打包为单个图集,获取每个字形在图集中的位置
  5. 构建Raylib Font对象:
    • 加载图集为纹理LoadTextureFromImage
    • 清理临时的单个字形Image和图集Image
    • 确保Font的glyphs和recs内存由Raylib管理或持久化
  6. 清理FreeType资源:调用FT_Done_Face释放字体Face
  7. 使用后清理Font:调用UnloadFont释放Raylib Font的所有资源

修正后的完整代码

import raylib;
import freetype;
import std.exception;
import std.stdio;

Font loadFont(FT_Library library, string fontPath, uint fontSize)
{
    FT_Face face;
    enforce(FT_New_Face(library, fontPath.ptr, 0, &face) == 0, "Freetype font load error");
    FT_Set_Pixel_Sizes(face, 0, fontSize);

    Font font = {0};
    font.baseSize = fontSize;
    font.glyphPadding = 1;

    GlyphInfo[] glyphs;
    FT_ULong charcode;
    uint glyphIndex;
    charcode = FT_Get_First_Char(face, &glyphIndex);

    while (glyphIndex != 0)
    {
        if (FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT) != 0)
        {
            writeln("Failed to load glyph for codepoint: ", charcode);
            charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
            continue;
        }

        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);
                charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
                continue;
            }
        }

        FT_Bitmap bitmap = face.glyph.bitmap;
        GlyphInfo glyphInfo = {0};
        glyphInfo.value = charcode;
        glyphInfo.offsetX = face.glyph.bitmap_left;
        // 转换FreeType的向上偏移为Raylib的向下偏移
        glyphInfo.offsetY = fontSize - face.glyph.bitmap_top;
        glyphInfo.advanceX = face.glyph.advance.x >> 6; // FreeType单位是1/64像素

        if (bitmap.width == 0 || bitmap.rows == 0)
        {
            glyphInfo.image = GenImageColor(1, 1, BLANK);
        }
        else
        {
            // 使用Raylib的GenImageFromData管理内存,避免GC回收问题
            ubyte[] pixels = new ubyte[bitmap.width * bitmap.rows];
            foreach (y; 0 .. bitmap.rows)
            {
                foreach (x; 0 .. bitmap.width)
                {
                    pixels[y * bitmap.width + x] = bitmap.buffer[y * bitmap.pitch + x];
                }
            }
            // 传递像素数据给Raylib,由其负责内存管理
            glyphInfo.image = GenImageFromData(bitmap.width, bitmap.rows, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, pixels.ptr);
            // 释放局部数组,因为Raylib已经复制了数据
            pixels = null;
        }

        glyphs ~= glyphInfo;
        charcode = FT_Get_Next_Char(face, charcode, &glyphIndex);
    }

    font.glyphCount = cast(int)glyphs.length;
    // 分配持久化内存存储GlyphInfo,避免动态数组失效
    font.glyphs = cast(GlyphInfo*)MemAlloc(font.glyphCount * GlyphInfo.sizeof);
    memcpy(font.glyphs, glyphs.ptr, font.glyphCount * GlyphInfo.sizeof);

    // 生成字体图集,传入正确的padding参数
    Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0);
    font.texture = LoadTextureFromImage(atlas);

    // 清理单个字形的临时Image
    foreach (ref g; glyphs)
    {
        if (g.image.data !is null) UnloadImage(g.image);
    }
    // 清理图集Image
    UnloadImage(atlas);

    FT_Done_Face(face);
    return font;
}

关键注意事项

  • 内存管理:所有Raylib创建的资源(Image、Texture、Font)必须使用对应的Unload*函数释放,避免内存泄漏。
  • 字形偏移转换:FreeType与Raylib的字形偏移方向不同,必须修正才能正确渲染文本。
  • 持久化数组:Raylib的Font需要稳定的glyphs和recs指针,不能直接使用D的动态数组指针,需手动分配内存。

内容的提问来源于stack exchange,提问作者Bagomot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:47:06