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

如何让FreeType库使用.NET Stream对象加载TTF字体?

在FreeType中使用.NET System.IO.Stream加载TTF字体

要让FreeType兼容.NET的System.IO.Stream,核心是把.NET Stream包装成FreeType要求的FT_Stream结构体,通过descriptor字段传递.NET Stream的引用,并实现符合Cdecl调用约定的非托管read和close回调,在回调中代理调用.NET Stream的方法。以下是具体实现步骤:

1. 实现非托管回调函数

FreeType需要非托管函数指针,我们用[UnmanagedCallersOnly]标记静态方法,确保生成符合Cdecl约定的函数指针:

internal static unsafe class FreeTypeStreamCallbacks
{
    [UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
    public static CULong Read(FT_Stream* stream, CULong offset, byte* buffer, CULong count)
    {
        var handle = GCHandle.FromIntPtr(stream->descriptor);
        var netStream = (Stream)handle.Target;

        try
        {
            if (netStream.Position != (long)offset)
            {
                netStream.Seek((long)offset, SeekOrigin.Begin);
            }

            var span = new Span<byte>(buffer, (int)count);
            return (CULong)netStream.Read(span);
        }
        catch
        {
            return 0; // FreeType将0识别为读取失败
        }
    }

    [UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
    public static void Close(FT_Stream* stream)
    {
        var handle = GCHandle.FromIntPtr(stream->descriptor);
        handle.Free();
    }
}

2. 包装.NET Stream为FT_Stream

创建FT_Stream实例,绑定回调并关联.NET Stream:

internal unsafe nint CreateFreeTypeStreamFromNetStream(Stream netStream)
{
    var handle = GCHandle.Alloc(netStream);
    var ftStreamPtr = (FT_Stream*)Marshal.AllocHGlobal(sizeof(FT_Stream));
    
    *ftStreamPtr = new FT_Stream
    {
        base = null, // 自定义流无需预加载内存基址
        size = (CULong)netStream.Length,
        pos = 0,
        descriptor = GCHandle.ToIntPtr(handle),
        read = &FreeTypeStreamCallbacks.Read,
        close = &FreeTypeStreamCallbacks.Close,
        pathname = 0,
        memory = 0,
        cursor = 0,
        limit = 0
    };

    return (nint)ftStreamPtr;
}

3. 配置FT_Open_Args并调用FT_Open_Face

设置FT_Open_Args的flags为FT_OPEN_STREAM(对应宏值0x10),指向自定义FT_Stream后调用加载接口:

internal unsafe bool LoadFontFromStream(nint ftLibrary, Stream fontStream, out nint ftFace)
{
    ftFace = 0;
    var ftStreamPtr = (FT_Stream*)CreateFreeTypeStreamFromNetStream(fontStream);

    try
    {
        var openArgs = new FT_Open_Args
        {
            flags = 0x10,
            stream = ftStreamPtr,
            memory_base = null,
            memory_size = 0,
            pathname = null,
            driver = 0,
            num_params = 0,
            params = null
        };

        var result = FT_Open_Face(ftLibrary, &openArgs, 0, &ftFace);
        return result == 0; // FreeType返回0表示加载成功
    }
    finally
    {
        // 加载失败时手动清理资源,成功后FreeType会接管FT_Stream生命周期
        if (ftFace == 0)
        {
            FreeTypeStreamCallbacks.Close(ftStreamPtr);
            Marshal.FreeHGlobal((nint)ftStreamPtr);
        }
    }
}

关键注意事项

  • GC安全:必须用GCHandle固定.NET Stream对象,避免GC在回调执行期间回收导致内存访问错误。
  • 资源释放:加载成功后,FreeType会在调用FT_Done_Face时自动触发close回调释放GCHandle;加载失败需手动清理FT_Stream内存和GCHandle。
  • 流位置同步:回调中要根据FreeType传入的offset调整.NET Stream的位置,适配FreeType的随机访问需求。
  • 异常处理:回调中捕获异常并返回0,避免非托管环境崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 00:23:12