如何让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
相关产品推荐
相关产品推荐

